luseweixiao 2 months ago
parent
commit
638b1ec30c
72 changed files with 4193 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 13 0
      Dockerfile
  3. 145 0
      pom.xml
  4. 16 0
      src/main/java/com/sl/WebEnterpriseApplication.java
  5. 39 0
      src/main/java/com/sl/ms/web/enterprise/config/EnterpriseExceptionHandler.java
  6. 39 0
      src/main/java/com/sl/ms/web/enterprise/config/EnterpriseWebConfig.java
  7. 28 0
      src/main/java/com/sl/ms/web/enterprise/config/LoadBalanceConfig.java
  8. 29 0
      src/main/java/com/sl/ms/web/enterprise/config/OSSConfiguration.java
  9. 52 0
      src/main/java/com/sl/ms/web/enterprise/config/RedissonConfiguration.java
  10. 30 0
      src/main/java/com/sl/ms/web/enterprise/config/RestTemplateConfig.java
  11. 17 0
      src/main/java/com/sl/ms/web/enterprise/config/SZColdChainConfig.java
  12. 30 0
      src/main/java/com/sl/ms/web/enterprise/config/SZColdChainConstant.java
  13. 20 0
      src/main/java/com/sl/ms/web/enterprise/config/WebFeignErrorDecoder.java
  14. 84 0
      src/main/java/com/sl/ms/web/enterprise/controller/LoginController.java
  15. 292 0
      src/main/java/com/sl/ms/web/enterprise/controller/OrderController.java
  16. 52 0
      src/main/java/com/sl/ms/web/enterprise/enums/PrintCodeEnum.java
  17. 58 0
      src/main/java/com/sl/ms/web/enterprise/enums/PrintModeEnum.java
  18. 24 0
      src/main/java/com/sl/ms/web/enterprise/properties/JwtProperties.java
  19. 15 0
      src/main/java/com/sl/ms/web/enterprise/service/LoginService.java
  20. 23 0
      src/main/java/com/sl/ms/web/enterprise/service/OrderService.java
  21. 21 0
      src/main/java/com/sl/ms/web/enterprise/service/TokenService.java
  22. 45 0
      src/main/java/com/sl/ms/web/enterprise/service/impl/LoginServiceImpl.java
  23. 1024 0
      src/main/java/com/sl/ms/web/enterprise/service/impl/OrderServiceImpl.java
  24. 30 0
      src/main/java/com/sl/ms/web/enterprise/service/impl/TokenServiceImpl.java
  25. 92 0
      src/main/java/com/sl/ms/web/enterprise/utils/HeadersValidator.java
  26. 87 0
      src/main/java/com/sl/ms/web/enterprise/utils/PrintApiUtils.java
  27. 75 0
      src/main/java/com/sl/ms/web/enterprise/utils/ReflectUtil.java
  28. 135 0
      src/main/java/com/sl/ms/web/enterprise/utils/SZSignUtil.java
  29. 75 0
      src/main/java/com/sl/ms/web/enterprise/utils/TransportOrderStatusUtils.java
  30. 18 0
      src/main/java/com/sl/ms/web/enterprise/vo/PrintServiceException.java
  31. 25 0
      src/main/java/com/sl/ms/web/enterprise/vo/ValidateVo.java
  32. 20 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/AccountLoginVO.java
  33. 54 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/BaseWaybill.java
  34. 21 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/CancelRequest.java
  35. 13 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/Cargo.java
  36. 23 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/DriverDelayDeliveryVO.java
  37. 23 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/DriverDeliverVO.java
  38. 23 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/DriverPickUpVO.java
  39. 65 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/DriverReturnRegisterVO.java
  40. 29 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/GoodsTypeVO.java
  41. 19 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/HouseWaybill.java
  42. 21 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/LoginRequest.java
  43. 18 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/MasterWaybill.java
  44. 49 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/MultPrintRequest.java
  45. 56 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/OrderCargoVO.java
  46. 146 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/OrderRequest.java
  47. 19 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/PodSigned.java
  48. 25 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/PrintApiRequest.java
  49. 37 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/PrintRequest.java
  50. 19 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/ReceiverReceipt.java
  51. 19 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/RetentionCopy.java
  52. 19 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/SenderReceipt.java
  53. 29 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/SubTransportOrderVo.java
  54. 16 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/TemperatureRegion.java
  55. 21 0
      src/main/java/com/sl/ms/web/enterprise/vo/request/TransportInfoRequest.java
  56. 44 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/ApiResponse.java
  57. 17 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/FieldError.java
  58. 62 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/OrderResponse.java
  59. 19 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/PrintApiResponse.java
  60. 17 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/PrintResponse.java
  61. 27 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/TokenResponse.java
  62. 29 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/TransportInfoResponse.java
  63. 25 0
      src/main/java/com/sl/ms/web/enterprise/vo/response/TransportOrderVO.java
  64. 7 0
      src/main/resources/banner.txt
  65. 27 0
      src/main/resources/bootstrap-local.yml
  66. 27 0
      src/main/resources/bootstrap-prod.yml
  67. 27 0
      src/main/resources/bootstrap-stu.yml
  68. 27 0
      src/main/resources/bootstrap-test.yml
  69. 28 0
      src/main/resources/bootstrap.yml
  70. 41 0
      src/main/resources/logback-spring.xml
  71. 144 0
      src/test/java/com/sl/ms/web/enterprise/signature/ReflectUtilTest.java
  72. 205 0
      src/test/java/com/sl/ms/web/enterprise/signature/SZSignUtilTest.java

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.idea
+target/
+*.iml

+ 13 - 0
Dockerfile

@@ -0,0 +1,13 @@
+FROM apache/skywalking-java-agent:8.11.0-java11
+LABEL maintainer="研究院研发组 <research@itcast.cn>"
+ 
+# 时区修改为东八区
+ENV TZ=Asia/Shanghai
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+ 
+WORKDIR /app
+ARG JAR_FILE=target/*.jar
+ADD ${JAR_FILE} app.jar
+
+EXPOSE 8080
+ENTRYPOINT ["sh","-c","java -Djava.security.egd=file:/dev/./urandom -jar $JAVA_OPTS app.jar"]

+ 145 - 0
pom.xml

@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    
+    <parent>
+        <groupId>com.sl-express</groupId>
+        <artifactId>sl-express-parent</artifactId>
+        <version>1.5-SNAPSHOT</version>
+    </parent>
+
+    <groupId>com.sl-express.ms.web</groupId>
+    <artifactId>sl-express-ms-web-enterprise</artifactId>
+    <version>1.1-SNAPSHOT</version>
+    <description>第三方接口层微服务</description>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <sl-express-common.version>1.2-SNAPSHOT</sl-express-common.version>
+        <sl-express-ms-base-api.version>1.1-SNAPSHOT</sl-express-ms-base-api.version>
+        <sl-express-ms-work-api.version>1.2-SNAPSHOT</sl-express-ms-work-api.version>
+        <sl-express-ms-transport-api.version>1.1-SNAPSHOT</sl-express-ms-transport-api.version>
+        <sl-express-ms-user-api.version>1.1-SNAPSHOT</sl-express-ms-user-api.version>
+        <skipTests>true</skipTests>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sl-express.common</groupId>
+            <artifactId>sl-express-common</artifactId>
+            <version>${sl-express-common.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sl-express.ms.base</groupId>
+            <artifactId>sl-express-ms-base-api</artifactId>
+            <version>${sl-express-ms-base-api.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sl-express.ms.work</groupId>
+            <artifactId>sl-express-ms-work-api</artifactId>
+            <version>${sl-express-ms-work-api.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sl-express.ms.transport</groupId>
+            <artifactId>sl-express-ms-transport-api</artifactId>
+            <version>${sl-express-ms-transport-api.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sl-express.ms.user</groupId>
+            <artifactId>sl-express-ms-user-api</artifactId>
+            <version>${sl-express-ms-user-api.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.itheima.em.auth</groupId>
+            <artifactId>itcast-auth-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+            <version>2.3.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sl-express.ms.oms</groupId>
+            <artifactId>sl-express-ms-oms-api</artifactId>
+            <version>1.1-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sl-express.ms.transport-info</groupId>
+            <artifactId>sl-express-ms-transport-info-api</artifactId>
+            <version>1.1-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sl-express.ms.trade</groupId>
+            <artifactId>sl-express-ms-trade-api</artifactId>
+            <version>1.1-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.retry</groupId>
+            <artifactId>spring-retry</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                            <goal>build-info</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <!--指定主类-->
+                    <mainClass>com.sl.WebEnterpriseApplication</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 16 - 0
src/main/java/com/sl/WebEnterpriseApplication.java

@@ -0,0 +1,16 @@
+package com.sl;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients
+public class WebEnterpriseApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(WebEnterpriseApplication.class, args);
+    }
+
+}

+ 39 - 0
src/main/java/com/sl/ms/web/enterprise/config/EnterpriseExceptionHandler.java

@@ -0,0 +1,39 @@
+package com.sl.ms.web.enterprise.config;
+
+
+import com.sl.ms.web.enterprise.vo.response.ApiResponse;
+import com.sl.ms.web.enterprise.vo.response.FieldError;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 20:23
+ * @className GlobalExceptionHandler
+ */
+@RestControllerAdvice
+public class EnterpriseExceptionHandler {
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseStatus(HttpStatus.OK)
+    public ApiResponse< List<FieldError>> handleValidationException(MethodArgumentNotValidException ex) {
+        List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors().stream()
+                .map(error -> {
+                    FieldError fieldError = new FieldError();
+                    fieldError.setField(error.getField());
+                    fieldError.setMessage(error.getDefaultMessage());
+                    fieldError.setRejectedValue(error.getRejectedValue());
+                    return fieldError;
+                })
+                .collect(Collectors.toList());
+        return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,
+                "参数校验失败",
+                fieldErrors);
+    }
+}

+ 39 - 0
src/main/java/com/sl/ms/web/enterprise/config/EnterpriseWebConfig.java

@@ -0,0 +1,39 @@
+package com.sl.ms.web.enterprise.config;
+
+import com.sl.transport.common.interceptor.TokenInterceptor;
+import com.sl.transport.common.interceptor.UserInterceptor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+
+
+/**
+ * 司机端配置拦截器
+ */
+@Configuration
+@Slf4j
+public class EnterpriseWebConfig implements WebMvcConfigurer {
+
+    @Resource
+    private UserInterceptor userInterceptor;
+
+    @Resource
+    private TokenInterceptor tokenInterceptor;
+
+    private static final String[] EXCLUDE_PATH_PATTERNS = new String[]{
+            "/swagger-ui.html",
+            "/webjars/**",
+            "/swagger-resources",
+            "/v2/api-docs",
+            "/login/**"};
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        //拦截的时候过滤掉swagger相关路径和登录相关接口
+        registry.addInterceptor(userInterceptor).excludePathPatterns(EXCLUDE_PATH_PATTERNS).addPathPatterns("/**");
+        registry.addInterceptor(tokenInterceptor).excludePathPatterns(EXCLUDE_PATH_PATTERNS).addPathPatterns("/**");
+    }
+}

+ 28 - 0
src/main/java/com/sl/ms/web/enterprise/config/LoadBalanceConfig.java

@@ -0,0 +1,28 @@
+package com.sl.ms.web.enterprise.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class LoadBalanceConfig {
+
+    @Bean
+    @ConditionalOnMissingBean
+    public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {
+        return new LoadBalancerClientFactory(properties) {
+            @Override
+            protected AnnotationConfigApplicationContext createContext(String name) {
+                // FIXME: temporary switch classloader to use the correct one when creating the context
+                ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+                Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+                AnnotationConfigApplicationContext context = super.createContext(name);
+                Thread.currentThread().setContextClassLoader(originalClassLoader);
+                return context;
+            }
+        };
+    }
+}

+ 29 - 0
src/main/java/com/sl/ms/web/enterprise/config/OSSConfiguration.java

@@ -0,0 +1,29 @@
+package com.sl.ms.web.enterprise.config;
+
+import com.sl.transport.common.properties.AliOSSProperties;
+import com.sl.transport.common.service.AliOssService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 配置对象存储工具类
+ * 主要用于上传图片和文件
+ */
+@Configuration
+@Slf4j
+public class OSSConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean
+    public AliOssService aliOSSUtil(AliOSSProperties aliOSSProperties) {
+        log.info("创建阿里云OSS工具类...");
+
+        return new AliOssService(
+                aliOSSProperties.getEndpoint(),
+                aliOSSProperties.getAccessKeyId(),
+                aliOSSProperties.getAccessKeySecret(),
+                aliOSSProperties.getBucketName());
+    }
+}

+ 52 - 0
src/main/java/com/sl/ms/web/enterprise/config/RedissonConfiguration.java

@@ -0,0 +1,52 @@
+package com.sl.ms.web.enterprise.config;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.redisson.config.SingleServerConfig;
+import org.redisson.spring.cache.CacheConfig;
+import org.redisson.spring.cache.RedissonSpringCacheManager;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class RedissonConfiguration {
+
+    @Resource
+    private RedisProperties redisProperties;
+
+    @Bean
+    public RedissonClient redissonSingle() {
+        Config config = new Config();
+        SingleServerConfig serverConfig = config.useSingleServer()
+                .setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort());
+        if (null != (redisProperties.getTimeout())) {
+            serverConfig.setTimeout(1000 * Convert.toInt(redisProperties.getTimeout().getSeconds()));
+        }
+        if (StrUtil.isNotEmpty(redisProperties.getPassword())) {
+            serverConfig.setPassword(redisProperties.getPassword());
+        }
+        return Redisson.create(config);
+    }
+    @Bean
+    public CacheManager cacheManager(RedissonClient redissonClient) {
+        Map<String, CacheConfig> configMap = new HashMap<>();
+
+        // 为特定缓存设置 3 分钟过期时间
+        configMap.put("transport-order-id-related-agency",
+                new CacheConfig(3 * 60 * 1000,  // TTL (30分钟)
+                        0));              // 最大空闲时间(0=禁用)
+
+        // 其他缓存使用默认配置(永不过期)
+        return new RedissonSpringCacheManager(redissonClient, configMap);
+    }
+
+}

+ 30 - 0
src/main/java/com/sl/ms/web/enterprise/config/RestTemplateConfig.java

@@ -0,0 +1,30 @@
+package com.sl.ms.web.enterprise.config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 15:12
+ * @className RestTemplateConfig
+ */
+
+
+@Configuration
+public class RestTemplateConfig {
+
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate(clientHttpRequestFactory());
+    }
+
+    private ClientHttpRequestFactory clientHttpRequestFactory() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setConnectTimeout(5000);  // 5秒连接超时
+        factory.setReadTimeout(10000);    // 10秒读取超时
+        return factory;
+    }
+}

+ 17 - 0
src/main/java/com/sl/ms/web/enterprise/config/SZColdChainConfig.java

@@ -0,0 +1,17 @@
+package com.sl.ms.web.enterprise.config;
+
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 21:03
+ * @className SZColdChainConfig
+ */
+@Configuration
+@ConfigurationProperties(prefix = "szcoldchain")
+public class SZColdChainConfig {
+    private long tokenExpireSeconds = 7200;
+}

+ 30 - 0
src/main/java/com/sl/ms/web/enterprise/config/SZColdChainConstant.java

@@ -0,0 +1,30 @@
+package com.sl.ms.web.enterprise.config;
+
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 21:02
+ * @className SZColdChainConstant
+ */
+public class SZColdChainConstant {
+    public static final String HEADER_SIGNATURE = "sz-signature";
+    public static final String HEADER_NONCE = "sz-nonce";
+    public static final String HEADER_AUTHORIZATION = "Authorization";
+    public static final String ACCESS_TOKEN_TYPE = "Bearer";
+
+    public static final int CODE_SUCCESS = 200;
+    public static final int CODE_SIGN_MISSING = 1001;
+    public static final int CODE_SIGN_INVALID = 1002;
+    public static final int CODE_TIMESTAMP_EXPIRED = 1003;
+    public static final int CODE_NONCE_REPEAT = 1004;
+    public static final int CODE_TOKEN_INVALID = 1005;
+    public static final int CODE_PARAM_MISSING = 2001;
+    public static final int CODE_PARAM_FORMAT_ERROR = 2002;
+    public static final int CODE_PARAM_VALUE_INVALID = 2003;
+    public static final int CODE_ENTERPRISE_ORDER_BUSINESS_ERROR = 3000;
+    public static final int CODE_ENTERPRISE_ORDER_NOT_FOUND = 3001;
+    public static final int CODE_ENTERPRISE_ORDER_STATUS_CANNOT_DELETE = 3002;
+    public static final int CODE_SERVICE_INNER_ERROR = 5001;
+    public static final String MSG_SUCCESS = "ok";
+}

+ 20 - 0
src/main/java/com/sl/ms/web/enterprise/config/WebFeignErrorDecoder.java

@@ -0,0 +1,20 @@
+package com.sl.ms.web.enterprise.config;
+
+import com.sl.transport.common.config.FeignErrorDecoder;
+import com.sl.transport.common.exception.SLWebException;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * web调用feign失败解码器实现
+ *
+ * @author zzj
+ * @version 1.0
+ */
+@Configuration
+public class WebFeignErrorDecoder extends FeignErrorDecoder {
+
+    @Override
+    public Exception call(int status, int code, String msg) {
+        return new SLWebException(msg);
+    }
+}

+ 84 - 0
src/main/java/com/sl/ms/web/enterprise/controller/LoginController.java

@@ -0,0 +1,84 @@
+package com.sl.ms.web.enterprise.controller;
+
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.itheima.auth.sdk.common.Result;
+import com.itheima.auth.sdk.dto.LoginDTO;
+import com.sl.ms.user.api.EnterpriseFeign;
+import com.sl.ms.user.domain.dto.EnterpriseWithSecretDTO;
+import com.sl.ms.web.enterprise.config.SZColdChainConstant;
+import com.sl.ms.web.enterprise.service.LoginService;
+import com.sl.ms.web.enterprise.utils.HeadersValidator;
+import com.sl.ms.web.enterprise.utils.SZSignUtil;
+import com.sl.ms.web.enterprise.vo.ValidateVo;
+import com.sl.ms.web.enterprise.vo.request.LoginRequest;
+import com.sl.ms.web.enterprise.vo.response.ApiResponse;
+import com.sl.ms.web.enterprise.vo.response.TokenResponse;
+import com.sl.transport.common.exception.SLException;
+import com.sl.transport.common.exception.SLWebException;
+import com.sl.transport.common.vo.R;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Api(tags = "登录相关接口")
+@RestController
+@RequestMapping("/login")
+public class LoginController {
+    @Resource
+    private HeadersValidator headersValidator;
+
+    @Resource
+    private EnterpriseFeign enterpriseFeign;
+
+    @Resource
+    private LoginService loginService;
+    final static String salt = "123456";
+    final static String HMAC_SHA256_SECRET_KEY = "a70a4ca4b6c1c94da562fe3203760936";
+    @ApiOperation(value = "账号登录", notes = "登录")
+    @PostMapping
+    public ApiResponse<TokenResponse> login(
+            @RequestHeader(SZColdChainConstant.HEADER_SIGNATURE) String signature,
+            @RequestHeader(SZColdChainConstant.HEADER_NONCE) String nonce,
+            @Validated @RequestBody LoginRequest loginRequest) {
+        // todo:根据enterprise 获取salt、secretkey
+        // 根据 enterpriseId 查询对应的配置,校验是否正确
+        EnterpriseWithSecretDTO enterprise = null;
+        try {
+            enterprise = enterpriseFeign.getByEnterpriseId(new Long(loginRequest.getEnterpriseId()));
+            if (ObjectUtil.isEmpty(enterprise)) {
+                throw new SLException("无效的enterpriseId");
+            }
+            if (!loginRequest.getEnterpriseSecret().equals(enterprise.getEnterpriseSecret())) {
+                throw new SLException("enterpriseSecret校验错误");
+            }
+        } catch (SLException e) {
+            throw e;
+        }
+
+        ValidateVo validateVo = new ValidateVo();
+        validateVo.setNonce(nonce);
+        validateVo.setSignature(signature);
+        validateVo.setHttpMethod("POST");
+        validateVo.setHttpUrl("/enterprise/login");
+        validateVo.setParams(  Map.of(
+                "enterpriseId", loginRequest.getEnterpriseId(),
+                "enterpriseSecret", loginRequest.getEnterpriseSecret()
+        ));
+        validateVo.setSalt(enterprise.getSalt());
+        validateVo.setHmacSha256SecretKey(enterprise.getHmacSha256SecretKey());
+
+        headersValidator.validate(validateVo);
+        // 业务处理
+        TokenResponse tokenResponse = loginService.login(loginRequest);
+
+        return ApiResponse.success(tokenResponse);
+    }
+}

+ 292 - 0
src/main/java/com/sl/ms/web/enterprise/controller/OrderController.java

@@ -0,0 +1,292 @@
+package com.sl.ms.web.enterprise.controller;
+
+
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.itheima.auth.sdk.common.Result;
+import com.itheima.auth.sdk.dto.PageDTO;
+import com.itheima.auth.sdk.dto.UserDTO;
+import com.itheima.auth.sdk.dto.UserPageDTO;
+import com.sl.ms.base.api.common.AuthFeign;
+import com.sl.ms.base.api.common.MessageFeign;
+import com.sl.ms.base.api.common.WorkSchedulingFeign;
+import com.sl.ms.base.domain.base.MessageAddDTO;
+import com.sl.ms.base.domain.constants.MessageConstants;
+import com.sl.ms.base.domain.enums.MessageBussinessTypeEnum;
+import com.sl.ms.base.domain.enums.MessageContentTypeEnum;
+import com.sl.ms.oms.dto.OrderDTO;
+import com.sl.ms.oms.enums.OrderPickupType;
+import com.sl.ms.user.api.EnterpriseFeign;
+import com.sl.ms.user.domain.dto.EnterpriseWithSecretDTO;
+import com.sl.ms.web.enterprise.config.SZColdChainConstant;
+import com.sl.ms.web.enterprise.service.OrderService;
+import com.sl.ms.web.enterprise.utils.HeadersValidator;
+import com.sl.ms.web.enterprise.utils.ReflectUtil;
+import com.sl.ms.web.enterprise.utils.SZSignUtil;
+import com.sl.ms.web.enterprise.vo.ValidateVo;
+import com.sl.ms.web.enterprise.vo.request.*;
+import com.sl.ms.web.enterprise.vo.response.ApiResponse;
+import com.sl.ms.web.enterprise.vo.response.OrderResponse;
+import com.sl.ms.web.enterprise.vo.response.PrintResponse;
+import com.sl.ms.web.enterprise.vo.response.TransportInfoResponse;
+import com.sl.transport.common.exception.SLException;
+import com.sl.transport.common.exception.SLWebException;
+import com.sl.transport.common.util.AuthTemplateThreadLocal;
+import com.sl.transport.common.util.UserThreadLocal;
+import com.sl.transport.common.vo.R;
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 22:03
+ * @className OrderController
+ */
+@Slf4j
+@Api(tags = "登录相关接口")
+@RestController
+@RequestMapping("/order")
+public class OrderController {
+    @Resource
+    OrderService orderService;
+
+    @Resource
+    EnterpriseFeign enterpriseFeign;
+
+    @Resource
+    private HeadersValidator headersValidator;
+
+    @Resource
+    MessageFeign messageFeign;
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    @Value("${role.notice.roleIds}")
+    private List<String> noticeRoleIds;
+
+    private static boolean DEBUG = false;
+    @Autowired
+    private AuthFeign authFeign;
+    @Autowired
+    private WorkSchedulingFeign workSchedulingFeign;
+
+    @PostMapping
+    public ApiResponse<OrderResponse> createOrder(
+            @RequestHeader(SZColdChainConstant.HEADER_SIGNATURE) String signature,
+            @RequestHeader(SZColdChainConstant.HEADER_NONCE) String nonce,
+            @Validated @RequestBody OrderRequest orderRequest) {
+
+        Long enterpriseId = UserThreadLocal.getUserId();
+        EnterpriseWithSecretDTO enterprise = null;
+        try {
+            enterprise = enterpriseFeign.getByEnterpriseId(enterpriseId);
+            if (ObjectUtil.isEmpty(enterprise)) {
+                return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR, "无效的enterpriseId");
+            }
+        } catch (SLException e) {
+            throw e;
+        }
+        if (!orderRequest.getEnterpriseId().equals(enterpriseId.toString())) {
+            return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,"无效的enterpriseId");
+        }
+
+        ValidateVo validateVo = new ValidateVo();
+        validateVo.setNonce(nonce);
+        validateVo.setSignature(signature);
+        validateVo.setHttpMethod("POST");
+        validateVo.setHttpUrl("/enterprise/order");
+        Map<String, Object> allNonNullFieldsSorted = ReflectUtil.getAllNonNullFieldsSorted(orderRequest);
+        validateVo.setParams(allNonNullFieldsSorted);
+        validateVo.setSalt(enterprise.getSalt());
+        validateVo.setHmacSha256SecretKey(enterprise.getHmacSha256SecretKey());
+        if (!DEBUG) {
+            headersValidator.validate(validateVo);
+        }
+
+        // 业务处理
+        ApiResponse<OrderResponse> response = orderService.createOrder(orderRequest);
+
+        return response;
+    }
+
+    @PostMapping("/transport-info")
+    public ApiResponse<TransportInfoResponse> queryTransportInfo(
+            @RequestHeader(SZColdChainConstant.HEADER_AUTHORIZATION) String authorization,
+            @RequestHeader(SZColdChainConstant.HEADER_SIGNATURE) String signature,
+            @RequestHeader(SZColdChainConstant.HEADER_NONCE) String nonce,
+            @Validated @RequestBody TransportInfoRequest transportInfoRequest) {
+        // todo:参数判断
+        if (ObjectUtil.isEmpty(transportInfoRequest.getEnterpriseId())) {
+            return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR, "enterpriseId不可为空");
+        }
+
+        if (ObjectUtils.allNull(transportInfoRequest.getTransportOrderId(), transportInfoRequest.getPartnerOrderCode())) {
+            return ApiResponse.error( SZColdChainConstant.CODE_PARAM_FORMAT_ERROR, "transportOrderId、partnerOrderCode不可同时为空");
+        }
+
+
+        Long enterpriseId = UserThreadLocal.getUserId();
+        EnterpriseWithSecretDTO enterprise = null;
+        try {
+            enterprise = enterpriseFeign.getByEnterpriseId(enterpriseId);
+            if (ObjectUtil.isEmpty(enterprise)) {
+                return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,"无效的enterpriseId");
+            }
+        } catch (SLException e) {
+            throw e;
+        }
+
+        if (!transportInfoRequest.getEnterpriseId().equals(enterpriseId.toString())) {
+            return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,"无效的enterpriseId");
+        }
+
+        ValidateVo validateVo = new ValidateVo();
+        validateVo.setNonce(nonce);
+        validateVo.setSignature(signature);
+        validateVo.setHttpMethod("POST");
+        validateVo.setHttpUrl("/enterprise/order/transport-info");
+        Map<String, Object> allNonNullFieldsSorted = ReflectUtil.getAllNonNullFieldsSorted(transportInfoRequest);
+        validateVo.setParams(allNonNullFieldsSorted);
+        validateVo.setSalt(enterprise.getSalt());
+        validateVo.setHmacSha256SecretKey(enterprise.getHmacSha256SecretKey());
+        if (!DEBUG) {
+            headersValidator.validate(validateVo);
+        }
+        // 业务处理
+        ApiResponse<TransportInfoResponse> response = orderService.queryTransportInfo(transportInfoRequest);
+        return response;
+    }
+
+    @PostMapping("/print")
+    public ApiResponse<PrintResponse> getPrintInfo(
+            @RequestHeader(SZColdChainConstant.HEADER_AUTHORIZATION) String authorization,
+            @RequestHeader(SZColdChainConstant.HEADER_SIGNATURE) String signature,
+            @RequestHeader(SZColdChainConstant.HEADER_NONCE) String nonce,
+            @Validated @RequestBody PrintRequest printRequest) {
+
+        Long enterpriseId = UserThreadLocal.getUserId();
+        EnterpriseWithSecretDTO enterprise = null;
+        try {
+            enterprise = enterpriseFeign.getByEnterpriseId(enterpriseId);
+            if (ObjectUtil.isEmpty(enterprise)) {
+                return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,"无效的enterpriseId");
+            }
+        } catch (SLException e) {
+            return ApiResponse.error(SZColdChainConstant.CODE_SERVICE_INNER_ERROR, "服务器错误");
+        }
+
+        ValidateVo validateVo = new ValidateVo();
+        validateVo.setNonce(nonce);
+        validateVo.setSignature(signature);
+        validateVo.setHttpMethod("POST");
+        validateVo.setHttpUrl("/enterprise/order/print");
+        Map<String, Object> allNonNullFieldsSorted = ReflectUtil.getAllNonNullFieldsSorted(printRequest);
+        validateVo.setParams(allNonNullFieldsSorted);
+        validateVo.setSalt(enterprise.getSalt());
+        validateVo.setHmacSha256SecretKey(enterprise.getHmacSha256SecretKey());
+        if (!DEBUG) {
+            headersValidator.validate(validateVo);
+        }
+        // 业务处理
+        ApiResponse<PrintResponse> response = orderService.getPrintInfo(printRequest, enterprise);
+        return response;
+    }
+
+    @PostMapping("/multPrint")
+    public ApiResponse<PrintResponse> getMultPrintInfo(
+            @RequestHeader(SZColdChainConstant.HEADER_AUTHORIZATION) String authorization,
+            @RequestHeader(SZColdChainConstant.HEADER_SIGNATURE) String signature,
+            @RequestHeader(SZColdChainConstant.HEADER_NONCE) String nonce,
+            @Validated @RequestBody MultPrintRequest multPrintRequest) {
+
+        Long enterpriseId = UserThreadLocal.getUserId();
+        EnterpriseWithSecretDTO enterprise = null;
+        try {
+            enterprise = enterpriseFeign.getByEnterpriseId(enterpriseId);
+            if (ObjectUtil.isEmpty(enterprise)) {
+                return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,"无效的enterpriseId");
+            }
+        } catch (SLException e) {
+            return ApiResponse.error(SZColdChainConstant.CODE_SERVICE_INNER_ERROR, "服务器错误");
+        }
+
+        if (ObjectUtil.isEmpty(multPrintRequest.getTransportOrderIds()) &&
+                ObjectUtil.isEmpty(multPrintRequest.getSubTransportOrderIds()) &&
+                ObjectUtil.isEmpty(multPrintRequest.getSenderReceiptOrderIds()) &&
+                ObjectUtil.isEmpty(multPrintRequest.getReceiverReceiptOrderIds()) &&
+                ObjectUtil.isEmpty(multPrintRequest.getPodSignedOrderIds()) &&
+                ObjectUtil.isEmpty(multPrintRequest.getRetentionCopyOrderIds())) {
+            return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,
+                    "transportOrderIds、subTransportOrderIds、senderReceiptOrderIds、receiverReceiptOrderIds、podSignedOrderIds、retentionCopyOrderIds不可同时为空");
+        }
+
+        ValidateVo validateVo = new ValidateVo();
+        validateVo.setNonce(nonce);
+        validateVo.setSignature(signature);
+        validateVo.setHttpMethod("POST");
+        validateVo.setHttpUrl("/enterprise/order/multPrint");
+        Map<String, Object> allNonNullFieldsSorted = ReflectUtil.getAllNonNullFieldsSorted(multPrintRequest);
+        validateVo.setParams(allNonNullFieldsSorted);
+        validateVo.setSalt(enterprise.getSalt());
+        validateVo.setHmacSha256SecretKey(enterprise.getHmacSha256SecretKey());
+        if (!DEBUG) {
+            headersValidator.validate(validateVo);
+        }
+        // 业务处理
+        ApiResponse<PrintResponse> response = orderService.getMultPrintInfo(multPrintRequest, enterprise);
+        return response;
+    }
+
+    @PostMapping("/cancel")
+    public ApiResponse<Boolean> cancelOrder(
+            @RequestHeader(SZColdChainConstant.HEADER_AUTHORIZATION) String authorization,
+            @RequestHeader(SZColdChainConstant.HEADER_SIGNATURE) String signature,
+            @RequestHeader(SZColdChainConstant.HEADER_NONCE) String nonce,
+            @Validated @RequestBody CancelRequest cancelRequest) {
+
+        Long enterpriseId = UserThreadLocal.getUserId();
+        EnterpriseWithSecretDTO enterprise = null;
+        try {
+            enterprise = enterpriseFeign.getByEnterpriseId(enterpriseId);
+            if (ObjectUtil.isEmpty(enterprise)) {
+                return ApiResponse.error(  SZColdChainConstant.CODE_PARAM_FORMAT_ERROR,"无效的enterpriseId");
+            }
+        } catch (SLException e) {
+            e.printStackTrace();
+            return ApiResponse.error(SZColdChainConstant.CODE_SERVICE_INNER_ERROR, "服务器错误");
+        }
+
+        ValidateVo validateVo = new ValidateVo();
+        validateVo.setNonce(nonce);
+        validateVo.setSignature(signature);
+        validateVo.setHttpMethod("POST");
+        validateVo.setHttpUrl("/enterprise/order/cancel");
+        Map<String, Object> allNonNullFieldsSorted = ReflectUtil.getAllNonNullFieldsSorted(cancelRequest);
+        validateVo.setParams(allNonNullFieldsSorted);
+        validateVo.setSalt(enterprise.getSalt());
+        validateVo.setHmacSha256SecretKey(enterprise.getHmacSha256SecretKey());
+        if (!DEBUG) {
+            headersValidator.validate(validateVo);
+        }
+
+        return orderService.cancelOrder(cancelRequest);
+    }
+}

+ 52 - 0
src/main/java/com/sl/ms/web/enterprise/enums/PrintCodeEnum.java

@@ -0,0 +1,52 @@
+package com.sl.ms.web.enterprise.enums;
+
+
+import cn.hutool.core.util.EnumUtil;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 14:08
+ * @className PrintCode
+ */
+public enum PrintCodeEnum {
+    NOT_USED(0x0001L, "保留"),
+    RPINT_TEST_PAGE(0x0002L, "打印测试页"),
+    PRINT_MASTER(0x0010L, "打印主单"),
+    PRINT_HOUSE_WAYBILL(0x0020L, "打印子单"),
+    PRINT_SENDER_RECEIPT(0x0040L, "打印寄方回单"),
+    PRINT_RECEVIER_RECEIPT(0x0080L, "打印派方回单"),
+    PRINT_POD_SIGNED(0x0100L, "打印签收联"),
+    PRINT_RETENTION_COPY(0x0200L, "打印留存联");
+
+    PrintCodeEnum(Long code, String value) {
+        this.code = code;
+        this.value = value;
+    }
+
+    /**
+     * 类型编码
+     */
+    @JsonValue
+    private final Long code;
+
+    /**
+     * 类型值
+     */
+    private final String value;
+
+
+    public Long getCode() {
+        return code;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static PrintCodeEnum codeOf(Long code) {
+        return EnumUtil.getBy(PrintCodeEnum::getCode, code);
+    }
+
+}

+ 58 - 0
src/main/java/com/sl/ms/web/enterprise/enums/PrintModeEnum.java

@@ -0,0 +1,58 @@
+package com.sl.ms.web.enterprise.enums;
+
+
+import cn.hutool.core.util.EnumUtil;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.sl.ms.user.domain.enums.EnterpriseStatus;
+
+/**
+ * @author sll
+ * @description: 模式
+ * @date 2025/9/9 14:08
+ * @className PrintMode
+ */
+public enum PrintModeEnum {
+    /**
+     * 打印
+     */
+    PRINT(0L, "打印"),
+
+    /**
+     * 预览PNG
+     */
+    REVIEW_PNG(1L, "预览PNG"),
+    /**
+     * 预览PDF
+     */
+    REVIEW_PDF(2L, "预览PDF"),;
+
+    PrintModeEnum(Long code, String value) {
+        this.code = code;
+        this.value = value;
+    }
+
+    /**
+     * 类型编码
+     */
+    @JsonValue
+    private final Long code;
+
+    /**
+     * 类型值
+     */
+    private final String value;
+
+
+    public Long getCode() {
+        return code;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static PrintModeEnum codeOf(Long code) {
+        return EnumUtil.getBy(PrintModeEnum::getCode, code);
+    }
+
+}

+ 24 - 0
src/main/java/com/sl/ms/web/enterprise/properties/JwtProperties.java

@@ -0,0 +1,24 @@
+package com.sl.ms.web.enterprise.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix = "sl.jwt")
+@Data
+public class JwtProperties {
+
+    /**
+     * RSA私钥(base64加密后)
+     */
+    private String privateKey;
+    /**
+     * RSA公钥(base64加密后)
+     */
+    private String publicKey;
+    /**
+     * 短令牌有效期时间,单位:小时
+     */
+    private Integer accessTtl;
+}

+ 15 - 0
src/main/java/com/sl/ms/web/enterprise/service/LoginService.java

@@ -0,0 +1,15 @@
+package com.sl.ms.web.enterprise.service;
+
+import com.sl.ms.web.enterprise.vo.request.AccountLoginVO;
+import com.sl.ms.web.enterprise.vo.request.LoginRequest;
+import com.sl.ms.web.enterprise.vo.response.TokenResponse;
+
+public interface LoginService {
+    /**
+     * 账号登录
+     *
+     * @param loginRequest 账号登录请求
+     * @return TokenResponse
+     */
+    TokenResponse login(LoginRequest loginRequest);
+}

+ 23 - 0
src/main/java/com/sl/ms/web/enterprise/service/OrderService.java

@@ -0,0 +1,23 @@
+package com.sl.ms.web.enterprise.service;
+
+
+import com.sl.ms.user.domain.dto.EnterpriseWithSecretDTO;
+import com.sl.ms.web.enterprise.vo.request.*;
+import com.sl.ms.web.enterprise.vo.response.ApiResponse;
+import com.sl.ms.web.enterprise.vo.response.OrderResponse;
+import com.sl.ms.web.enterprise.vo.response.PrintResponse;
+import com.sl.ms.web.enterprise.vo.response.TransportInfoResponse;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 22:02
+ * @className OrderService
+ */
+public interface OrderService {
+    ApiResponse<OrderResponse> createOrder(OrderRequest orderRequest);
+    ApiResponse<TransportInfoResponse> queryTransportInfo(TransportInfoRequest transportInfoRequest);
+    ApiResponse<PrintResponse> getPrintInfo(PrintRequest printRequest, EnterpriseWithSecretDTO enterprise);
+    ApiResponse<PrintResponse> getMultPrintInfo(MultPrintRequest printRequest, EnterpriseWithSecretDTO enterprise);
+    ApiResponse<Boolean> cancelOrder(CancelRequest cancelRequest);
+}

+ 21 - 0
src/main/java/com/sl/ms/web/enterprise/service/TokenService.java

@@ -0,0 +1,21 @@
+package com.sl.ms.web.enterprise.service;
+
+
+import java.util.Map;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/3 18:57
+ * @className TokenService
+ */
+public interface TokenService {
+    /**
+     * 生成短令牌
+     *
+     * @param claims token中存储的数据
+     * @return 短令牌
+     */
+    String createAccessToken(Map<String, Object> claims);
+
+}

+ 45 - 0
src/main/java/com/sl/ms/web/enterprise/service/impl/LoginServiceImpl.java

@@ -0,0 +1,45 @@
+package com.sl.ms.web.enterprise.service.impl;
+
+import cn.hutool.core.map.MapUtil;
+import com.itheima.auth.sdk.AuthTemplate;
+import com.itheima.auth.sdk.common.Result;
+import com.itheima.auth.sdk.dto.LoginDTO;
+import com.sl.ms.web.enterprise.config.SZColdChainConstant;
+import com.sl.ms.web.enterprise.properties.JwtProperties;
+import com.sl.ms.web.enterprise.service.LoginService;
+import com.sl.ms.web.enterprise.service.TokenService;
+import com.sl.ms.web.enterprise.vo.request.AccountLoginVO;
+import com.sl.ms.web.enterprise.vo.request.LoginRequest;
+import com.sl.ms.web.enterprise.vo.response.TokenResponse;
+import com.sl.transport.common.constant.Constants;
+import com.sl.transport.common.exception.SLWebException;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+@Service
+public class LoginServiceImpl implements LoginService {
+    @Resource
+    private AuthTemplate authTemplate;
+
+    @Resource
+    private TokenService tokenService;
+
+    @Resource
+    private JwtProperties jwtProperties;
+
+    @Override
+    public TokenResponse login(LoginRequest loginRequest) {
+        Map<String, Object> claims = MapUtil.<String, Object>builder()
+                .put(Constants.GATEWAY.ENTERPRISE_ID, loginRequest.getEnterpriseId()).build();
+        long expiresIn = jwtProperties.getAccessTtl() * 3600L; // 将小时转换为秒
+        return TokenResponse
+                .builder()
+                .accessToken(tokenService.createAccessToken(claims))
+                // 这里直接设置一个固定的超时时间吗?比如 2个小时超时,就返回 当前时间+2h 后的 时间戳 (单位s)吗?
+                .expiresIn(expiresIn)
+                .tokenType(SZColdChainConstant.ACCESS_TOKEN_TYPE)
+                .build();
+    }
+}

+ 1024 - 0
src/main/java/com/sl/ms/web/enterprise/service/impl/OrderServiceImpl.java

@@ -0,0 +1,1024 @@
+package com.sl.ms.web.enterprise.service.impl;
+
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.itheima.auth.sdk.dto.UserDTO;
+import com.sl.ms.base.api.common.AreaFeign;
+import com.sl.ms.base.domain.base.AreaDto;
+import com.sl.ms.base.domain.base.UserResponseDTO;
+import com.sl.ms.oms.api.CargoFeign;
+import com.sl.ms.oms.api.EnterpriseOrderFeign;
+import com.sl.ms.oms.api.OrderFeign;
+import com.sl.ms.oms.dto.CargoSaveDTO;
+import com.sl.ms.oms.dto.EnterpriseOrderDTO;
+import com.sl.ms.oms.dto.MailingSaveMultipleDTO;
+import com.sl.ms.oms.dto.OrderDTO;
+import com.sl.ms.oms.enums.*;
+import com.sl.ms.trade.api.AgencyTradingFeign;
+import com.sl.ms.trade.enums.ReturnFeeTypeEnum;
+import com.sl.ms.transport.api.OrganFeign;
+import com.sl.ms.user.api.AddressBookFeign;
+import com.sl.ms.user.api.EnterpriseFeign;
+import com.sl.ms.user.domain.dto.AddressBookDTO;
+import com.sl.ms.user.domain.dto.EnterpriseWithSecretDTO;
+import com.sl.ms.web.enterprise.config.SZColdChainConstant;
+import com.sl.ms.web.enterprise.enums.PrintCodeEnum;
+import com.sl.ms.web.enterprise.enums.PrintModeEnum;
+import com.sl.ms.web.enterprise.service.OrderService;
+import com.sl.ms.web.enterprise.utils.PrintApiUtils;
+import com.sl.ms.web.enterprise.utils.TransportOrderStatusUtils;
+import com.sl.ms.web.enterprise.vo.request.*;
+import com.sl.ms.web.enterprise.vo.response.*;
+import com.sl.ms.work.api.PickupDispatchTaskFeign;
+import com.sl.ms.work.api.RetentionScanRecordFeign;
+import com.sl.ms.work.api.TransportOrderFeign;
+import com.sl.ms.work.domain.dto.PickupDispatchTaskDTO;
+import com.sl.ms.work.domain.dto.SubTransportOrderDTO;
+import com.sl.ms.work.domain.dto.TransportOrderDTO;
+import com.sl.ms.work.domain.dto.request.TransportOrderQueryDTO;
+import com.sl.ms.work.domain.enums.ReceiptOrderTypeEnum;
+import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskCancelReason;
+import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskStatus;
+import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskType;
+import com.sl.ms.work.domain.enums.transportorder.TransportOrderStatus;
+import com.sl.transport.common.exception.SLException;
+import com.sl.transport.common.exception.SLWebException;
+import com.sl.transport.common.util.AuthTemplateThreadLocal;
+import com.sl.transport.common.util.ObjectUtil;
+import com.sl.transport.common.util.PageResponse;
+import com.sl.transport.common.util.UserThreadLocal;
+import com.sl.transport.domain.OrganDTO;
+import com.sl.transport.info.api.TransportInfoFeign;
+import com.sl.transport.info.domain.TransportInfoDTO;
+import com.sl.transport.info.domain.TransportInfoDetailDTO;
+import io.seata.spring.annotation.GlobalTransactional;
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 22:02
+ * @className OrderServoceImpl
+ */
+@Slf4j
+@Service
+public class OrderServiceImpl implements OrderService {
+    @Resource
+    OrderFeign orderFeign;
+
+    @Resource
+    AddressBookFeign addressBookFeign;
+
+    @Resource
+    AreaFeign areaFeign;
+
+    @Resource
+    TransportInfoFeign transportInfoFeign;
+
+    @Resource
+    EnterpriseOrderFeign enterpriseOrderFeign;
+
+    final BigDecimal VOLUME_FACTOR = BigDecimal.valueOf(1000000L);
+    @Autowired
+    private OrganFeign organFeign;
+    @Autowired
+    private TransportOrderFeign transportOrderFeign;
+
+    @Resource
+    AgencyTradingFeign agencyTradingFeign;
+
+    @Resource
+    PickupDispatchTaskFeign pickupDispatchTaskFeign;
+    private EnterpriseFeign enterpriseFeign;
+
+    @Resource
+    PrintApiUtils printApiUtils;
+
+    @Resource
+    CargoFeign cargoFeign;
+    @Autowired
+    private RetentionScanRecordFeign retentionScanRecordFeign;
+
+    @Override
+    @GlobalTransactional
+    public ApiResponse<OrderResponse> createOrder(OrderRequest orderRequest) {
+        log.info("【开放第三方接口-createOrder-参数】" + JSONObject.toJSONString(orderRequest));
+        // 校验数据
+        // todo: 校验数据
+
+        // 组装参数
+        // 将地址进行保存,组装寄件地址、派件地址
+        HashSet<String> areaNames = new HashSet<>();
+        areaNames.add(orderRequest.getSenderProvince());
+        areaNames.add(orderRequest.getReceiverProvince());
+        areaNames.add(orderRequest.getSenderCity());
+        areaNames.add(orderRequest.getReceiverCity());
+        areaNames.add(orderRequest.getSenderCounty());
+        areaNames.add(orderRequest.getReceiverCounty());
+
+        List<AreaDto> areaDtoList = areaFeign.findByNamesV2(areaNames);
+        Map<String, AreaDto> map = new HashMap<>();
+        areaDtoList.stream().forEach(areaDto -> {
+            map.put(areaDto.getName(), areaDto);
+        });
+        AreaDto senderProvince = map.get(orderRequest.getSenderProvince());
+        AreaDto receiverProvince = map.get(orderRequest.getReceiverProvince());
+        AreaDto senderCity = map.get(orderRequest.getSenderCity());
+        AreaDto receiverCity = map.get(orderRequest.getReceiverCity());
+        AreaDto senderCounty = map.get(orderRequest.getSenderCounty());
+        AreaDto receiverCounty = map.get(orderRequest.getReceiverCounty());
+
+        if (ObjectUtils.anyNull(senderProvince, receiverProvince, senderCity, receiverCity, senderCounty, receiverCounty)) {
+            return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR, "地址解析错误");
+        }
+
+        // 新增地址簿
+        AddressBookDTO senderAddress =  saveSenderAddress(orderRequest, senderProvince.getId(), senderCity.getId(), senderCounty.getId());
+        // 新增地址簿
+        AddressBookDTO receiverAddress = saveReceiverAddress(orderRequest, receiverProvince.getId(), receiverCity.getId(), receiverCounty.getId());
+
+        MailingSaveMultipleDTO mailingSaveMultipleDTO = new MailingSaveMultipleDTO();
+        mailingSaveMultipleDTO.setSendAddress(senderAddress.getId());
+        mailingSaveMultipleDTO.setReceiptAddress(receiverAddress.getId());
+        mailingSaveMultipleDTO.setPickupType(orderRequest.getPickupType());
+        mailingSaveMultipleDTO.setDispatchType(orderRequest.getDispatchType());
+        mailingSaveMultipleDTO.setLogisticsType(orderRequest.getLogisticsType());
+        mailingSaveMultipleDTO.setPayMethod(orderRequest.getPayMethod());
+        mailingSaveMultipleDTO.setPickUpTime(LocalDateTime.now().plusHours(2));
+        // 通过寄件网点编码 确认寄件网点,如果为空就匹配寄件网点
+        if (ObjectUtil.isNotEmpty(orderRequest.getSenderAgencyNumber())) {
+            List<OrganDTO> organDTOS = organFeign.findByOrganNumber(orderRequest.getSenderAgencyNumber());
+            if(ObjectUtil.isEmpty(organDTOS)){
+                return ApiResponse.error(SZColdChainConstant.CODE_PARAM_FORMAT_ERROR, "寄件网点不存在!");
+            }
+            mailingSaveMultipleDTO.setSendAgencyId(organDTOS.get(0).getId());
+        }
+
+        List<CargoSaveDTO> cargoSaveDTOList = new ArrayList<CargoSaveDTO>();
+        CargoSaveDTO cargoSaveDTO = new CargoSaveDTO();
+        cargoSaveDTO.setCargoBarcode(orderRequest.getCargoBarcode());
+        cargoSaveDTO.setName(orderRequest.getGoodsName());
+        cargoSaveDTO.setQuantity(orderRequest.getQuantity());
+        cargoSaveDTO.setVolume(orderRequest.getTotalVolume().multiply(VOLUME_FACTOR));
+        cargoSaveDTO.setWeight(orderRequest.getTotalWeight());
+        cargoSaveDTO.setTemperatureRegion(orderRequest.getTemperatureRegion());
+        cargoSaveDTO.setGoodsTypeId(1842957154249089025L);
+        cargoSaveDTOList.add(cargoSaveDTO);
+        mailingSaveMultipleDTO.setCargos(cargoSaveDTOList);
+
+        mailingSaveMultipleDTO.setOrderChannel(OrderChannelEnum.ENTERPRISE_OPEN_API.getCode());
+        mailingSaveMultipleDTO.setMark(orderRequest.getMark());
+        mailingSaveMultipleDTO.setChargeFee(orderRequest.getChargeFee());
+        mailingSaveMultipleDTO.setIsInsure(orderRequest.getIsInsure());
+        mailingSaveMultipleDTO.setDeclareValue(orderRequest.getDeclareValue());
+        mailingSaveMultipleDTO.setReceiptType(orderRequest.getReceiptType());
+        mailingSaveMultipleDTO.setEnterpriseId(orderRequest.getEnterpriseId()); // 企业客户id
+        mailingSaveMultipleDTO.setPartnerOrderCode(orderRequest.getPartnerOrderCode()); // 第三方订单编码
+        mailingSaveMultipleDTO.setTotalVolume(orderRequest.getTotalVolume().multiply(VOLUME_FACTOR));
+        mailingSaveMultipleDTO.setTotalWeight(orderRequest.getTotalWeight());
+        // 数据提交
+        OrderDTO orderDTO;
+        try {
+            orderDTO = orderFeign.mailingSaveMultiple(mailingSaveMultipleDTO);
+        } catch (SLException e) {
+            log.error("【mailingSaveMultiple报错】", e);
+            e.printStackTrace();
+            return ApiResponse.error(SZColdChainConstant.CODE_SERVICE_INNER_ERROR, "服务器错误");
+        }
+
+        OrderResponse orderResponse = orderDTO2OrderResponse(orderDTO, orderRequest);
+
+        EnterpriseOrderDTO enterpriseOrderDTO = new EnterpriseOrderDTO();
+        enterpriseOrderDTO.setOrderId(orderDTO.getId());
+        enterpriseOrderDTO.setEnterpriseId(orderRequest.getEnterpriseId());
+        enterpriseOrderDTO.setTransportOrderId(orderDTO.getTransportOrderId());
+        enterpriseOrderDTO.setPartnerOrderCode(orderRequest.getPartnerOrderCode());
+        enterpriseOrderDTO.setCustomerCode(orderRequest.getCustomerCode());
+        enterpriseOrderFeign.addNew(enterpriseOrderDTO);
+
+        return ApiResponse.success(orderResponse);
+    }
+    private AddressBookDTO saveSenderAddress(OrderRequest orderRequest, Long provinceId, Long cityId, Long countyId) {
+        LocalDateTime now = LocalDateTime.now();
+        AddressBookDTO addressBookDTO = AddressBookDTO.builder()
+                .name(orderRequest.getSenderName())
+                .phoneNumber(orderRequest.getSenderPhone())
+                .provinceId(provinceId)
+                .cityId(cityId)
+                .countyId(countyId)
+                .address(orderRequest.getSenderAddress())
+                .isDefault(1) // 默认,这里没有设置客户
+                .updated(now)
+                .isShow(1)
+                .type(1) // 寄件
+                .build();
+
+            addressBookDTO.setCreated(now);
+            AddressBookDTO savedDto = addressBookFeign.save(addressBookDTO);
+            return savedDto;
+
+    }
+
+    private AddressBookDTO saveReceiverAddress(OrderRequest orderRequest, Long provinceId, Long cityId, Long countyId) {
+        LocalDateTime now = LocalDateTime.now();
+        AddressBookDTO addressBookDTO = AddressBookDTO.builder()
+                .name(orderRequest.getReceiverName())
+                .phoneNumber(orderRequest.getReceiverPhone())
+                .provinceId(provinceId)
+                .cityId(cityId)
+                .countyId(countyId)
+                .address(orderRequest.getReceiverAddress())
+                .isDefault(1) // 默认,这里没有设置客户
+                .updated(now)
+                .isShow(1)
+                .type(2) // 收件
+                .build();
+
+            addressBookDTO.setCreated(now);
+            AddressBookDTO savedDto = addressBookFeign.save(addressBookDTO);
+            return savedDto;
+
+    }
+    private OrderResponse orderDTO2OrderResponse(OrderDTO orderDTO, OrderRequest orderRequest) {
+        OrderResponse orderResponse = BeanUtil.toBean(orderDTO, OrderResponse.class);
+        List<OrganDTO> organDTOS = organFeign.findAll("");
+        Map<Long, OrganDTO> organDTOMap = new HashMap<>(organDTOS.size());
+        for (OrganDTO organDTO : organDTOS) {
+            organDTOMap.put(organDTO.getId(), organDTO);
+        }
+
+        OrganDTO senderAgency = organDTOMap.get(orderDTO.getSendAgencyId());
+        OrganDTO receiverAgency = organDTOMap.get(orderDTO.getReceiveAgencyId());
+        OrganDTO senderParentAgency = organDTOMap.get(senderAgency.getParentId());
+        OrganDTO receiverParentAgency = organDTOMap.get(receiverAgency.getParentId());
+
+        orderResponse.setPayMethod(orderDTO.getPaymentMethod());
+        orderResponse.setPartnerOrderCode(orderRequest.getPartnerOrderCode());
+        orderResponse.setReceiverParentAgencyName(receiverParentAgency.getName());
+        orderResponse.setSenderParentAgencyName(senderParentAgency.getName());
+        orderResponse.setSenderAgencyName(senderAgency.getName());
+        orderResponse.setReceiverAgencyName(receiverAgency.getName());
+        orderResponse.setGoodsName(orderRequest.getGoodsName());
+        orderResponse.setCargoBarcode(orderRequest.getCargoBarcode());
+        orderResponse.setQuantity(orderRequest.getQuantity());
+        orderResponse.setTemperatureRegion(orderRequest.getTemperatureRegion());
+        orderResponse.setTotalVolume(orderRequest.getTotalVolume());
+        orderResponse.setTotalWeight(orderRequest.getTotalWeight());
+        orderResponse.setComputedWeight(orderDTO.getComputeWeight());
+        orderResponse.setSenderProvince(orderRequest.getSenderProvince());
+        orderResponse.setSenderCity(orderRequest.getSenderCity());
+        orderResponse.setSenderCounty(orderRequest.getSenderCounty());
+        orderResponse.setReceiverProvince(orderRequest.getReceiverProvince());
+        orderResponse.setReceiverCity(orderRequest.getReceiverCity());
+        orderResponse.setReceiverCounty(orderRequest.getReceiverCounty());
+        orderResponse.setSenderAgencyId(senderAgency.getId());
+        if (ObjectUtil.isNotEmpty(orderDTO.getReceiptType()) ) {
+            orderResponse.setReceiptOrderId("HD" + orderDTO.getTransportOrderId());
+        }
+
+        List<OrderResponse.SubTransportOrder> subTransportOrderList = new ArrayList<>();
+        for (Integer i = 1; i <= orderRequest.getQuantity(); i++) {
+            String subTransportOrderId = generateSubOrderNumber(orderDTO.getTransportOrderId(), i, orderRequest.getQuantity());
+            OrderResponse.SubTransportOrder subTransportOrder = new OrderResponse.SubTransportOrder();
+            subTransportOrder.setSubIndex(i);
+            subTransportOrder.setSubTransportOrderId(subTransportOrderId);
+            subTransportOrderList.add(subTransportOrder);
+        }
+        orderResponse.setSubTransportOrderList(subTransportOrderList);
+        return orderResponse;
+    }
+
+    private   String generateSubOrderNumber(String mainOrderNumber, int itemIndex, int totalItems) {
+        // 检查输入是否合理
+//        if (mainOrderNumber == null || mainOrderNumber.isEmpty() || itemIndex < 1 || itemIndex > 999 || totalItems < 1 || totalItems > 999) {
+//            throw new IllegalArgumentException("Invalid input: mainOrderNumber must be a non-empty string, and itemIndex and totalItems must be between 1 and 999.");
+//        }
+
+        // 确保 itemIndex 和 totalItems 为四位数格式
+        String paddedItemIndex = String.format("%04d", itemIndex);
+        String paddedTotalItems = String.format("%04d", totalItems);
+
+        // 构建子单号
+        return mainOrderNumber + paddedTotalItems + paddedItemIndex;
+    }
+
+    @Override
+    public ApiResponse<TransportInfoResponse> queryTransportInfo(TransportInfoRequest transportInfoRequest) {
+        EnterpriseOrderDTO enterpriseOrderDTO;
+        if (ObjectUtil.isNotEmpty(transportInfoRequest.getTransportOrderId())) {
+            enterpriseOrderDTO = enterpriseOrderFeign.getByTransportOrderId(transportInfoRequest.getTransportOrderId(), transportInfoRequest.getEnterpriseId());
+        } else {
+            enterpriseOrderDTO = enterpriseOrderFeign.getByPartnerOrderCode(transportInfoRequest.getPartnerOrderCode(), transportInfoRequest.getEnterpriseId());
+        }
+        if (ObjectUtil.isNull(enterpriseOrderDTO)) {
+            return ApiResponse.error(SZColdChainConstant.CODE_ENTERPRISE_ORDER_NOT_FOUND, "未查到该运单");
+        }
+        TransportOrderDTO transportOrderDTO = transportOrderFeign.findById(enterpriseOrderDTO.getTransportOrderId());
+        if (ObjectUtil.isEmpty(transportOrderDTO)) {
+            return ApiResponse.error(SZColdChainConstant.CODE_ENTERPRISE_ORDER_NOT_FOUND, "未查到该运单");
+        }
+
+        TransportInfoDTO transportInfoDTO;
+        try {
+            transportInfoDTO = transportInfoFeign.queryByTransportOrderId(transportInfoRequest.getTransportOrderId());
+        } catch (SLException e) {
+            TransportInfoResponse transportInfoResponse = new TransportInfoResponse();
+            transportInfoResponse.setTransportOrderId(transportOrderDTO.getId());
+            transportInfoResponse.setStatus(transportOrderDTO.getStatus().getCode());
+            transportInfoResponse.setDetails(new ArrayList<>());
+            return ApiResponse.success(transportInfoResponse);
+        }
+
+        TransportInfoResponse transportInfoResponse = new TransportInfoResponse();
+        transportInfoResponse.setTransportOrderId(transportOrderDTO.getId());
+        transportInfoResponse.setStatus(transportOrderDTO.getStatus().getCode());
+        transportInfoResponse.setStatusText(transportOrderDTO.getStatus().getValue());
+
+        List<TransportInfoDetailDTO> infoDetailDTOS = transportInfoDTO.getInfoList();
+        List<TransportInfoResponse.TransportDetail> details = infoDetailDTOS.stream().map(dto -> {
+            TransportInfoResponse.TransportDetail detail = new TransportInfoResponse.TransportDetail();
+            detail.setContext(dto.getInfo());
+            detail.setLocation(dto.getStatus());
+            LocalDateTime localDateTime = LocalDateTimeUtil.of(dto.getCreated());
+            String formattedTime = LocalDateTimeUtil.format(localDateTime, "yyyy-MM-dd HH:mm:ss");
+            detail.setTime(formattedTime);
+            return detail;
+        }).collect(Collectors.toList());
+        transportInfoResponse.setDetails(details);
+
+        return ApiResponse.success(transportInfoResponse);
+    }
+
+    /**
+     * 根据面单类型,调用面单接口获取到图片地址
+     * @param printRequest
+     * @return
+     */
+    @Override
+    public ApiResponse<PrintResponse> getPrintInfo(PrintRequest printRequest, EnterpriseWithSecretDTO enterprise) {
+        String transportOrderId = printRequest.getTransportOrderId();
+        // 根据运单号获取数据详情,组装打印数据
+        EnterpriseOrderDTO enterpriseOrderDTO = enterpriseOrderFeign.getByTransportOrderId(printRequest.getTransportOrderId(), printRequest.getEnterpriseId());
+        if (ObjectUtil.isNull(enterpriseOrderDTO)) {
+            throw new SLException("未查到该运单");
+        }
+        PrintApiRequest printApiRequest = new PrintApiRequest();
+        Integer type = printRequest.getType();
+        printApiRequest.setCode(type2Code(type));
+        printApiRequest.setMode(PrintModeEnum.REVIEW_PDF.getCode());
+        printApiRequest.setPrintAgencyName(enterprise.getEnterpriseName()); // 企业名称
+        List<String> ids = new ArrayList<>();
+        switch (type) {
+            case 1:
+                ids.add(printRequest.getTransportOrderId());
+                createPrintData(printApiRequest, ids, null, null, null, null, null);
+                break;
+            case 2:
+               Integer subIndex = printRequest.getSubIndex();
+                createPrintData(printApiRequest, null, ids, null, null, null, null);
+                break;
+            case 3:
+                ids.add("HD" + printRequest.getTransportOrderId());
+                createPrintData(printApiRequest, null, null, ids, null, null, null);
+                break;
+            case 4:
+                ids.add("HD" + printRequest.getTransportOrderId());
+                createPrintData(printApiRequest, null, null, null, ids, null, null);
+                break;
+            case 5:
+                ids.add(printRequest.getTransportOrderId());
+                createPrintData(printApiRequest, null, null, null, null, ids, null);
+                break;
+            case 6:
+                ids.add(printRequest.getTransportOrderId());
+                createPrintData(printApiRequest, null, null, null, null, null, ids);
+                break;
+
+        }
+        PrintApiResponse printApiResponse;
+        try {
+            printApiResponse = printApiUtils.sendPrintRequest(printApiRequest);
+        } catch (SLException e) {
+            return ApiResponse.error(SZColdChainConstant.CODE_ENTERPRISE_ORDER_BUSINESS_ERROR, e.getMsg());
+        }
+
+        // 判断是否返回成功
+        PrintResponse printResponse = new PrintResponse();
+        printResponse.setPrintUrl(String.join(",", printApiResponse.getUrlList()));
+        printResponse.setTransportOrderId(printRequest.getTransportOrderId());
+        printResponse.setType(printRequest.getType().toString());
+        return ApiResponse.success(printResponse) ;
+    }
+
+
+    @Override
+    public ApiResponse<PrintResponse> getMultPrintInfo(MultPrintRequest printRequest, EnterpriseWithSecretDTO enterprise) {
+//        String transportOrderId = printRequest.getTransportOrderId();
+//        // 根据运单号获取数据详情,组装打印数据
+//        EnterpriseOrderDTO enterpriseOrderDTO = enterpriseOrderFeign.getByTransportOrderId(printRequest.getTransportOrderId(), printRequest.getEnterpriseId());
+//        if (ObjectUtil.isNull(enterpriseOrderDTO)) {
+//            throw new SLException("未查到该运单");
+//        }
+//        TransportOrderDTO transportOrderDTO = transportOrderFeign.findById(enterpriseOrderDTO.getTransportOrderId());
+        // 组装数据
+        String transportOrderIds =  printRequest.getTransportOrderIds();
+        String subTransportOrderIds =  printRequest.getSubTransportOrderIds();
+        String senderReceiptOrderIds =  printRequest.getSenderReceiptOrderIds();
+        String receiverReceiptOrderIds =  printRequest.getReceiverReceiptOrderIds();
+        String podSignedOrderIds =  printRequest.getPodSignedOrderIds();
+        String retentionCopyOrderIds =  printRequest.getRetentionCopyOrderIds();
+
+        List<String> transportOrderIdList = ObjectUtil.isNotEmpty(transportOrderIds) ? Arrays.asList(transportOrderIds.split(",")) : null;
+        List<String> subTransportOrderIdList = ObjectUtil.isNotEmpty(subTransportOrderIds) ? Arrays.asList(subTransportOrderIds.split(",")) : null;
+        List<String> senderReceiptOrderIdList = ObjectUtil.isNotEmpty(senderReceiptOrderIds) ? Arrays.asList(senderReceiptOrderIds.split(",")) : null;
+        List<String> receiverReceiptOrderIdList = ObjectUtil.isNotEmpty(receiverReceiptOrderIds) ? Arrays.asList(receiverReceiptOrderIds.split(",")) : null;
+        List<String> podSignedOrderIdList = ObjectUtil.isNotEmpty(podSignedOrderIds) ? Arrays.asList(podSignedOrderIds.split(",")) : null;
+        List<String> retentionCopyOrderIdList = ObjectUtil.isNotEmpty(retentionCopyOrderIds) ? Arrays.asList(retentionCopyOrderIds.split(",")) : null;
+
+        PrintApiRequest printApiRequest = new PrintApiRequest();
+        printApiRequest.setCode(getCodeByList(transportOrderIdList,
+                subTransportOrderIdList,
+                senderReceiptOrderIdList,
+                receiverReceiptOrderIdList,
+                podSignedOrderIdList,
+                retentionCopyOrderIdList));
+        printApiRequest.setMode(PrintModeEnum.REVIEW_PDF.getCode());
+        printApiRequest.setPrintAgencyName(enterprise.getEnterpriseName()); // 企业名称
+
+
+
+        createPrintData(printApiRequest, transportOrderIdList,
+                subTransportOrderIdList,
+                senderReceiptOrderIdList,
+                receiverReceiptOrderIdList,
+                podSignedOrderIdList,
+                retentionCopyOrderIdList);
+
+        PrintApiResponse printApiResponse;
+        try {
+            printApiResponse = printApiUtils.sendPrintRequest(printApiRequest);
+        } catch (SLException e) {
+            return ApiResponse.error(SZColdChainConstant.CODE_ENTERPRISE_ORDER_BUSINESS_ERROR, e.getMsg());
+        }
+
+        // 判断是否返回成功
+        PrintResponse printResponse = new PrintResponse();
+        printResponse.setPrintUrl(String.join(",", printApiResponse.getUrlList()));
+        printResponse.setTransportOrderId("");
+        printResponse.setType("");
+        return ApiResponse.success(printResponse) ;
+    }
+
+    private Long getCodeByList(List<String> transportOrderIds,
+                                  List<String> subTransportOrderIds,
+                                  List<String> senderReceiptOrderIds,
+                                  List<String> receiverReceiptOrderIds,
+                                  List<String> podSignedOrderIds,
+                                  List<String> retentionCopyOrderIds) {
+        Long code = 0L;
+        if (ObjectUtil.isNotEmpty(transportOrderIds)) {
+            code = code | PrintCodeEnum.PRINT_MASTER.getCode();
+        }
+        if (ObjectUtil.isNotEmpty(subTransportOrderIds)) {
+            code = code | PrintCodeEnum.PRINT_HOUSE_WAYBILL.getCode();
+        }
+        if (ObjectUtil.isNotEmpty(senderReceiptOrderIds)) {
+            code = code | PrintCodeEnum.PRINT_SENDER_RECEIPT.getCode();
+        }
+        if (ObjectUtil.isNotEmpty(receiverReceiptOrderIds)) {
+            code = code | PrintCodeEnum.PRINT_SENDER_RECEIPT.getCode();
+        }
+        if (ObjectUtil.isNotEmpty(podSignedOrderIds)) {
+            code = code | PrintCodeEnum.PRINT_POD_SIGNED.getCode();
+        }
+        if (ObjectUtil.isNotEmpty(retentionCopyOrderIds)) {
+            code = code | PrintCodeEnum.PRINT_RETENTION_COPY.getCode();
+        }
+        return code;
+    }
+
+    private void createPrintData(PrintApiRequest printApiRequest, List<String> transportOrderIds,
+                                 List<String> subTransportOrderIds,
+                                 List<String> senderReceiptOrderIds,
+                                 List<String> receiverReceiptOrderIds,
+                                 List<String> podSignedOrderIds,
+                                 List<String> retentionCopyOrderIds) {
+        // 1. 获取需要查询的运单号,查询运单详情
+        Set<String> transportOrderIdSet = new HashSet<>();
+        if (ObjectUtil.isNotNull(transportOrderIdSet) && !transportOrderIdSet.isEmpty()) {
+            transportOrderIdSet.addAll(transportOrderIds);
+        }
+        if (ObjectUtil.isNotNull(subTransportOrderIds) && !subTransportOrderIds.isEmpty()) {
+            subTransportOrderIds.forEach(subTransportOrderId -> {
+                if (ObjectUtil.isNotEmpty(subTransportOrderId) && subTransportOrderId.length() == 20) {
+                    transportOrderIdSet.add(subTransportOrderId.substring(0, 12));
+                }
+            });
+        }
+        if (ObjectUtil.isNotNull(senderReceiptOrderIds) && !subTransportOrderIds.isEmpty()) {
+            senderReceiptOrderIds.forEach(senderReceiptOrderId -> {
+                if (ObjectUtil.isNotEmpty(senderReceiptOrderId) && senderReceiptOrderId.length() == 14) {
+                    transportOrderIdSet.add(senderReceiptOrderId.replace("HD", ""));
+                }
+            });
+        }
+        if (ObjectUtil.isNotNull(receiverReceiptOrderIds) && !receiverReceiptOrderIds.isEmpty()) {
+            receiverReceiptOrderIds.forEach(receiverReceiptOrderId -> {
+                if (ObjectUtil.isNotEmpty(receiverReceiptOrderId) && receiverReceiptOrderId.length() == 14) {
+                    transportOrderIdSet.add(receiverReceiptOrderId.replace("HD", ""));
+                }
+            });
+        }
+        if (ObjectUtil.isNotNull(podSignedOrderIds) && !podSignedOrderIds.isEmpty()) {
+            podSignedOrderIds.forEach(podSignedOrderId -> {
+                if (ObjectUtil.isNotEmpty(podSignedOrderId) && podSignedOrderId.length() == 12) {
+                    transportOrderIdSet.add(podSignedOrderId);
+                }
+            });
+        }
+        if (ObjectUtil.isNotNull(retentionCopyOrderIds) && !retentionCopyOrderIds.isEmpty()) {
+            retentionCopyOrderIds.forEach(retentionCopyOrderId -> {
+                if (ObjectUtil.isNotEmpty(retentionCopyOrderId) && retentionCopyOrderId.length() == 12) {
+                    transportOrderIdSet.add(retentionCopyOrderId);
+                }
+            });
+        }
+        TransportOrderQueryDTO transportOrderDTO = new TransportOrderQueryDTO();
+        transportOrderDTO.setRelatedAgencyIds(null);
+        transportOrderDTO.setSearchAgencyId(null);
+        transportOrderDTO.setPage(1);
+        transportOrderDTO.setPageSize(1000);
+        transportOrderDTO.setTransportOrderIds(new ArrayList<>(transportOrderIdSet));
+        PageResponse<TransportOrderDTO> dtoPageResponse = transportOrderFeign.findByPageWithSubTransportOrderList(transportOrderDTO);
+
+        List<BaseWaybill> baseWaybills = batchParseTransportOrderDTO2Vo(dtoPageResponse.getItems());
+
+        // 2. 拼接数据
+        Map<String, BaseWaybill> baseWaybillMap = new HashMap<>(baseWaybills.size());
+        baseWaybills.forEach(bw -> {
+            baseWaybillMap.put(bw.getTransportOrderId(), bw);
+        });
+
+
+        // 3. 根据 各项数据 创建参数,赋值
+        if (ObjectUtil.isNotEmpty(transportOrderIds)) {
+            List<BaseWaybill> masterWaybills = new ArrayList<>(transportOrderIds.size());
+            for (String transportOrderId : transportOrderIds) {
+                masterWaybills.add(baseWaybillMap.get(transportOrderId));
+            }
+            printApiRequest.setMasterWaybillList(masterWaybills);
+        }
+
+        if (ObjectUtil.isNotEmpty(subTransportOrderIds)) {
+            List<BaseWaybill> houseWaybillList = new ArrayList<>(subTransportOrderIds.size());
+            for (String subTransportOrderId : subTransportOrderIds) {
+                String transportOrderId = subTransportOrderId.substring(0, 12);
+                BaseWaybill baseWaybill =  baseWaybillMap.get(transportOrderId);
+                // todo: 子单
+                HouseWaybill houseWaybill = BeanUtil.toBean(baseWaybill, HouseWaybill.class);
+                houseWaybill.setSubTransportOrderId(subTransportOrderId);
+                SubTransportOrderVo subTransportOrderVo = baseWaybill.getSubTransportOrderMap().get(subTransportOrderId);
+                houseWaybill.setSubIndex(subTransportOrderVo.getIndex());
+                houseWaybill.setTemperatureRegion(subTransportOrderVo.getTemperatureRegionText());
+                houseWaybillList.add(houseWaybill);
+            }
+            printApiRequest.setHouseWaybillList(houseWaybillList);
+        }
+
+        if (ObjectUtil.isNotEmpty(senderReceiptOrderIds)) {
+            List<BaseWaybill> senderReceiptList = new ArrayList<>(senderReceiptOrderIds.size());
+            for (String senderReceiptOrderId : senderReceiptOrderIds) {
+                String transportOrderId = senderReceiptOrderId.replace("HD", "");
+                BaseWaybill baseWaybill =  baseWaybillMap.get(transportOrderId);
+                senderReceiptList.add(baseWaybill);
+            }
+            printApiRequest.setSenderReceiptList(senderReceiptList);
+        }
+
+        if (ObjectUtil.isNotEmpty(receiverReceiptOrderIds)) {
+            List<BaseWaybill> receiverReceiptList = new ArrayList<>(receiverReceiptOrderIds.size());
+            for (String receiverReceiptOrderId : receiverReceiptOrderIds) {
+                String transportOrderId = receiverReceiptOrderId.replace("HD", "");
+                BaseWaybill baseWaybill =  baseWaybillMap.get(transportOrderId);
+                receiverReceiptList.add(baseWaybill);
+            }
+            printApiRequest.setReceiverReceiptList(receiverReceiptList);
+        }
+
+        if (ObjectUtil.isNotEmpty(podSignedOrderIds)) {
+            List<BaseWaybill> podSignedList = new ArrayList<>(podSignedOrderIds.size());
+            for (String podSignedOrderId : podSignedOrderIds) {
+                String transportOrderId = podSignedOrderId;
+                BaseWaybill baseWaybill =  baseWaybillMap.get(transportOrderId);
+                podSignedList.add(baseWaybill);
+            }
+            printApiRequest.setPodSignedList(podSignedList);
+        }
+
+        if (ObjectUtil.isNotEmpty(retentionCopyOrderIds)) {
+            List<BaseWaybill> retentionCopyList = new ArrayList<>(retentionCopyOrderIds.size());
+            for (String retentionCopyOrderId : retentionCopyOrderIds) {
+                String transportOrderId = retentionCopyOrderId;
+                BaseWaybill baseWaybill =  baseWaybillMap.get(transportOrderId);
+                retentionCopyList.add(baseWaybill);
+            }
+            printApiRequest.setRetentionCopyList(retentionCopyList);
+        }
+    }
+
+    private List<BaseWaybill> batchParseTransportOrderDTO2Vo(List<TransportOrderDTO> dtoList, Boolean... needReceiverLocation) {
+        if (CollUtil.isEmpty(dtoList)) {
+            return new ArrayList<>();
+        }
+        // 机构
+        Map<Long, OrganDTO> agencySimpleVOMap = organFeign.findAll("").stream()
+                .collect(Collectors.toMap(OrganDTO::getId, v -> v));
+
+        List<Long> orderIds = dtoList.parallelStream().map(TransportOrderDTO::getOrderId).distinct().collect(Collectors.toList());
+        // 货物
+        Map<Long, List<OrderCargoVO>> orderCargoDTOMap = new HashMap<>();
+        Map<Long, OrderCargoVO> orderCargoVOOrderIdMap = new HashMap<>();
+        // 订单
+        Map<Long, OrderDTO> orderMap = new HashMap<>();
+
+        Set<Long> areaIdSet = new HashSet<>();
+        if (ObjectUtil.isNotNull(orderIds) && orderIds.size() > 0) {
+            cargoFeign.listV2(orderIds).stream().forEach(cargoDTO -> {
+                OrderCargoVO orderCargoVO = BeanUtil.toBean(cargoDTO, OrderCargoVO.class);
+                Long keyOrderId = cargoDTO.getOrderId();
+                if (orderCargoDTOMap.containsKey(keyOrderId)) {
+                    orderCargoDTOMap.get(keyOrderId).add(orderCargoVO);
+                } else {
+                    List<OrderCargoVO> orderCargoDTOList = new ArrayList<>();
+                    orderCargoDTOList.add(orderCargoVO);
+                    orderCargoDTOMap.put(keyOrderId, orderCargoDTOList);
+                }
+                orderCargoVOOrderIdMap.put(orderCargoVO.getId(), orderCargoVO);
+            });
+
+//            List<OrderDTO>  orderDTOS =  orderFeign.findByIds(orderIds.stream().map(id -> id.toString()).collect(Collectors.toList()));
+            List<OrderDTO>  orderDTOS =  orderFeign.findByIdList(orderIds);
+            // 生成 memberNameMap
+
+            orderDTOS.stream().forEach(dto -> {
+                if (dto.getId() != null) {
+                    orderMap.put(dto.getId(), dto);
+                    areaIdSet.add(dto.getReceiverProvinceId());
+                    areaIdSet.add(dto.getReceiverCityId());
+                    areaIdSet.add(dto.getReceiverCountyId());
+                    areaIdSet.add(dto.getSenderProvinceId());
+                    areaIdSet.add(dto.getSenderCityId());
+                    areaIdSet.add(dto.getSenderCountyId());
+                }
+            });
+        }
+
+        List<AreaDto> areaDtoList = areaFeign.findBatch(new ArrayList<>(areaIdSet));
+        areaDtoList.stream().forEach(areaDto -> {areaDto.setName(areaDto.getOriginalName());});
+        Map<Long, AreaDto> areaMap = areaDtoList.stream().collect(Collectors.toMap(AreaDto::getId, vo -> vo));
+
+
+        Map<Long, List<OrderCargoVO>> finalOrderCargoVOMap = orderCargoDTOMap;
+        Map<Long, OrderDTO> finalOrderMap = orderMap;
+
+        // 派件任务
+        Map<Long, PickupDispatchTaskDTO> dispatchTaskMap = new HashMap<>();
+
+        List<PickupDispatchTaskDTO> pickupDispatchTaskDTOList = pickupDispatchTaskFeign.findByOrderIds(orderIds);
+        List<Long> userIdList = new ArrayList<>();
+
+        List<Long> finalUserIdList = userIdList;
+        pickupDispatchTaskDTOList.stream().forEach(dto -> {
+            if (dto.getTaskType().equals(PickupDispatchTaskType.DISPATCH) &&
+                    (dto.getStatus().equals(PickupDispatchTaskStatus.COMPLETED) ||
+                            dto.getStatus().equals(PickupDispatchTaskStatus.NEW))) {
+                if (dto.getOrderId() != null) {
+                    if (ObjectUtil.isNotEmpty(dto.getCourierId())) {
+                        finalUserIdList.add(dto.getCourierId());
+                    }
+                    dispatchTaskMap.put(dto.getOrderId(), dto);
+                }
+            }
+        });
+
+        List<BaseWaybill> resultVOs = dtoList.stream().map(dto -> {
+            BaseWaybill vo = BeanUtil.toBean(dto, BaseWaybill.class);
+            if (true) {
+                if (ObjectUtil.isEmpty(dto.getOrderId())) {
+                    return vo;
+                }
+
+                if (ObjectUtil.isEmpty(dto.getCurrentAgencyId())) {
+                    return vo;
+                }
+
+
+                OrderDTO orderDTO = finalOrderMap.get(dto.getOrderId());
+                if (ObjectUtil.isNotNull(orderDTO)) {
+                    vo.setMark(orderDTO.getMark());
+                    LocalDateTime localDateTime = LocalDateTimeUtil.of(dto.getCreated());
+                    String formattedTime = LocalDateTimeUtil.format(localDateTime, "yyyy-MM-dd HH:mm:ss");
+                    vo.setCreateTime(formattedTime);
+                    vo.setSenderName(orderDTO.getSenderName());
+                    vo.setSenderPhone(orderDTO.getSenderPhone());
+                    vo.setSenderAddress(orderDTO.getSenderAddress());
+                    vo.setReceiverName(orderDTO.getReceiverName());
+                    vo.setReceiverPhone(orderDTO.getReceiverPhone());
+                    vo.setReceiverAddress(orderDTO.getReceiverAddress());
+                    vo.setPaymentMethod(OrderPaymentMethod.lookup(orderDTO.getPaymentMethod()).getValue() );
+                    vo.setTemperatureRegion(OrderTemperatureRegionType.lookup(orderDTO.getTemperatureRegion()));
+                    vo.setDispatchType(OrderDeliveryMethod.getValueByCode(orderDTO.getDispatchType()));
+                    vo.setLogisticsType(OrderLogisticsTypeEnum.lookup(orderDTO.getLogisticsType()).getValue());
+                    vo.setStorageType(OrderStorageType.getValueByCode(orderDTO.getStorageType()));
+                    if (orderDTO.getIsInsure().equals(OrderIsInsureEnum.VALUE_DECLARED.getCode())) {
+                        vo.setDeclareValue(orderDTO.getDeclareValue().doubleValue());
+                    }
+                    if (orderDTO.getPaymentMethod().equals(OrderPaymentMethod.END_PAY.getCode())) {
+                        vo.setChargeFee(orderDTO.getChargeFee().doubleValue());
+                        vo.setChargeFeePrint(orderDTO.getChargeFee().toString() + "元");
+                    } else {
+                        vo.setChargeFee(new Double(0));
+                        vo.setChargeFeePrint("**");
+                    }
+                    
+                    if (ObjectUtil.isNotEmpty(orderDTO.getSenderProvinceId())) {
+                        AreaDto result = areaMap.get(orderDTO.getSenderProvinceId());
+                        if (ObjectUtil.isNotNull(result)) {
+                            vo.setSenderProvince(result.getName());
+                        }else {
+                            vo.setSenderProvince("");
+                        }
+                    }
+                    if (ObjectUtil.isNotEmpty(orderDTO.getSenderCityId())) {
+                        AreaDto result = areaMap.get(orderDTO.getSenderCityId());
+                        if (ObjectUtil.isNotNull(result)) {
+                            vo.setSenderCity(result.getName());
+                        }else {
+                            vo.setSenderCity("");
+                        }
+                    }
+                    if (ObjectUtil.isNotEmpty(orderDTO.getSenderCountyId())) {
+                        AreaDto result = areaMap.get(orderDTO.getSenderCountyId());
+                        if (ObjectUtil.isNotNull(result)) {
+                            vo.setSenderCounty(result.getName());
+                        }else {
+                            vo.setSenderCounty("");
+                        }
+                    }
+                    if (ObjectUtil.isNotEmpty(orderDTO.getReceiverProvinceId())) {
+                        AreaDto result = areaMap.get(orderDTO.getReceiverProvinceId());
+                        if (ObjectUtil.isNotNull(result)) {
+                            vo.setReceiverProvince(result.getName());
+                        }else {
+                            vo.setReceiverProvince("");
+                        }
+                    }
+                    if (ObjectUtil.isNotEmpty(orderDTO.getReceiverCityId())) {
+                        AreaDto result = areaMap.get(orderDTO.getReceiverCityId());
+                        if (ObjectUtil.isNotNull(result)) {
+                            vo.setReceiverCity(result.getName());
+                        }else {
+                            vo.setReceiverCity("");
+                        }
+                    }
+                    if (ObjectUtil.isNotEmpty(orderDTO.getReceiverCountyId())) {
+                        AreaDto result = areaMap.get(orderDTO.getReceiverCountyId());
+                        if (ObjectUtil.isNotNull(result)) {
+                            vo.setReceiverCounty(result.getName());
+                        }else {
+                            vo.setReceiverCounty("");
+                        }
+                    }
+
+                    if (ObjectUtil.isNotEmpty(orderDTO.getReceiptType())) {
+                        vo.setReceiptType(ReceiptOrderTypeEnum.getValueByCode(orderDTO.getReceiptType()) );
+                        vo.setReceiptOrderId("HD" + vo.getTransportOrderId());
+                    } else {
+                        vo.setReceiptType("");
+                        vo.setReceiptOrderId("");
+                    }
+                }
+
+                List<OrderCargoVO> orderCargoVOs = finalOrderCargoVOMap.get(dto.getOrderId());
+                if (ObjectUtil.isNotEmpty(orderCargoVOs)) {
+                    vo.setComputeWeight(ObjectUtil.isNotEmpty(orderCargoVOs.get(0).getComputeWeight()) ? orderCargoVOs.get(0).getComputeWeight().doubleValue() : orderCargoVOs.get(0).getTotalWeight().doubleValue());
+                    vo.setTotalVolume(ObjectUtil.isNotEmpty(orderCargoVOs.get(0).getTotalVolume()) ? orderCargoVOs.get(0).getTotalVolume().doubleValue()
+                            : orderCargoVOs.get(0).getVolume().doubleValue());
+                    Integer quantity = 0;
+                    List<String> cargoName = new ArrayList<>();
+                    Integer[] quantitys = new Integer[]{0, 0, 0};
+                    for (OrderCargoVO orderCargoVO : orderCargoVOs) {
+                        cargoName.add(orderCargoVO.getName());
+                        if (orderCargoVO.getTemperatureRegion().equals(OrderTemperatureRegionType.Frozen.getCode())) {
+                            quantitys[0] += quantitys[0] + orderCargoVO.getQuantity();
+                            quantity += orderCargoVO.getQuantity();
+                        }else if (orderCargoVO.getTemperatureRegion().equals(OrderTemperatureRegionType.Chilled.getCode())) {
+                            quantitys[1] += quantitys[1] + orderCargoVO.getQuantity();
+                            quantity += orderCargoVO.getQuantity();
+                        }else if (orderCargoVO.getTemperatureRegion().equals(OrderTemperatureRegionType.Room.getCode())) {
+                            quantitys[2] += quantitys[2] + orderCargoVO.getQuantity();
+                            quantity += orderCargoVO.getQuantity();
+                        }
+                    }
+                    vo.setQuantity(quantity);
+                    vo.setQuantityPrint(quantity + "("+ quantitys[0] + "," + quantitys[1] + "," + quantitys[2] + ")");
+                    vo.setCargoName(String.join(",", cargoName));
+                } else {
+                    vo.setComputeWeight(new Double(1));
+                    vo.setComputeWeight(new Double(0.01));
+                    vo.setQuantity(0);
+                    vo.setQuantityPrint(0 + "("+ 0 + "," + 0 + "," + 0 + ")");
+                    vo.setCargoName("");
+                }
+
+                OrganDTO startAgent = agencySimpleVOMap.get(dto.getStartAgencyId());
+                if (!ObjectUtil.isEmpty(startAgent)) {
+                    vo.setSenderAgencyName(startAgent.getName());
+                    OrganDTO startAgencyParent = agencySimpleVOMap.get(startAgent.getParentId());
+                    if (!ObjectUtil.isEmpty(startAgencyParent)) {
+                        vo.setStartParentAgencyName(startAgencyParent.getName());
+                    }
+                } else {
+                    vo.setSenderAgencyName("");
+                    vo.setStartParentAgencyName("");
+                }
+
+
+                OrganDTO endAgent = agencySimpleVOMap.get(dto.getEndAgencyId());
+                if (!ObjectUtil.isEmpty(endAgent)) {
+                    vo.setReceiverAgencyName(endAgent.getName());
+                    OrganDTO endAgencyParent = agencySimpleVOMap.get(endAgent.getParentId());
+                    if (!ObjectUtil.isEmpty(endAgencyParent)) {
+                        vo.setEndParentAgencyName(endAgencyParent.getName());
+                    } else {
+                        vo.setEndParentAgencyName("");
+                    }
+                } else {
+                    vo.setReceiverAgencyName("");
+                    vo.setEndParentAgencyName("");
+                }
+
+                List<SubTransportOrderDTO>  subTransportOrderDTOList = dto.getSubTransportOrderDTOList();
+                List<SubTransportOrderVo> subTransportOrderVoList = subTransportOrderDTO2VO(subTransportOrderDTOList, orderCargoVOOrderIdMap);
+                Map<String, SubTransportOrderVo> subTransportOrderVoMap = new LinkedHashMap<>();
+                subTransportOrderVoList.stream().forEach(item -> {
+                    subTransportOrderVoMap.put(item.getSubTransportOrderId(), item);
+                });
+
+                vo.setSubTransportOrderMap(subTransportOrderVoMap);
+                vo.setTransportOrderId(dto.getId());
+            }
+            return vo;
+        }).collect(Collectors.toList());
+
+        return resultVOs;
+    }
+    private  List<SubTransportOrderVo> subTransportOrderDTO2VO(List<SubTransportOrderDTO> subTransportOrderDTOList, Map<Long, OrderCargoVO> orderCargoVOOrderIdMap) {
+        if (cn.hutool.core.util.ObjectUtil.isNotEmpty(subTransportOrderDTOList)) {
+            return subTransportOrderDTOList.stream().map(subDto -> {
+                OrderCargoVO orderCargoVO = orderCargoVOOrderIdMap.get(subDto.getOrderCargoId());
+                if (cn.hutool.core.util.ObjectUtil.isNotNull(orderCargoVO)) {
+                    SubTransportOrderVo subTransportOrderVo = new SubTransportOrderVo();
+                    subTransportOrderVo.setIndex(subDto.getSubIndex());
+                    subTransportOrderVo.setGoodsName(orderCargoVO.getName());
+                    subTransportOrderVo.setCargoBarcode(orderCargoVO.getCargoBarcode());
+                    subTransportOrderVo.setTemperatureRegion(orderCargoVO.getTemperatureRegion());
+                    subTransportOrderVo.setTemperatureRegionText(OrderTemperatureRegionType.lookup(orderCargoVO.getTemperatureRegion()));
+                    subTransportOrderVo.setSubTransportOrderId(subDto.getId());
+                    return subTransportOrderVo;
+                }
+                return new SubTransportOrderVo();
+            }).collect(Collectors.toList());
+        }
+        return new ArrayList<>();
+    }
+    private Long type2Code(Integer type) {
+        switch (type) {
+            case 1:
+                return PrintCodeEnum.PRINT_MASTER.getCode();
+            case 2:
+                return PrintCodeEnum.PRINT_HOUSE_WAYBILL.getCode();
+            case 3:
+                return PrintCodeEnum.PRINT_SENDER_RECEIPT.getCode();
+            case 4:
+                return PrintCodeEnum.PRINT_RECEVIER_RECEIPT.getCode();
+            case 5:
+                return PrintCodeEnum.PRINT_POD_SIGNED.getCode();
+            case 6:
+                return PrintCodeEnum.PRINT_RETENTION_COPY.getCode();
+
+        }
+        throw new SLException("面单类型type无效");
+    }
+
+    @Override
+    @GlobalTransactional
+    public ApiResponse<Boolean> cancelOrder(CancelRequest cancelRequest) {
+       EnterpriseOrderDTO enterpriseOrderDTO =  enterpriseOrderFeign.getByTransportOrderId(cancelRequest.getTransportOrderId(), cancelRequest.getEnterpriseId());
+        if (ObjectUtil.isNull(enterpriseOrderDTO)) {
+            return ApiResponse.error( SZColdChainConstant.CODE_ENTERPRISE_ORDER_NOT_FOUND, "未查到该运单,删除失败");
+        }
+       Long orderId = enterpriseOrderDTO.getOrderId();
+       log.info("删单:" + orderId);
+        // 1. 判断订单状态,是否还在寄件网点,是否还未运出
+        OrderDTO orderDTO = orderFeign.findById(orderId);
+
+        Long sendAgencyId = orderDTO.getSendAgencyId();
+        Long currentAgencyId = orderDTO.getCurrentAgencyId();
+
+        Boolean canDeleteAfterTransport = false;
+
+        if (orderDTO.getStatus().equals(OrderStatus.CLOSE.getCode())
+                || orderDTO.getStatus().equals(OrderStatus.RECEIVED.getCode())
+                || orderDTO.getStatus().equals(OrderStatus.REJECTION.getCode())
+                || orderDTO.getStatus().equals(OrderStatus.DEL.getCode())
+                || orderDTO.getStatus().equals(OrderStatus.CANCELLED.getCode())) {
+            return ApiResponse.error(SZColdChainConstant.CODE_ENTERPRISE_ORDER_STATUS_CANNOT_DELETE, "订单当前状态无法进行删单操作");
+        }
+        // 普通账户,不可以删除已经运输中、派送中的、不在当前网点的单
+        if (!canDeleteAfterTransport
+                && (orderDTO.getStatus().equals(OrderStatus.IN_TRANSIT.getCode())
+                || orderDTO.getStatus().equals(OrderStatus.DISPATCHING.getCode())
+                || !orderDTO.getCurrentAgencyId().equals(orderDTO.getSendAgencyId()))){
+            return ApiResponse.error(SZColdChainConstant.CODE_ENTERPRISE_ORDER_STATUS_CANNOT_DELETE, "订单当前状态无法进行删单操作");
+        }
+        try {
+            // 还在寄件网点,并且不是已关闭、派送中、已签收、用户拒收、已删除、运输中、已取消的状态,其他都可以删除
+            // 2. 删单
+            OrderDTO orderDTO1 = new OrderDTO();
+            orderDTO1.setId(orderId);
+            orderDTO1.setStatus(OrderStatus.CANCELLED.getCode());
+            orderDTO1.setUpdated(LocalDateTime.now());
+            log.info("删单,修改订单状态");
+            TransportOrderDTO transportOrderDTO = transportOrderFeign.findByOrderId(orderDTO.getId());
+            if (ObjectUtil.isNotNull(transportOrderDTO)) {
+                if (!canDeleteAfterTransport && !(transportOrderDTO.getCurrentAgencyId().equals(sendAgencyId))) {
+                    return ApiResponse.error(SZColdChainConstant.CODE_ENTERPRISE_ORDER_STATUS_CANNOT_DELETE, "订单已从寄件网点寄出,无法删单,请联系平台");
+                }
+                transportOrderFeign.updateStatus(transportOrderDTO.getId(), TransportOrderStatus.DEL);
+
+                log.info("删单,修改运单状态");
+            }
+            orderFeign.updateById(orderId, orderDTO1);
+
+
+            // 3.1. 如果是已支付的,需要进行退费,在客户费用明细中进行标明,已删单
+
+            // 3.2. 如果已结算,需要回退费用
+            if (orderDTO.getSettleStatus().equals(OrderSettleStatus.SETTLED.getStatus())) {
+                agencyTradingFeign.returnFee(transportOrderDTO.getId(), ReturnFeeTypeEnum.DELETE_ORDER.getCode());
+                log.info("删单,回退费用");
+            }
+            // 3.3. 如果有取件任务,取消
+            if (orderDTO.getStatus().equals(OrderStatus.PENDING.getCode())) {
+                List<PickupDispatchTaskDTO> taskDTOList = pickupDispatchTaskFeign.findByOrderId(orderId, PickupDispatchTaskType.PICKUP);
+                taskDTOList.forEach(item -> {
+                    PickupDispatchTaskDTO taskDTO = new PickupDispatchTaskDTO();
+                    taskDTO.setId(item.getId());
+                    taskDTO.setStatus(PickupDispatchTaskStatus.CANCELLED);//取消状态
+                    taskDTO.setCancelReason(PickupDispatchTaskCancelReason.CANCEL_BY_USER);//取消原因
+                    taskDTO.setCancelReasonDescription("管理员删单");//取消原因具体描述
+                    pickupDispatchTaskFeign.updateStatus(taskDTO);
+
+                    log.info("删单,取消取件任务");
+                });
+            } else if (orderDTO.getStatus().equals(OrderStatus.TO_BE_DISPATCHED.getCode())
+                    || orderDTO.getStatus().equals(OrderStatus.DISPATCHING.getCode())) {
+                List<PickupDispatchTaskDTO> taskDTOList = pickupDispatchTaskFeign.findByOrderId(orderId, PickupDispatchTaskType.DISPATCH);
+                taskDTOList.forEach(item -> {
+                    PickupDispatchTaskDTO taskDTO = new PickupDispatchTaskDTO();
+                    taskDTO.setId(item.getId());
+                    taskDTO.setStatus(PickupDispatchTaskStatus.CANCELLED);//取消状态
+                    taskDTO.setCancelReason(PickupDispatchTaskCancelReason.CANCEL_BY_USER);//取消原因
+                    taskDTO.setCancelReasonDescription("管理员删单");//取消原因具体描述
+                    pickupDispatchTaskFeign.updateStatus(taskDTO);
+                    log.info("删单,取消取件任务");
+                });
+            }
+        } catch (SLException e) {
+            log.error("【cancelOrder】" + JSON.toJSONString(cancelRequest) + "," + e);
+            return ApiResponse.error( SZColdChainConstant.CODE_ENTERPRISE_ORDER_BUSINESS_ERROR, e.getMsg());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ApiResponse.error( SZColdChainConstant.CODE_ENTERPRISE_ORDER_STATUS_CANNOT_DELETE, "服务器错误");
+        }
+        return ApiResponse.success( true);
+    }
+}

+ 30 - 0
src/main/java/com/sl/ms/web/enterprise/service/impl/TokenServiceImpl.java

@@ -0,0 +1,30 @@
+package com.sl.ms.web.enterprise.service.impl;
+
+
+import cn.hutool.core.date.DateField;
+import com.sl.ms.web.enterprise.properties.JwtProperties;
+import com.sl.ms.web.enterprise.service.TokenService;
+import com.sl.transport.common.util.JwtUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/3 18:58
+ * @className TokenServiceImpl
+ */
+@Service
+public class TokenServiceImpl implements TokenService {
+    @Resource
+    private JwtProperties jwtProperties;
+
+    @Override
+    public String createAccessToken(Map<String, Object> claims) {
+        //生成令牌的有效期时间单位为:小时
+        return JwtUtils.createToken(claims, jwtProperties.getPrivateKey(), jwtProperties.getAccessTtl(),
+                DateField.HOUR);
+    }
+}

+ 92 - 0
src/main/java/com/sl/ms/web/enterprise/utils/HeadersValidator.java

@@ -0,0 +1,92 @@
+package com.sl.ms.web.enterprise.utils;
+
+
+import com.sl.ms.user.domain.dto.EnterpriseWithSecretDTO;
+import com.sl.ms.web.enterprise.config.SZColdChainConstant;
+import com.sl.ms.web.enterprise.vo.ValidateVo;
+import com.sl.transport.common.exception.SLWebException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/3 21:25
+ * @className NonceValidator
+ */
+@Component
+public class HeadersValidator {
+    private static final String NONCE_PREFIX = "sz-enterprise:nonce:";
+    private static final long NONCE_EXPIRE_MINUTES = 10; // 10分钟有效期
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    public void validate(ValidateVo validateVo) {
+        String signature = validateVo.getSignature();
+        String nonce = validateVo.getNonce();
+        // 验证时间戳是否过期
+        long timestamp = Long.parseLong(nonce.split(":")[0]);
+        if (Math.abs(System.currentTimeMillis() - timestamp ) > 5 * 60 * 1000) { // 5分钟有效期
+            throw new SLWebException("timestamp过期", SZColdChainConstant.CODE_TIMESTAMP_EXPIRED);
+        }
+
+        if (isNonceUsed(nonce)) {
+            throw new SLWebException("nonce重复", SZColdChainConstant.CODE_NONCE_REPEAT);
+        }
+
+        // 验证签名
+        String calculatedSignature = SZSignUtil.generateSignature(
+                validateVo.getHttpMethod().toUpperCase(),
+                validateVo.getHttpUrl(),
+                validateVo.getParams(),
+                nonce,
+                validateVo.getSalt(),
+                validateVo.getHmacSha256SecretKey()
+        );
+
+        storeNonce(nonce);
+
+        if (!calculatedSignature.equals(signature)) {
+            throw new SLWebException("签名无效", SZColdChainConstant.CODE_SIGN_INVALID);
+        }
+
+    }
+
+    /**
+     * 检查Nonce是否已使用过
+     * @param nonce 要检查的nonce值
+     * @return true如果已使用过,false如果未使用过
+     */
+    public boolean isNonceUsed(String nonce) {
+        String key = NONCE_PREFIX + nonce;
+        Boolean exists = redisTemplate.hasKey(key);
+        return exists != null && exists;
+    }
+
+    /**
+     * 存储Nonce到Redis
+     * @param nonce 要存储的nonce值
+     */
+    public void storeNonce(String nonce) {
+        String key = NONCE_PREFIX + nonce;
+        redisTemplate.opsForValue().set(key, "1", NONCE_EXPIRE_MINUTES, TimeUnit.MINUTES);
+    }
+
+    /**
+     * 验证Nonce并存储
+     * @param nonce 要验证的nonce值
+     * @return true如果验证通过,false如果nonce已存在
+     */
+    public boolean validateAndStoreNonce(String nonce) {
+        if (isNonceUsed(nonce)) {
+            return false;
+        }
+        storeNonce(nonce);
+        return true;
+    }
+}

+ 87 - 0
src/main/java/com/sl/ms/web/enterprise/utils/PrintApiUtils.java

@@ -0,0 +1,87 @@
+package com.sl.ms.web.enterprise.utils;
+
+
+import com.alibaba.fastjson.JSON;
+import com.sl.ms.web.enterprise.vo.PrintServiceException;
+import com.sl.ms.web.enterprise.vo.request.PrintApiRequest;
+import com.sl.ms.web.enterprise.vo.response.PrintApiResponse;
+import com.sl.ms.web.enterprise.vo.response.PrintResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Retryable;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 14:37
+ * @className PrintApiUtils
+ */
+@Slf4j
+@Service
+public class PrintApiUtils {
+    @Resource
+     RestTemplate restTemplate;
+
+    @Value("${print.service.url:http://121.196.179.31:36610/print}")
+    private String printServiceUrl;
+
+    @Retryable(value = {PrintServiceException.class},
+            maxAttempts = 3,
+            backoff = @Backoff(delay = 1000))
+    public PrintApiResponse sendPrintRequest(PrintApiRequest request) {
+        try {
+            log.info("【PrintApiRequest】" + JSON.toJSONString(request));
+
+            // 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+
+            // 构建请求实体
+            HttpEntity<PrintApiRequest> requestEntity = new HttpEntity<>(request, headers);
+
+            // 1. 先接收 String 类型
+            ResponseEntity<String> responseEntity = restTemplate.exchange(
+                    printServiceUrl,
+                    HttpMethod.POST,
+                    requestEntity,
+                    String.class);  // 注意这里改为 String.class
+
+            // 检查响应状态
+            if (responseEntity.getStatusCode() != HttpStatus.OK) {
+                throw new PrintServiceException("打印服务返回非200状态: " + responseEntity.getStatusCodeValue());
+            }
+
+            String responseBody = responseEntity.getBody();
+            if (responseBody == null) {
+                throw new PrintServiceException("打印服务返回空响应");
+            }
+
+            // 2. 手动解析 JSON
+            PrintApiResponse response = JSON.parseObject(responseBody, PrintApiResponse.class);
+
+            // 检查业务码
+            if (response.getCode() != 0) {
+                throw new PrintServiceException("打印服务业务异常: " + response.getMessage());
+            }
+
+            return response;
+        } catch (Exception e) {
+            throw new PrintServiceException("调用打印服务失败: " + e.getMessage(), e);
+        }
+    }
+
+    // 重试失败后的降级方法
+    public PrintResponse sendPrintRequestFallback(PrintApiRequest request, Exception e) {
+        // 这里可以实现降级逻辑,比如记录日志、保存到数据库稍后重试等
+        throw new PrintServiceException("打印服务调用失败,已重试3次: " + e.getMessage());
+    }
+}

+ 75 - 0
src/main/java/com/sl/ms/web/enterprise/utils/ReflectUtil.java

@@ -0,0 +1,75 @@
+package com.sl.ms.web.enterprise.utils;
+
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/5 16:39
+ * @className ReflectUtil
+ */
+public class ReflectUtil {
+    /**
+     * 获取对象的所有非空字段,并按字段名ASCII码排序
+     * @param obj 要反射的对象
+     * @return 排序后的字段Map
+     */
+    public static Map<String, Object> getNonNullFieldsSorted(Object obj) {
+        if (obj == null) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> fieldMap = new TreeMap<>(); // TreeMap自动按键排序
+
+        Class<?> clazz = obj.getClass();
+        for (Field field : clazz.getDeclaredFields()) {
+            try {
+                field.setAccessible(true);
+                Object value = field.get(obj);
+                if (value != null && !"".equals(value)) {
+                    fieldMap.put(field.getName(), value);
+                }
+            } catch (IllegalAccessException e) {
+                // 忽略无法访问的字段
+            }
+        }
+
+        return fieldMap;
+    }
+
+    /**
+     * 获取对象的所有字段(包括父类),并按字段名ASCII码排序
+     * @param obj 要反射的对象
+     * @return 排序后的字段Map
+     */
+    public static Map<String, Object> getAllNonNullFieldsSorted(Object obj) {
+        if (obj == null) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> fieldMap = new TreeMap<>();
+        Class<?> clazz = obj.getClass();
+
+        // 递归获取所有父类的字段
+        while (clazz != null && clazz != Object.class) {
+            for (Field field : clazz.getDeclaredFields()) {
+                try {
+                    field.setAccessible(true);
+                    Object value = field.get(obj);
+                    if (value != null && !"".equals(value)) {
+                        fieldMap.put(field.getName(), value);
+                    }
+                } catch (IllegalAccessException e) {
+                    // 忽略无法访问的字段
+                }
+            }
+            clazz = clazz.getSuperclass();
+        }
+
+        return fieldMap;
+    }
+}

+ 135 - 0
src/main/java/com/sl/ms/web/enterprise/utils/SZSignUtil.java

@@ -0,0 +1,135 @@
+package com.sl.ms.web.enterprise.utils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:45
+ * @className SZSignUtil
+ */
+
+
+public class SZSignUtil {
+
+    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+    /**
+     * 生成签名
+     * @param httpMethod 请求方法(GET/POST等)
+     * @param httpUrlPath 请求路径
+     * @param params 业务参数
+     * @param nonce 随机字符串
+     * @param salt 盐值
+     * @param hmacSha256SecretKey 密钥
+     * @return 签名
+     */
+    public static String generateSignature(String httpMethod, String httpUrlPath,
+                                           Map<String, Object> params,
+                                           String nonce, String salt,
+                                           String hmacSha256SecretKey) {
+        try {
+            // 处理业务参数
+            String sortedData = generateSortedData(params);
+
+            // 拼接签名字符串
+            String signString = httpMethod + httpUrlPath + sortedData + nonce + salt;
+
+            // 计算HMAC-SHA256
+            Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+            SecretKeySpec secretKeySpec = new SecretKeySpec(hmacSha256SecretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM);
+            mac.init(secretKeySpec);
+            byte[] bytes = mac.doFinal(signString.getBytes(StandardCharsets.UTF_8));
+
+            // 转换为16进制字符串
+            return bytesToHex(bytes);
+        } catch (Exception e) {
+            throw new RuntimeException("生成签名失败", e);
+        }
+    }
+
+    /**
+     * 生成排序后的参数字符串
+     * @param params 业务参数
+     * @return 排序后的参数字符串
+     */
+    private static String generateSortedData(Map<String, Object> params) {
+        // 过滤空值
+        Map<String, Object> filteredParams = params.entrySet().stream()
+                .filter(entry -> entry.getValue() != null && !"".equals(entry.getValue()))
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+        // 按ASCII码排序
+        List<String> sortedKeys = new ArrayList<>(filteredParams.keySet());
+        Collections.sort(sortedKeys);
+
+        // 拼接键值对
+        return sortedKeys.stream()
+                .map(key -> key + "=" + filteredParams.get(key))
+                .collect(Collectors.joining("&"));
+    }
+
+    /**
+     * 生成nonce
+     * @return nonce字符串
+     */
+    public static String generateNonce() {
+        long timestamp = System.currentTimeMillis();
+        String randomString = generateRandomHexString(16);
+        return timestamp + ":" + randomString;
+    }
+
+    /**
+     * 生成随机16进制字符串
+     * @param length 长度
+     * @return 随机字符串
+     */
+    private static String generateRandomHexString(int length) {
+        Random random = new Random();
+        StringBuilder sb = new StringBuilder();
+        while (sb.length() < length) {
+            sb.append(Integer.toHexString(random.nextInt(16)));
+        }
+        return sb.toString().substring(0, length);
+    }
+
+    /**
+     * 字节数组转16进制字符串
+     * @param bytes 字节数组
+     * @return 16进制字符串
+     */
+    private static String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+    }
+
+    public static void main(String[] args) {
+        getAccessToken();
+
+    }
+    final static String salt = "oxkq2b";
+    final static String HMAC_SHA256_SECRET_KEY = "dea35258b066451fa206f9a7f6bfd15d";
+    final static String enterpriseId = "1963842095428870145";
+    final static String enterpriseSecret = "a1cbb2a5d64c4937b76a9a9238f782d9";
+    private static void getAccessToken() {
+        String nonce = SZSignUtil.generateNonce();
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("enterpriseId", enterpriseId);
+        params.put("enterpriseSecret", enterpriseSecret);
+
+        String signature = SZSignUtil.generateSignature(
+                "POST", "/enterprise/login",
+                params, nonce, salt, HMAC_SHA256_SECRET_KEY);
+        System.out.println("sz-signature" + signature);
+        System.out.println("sz-nonce" + nonce);
+    }
+}

+ 75 - 0
src/main/java/com/sl/ms/web/enterprise/utils/TransportOrderStatusUtils.java

@@ -0,0 +1,75 @@
+package com.sl.ms.web.enterprise.utils;
+
+
+import com.sl.ms.web.enterprise.vo.request.BaseWaybill;
+import com.sl.ms.work.domain.dto.TransportOrderDTO;
+import com.sl.ms.work.domain.enums.transportorder.TransportOrderStatus;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 16:22
+ * @className TransportOrderStatusUtils
+ */
+public class TransportOrderStatusUtils {
+//    public static String getWarehousingTypeString (TransportOrderDTO transportOrder) {
+//        Long currentAgencyId = transportOrder.getCurrentAgencyId();
+//        Long startAgencyId = transportOrder.getStartAgencyId();
+//        Long endAgencyId = transportOrder.getEndAgencyId();
+//        if (currentAgencyId == null ||  startAgencyId == null || endAgencyId == null) {
+//            return "网点入库";
+//        } else {
+//            if(currentAgencyId.equals(endAgencyId)) {
+//                return "到件入库";
+//            } else if (currentAgencyId.equals(startAgencyId)) {
+//                return "揽件入库";
+//            } else {
+//                return "中转入库";
+//            }
+//        }
+//
+//    }
+
+    public static String getWarehousingTypeString (TransportOrderDTO transportOrder) {
+        Long currentAgencyId = transportOrder.getCurrentAgencyId();
+        Long startAgencyId = transportOrder.getStartAgencyId();
+        Long endAgencyId = transportOrder.getEndAgencyId();
+        if (currentAgencyId == null ||  startAgencyId == null || endAgencyId == null) {
+            return "网点入库";
+        } else {
+            if(currentAgencyId.equals(endAgencyId)) {
+                return "到件入库";
+            } else if (currentAgencyId.equals(startAgencyId)) {
+                return "揽件入库";
+            } else {
+                return "中转入库";
+            }
+        }
+
+    }
+
+    public static String getTransportOrderStatusString (Long currentAgencyId, Long startAgencyId, Long endAgencyId, Integer transportOrderStatus) {
+        if (transportOrderStatus.equals(TransportOrderStatus.ARRIVED_END.getCode()) ||
+                transportOrderStatus.equals(TransportOrderStatus.POINT_PICKUP.getCode())
+        ) {
+            if (currentAgencyId == null ||  startAgencyId == null || endAgencyId == null) {
+                return "网点入库";
+            } else {
+                if(currentAgencyId.equals(endAgencyId)) {
+                    return "到件入库";
+                } else if (currentAgencyId.equals(startAgencyId)) {
+                    return "揽件入库";
+                } else {
+                    return "中转入库";
+                }
+            }
+        } else {
+            TransportOrderStatus status = TransportOrderStatus.getByCode(transportOrderStatus);
+            if (status != null) {
+                return status.getValue();
+            } else {
+                return "未知状态";
+            }
+        }
+    }
+}

+ 18 - 0
src/main/java/com/sl/ms/web/enterprise/vo/PrintServiceException.java

@@ -0,0 +1,18 @@
+package com.sl.ms.web.enterprise.vo;
+
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 15:05
+ * @className PrintServiceException
+ */
+public class PrintServiceException extends RuntimeException {
+    public PrintServiceException(String message) {
+        super(message);
+    }
+
+    public PrintServiceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

+ 25 - 0
src/main/java/com/sl/ms/web/enterprise/vo/ValidateVo.java

@@ -0,0 +1,25 @@
+package com.sl.ms.web.enterprise.vo;
+
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/3 21:38
+ * @className ValidateVo
+ */
+@Data
+public class ValidateVo {
+    String enterpriseId;
+    String enterpriseSecret;
+    String salt;
+    String hmacSha256SecretKey;
+    String httpMethod;
+    String httpUrl;
+    String signature;
+    String nonce;
+    Map<String, Object> params;
+}

+ 20 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/AccountLoginVO.java

@@ -0,0 +1,20 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel("账号登录请求")
+public class AccountLoginVO {
+    @ApiModelProperty(value = "登录账号", required = true)
+    @NotNull(message = "登录账号不能为空")
+    private String account;
+
+    @ApiModelProperty(value = "登录密码", required = true)
+    @NotNull(message = "登录密码不能为空")
+    private String password;
+
+}

+ 54 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/BaseWaybill.java

@@ -0,0 +1,54 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 16:02
+ * @className BaseWaybill
+ */
+@Data
+public class BaseWaybill {
+    // 公共字段
+    private String transportOrderId;
+    private String receiverCounty;
+    private String receiverCity;
+    private String receiverProvince;
+    private String receiverName;
+    private String receiverPhone;
+    private String receiverAddress;
+    private String senderCounty;
+    private String senderCity;
+    private String senderProvince;
+    private String senderName;
+    private String senderPhone;
+    private String senderAddress;
+    private String createTime;
+    private String dispatchType;
+    private String logisticsType;
+    private String paymentMethod;
+    private String temperatureRegion;
+    private List<TemperatureRegion> temperatureRegionList;
+    private String cargoName;
+    private String mark;
+    private String receiptOrderId;
+    private String receiptType;
+    private Double chargeFee;
+    private String chargeFeePrint;
+    private Double declareValue;
+    private String senderAgencyName;
+    private String receiverAgencyName;
+    private Double computeWeight;
+    private Double totalVolume;
+    private Integer quantity;
+    private String quantityPrint;
+    private String storageType;
+    private String endParentAgencyName;
+    private String startParentAgencyName;
+    private Map<String, SubTransportOrderVo> subTransportOrderMap;
+}

+ 21 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/CancelRequest.java

@@ -0,0 +1,21 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:53
+ * @className CancelRequest
+ */
+@Data
+public class CancelRequest {
+    @NotBlank(message = "企业ID不能为空")
+    private String enterpriseId;
+
+    @NotBlank(message = "主单号不能为空")
+    private String transportOrderId;
+}

+ 13 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/Cargo.java

@@ -0,0 +1,13 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 11:46
+ * @className Cargo
+ */
+public class Cargo {
+    private String cargoName;
+    private Integer quantity;
+}

+ 23 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/DriverDelayDeliveryVO.java

@@ -0,0 +1,23 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel("延迟提货对象")
+public class DriverDelayDeliveryVO {
+    @ApiModelProperty(value = "司机作业单id", required = true)
+    @NotNull(message = "司机作业单id不能为空")
+    private String id;
+
+    @ApiModelProperty(value = "延迟时间", required = false, example = "2022-07-18 15:20:00")
+    @NotNull(message = "延迟时间不能为空")
+    private String delayTime;
+
+    @ApiModelProperty(value = "延迟原因", required = false)
+    @NotNull(message = "延迟原因不能为空")
+    private String delayReason;
+}

+ 23 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/DriverDeliverVO.java

@@ -0,0 +1,23 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel("司机交付对象")
+public class DriverDeliverVO {
+    @ApiModelProperty(value = "司机作业id", required = true)
+    @NotNull
+    private String id;
+
+    @ApiModelProperty(value = "交付凭证,多个图片url以逗号分隔", required = false)
+    @NotNull
+    private String transportCertificate;
+
+    @ApiModelProperty(value = "交付图片,多个图片url以逗号分隔", required = false)
+    @NotNull
+    private String deliverPicture;
+}

+ 23 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/DriverPickUpVO.java

@@ -0,0 +1,23 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel("提货对象")
+public class DriverPickUpVO {
+    @ApiModelProperty(value = "司机作业id", required = true)
+    @NotNull
+    private String id;
+
+    @ApiModelProperty(value = "提货凭证,多个图片url以逗号分隔", required = false)
+    @NotNull
+    private String cargoPickUpPicture;
+
+    @ApiModelProperty(value = "货物照片,多个图片url以逗号分隔", required = false)
+    @NotNull
+    private String cargoPicture;
+}

+ 65 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/DriverReturnRegisterVO.java

@@ -0,0 +1,65 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+import com.sl.ms.base.domain.enums.TruckAccidentTypeEnum;
+import com.sl.ms.base.domain.enums.TruckBreakRulesTypeEnum;
+import com.sl.ms.base.domain.enums.TruckFaultTypeEnum;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+
+@Data
+public class DriverReturnRegisterVO {
+
+    @ApiModelProperty(value = "运输任务id", required = true)
+    private String id;
+
+    @ApiModelProperty(value = "出车时间", required = true, example = "2022-07-18 17:00:00")
+    private String startTime;
+
+    @ApiModelProperty(value = "回车时间", required = true, example = "2022-07-18 17:00:00")
+    private String endTime;
+
+    @ApiModelProperty(value = "车辆是否违章", required = true)
+    private Boolean isBreakRules;
+
+    @ApiModelProperty(value = "违章类型,1-闯红灯,2-无证驾驶,3-超载,4-酒后驾驶,5-超速行驶,6-其他")
+    private TruckBreakRulesTypeEnum breakRulesType;
+
+    @ApiModelProperty(value = "违章说明,类型为“其他”时填写")
+    private String breakRulesDescription;
+
+    @ApiModelProperty(value = "罚款金额")
+    private BigDecimal penaltyAmount;
+
+    @ApiModelProperty(value = "扣分数据")
+    private Integer deductPoints;
+
+    @ApiModelProperty(value = "车辆是否故障", required = true)
+    private Boolean isFault;
+
+    @ApiModelProperty(value = "车辆是否可用")
+    private Boolean isAvailable;
+
+    @ApiModelProperty(value = "故障类型,1-发动机启动困难,2-不着车,3-漏油,4-漏水,5-照明失灵,6-有异响,7-排烟异常,8-温度异常,9-其他")
+    private TruckFaultTypeEnum faultType;
+
+    @ApiModelProperty(value = "故障说明,类型为“其他”时填写")
+    private String faultDescription;
+
+    @ApiModelProperty(value = "故障图片")
+    private String faultImages;
+
+    @ApiModelProperty(value = "是否出现事故", required = true)
+    private Boolean isAccident;
+
+    @ApiModelProperty(value = "事故类型,1-直行事故,2-追尾事故,3-超车事故,4-左转弯事故,5-右转弯事故,6-弯道事故,7-坡道事故,8-会车事故,9-其他")
+    private TruckAccidentTypeEnum accidentType;
+
+    @ApiModelProperty(value = "事故说明,类型为“其他”时填写")
+    private String accidentDescription;
+
+    @ApiModelProperty(value = "事故图片")
+    private String accidentImages;
+}

+ 29 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/GoodsTypeVO.java

@@ -0,0 +1,29 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(value = "货物类型信息")
+public class GoodsTypeVO {
+    @ApiModelProperty(value = "id")
+    private Long id;
+
+    @ApiModelProperty(value = "货物类型名称")
+    private String name;
+
+    @ApiModelProperty(value = "默认重量,单位:千克")
+    private BigDecimal defaultWeight = new BigDecimal("0.0");
+
+    @ApiModelProperty(value = "默认体积,单位:方")
+    private BigDecimal defaultVolume = new BigDecimal("0.0");
+
+    @ApiModelProperty(value = "说明")
+    private String remark;
+
+    @ApiModelProperty("状态 0:禁用 1:正常")
+    private Integer status;
+}

+ 19 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/HouseWaybill.java

@@ -0,0 +1,19 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 11:45
+ * @className HouseWaybill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class HouseWaybill extends BaseWaybill {
+    // 子单特有字段
+    private String subTransportOrderId;
+    private Integer subIndex;
+}

+ 21 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/LoginRequest.java

@@ -0,0 +1,21 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:48
+ * @className LoginRequest
+ */
+@Data
+public class LoginRequest {
+    @NotBlank(message = "企业ID不能为空")
+    private String enterpriseId;
+
+    @NotBlank(message = "企业密钥不能为空")
+    private String enterpriseSecret;
+}

+ 18 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/MasterWaybill.java

@@ -0,0 +1,18 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 11:45
+ * @className MasterWaybill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MasterWaybill extends BaseWaybill{
+}

+ 49 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/MultPrintRequest.java

@@ -0,0 +1,49 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author sll
+ * @description: 开放接口打印请求参数(批量打印)
+ * @date 2025/9/2 20:52
+ * @className PrintRequest
+ */
+
+@Data
+public class MultPrintRequest {
+    @NotBlank(message = "企业ID不能为空")
+    private String enterpriseId;
+
+    /**
+     * 多个主单号使用 , 分割
+     */
+    private String transportOrderIds;
+
+    /**
+     * 多个子单号使用 , 分割
+     */
+    private String subTransportOrderIds;
+
+    /**
+     * 寄放回单,使用 , 分割
+     */
+    public String senderReceiptOrderIds;
+
+    /**
+     * 派发回单,使用 , 分割
+     */
+    public String receiverReceiptOrderIds;
+
+    /**
+     * 签收联,使用 , 分割
+     */
+    public String podSignedOrderIds;
+
+    /**
+     * 留存联,使用 , 分割
+     */
+    public String retentionCopyOrderIds;
+}

+ 56 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/OrderCargoVO.java

@@ -0,0 +1,56 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 货品总重量
+ */
+@Data
+@ApiModel(value = "货物信息")
+public class OrderCargoVO {
+
+    @ApiModelProperty(value = "id")
+    private Long id;
+
+    @ApiModelProperty(value = "货物类型信息")
+    private GoodsTypeVO goodsType;
+
+    @ApiModelProperty(value = "货物名称")
+    private String name;
+
+    @ApiModelProperty(value = "货物单位")
+    private String unit;
+
+    @ApiModelProperty(value = "货品货值")
+    private BigDecimal cargoValue;
+
+    @ApiModelProperty(value = "货品条码")
+    private String cargoBarcode;
+
+    @ApiModelProperty(value = "货品数量")
+    private Integer quantity;
+
+    @ApiModelProperty(value = "货品体积")
+    private BigDecimal volume;
+
+    @ApiModelProperty(value = "货品重量")
+    private BigDecimal weight;
+
+    @ApiModelProperty(value = "货品备注")
+    private String remark;
+
+    @ApiModelProperty(value = "货品总体积")
+    private BigDecimal totalVolume;
+
+    @ApiModelProperty(value = "货品总重量")
+    private BigDecimal totalWeight;
+
+    private BigDecimal computeWeight;
+
+    private Integer temperatureRegion;
+
+}

+ 146 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/OrderRequest.java

@@ -0,0 +1,146 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.*;
+import java.math.BigDecimal;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:48
+ * @className OrderRequestVO
+ */
+@Data
+public class OrderRequest {
+    @NotBlank(message = "企业ID不能为空")
+    private String enterpriseId;
+
+    @NotBlank(message = "第三方订单号不能为空")
+    private String partnerOrderCode;
+
+    @NotNull(message = "取件方式不能为空")
+    @Min(value = 1, message = "取件方式值最小为1")
+    @Max(value = 2, message = "取件方式值最大为2")
+    private Integer pickupType;
+
+    @NotNull(message = "付款方式不能为空")
+    @Min(value = 1, message = "取件方式值最小为1")
+    @Max(value = 4, message = "取件方式值最大为4")
+    private Integer payMethod;
+
+    @NotBlank(message = "物品名称不能为空")
+    private String goodsName;
+
+    @Size(max = 32, message = "物品长度不能超过32个字符")
+    private String cargoBarcode;
+//
+    @NotNull(message = "总件数不能为空")
+    @Min(value = 1, message = "总件数必须大于0")
+    @Max(value = 9999, message = "取件方式值最大为9999")
+    private Integer quantity;
+
+    @NotNull(message = "温区不能为空")
+    @Min(value = 1, message = "温区值最小为1")
+    @Max(value = 3, message = "温区值最大为3")
+    private Integer temperatureRegion;
+
+    @NotNull(message = "派件方式不能为空;1网点派送 2自提 3派送并上楼")
+    @Min(value = 1, message = "派件方式值最小为1")
+    @Max(value = 3, message = "派件方式值最大为3")
+    private Integer dispatchType;
+
+    @NotNull(message = "下单渠道不能为空")
+    @Min(value = 4, message = "下单渠道必须为4")
+    @Max(value = 4, message = "下单渠道必须为4")
+    private Integer orderChannel;
+
+    @NotNull(message = "客户运费不能为空")
+    @DecimalMin(value = "0.00", message = "客户运费不能小于0")
+    @Digits(integer = 10, fraction = 2, message = "客户运费最多支持2位小数")
+    private BigDecimal chargeFee;
+
+    @NotNull(message = "是否保价不能为空;0不保价1保价")
+    @Min(value = 0, message = "是否保价值最小为0")
+    @Max(value = 1, message = "是否保价值最大为1")
+    private Integer isInsure;
+
+    @NotNull(message = "声明价格不能为空")
+    @DecimalMin(value = "0.00", message = "声明价格不能小于0")
+    @DecimalMax(value = "100000.00", message = "声明价格不能大于100000")
+    private BigDecimal declareValue;
+
+    @NotNull(message = "货品总重量不能为空")
+    @DecimalMin(value = "1.00", message = "货品总重量不能小于1kg")
+    @DecimalMax(value = "99999.00", message = "货品总重量不能超过99999kg")
+    @Digits(integer = 5, fraction = 2, message = "货品总重量最多支持2位小数")
+    private BigDecimal totalWeight;
+
+    @NotNull(message = "货品总体积不能为空")
+    @DecimalMin(value = "0.01", message = "货品总体积不能小于0.01")
+    @DecimalMax(value = "99.99", message = "货品总体积不能超过99.99")
+    @Digits(integer = 2, fraction = 2, message = "货品总体积最多支持2位小数")
+    private BigDecimal totalVolume;
+
+    @Size(max = 100, message = "备注信息长度不能超过100个字符")
+    private String mark;
+
+    @NotBlank(message = "寄件人姓名不能为空")
+    private String senderName;
+
+    @NotBlank(message = "寄件人手机号不能为空")
+    private String senderPhone;
+
+    @NotBlank(message = "寄件省不能为空")
+    private String senderProvince;
+
+    @NotBlank(message = "寄件市不能为空")
+    private String senderCity;
+
+    @NotBlank(message = "寄件区县不能为空")
+    private String senderCounty;
+
+    @NotBlank(message = "寄件详细地址不能为空")
+    private String senderAddress;
+
+    @NotBlank(message = "收件人姓名不能为空")
+    private String receiverName;
+
+    @NotBlank(message = "收件省不能为空")
+    private String receiverProvince;
+
+    @NotBlank(message = "收件市不能为空")
+    private String receiverCity;
+
+    @NotBlank(message = "收件人手机号不能为空")
+    private String receiverPhone;
+
+    @NotBlank(message = "收件区县不能为空")
+    private String receiverCounty;
+
+    @NotBlank(message = "收件地址不能为空")
+    private String receiverAddress;
+
+    @NotNull(message = "是否入仓不能为空;1不入仓 2入仓")
+    @Min(value = 1, message = "是否入仓值最小1")
+    @Max(value = 2, message = "是否入仓值最大2")
+    private Integer storageType;
+
+    @NotNull(message = "物流类型不能为空;2小零担 3拼车 4整车 5门店配送")
+    @Min(value = 2, message = "物流类型值最小2")
+    @Max(value = 5, message = "物流类型值最大5")
+    private Integer logisticsType;
+
+    @Min(value = 1, message = "回单类型最小为1")
+    @Max(value = 4, message = "回单类型最大为4")
+    private Integer receiptType;
+
+    @Size(max = 32, message = "寄件网点机构编码长度不能超过32个字符")
+    private String senderAgencyNumber;
+
+    @Size(max = 32, message = "客户代码长度不能超过32个字符")
+    private String customerCode;
+
+}

+ 19 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/PodSigned.java

@@ -0,0 +1,19 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 11:47
+ * @className PodSigned
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PodSigned extends BaseWaybill {
+    // 签收联特有字段(如果有)
+}

+ 25 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/PrintApiRequest.java

@@ -0,0 +1,25 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 11:44
+ * @className PrintApiRequest
+ */
+@Data
+public class PrintApiRequest {
+    private Long code;
+    private Long mode;
+    private String printAgencyName;
+    private List<BaseWaybill> masterWaybillList; // 主单
+    private List<BaseWaybill> houseWaybillList; // 子单
+    private List<BaseWaybill> senderReceiptList; // 寄放回单
+    private List<BaseWaybill> receiverReceiptList; // 派方回单
+    private List<BaseWaybill> podSignedList; // 签收联
+    private List<BaseWaybill> retentionCopyList; // 留存联
+}

+ 37 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/PrintRequest.java

@@ -0,0 +1,37 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import io.swagger.models.auth.In;
+import lombok.Data;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author sll
+ * @description: 开放接口打印请求参数
+ * @date 2025/9/2 20:52
+ * @className PrintRequest
+ */
+
+@Data
+public class PrintRequest {
+    @NotBlank(message = "企业ID不能为空")
+    private String enterpriseId;
+
+    @NotBlank(message = "主运单号不能为空")
+    private String transportOrderId;
+
+    /**
+     * 面单类型:1主单2子单3寄方
+     * 回单4派方回单5签收联6留存联
+     */
+    @NotNull(message = "面单类型不能为空;1主单 2子单 3寄方回单 4派方回单 5签收联 6留存联")
+    @Min(value = 1, message = "面单类型值最小为1")
+    @Max(value = 6, message = "面单类型值最大为6")
+    private Integer type;
+
+    private Integer subIndex;
+}

+ 19 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/ReceiverReceipt.java

@@ -0,0 +1,19 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: 派方回单
+ * @date 2025/9/9 11:47
+ * @className ReceiverReceipt
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ReceiverReceipt extends BaseWaybill {
+    // 派方回单特有字段(如果有)
+}

+ 19 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/RetentionCopy.java

@@ -0,0 +1,19 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description:
+ * @date 2025/9/9 11:48
+ * @className RetentionCopy
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RetentionCopy extends BaseWaybill {
+    // 留存联特有字段(如果有)
+}

+ 19 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/SenderReceipt.java

@@ -0,0 +1,19 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: 寄方回单
+ * @date 2025/9/9 11:46
+ * @className SenderReceipt
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SenderReceipt extends BaseWaybill {
+    // 寄方回单特有字段(如果有)
+}

+ 29 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/SubTransportOrderVo.java

@@ -0,0 +1,29 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 17:01
+ * @className SubTransportOrderVo
+ */
+@Data
+@ApiModel(value = "子单")
+public class SubTransportOrderVo {
+    private String subTransportOrderId;
+
+    private Integer temperatureRegion;
+
+    private String temperatureRegionText;
+
+    private Integer index;
+
+    private String goodsName;
+
+    private Long goodsType;
+
+    private String cargoBarcode;
+}

+ 16 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/TemperatureRegion.java

@@ -0,0 +1,16 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 11:46
+ * @className TemperatureRegion
+ */
+public class TemperatureRegion {
+    private String subTemperatureRegion;
+    private List<Cargo> cargoList;
+    private Integer total;
+}

+ 21 - 0
src/main/java/com/sl/ms/web/enterprise/vo/request/TransportInfoRequest.java

@@ -0,0 +1,21 @@
+package com.sl.ms.web.enterprise.vo.request;
+
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:50
+ * @className TransportInfoRequest
+ */
+@Data
+public class TransportInfoRequest {
+    @NotBlank(message = "企业ID不能为空")
+    private String enterpriseId;
+
+    private String transportOrderId;
+    private String partnerOrderCode;
+}

+ 44 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/ApiResponse.java

@@ -0,0 +1,44 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+
+import com.sl.ms.web.enterprise.config.SZColdChainConstant;
+import com.sl.transport.common.util.ObjectUtil;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/8 19:23
+ * @className ApiResponse
+ */
+@Getter
+@Setter
+public class ApiResponse<T> {
+    private String msg;
+    private Integer code;
+    private T data;
+
+    // 移除了构造函数,因为 Lombok 会自动生成
+
+    public static <T> ApiResponse<T> success( T data) {
+        return new ApiResponse<>( 200, SZColdChainConstant.MSG_SUCCESS, data);
+    }
+
+    public static <T> ApiResponse<T> error(Integer code, String msg, T data) {
+        return new ApiResponse<>( code, msg, data);
+    }
+
+    public static <T> ApiResponse<T> error(Integer code, String msg) {
+        return new ApiResponse<>( code, msg, null);
+    }
+
+    private ApiResponse(Integer code, String message,  T data) {
+        this.msg = message;
+        this.code = code;
+        this.data = data;
+    }
+}

+ 17 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/FieldError.java

@@ -0,0 +1,17 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+
+import lombok.Data;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 20:27
+ * @className FieldError
+ */
+@Data
+public class FieldError {
+    private String field;      // 字段名
+    private String message;    // 错误消息
+    private Object rejectedValue; // 被拒绝的值
+}

+ 62 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/OrderResponse.java

@@ -0,0 +1,62 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:53
+ * @className OrderResponse
+ */
+@Data
+public class OrderResponse {
+    private Long id;
+    private String partnerOrderCode;
+    private String transportOrderId;
+    private Integer pickupType;
+    private Integer payMethod;
+    private String receiverParentAgencyName;
+    private String senderParentAgencyName;
+    private String receiverAgencyName;
+    private String senderAgencyName;
+    private String goodsName;
+    private String cargoBarcode;
+    private Integer temperatureRegion;
+    private Integer quantity;
+    private BigDecimal totalWeight;
+    private BigDecimal totalVolume;
+    private String mark;
+    private String senderName;
+    private String senderPhone;
+    private String senderProvince;
+    private String senderCity;
+    private String senderCounty;
+    private String senderAddress;
+    private String receiverName;
+    private String receiverPhone;
+    private String receiverProvince;
+    private String receiverCity;
+    private String receiverCounty;
+    private String receiverAddress;
+    private Integer storageType;
+    private Integer logisticsType;
+    private Integer receiptType;
+    private String receiptOrderId;
+    private BigDecimal chargeFee;
+    private Double computedWeight;
+    private Integer dispatchType;
+    private Integer isInsure;
+    private BigDecimal declareValue;
+    private String orderChannel;
+    private List<SubTransportOrder> subTransportOrderList;
+    private Long senderAgencyId;
+
+    @Data
+    public static class SubTransportOrder {
+        private String subTransportOrderId;
+        private Integer subIndex;
+    }
+}

+ 19 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/PrintApiResponse.java

@@ -0,0 +1,19 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/9 14:59
+ * @className PrintApiResponse
+ */
+@Data
+public class PrintApiResponse {
+    private Integer code;
+    private String message;
+    private List<String> urlList;
+}

+ 17 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/PrintResponse.java

@@ -0,0 +1,17 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+import lombok.Data;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:55
+ * @className PrintResponse
+ */
+
+@Data
+public class PrintResponse {
+    private String transportOrderId;
+    private String type;
+    private String printUrl;
+}

+ 27 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/TokenResponse.java

@@ -0,0 +1,27 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 21:58
+ * @className TokenResponse
+ */
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TokenResponse {
+    @ApiModelProperty("accessToken")
+    private String accessToken;
+    @ApiModelProperty("超时时间(s)")
+    private Long expiresIn;
+    @ApiModelProperty("token类型")
+    private String tokenType = "Bearer";
+}

+ 29 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/TransportInfoResponse.java

@@ -0,0 +1,29 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+import com.sl.transport.info.domain.TransportInfoDetailDTO;
+import lombok.Data;
+
+import java.util.List;
+
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/2 20:54
+ * @className TransportInfoResponse
+ */
+
+@Data
+public class TransportInfoResponse {
+    private String transportOrderId;
+    private Integer status;
+    private String statusText;
+    private List<TransportDetail> details;
+
+    @Data
+    public static class TransportDetail {
+        private String context;
+        private String location;
+        private String time;
+    }
+}

+ 25 - 0
src/main/java/com/sl/ms/web/enterprise/vo/response/TransportOrderVO.java

@@ -0,0 +1,25 @@
+package com.sl.ms.web.enterprise.vo.response;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@ApiModel("运单")
+public class TransportOrderVO {
+
+    @ApiModelProperty(value = "运单id")
+    private String id;
+
+    @ApiModelProperty(value = "货品总重量")
+    private BigDecimal totalWeight;
+
+    @ApiModelProperty(value = "数量")
+    private Integer quantity;
+
+    @ApiModelProperty(value = "创建时间")
+    private LocalDateTime createTime;
+}

+ 7 - 0
src/main/resources/banner.txt

@@ -0,0 +1,7 @@
+
+       _                                                     ${spring.application.name} ${application.version}
+  ___ | |         ___ __  __ _ __   _ __  ___  ___  ___      Port: ${server.port}
+ / __|| | _____  / _ \\ \/ /| '_ \ | '__|/ _ \/ __|/ __|     Pid: ${pid}  Profile(s): ${AnsiColor.GREEN}${spring.profiles.active}${AnsiColor.DEFAULT}
+ \__ \| ||_____||  __/ >  < | |_) || |  |  __/\__ \\__ \
+ |___/|_|        \___|/_/\_\| .__/ |_|   \___||___/|___/     https://sl-express.itheima.net/
+                            |_|

+ 27 - 0
src/main/resources/bootstrap-local.yml

@@ -0,0 +1,27 @@
+server:
+  port: 18100
+  tomcat:
+    uri-encoding: UTF-8
+    threads:
+      max: 1000
+      min-spare: 30
+spring:
+  cloud:
+    nacos:
+      username: nacos
+      password: nacos
+      server-addr: 172.25.107.111:8848
+      discovery:
+        namespace: b5ce745d-7ac6-4b5d-9522-692272f94ecd
+      config:
+        namespace: b5ce745d-7ac6-4b5d-9522-692272f94ecd
+        shared-configs: #共享配置
+          - data-id: shared-spring-redis.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-seata.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-authority.yml
+            group: SHARED_GROUP
+            refresh: false

+ 27 - 0
src/main/resources/bootstrap-prod.yml

@@ -0,0 +1,27 @@
+server:
+  port: 18100
+  tomcat:
+    uri-encoding: UTF-8
+    threads:
+      max: 1000
+      min-spare: 30
+spring:
+  cloud:
+    nacos:
+      username: nacos
+      password: vO5/dZ9,iL
+      server-addr: nacos-service.yjy-public-slwl-java-prod.svc.cluster.local:8848
+      discovery:
+        namespace: 92312ba8-1119-440f-81af-c29618df303b
+      config:
+        namespace: 92312ba8-1119-440f-81af-c29618df303b
+        shared-configs: #共享配置
+          - data-id: shared-spring-redis.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-seata.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-authority.yml
+            group: SHARED_GROUP
+            refresh: false

+ 27 - 0
src/main/resources/bootstrap-stu.yml

@@ -0,0 +1,27 @@
+server:
+  port: 18100
+  tomcat:
+    uri-encoding: UTF-8
+    threads:
+      max: 1000
+      min-spare: 30
+spring:
+  cloud:
+    nacos:
+      username: nacos
+      password: nacos
+      server-addr: 192.168.0.106:8848
+      discovery:
+        namespace: b5ce745d-7ac6-4b5d-9522-692272f94ecd
+      config:
+        namespace: b5ce745d-7ac6-4b5d-9522-692272f94ecd
+        shared-configs: #共享配置
+          - data-id: shared-spring-redis.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-seata.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-authority.yml
+            group: SHARED_GROUP
+            refresh: false

+ 27 - 0
src/main/resources/bootstrap-test.yml

@@ -0,0 +1,27 @@
+server:
+  port: 18100
+  tomcat:
+    uri-encoding: UTF-8
+    threads:
+      max: 1000
+      min-spare: 30
+spring:
+  cloud:
+    nacos:
+      username: nacos
+      password: nacos
+      server-addr: 118.31.56.153:8848
+      discovery:
+        namespace: b5ce745d-7ac6-4b5d-9522-692272f94ecd
+      config:
+        namespace: b5ce745d-7ac6-4b5d-9522-692272f94ecd
+        shared-configs: #共享配置
+          - data-id: shared-spring-redis.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-seata.yml
+            group: SHARED_GROUP
+            refresh: false
+          - data-id: shared-spring-authority.yml
+            group: SHARED_GROUP
+            refresh: false

+ 28 - 0
src/main/resources/bootstrap.yml

@@ -0,0 +1,28 @@
+application:
+  version: v1.0
+logging:
+  config: classpath:logback-spring.xml
+spring:
+  application:
+    name: sl-express-ms-web-enterprise
+  profiles:
+    active: stu
+  main:
+    allow-bean-definition-overriding: true
+  mvc:
+    pathmatch:
+      #解决异常:swagger Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
+      #因为Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher
+      matching-strategy: ant_path_matcher
+szcoldchain:
+  token-expire-seconds: 7200
+sl:
+  swagger:
+    package-path: com.sl.ms.web.enterprise.controller
+    title: 神州冷链 - 企业客户端接口文档
+    description: 该服务用于企业客户端
+    contact-name: 神州冷链·szcc
+    contact-url: https://www.szcc.net/
+    contact-email: yi.zijin@163.cm
+    version: ${application.version}
+

+ 41 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。-->
+<!--scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。-->
+<!--debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。-->
+<configuration debug="false" scan="false" scanPeriod="60 seconds">
+    <springProperty scope="context" name="appName" source="spring.application.name"/>
+    <!--文件名-->
+    <property name="logback.appname" value="${appName}"/>
+    <!--文件位置-->
+    <property name="logback.logdir" value="/data/logs"/>
+
+    <!-- 定义控制台输出 -->
+    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] - %-5level - %logger{50} - %msg%n</pattern>
+        </layout>
+    </appender>
+
+
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>DEBUG</level>
+        </filter>
+        <File>${logback.logdir}/${logback.appname}/${logback.appname}.log</File>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <FileNamePattern>${logback.logdir}/${logback.appname}/${logback.appname}.%d{yyyy-MM-dd}.log.zip</FileNamePattern>
+            <maxHistory>90</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <charset>UTF-8</charset>
+            <pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+
+    <!--evel:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,-->
+    <!--不能设置为INHERITED或者同义词NULL。默认是DEBUG。-->
+    <root level="INFO">
+        <appender-ref ref="stdout"/>
+    </root>
+</configuration>

+ 144 - 0
src/test/java/com/sl/ms/web/enterprise/signature/ReflectUtilTest.java

@@ -0,0 +1,144 @@
+package com.sl.ms.web.enterprise.signature;
+
+
+import com.sl.ms.web.enterprise.utils.ReflectUtil;
+import com.sl.ms.web.enterprise.vo.request.LoginRequest;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/5 16:55
+ * @className ReflectUtilTest
+ */
+public class ReflectUtilTest {
+    // 测试用内部类
+    static class ParentClass {
+        private String parentField = "parentValue";
+        private String nullField = null;
+        private String emptyField = "";
+    }
+
+    static class ChildClass extends ParentClass {
+        private String childField = "childValue";
+        private int numberField = 123;
+        private String transientField = "transientValue";
+    }
+
+    static class EmptyClass {
+        // 无字段
+    }
+
+    @Test
+    void getNonNullFieldsSorted_ShouldReturnNonEmptyFieldsOfCurrentClass() {
+        // 准备测试数据
+        ChildClass testObj = new ChildClass();
+
+        // 执行方法
+        Map<String, Object> result = ReflectUtil.getNonNullFieldsSorted(testObj);
+
+        // 验证结果
+        assertThat(result)
+                .hasSize(3)
+                .containsOnlyKeys("childField", "numberField", "transientField")
+                .containsEntry("childField", "childValue")
+                .containsEntry("numberField", 123)
+                .containsEntry("transientField", "transientValue");
+
+        // 验证不包含父类字段
+        assertThat(result).doesNotContainKey("parentField");
+//        assertThat(result).doesNotContainKey("transientField");
+    }
+
+    @Test
+    void getNonNullFieldsSorted_ShouldExcludeNullAndEmptyFields() {
+        // 准备测试数据
+        ParentClass testObj = new ParentClass();
+
+        // 执行方法
+        Map<String, Object> result = ReflectUtil.getNonNullFieldsSorted(testObj);
+
+        // 验证结果
+        assertThat(result)
+                .hasSize(1)
+                .containsOnlyKeys("parentField")
+                .containsEntry("parentField", "parentValue");
+    }
+
+    @Test
+    void getNonNullFieldsSorted_ShouldReturnEmptyMapForEmptyClass() {
+        // 准备测试数据
+        EmptyClass testObj = new EmptyClass();
+
+        // 执行方法
+        Map<String, Object> result = ReflectUtil.getNonNullFieldsSorted(testObj);
+
+        // 验证结果
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    void getNonNullFieldsSorted_ShouldReturnEmptyMapForNullInput() {
+        // 执行方法
+        Map<String, Object> result = ReflectUtil.getNonNullFieldsSorted(null);
+
+        // 验证结果
+        assertThat(result).isEmpty();
+    }
+    static class TestClass {
+        private String zField = "z";
+        private String aField = "a";
+        private String mField = "m";
+    }
+    @Test
+    void getNonNullFieldsSorted_ShouldReturnFieldsInAlphabeticalOrder() {
+        // 准备测试数据
+
+
+        TestClass testObj = new TestClass();
+
+        // 执行方法
+        Map<String, Object> result = ReflectUtil.getNonNullFieldsSorted(testObj);
+
+        // 验证顺序
+        assertThat(result.keySet())
+                .containsExactly("aField", "mField", "zField");
+    }
+
+    // 准备测试数据
+    static class Parent {
+        private String bField = "b";
+    }
+    static class Child extends Parent {
+        private String aField = "a";
+        private String cField = "c";
+    }
+    @Test
+    void getAllNonNullFieldsSorted_ShouldReturnFieldsInAlphabeticalOrder() {
+
+
+//        Child testObj = new Child();
+//
+//        // 执行方法
+//        Map<String, Object> result = ReflectUtil.getAllNonNullFieldsSorted(testObj);
+//
+//        // 验证顺序
+//        assertThat(result.keySet())
+//                .containsExactly("aField", "bField", "cField");
+
+        LoginRequest loginRequest = new LoginRequest();
+        loginRequest.setEnterpriseId("enterpriseId");
+        loginRequest.setEnterpriseSecret("enterpriseSecret");
+
+        Map<String, Object> result = ReflectUtil.getAllNonNullFieldsSorted(loginRequest);
+
+        assertThat(result.keySet())
+                .containsExactly("enterpriseId", "enterpriseSecret");
+
+    }
+}

+ 205 - 0
src/test/java/com/sl/ms/web/enterprise/signature/SZSignUtilTest.java

@@ -0,0 +1,205 @@
+package com.sl.ms.web.enterprise.signature;
+
+
+import com.sl.ms.web.enterprise.utils.ReflectUtil;
+import com.sl.ms.web.enterprise.utils.SZSignUtil;
+import com.sl.ms.web.enterprise.vo.request.OrderRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+/**
+ * @author sll
+ * @description: TODO
+ * @date 2025/9/5 16:53
+ * @className SZSignUtilTest
+ */
+@Slf4j
+public class SZSignUtilTest {
+    private static final String TEST_SALT = "testSalt";
+    private static final String TEST_SECRET_KEY = "testSecretKey1234567890";
+
+    // 测试用常量
+    private static final String ENTERPRISE_ID = "1963842095428870145";
+    private static final String PARTNER_ORDER_CODE = "ORDER123456";
+    @Test
+    void generateSignature_ShouldWorkWithValidOrderRequest() {
+        // 准备测试数据
+        OrderRequest orderRequest = createValidOrderRequest();
+
+        Map<String, Object> params = ReflectUtil.getNonNullFieldsSorted(orderRequest);
+        // 执行签名
+        String signature = SZSignUtil.generateSignature(
+                "POST",
+                "/enterprise/order",
+                params,
+                SZSignUtil.generateNonce(),
+                TEST_SALT,
+                TEST_SECRET_KEY);
+
+        // 验证签名
+        assertThat(signature)
+                .isNotBlank()
+                .hasSize(64) // SHA-256 produces 64-character hex string
+                .matches("[0-9a-f]+");
+    }
+
+    @Test
+    void generateSignature_ShouldProduceSameResultForSameInput() {
+        // 准备相同的数据
+        OrderRequest orderRequest1 = createValidOrderRequest();
+        OrderRequest orderRequest2 = createValidOrderRequest();
+        String nonce = SZSignUtil.generateNonce();
+
+        // 生成签名
+        String signature1 = SZSignUtil.generateSignature(
+                "POST", "/enterprise/order",
+                ReflectUtil.getNonNullFieldsSorted(orderRequest1),
+                nonce, TEST_SALT, TEST_SECRET_KEY);
+
+        String signature2 = SZSignUtil.generateSignature(
+                "POST", "/enterprise/order",
+                ReflectUtil.getNonNullFieldsSorted(orderRequest2),
+                nonce, TEST_SALT, TEST_SECRET_KEY);
+
+        // 验证签名一致
+        assertThat(signature1).isEqualTo(signature2);
+    }
+
+    @Test
+    void generateSignature_ShouldChangeWhenFieldValueChanges() {
+        // 准备基础订单
+        OrderRequest baseOrder = createValidOrderRequest();
+        String baseSignature = generateOrderSignature(baseOrder);
+
+        // 修改一个字段
+        OrderRequest modifiedOrder = createValidOrderRequest();
+        modifiedOrder.setGoodsName("Different Goods"); // 修改物品名称
+
+        // 生成新签名
+        String modifiedSignature = generateOrderSignature(modifiedOrder);
+
+        // 验证签名不同
+        assertThat(modifiedSignature).isNotEqualTo(baseSignature);
+    }
+
+    @Test
+    void generateSignature_ShouldChangeWhenNonNullFieldAdded() {
+        OrderRequest baseOrder = createValidOrderRequest();
+        String baseSignature = generateOrderSignature(baseOrder);
+
+        // 添加一个非空字段
+        OrderRequest modifiedOrder = createValidOrderRequest();
+        modifiedOrder.setCargoBarcode("NEW_BARCODE_123");
+
+        String modifiedSignature = generateOrderSignature(modifiedOrder);
+        assertThat(modifiedSignature).isNotEqualTo(baseSignature);
+    }
+
+    @Test
+    void generateSignature_ShouldExcludeNullAndEmptyFields() {
+        OrderRequest orderRequest = createValidOrderRequest();
+        orderRequest.setCargoBarcode(null); // 显式设置为null
+        orderRequest.setMark("");          // 设置为空字符串
+
+        Map<String, Object> fields = ReflectUtil.getNonNullFieldsSorted(orderRequest);
+
+        // 验证null和空字符串字段被排除
+
+        assertThat(fields.containsKey("cargoBarcode")).isFalse();
+        assertThat(fields.containsKey("mark")).isFalse();
+
+        // 签名应该仍然能正常生成
+        String signature = generateOrderSignature(orderRequest);
+        assertThat(signature).isNotBlank();
+    }
+
+    @Test
+    void generateSignature_ShouldHandleMinimalValidOrder() {
+        OrderRequest minimalOrder = new OrderRequest();
+        minimalOrder.setEnterpriseId(ENTERPRISE_ID);
+        minimalOrder.setPartnerOrderCode(PARTNER_ORDER_CODE);
+        minimalOrder.setPickupType(1);
+        minimalOrder.setPayMethod(1);
+        minimalOrder.setGoodsName("Test Goods");
+        minimalOrder.setQuantity(1);
+        minimalOrder.setTemperatureRegion(1);
+        minimalOrder.setDispatchType(1);
+        minimalOrder.setOrderChannel(3);
+        minimalOrder.setChargeFee(BigDecimal.ZERO);
+        minimalOrder.setIsInsure(0);
+        minimalOrder.setDeclareValue(BigDecimal.ZERO);
+        minimalOrder.setTotalWeight(new BigDecimal("1.00"));
+        minimalOrder.setTotalVolume(BigDecimal.valueOf(0.0001));
+        minimalOrder.setSenderName("Sender");
+        minimalOrder.setSenderPhone("13800138000");
+        minimalOrder.setSenderProvince("广东省");
+        minimalOrder.setSenderCity("广州市");
+        minimalOrder.setSenderCounty("天河区");
+        minimalOrder.setSenderAddress("Test Address");
+        minimalOrder.setReceiverName("Receiver");
+        minimalOrder.setReceiverPhone("13900139000");
+        minimalOrder.setReceiverProvince("广东省");
+        minimalOrder.setReceiverCity("深圳市");
+        minimalOrder.setReceiverCounty("南山区");
+        minimalOrder.setReceiverAddress("Test Address");
+        minimalOrder.setStorageType(1);
+        minimalOrder.setLogisticsType(2);
+
+        String signature = generateOrderSignature(minimalOrder);
+        assertThat(signature).isNotBlank();
+    }
+
+    private OrderRequest createValidOrderRequest() {
+        OrderRequest orderRequest = new OrderRequest();
+        orderRequest.setEnterpriseId(ENTERPRISE_ID);
+        orderRequest.setPartnerOrderCode(PARTNER_ORDER_CODE);
+        orderRequest.setPickupType(2);
+        orderRequest.setPayMethod(2);
+        orderRequest.setGoodsName("冷冻牛肉");
+        orderRequest.setCargoBarcode("MEAT001");
+        orderRequest.setQuantity(10);
+        orderRequest.setTemperatureRegion(1);
+        orderRequest.setDispatchType(1);
+        orderRequest.setOrderChannel(3);
+        orderRequest.setChargeFee(new BigDecimal("100.50"));
+        orderRequest.setIsInsure(1);
+        orderRequest.setDeclareValue(new BigDecimal("500.00"));
+        orderRequest.setTotalWeight(new BigDecimal("50.00"));
+        orderRequest.setTotalVolume(new BigDecimal("2.50"));
+        orderRequest.setMark("易碎品,小心轻放");
+        orderRequest.setSenderName("张先生");
+        orderRequest.setSenderPhone("13800138000");
+        orderRequest.setSenderProvince("广东省");
+        orderRequest.setSenderCity("广州市");
+        orderRequest.setSenderCounty("番禺区");
+        orderRequest.setSenderAddress("南村万博地铁站B口");
+        orderRequest.setReceiverName("李女士");
+        orderRequest.setReceiverPhone("13900139000");
+        orderRequest.setReceiverProvince("上海市");
+        orderRequest.setReceiverCity("上海市");
+        orderRequest.setReceiverCounty("浦东新区");
+        orderRequest.setReceiverAddress("张江高科技园区");
+        orderRequest.setStorageType(2);
+        orderRequest.setLogisticsType(3);
+        orderRequest.setReceiptType(1);
+
+        return orderRequest;
+    }
+
+    private String generateOrderSignature(OrderRequest orderRequest) {
+        return SZSignUtil.generateSignature(
+                "POST",
+                "/enterprise/order",
+                ReflectUtil.getNonNullFieldsSorted(orderRequest),
+                SZSignUtil.generateNonce(),
+                TEST_SALT,
+                TEST_SECRET_KEY);
+    }
+}