Selaa lähdekoodia

Merge remote-tracking branch 'origin/dev_20251130' into dev_20251130

donglang 1 kuukausi sitten
vanhempi
commit
0ec3dc9a17
43 muutettua tiedostoa jossa 2331 lisäystä ja 151 poistoa
  1. 20 0
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/config/AuthInterceptorConfig.java
  2. 48 0
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/interceptor/AuthenticationInterceptor.java
  3. 10 1
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/model/constant/Global.java
  4. 3 1
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/utils/EncryUtil.java
  5. 1 1
      sckw-common/sckw-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  6. 2 1
      sckw-common/sckw-common-redis/src/main/java/com/sckw/redis/config/RedissonConfig.java
  7. 13 0
      sckw-gateway/pom.xml
  8. 3 1
      sckw-gateway/src/main/java/com/sckw/gateway/GatewayApplication.java
  9. 100 97
      sckw-gateway/src/main/java/com/sckw/gateway/filter/AuthenticationFilter.java
  10. 2 2
      sckw-gateway/src/main/resources/bootstrap-dev.yml
  11. 5 2
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/feign/VehicleTraceClient.java
  12. 2 0
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/dto/VehicleReturnData.java
  13. 3 14
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/vo/VehicleTraceResponse.java
  14. 7 2
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/operateService/KwcContractLogisticsService.java
  15. 4 4
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/service/KwfTruckService.java
  16. 49 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/KwfTruckTraceController.java
  17. 26 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/KwtWaybillOrderController.java
  18. 14 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dao/KwtVehicleExceptionMapper.java
  19. 145 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtVehicleException.java
  20. 40 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/GenerateTraceReq.java
  21. 48 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/MapVehicleQueryReq.java
  22. 32 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/TruckSelectReq.java
  23. 32 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/VehicleExceptionQueryReq.java
  24. 23 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/WaybillOrderQueryByNoReq.java
  25. 65 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/ExceptionSortTypeEnum.java
  26. 53 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/MapVehicleSortTypeEnum.java
  27. 58 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/VehicleExceptionTypeEnum.java
  28. 1 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/VehiclesTrajectoryReq.java
  29. 92 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/MapVehicleVo.java
  30. 41 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/TruckSelectVo.java
  31. 56 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/VehicleExceptionVo.java
  32. 26 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/WaybillOrderSimpleVo.java
  33. 23 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/WaybillOrderTruckVo.java
  34. 9 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderGoodsRepository.java
  35. 7 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderRepository.java
  36. 9 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderUnitRepository.java
  37. 71 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtVehicleExceptionRepository.java
  38. 79 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderRepository.java
  39. 96 6
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtWaybillOrderV1Service.java
  40. 165 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/VehicleExceptionService.java
  41. 667 16
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/kwfTruckTraceService.java
  42. 152 0
      sql/2025/11/30/2025_11_30_cxf_creat.sql
  43. 29 0
      sql/2025/12/01/2025_12_01_vehicle_exception.sql

+ 20 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/config/AuthInterceptorConfig.java

@@ -0,0 +1,20 @@
+package com.sckw.core.config;
+
+import com.sckw.core.interceptor.AuthenticationInterceptor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 认证拦截器配置
+ */
+@ConditionalOnClass(WebMvcConfigurer.class)
+@Configuration
+public class AuthInterceptorConfig implements WebMvcConfigurer {
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 注册自定义拦截器实例,并指定拦截的路径模式
+        registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/**"); // 可以排除某些路径不被拦截
+    }
+}

+ 48 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/interceptor/AuthenticationInterceptor.java

@@ -0,0 +1,48 @@
+package com.sckw.core.interceptor;
+
+import com.alibaba.fastjson2.JSON;
+import com.sckw.core.model.constant.Global;
+import com.sckw.core.utils.StringUtils;
+import com.sckw.core.web.context.LoginEntHolder;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.core.web.model.LoginEntInfo;
+import com.sckw.core.web.model.LoginUserInfo;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 登录认证拦截器,用于从请求头中获取登录用户信息并设置到上下文中
+ */
+@Slf4j
+public class AuthenticationInterceptor implements HandlerInterceptor {
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        String userInfoStrEncode = request.getHeader(Global.USER_INFO_STR_ENCODE);
+        String entInfoStrEncode = request.getHeader(Global.ENT_INFO_STR_ENCODE);
+        LoginUserInfo loginUserInfo = null;
+        LoginEntInfo loginEntInfo = null;
+        if(StringUtils.isNotBlank(userInfoStrEncode)){
+            String userInfoStr = URLDecoder.decode(userInfoStrEncode, StandardCharsets.UTF_8);
+            loginUserInfo = JSON.parseObject(userInfoStr, LoginUserInfo.class);
+        }
+        if(StringUtils.isNotBlank(entInfoStrEncode)){
+            String entInfoStr = URLDecoder.decode(entInfoStrEncode, StandardCharsets.UTF_8);
+            loginEntInfo = JSON.parseObject(entInfoStr, LoginEntInfo.class);
+        }
+        LoginUserHolder.set(loginUserInfo);
+        LoginEntHolder.set(loginEntInfo);
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        //清理用户和企业上下文线程变量
+        LoginUserHolder.remove();
+        LoginEntHolder.remove();
+    }
+}

+ 10 - 1
sckw-common/sckw-common-core/src/main/java/com/sckw/core/model/constant/Global.java

@@ -1,6 +1,5 @@
 package com.sckw.core.model.constant;
 
-import com.sckw.core.common.enums.StringConstant;
 import com.sckw.core.utils.StringUtils;
 
 import java.util.Objects;
@@ -132,6 +131,16 @@ public class Global {
      */
     public static final String USER_LOGIN_CAPTCHA = "userLoginCaptcha:";
 
+    /**
+     * 请求头用户信息键名
+     */
+    public static final String USER_INFO_STR_ENCODE = "userInfoStrEncode";
+
+    /**
+     * 请求头企业信息键名
+     */
+    public static final String ENT_INFO_STR_ENCODE = "entInfoStrEncode";
+
     /**
      * redis用户信息前缀
      */

+ 3 - 1
sckw-common/sckw-common-core/src/main/java/com/sckw/core/utils/EncryUtil.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.sckw.core.model.constant.Global;
 import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
 import org.bouncycastle.util.encoders.Hex;
 import javax.crypto.Cipher;
 import javax.crypto.KeyGenerator;
@@ -17,6 +18,7 @@ import java.util.*;
  * @create 2019-02-27
  * @description 加密工具类
  */
+@Slf4j
 public class EncryUtil {
     /**可调用EncryUtil.generatorPriKey()方法生成*/
     public static final String PRI_KEY = "127f0400ff8a8b7a20d91dfc60a39725";
@@ -205,7 +207,7 @@ public class EncryUtil {
 
             return JSON.parseObject(decodeStr, HashMap.class);
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Access-Token解密失败:{}",e.getMessage());
             return null;
         }
     }

+ 1 - 1
sckw-common/sckw-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -3,7 +3,7 @@ com.sckw.core.aspect.NoRepeatSubmitAspect
 com.sckw.core.exception.GlobalSystemExceptionHandler
 com.sckw.core.web.config.CustomConfig
 #com.sckw.core.filter.LoginFilter
-com.sckw.core.filter.RequestCheckFilter
+com.sckw.core.config.AuthInterceptorConfig
 com.sckw.core.config.MybatisPlusConfig
 com.sckw.core.config.JacksonConfig
 com.sckw.core.filter.ExceptionFilterConfig

+ 2 - 1
sckw-common/sckw-common-redis/src/main/java/com/sckw/redis/config/RedissonConfig.java

@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.sckw.redis.utils.RedissonUtils;
 import org.redisson.api.RedissonClient;
+import org.redisson.spring.starter.RedissonAutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -19,7 +20,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
  * @Author xucaiqin
  * @date 2023-06-01 14:50:29
  */
-@AutoConfiguration
+@AutoConfiguration(before = {RedissonAutoConfiguration.class})
 public class RedissonConfig {
 
     /**

+ 13 - 0
sckw-gateway/pom.xml

@@ -18,6 +18,19 @@
     </properties>
 
     <dependencies>
+        <!-- 核心模块 -->
+        <dependency>
+            <groupId>com.sckw</groupId>
+            <artifactId>sckw-common-core</artifactId>
+            <version>${basic.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-webmvc</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
         <!--web-->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 3 - 1
sckw-gateway/src/main/java/com/sckw/gateway/GatewayApplication.java

@@ -1,7 +1,9 @@
 package com.sckw.gateway;
 
+import com.sckw.core.filter.ExceptionFilterConfig;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 
 /**
@@ -10,7 +12,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  * @date 2023/5/14 21:12
  **/
 @EnableDiscoveryClient
-@SpringBootApplication
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, ExceptionFilterConfig.class})
 public class GatewayApplication {
 
     public static void main(String[] args) {

+ 100 - 97
sckw-common/sckw-common-core/src/main/java/com/sckw/core/filter/RequestCheckFilter.java → sckw-gateway/src/main/java/com/sckw/gateway/filter/AuthenticationFilter.java

@@ -1,6 +1,7 @@
-package com.sckw.core.filter;
+package com.sckw.gateway.filter;
 
-import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
 import com.sckw.core.model.constant.Global;
 import com.sckw.core.model.enums.ClientTypeEnum;
@@ -12,31 +13,31 @@ import com.sckw.core.utils.StringUtils;
 import com.sckw.core.web.config.CustomConfig;
 import com.sckw.core.web.constant.HttpStatus;
 import com.sckw.core.web.constant.RequestConstant;
-import com.sckw.core.web.context.LoginEntHolder;
-import com.sckw.core.web.context.LoginUserHolder;
 import com.sckw.core.web.model.LoginEntInfo;
 import com.sckw.core.web.model.LoginUserInfo;
-import com.sckw.core.web.response.HttpResult;
-import com.sckw.core.web.response.ResponseUtil;
+import com.sckw.gateway.pojo.HttpResult;
 import com.sckw.redis.utils.RedissonUtils;
 import jakarta.annotation.PostConstruct;
-import jakarta.servlet.*;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
 import org.redisson.api.RSet;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
 
-import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.*;
 
-/**
- * @desc: 登录过滤
- * @author: czh
- * @date: 2023/6/14
- */
 @Slf4j
-public class RequestCheckFilter implements Filter {
+@Component
+public class AuthenticationFilter implements GlobalFilter, Ordered {
 
     @Autowired
     CustomConfig customConfig;
@@ -58,6 +59,7 @@ public class RequestCheckFilter implements Filter {
 
     private static final String REGISTER = "/kwsEnt/register";
 
+
     /**
      * @desc: 初始化放行路径
      * @author: czh
@@ -71,7 +73,7 @@ public class RequestCheckFilter implements Filter {
 
         String specialLinks = customConfig.getSpecialLinks();
         if (StringUtils.isNotBlank(specialLinks)) {
-            IMPORT_PASS_PATH.addAll(Arrays.asList(links.split(Global.COMMA)));
+            IMPORT_PASS_PATH.addAll(Arrays.asList(specialLinks.split(Global.COMMA)));
         }
 //        String withoutLinks = customConfig.getWithoutLinks();
 //        if (StringUtils.isNotBlank(withoutLinks)) {
@@ -80,60 +82,38 @@ public class RequestCheckFilter implements Filter {
     }
 
     @Override
-    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
-                         FilterChain filterChain) throws IOException, ServletException {
-        HttpServletRequest request = (HttpServletRequest) servletRequest;
-        HttpServletResponse response = (HttpServletResponse) servletResponse;
-        String token = request.getHeader(RequestConstant.TOKEN);
-        String clientType = request.getHeader(RequestConstant.CLIENT_TYPE);
-        Integer systemType = request.getIntHeader(RequestConstant.SYSTEM_TYPE);
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
+        String token = request.getHeaders().getFirst(RequestConstant.TOKEN);
+        String clientType = request.getHeaders().getFirst(RequestConstant.CLIENT_TYPE);
+        String systemTypeStr = request.getHeaders().getFirst(RequestConstant.SYSTEM_TYPE);
+        Integer systemType = systemTypeStr == null ? null : Integer.parseInt(systemTypeStr);
 //        String accessSpecial = request.getHeader(RequestConstant.ACCESS_SPECIAL);
-        String requestUri = request.getRequestURI();
+        String requestUri = request.getPath().value();
         /*1、非token校验接口放行*/
-        if (EXCLUDEPATH.contains(requestUri)) {//不校验token时,如果存在token,也设置上下文变量信息
-            Map<String, Object> tokenMap = EncryUtil.descryV2(Global.PRI_KEY, token);
-            if (tokenMap != null) {
-                Long userId = StringUtils.isNotBlank(tokenMap.get("userId")) ? NumberUtils.parseLong(tokenMap.get("userId")) : null;
-                String key = Global.getFullUserLoginKey(systemType, userId);
-
-                String userInfoStr = RedissonUtils.getString(key);
-                LoginUserInfo loginUserInfo = StringUtils.isNotBlank(userInfoStr) ? JSON.parseObject(userInfoStr, LoginUserInfo.class) : null;
-                if (Objects.nonNull(loginUserInfo)) {
-                    loginUserInfo.setClientType(clientType);
-                    String loginEntStr = RedissonUtils.getString(Global.getFullUserEntKey(loginUserInfo.getEntId()));
-                    LoginEntInfo loginEntInfo = StringUtils.isNotBlank(loginEntStr) ? JSON.parseObject(loginEntStr, LoginEntInfo.class) : null;
-                    LoginUserHolder.set(loginUserInfo);
-                    LoginEntHolder.set(loginEntInfo);
-                }
-            }
-            filterChain.doFilter(servletRequest, servletResponse);
-            LoginUserHolder.remove();
-            LoginEntHolder.remove();
-            return;
+        if (EXCLUDEPATH.contains(requestUri)) {
+            return chain.filter(exchange);
         }
 
         // 添加对Swagger相关路径的放行
         if (requestUri.startsWith("/swagger-ui") ||
-                requestUri.startsWith("/v3/api-docs") || requestUri.startsWith("/doc.htm") ||
+                requestUri.startsWith("/v3/api-docs") || requestUri.startsWith("/doc.htm")||
                 requestUri.startsWith("/webjars/")) {
-            filterChain.doFilter(servletRequest, servletResponse);
-            return;
+            return chain.filter(exchange);
         }
 
         /*2、校验token**/
         /*2.1、校验token非空*/
         HttpResult result = checkBlank(token, clientType, systemType, requestUri);
         if (result.getCode() != HttpStatus.SUCCESS_CODE) {
-            ResponseUtil.writer(response, result);
-            return;
+            return writeResponse(response,result.getCode(),result.getMsg());
         }
 
         /*2.2、token解析*/
         Map<String, Object> tokenMap = EncryUtil.descryV2(Global.PRI_KEY, token);
         if (tokenMap == null) {
-            log.error("认证失效: token {}", token);
-            ResponseUtil.writer(response, HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, HttpStatus.TOKEN_INVALID_MESSAGE));
-            return;
+            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
         }
 
         /*2.3、从redis获取用户登录token*/
@@ -141,15 +121,12 @@ public class RequestCheckFilter implements Filter {
         String key = Global.getFullUserTokenKey(clientType, userId);
         String redisUserToken = RedissonUtils.getString(key);
         if (StringUtils.isBlank(redisUserToken)) {
-            log.error("认证失效: clientType {} userId {}", clientType, userId);
-            ResponseUtil.writer(response, HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, HttpStatus.TOKEN_INVALID_MESSAGE));
-            return;
+            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
         }
 
         /*2.4、请求token和redis中token不一致,说明账号在别处登录了*/
         if (!token.equals(redisUserToken)) {
-            ResponseUtil.writer(response, HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, HttpStatus.ACCOUNT_OTHER_LOGIN_MESSAGE));
-            return;
+            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.ACCOUNT_OTHER_LOGIN_MESSAGE);
         }
 
         /*3、校验登录用户信息*/
@@ -157,22 +134,20 @@ public class RequestCheckFilter implements Filter {
         String userInfoStr = RedissonUtils.getString(key);
         LoginUserInfo loginUserInfo = StringUtils.isNotBlank(userInfoStr) ? JSON.parseObject(userInfoStr, LoginUserInfo.class) : null;
         if (Objects.isNull(loginUserInfo)) {
-            log.error("认证失效,systemType {} userId {}", systemType, userId);
-
-            ResponseUtil.writer(response, HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, HttpStatus.TOKEN_INVALID_MESSAGE));
-            return;
+            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
         }
         loginUserInfo.setClientType(clientType);
 
         //校验用户账号是否冻结
         if (loginUserInfo.getStatus() == Global.YES) {
-            ResponseUtil.writer(response, HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, "您的账号已被冻结,请联系系统管理员!"));
-            return;
+            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,"您的账号已被冻结,请联系系统管理员!");
         }
 
         /*4、登录中的企业信息*/
         String loginEntStr = RedissonUtils.getString(Global.getFullUserEntKey(loginUserInfo.getEntId()));
         LoginEntInfo loginEntInfo = StringUtils.isNotBlank(loginEntStr) ? JSON.parseObject(loginEntStr, LoginEntInfo.class) : null;
+
+        //运营端
         if (SystemTypeEnum.MANAGE.getCode().equals(systemType)) {
             //redis 获取客户经理绑定企业ID
             String managerKey = Global.getCustomerManagerUserLoginKey(SystemTypeEnum.MANAGE.getCode(), loginUserInfo.getId());
@@ -183,40 +158,35 @@ public class RequestCheckFilter implements Filter {
                     loginUserInfo.setAuthEntIdList(authUserIdList);
                 }
             }
-            LoginUserHolder.set(loginUserInfo);
-            LoginEntHolder.set(loginEntInfo);
+            //重置缓存有效期
             RedissonUtils.putString(Global.getFullUserTokenKey(clientType, userId), token, ClientTypeEnum.expireTime(clientType));
             RedissonUtils.putString(Global.getFullUserLoginKey(systemType, loginUserInfo.getId()), JSON.toJSONString(loginUserInfo), Global.APP_TOKEN_EXPIRE);
             RedissonUtils.putString(Global.getFullUserEntKey(loginEntInfo.getId()), JSON.toJSONString(loginEntInfo), Global.APP_TOKEN_EXPIRE);
-            filterChain.doFilter(servletRequest, servletResponse);
-            LoginUserHolder.remove();
-            return;
+            //将用户信息和企业信息放入header方便后续微服务获取
+            ServerWebExchange build = buildNewExchange(exchange,loginUserInfo,loginEntInfo);
+            return chain.filter(build);
         }
 
-        if ((StringUtils.isBlank(loginEntStr) || loginEntInfo == null) && !Objects.equals(systemType, SystemTypeEnum.MANAGE.getCode())) {
-            ResponseUtil.writer(response, HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, HttpStatus.UN_LOGIN_MESSAGE));
-            return;
+        //非运营端
+        if ((StringUtils.isBlank(loginEntStr) || loginEntInfo == null)) {
+            return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.UN_LOGIN_MESSAGE);
         } else {
             //校验用户企业是否冻结
-            if (Objects.isNull(loginEntInfo) || loginEntInfo.getStatus() == Global.YES) {
-                ResponseUtil.writer(response, HttpResult.error(HttpStatus.TOKEN_INVALID_CODE, "您所属企业已被冻结,请联系系统管理员!"));
-                return;
+            if (loginEntInfo.getStatus() == Global.YES) {
+                return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,"您所属企业已被冻结,请联系系统管理员!");
             }
 
             //校验用户企业审批状态
             if (!loginEntInfo.getValid() && !REGISTER.equals(requestUri)) {
                 if (loginEntInfo.getApproval() == Global.NO) {
-                    ResponseUtil.writer(response, HttpResult.error(HttpStatus.CODE_60603, HttpStatus.ENTCERTIFICATES_NOT_REGISTER));
-                    return;
+                    return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_NOT_REGISTER);
                 }
 
                 if (loginEntInfo.getApproval() == Global.NUMERICAL_THREE) {
-                    ResponseUtil.writer(response, HttpResult.error(HttpStatus.CODE_60603, HttpStatus.ENTCERTIFICATES_NOT_PASS));
-                    return;
+                    return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_NOT_PASS);
                 }
 
-                ResponseUtil.writer(response, HttpResult.error(HttpStatus.CODE_60603, HttpStatus.ENTCERTIFICATES_INVAILD));
-                return;
+                return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_INVAILD);
             }
         }
 
@@ -225,37 +195,54 @@ public class RequestCheckFilter implements Filter {
         if (loginUserInfo.getIsMain() == Global.NO
                 && !WITHOUTPATH.contains(requestUri)
                 && !checkMenu(clientType, loginUserInfo.getId(), requestUri)) {
-            ResponseUtil.writer(response, HttpResult.error(HttpStatus.AUTHORITY_NO_CODE, HttpStatus.ACCESS_FIAL));
-            return;
+            return writeResponse(response,HttpStatus.AUTHORITY_NO_CODE,HttpStatus.ACCESS_FIAL);
         }
-        LoginUserHolder.set(loginUserInfo);
-        LoginEntHolder.set(loginEntInfo);
         RedissonUtils.putString(Global.getFullUserLoginKey(systemType, loginUserInfo.getId()), JSON.toJSONString(loginUserInfo), Global.APP_TOKEN_EXPIRE);
         RedissonUtils.putString(Global.getFullUserEntKey(loginEntInfo.getId()), JSON.toJSONString(loginEntInfo), Global.APP_TOKEN_EXPIRE);
         RedissonUtils.putString(Global.getFullUserTokenKey(clientType, userId), token, ClientTypeEnum.expireTime(clientType));
-        filterChain.doFilter(servletRequest, servletResponse);
-        LoginUserHolder.remove();
-        LoginEntHolder.remove();
+        //将用户信息和企业信息放入header方便后续微服务获取
+        ServerWebExchange build = buildNewExchange(exchange,loginUserInfo,loginEntInfo);
+        return chain.filter(build);
     }
 
+    private ServerWebExchange buildNewExchange(ServerWebExchange exchange, LoginUserInfo loginUserInfo, LoginEntInfo loginEntInfo) {
+        SimplePropertyPreFilter filter = new SimplePropertyPreFilter("id","entTypes", "firmName","approval","status","special");
+        String userInfoStrEncode = URLEncoder.encode(JSON.toJSONString(loginUserInfo), StandardCharsets.UTF_8);
+        String entInfoStrEncode = URLEncoder.encode(JSON.toJSONString(loginEntInfo,filter), StandardCharsets.UTF_8);
+        ServerHttpRequest newRequest = exchange.getRequest().mutate()
+                .headers(h -> {
+                    h.add(Global.USER_INFO_STR_ENCODE, userInfoStrEncode);
+                    h.add(Global.ENT_INFO_STR_ENCODE, entInfoStrEncode);
+                    h.remove(RequestConstant.TOKEN);
+                }).build();
+        return exchange.mutate().request(newRequest).build();
+    }
 
     /**
-     * @param userId 用户菜单权限key  url 当前请求url
-     * @return boolean
-     * @desc: 校验url权限
-     * @author: czh
-     * @date: 2023/6/28
+     * 构建返回内容
+     *
+     * @param response ServerHttpResponse
+     * @param code     返回码
+     * @param msg     返回数据
+     * @return Mono
      */
-    private boolean checkMenu(String clientType, Long userId, String url) {
-        return true;
-        //return RedissonUtils.contains(Global.REDIS_SYS_MENU_PREFIX + clientType + Global.COLON + userId, url);
+    protected Mono<Void> writeResponse(ServerHttpResponse response, Integer code, String msg) {
+        JSONObject message = new JSONObject();
+        message.put("code", code);
+        message.put("msg", msg);
+        byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
+        DataBuffer buffer = response.bufferFactory().wrap(bits);
+        response.setStatusCode(org.springframework.http.HttpStatus.OK);
+        // 指定编码,否则在浏览器中会中文乱码
+        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
+        return response.writeWith(Mono.just(buffer));
     }
 
+
     /**
      * @param token         token
      * @param clientType    客户端类型
      * @param systemType    系统类型
-     * @param accessSpecial 专场标识
      * @param requestUri    请求地址
      * @return 校验结果
      * @desc Hearder内容校验
@@ -275,4 +262,20 @@ public class RequestCheckFilter implements Filter {
         return HttpResult.ok();
     }
 
+    /**
+     * @param userId 用户菜单权限key  url 当前请求url
+     * @return boolean
+     * @desc: 校验url权限
+     * @author: czh
+     * @date: 2023/6/28
+     */
+    private boolean checkMenu(String clientType, Long userId, String url) {
+        return true;
+        //return RedissonUtils.contains(Global.REDIS_SYS_MENU_PREFIX + clientType + Global.COLON + userId, url);
+    }
+
+    @Override
+    public int getOrder() {
+        return 2;
+    }
 }

+ 2 - 2
sckw-gateway/src/main/resources/bootstrap-dev.yml

@@ -19,10 +19,10 @@ spring:
         file-extension: yaml
         shared-configs:
           - data-id: sckw-common.yml
-            group: sckw-common
+            group: sckw-ng-common
             refresh: true
         #可以读多个配置文件 需要在同一个命名空间下面可以是不同的组
         extension-configs:
-          - dataId: sckw-common.yml
+          - data-id: sckw-common.yml
             group: sckw-ng-service-platform
             refresh: true

+ 5 - 2
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/feign/VehicleTraceClient.java

@@ -2,11 +2,14 @@ package com.sckw.transport.api.feign;
 
 import com.sckw.core.web.response.BaseResult;
 import com.sckw.transport.api.model.dto.VehicleDataDTO;
+import com.sckw.transport.api.model.dto.VehicleReturnData;
 import com.sckw.transport.api.model.vo.VehicleTraceResponse;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 
+import java.util.List;
+
 /**
  * 车辆轨迹服务 Feign 客户端
  * 调用数据中台的车辆轨迹接口
@@ -27,7 +30,7 @@ public interface VehicleTraceClient {
      * @return 车辆位置信息
      */
     @PostMapping("/queryRealTimeLocation")
-    BaseResult<VehicleTraceResponse> queryRealTimeLocation(@RequestBody VehicleDataDTO vehicleDataDTO);
+    BaseResult<VehicleReturnData> queryRealTimeLocation(@RequestBody VehicleDataDTO vehicleDataDTO);
 
     /**
      * 查询车辆轨迹列表
@@ -35,7 +38,7 @@ public interface VehicleTraceClient {
      * @return 车辆轨迹列表
      */
     @PostMapping("/queryVehicleDataList")
-    BaseResult<VehicleTraceResponse> queryVehicleDataList(@RequestBody VehicleDataDTO vehicleDataDTO);
+    BaseResult<List<VehicleReturnData>> queryVehicleDataList(@RequestBody VehicleDataDTO vehicleDataDTO);
 
     /**
      * 上报车辆轨迹

+ 2 - 0
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/dto/VehicleReturnData.java

@@ -2,6 +2,7 @@ package com.sckw.transport.api.model.dto;
 
 import lombok.Data;
 
+import java.io.Serial;
 import java.io.Serializable;
 import java.time.LocalDateTime;
 
@@ -13,6 +14,7 @@ import java.time.LocalDateTime;
 @Data
 public class VehicleReturnData implements Serializable {
 
+    @Serial
     private static final long serialVersionUID = 1L;
 
     /**

+ 3 - 14
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/vo/VehicleTraceResponse.java

@@ -3,6 +3,7 @@ package com.sckw.transport.api.model.vo;
 import com.sckw.transport.api.model.dto.VehicleReturnData;
 import lombok.Data;
 
+import java.io.Serial;
 import java.io.Serializable;
 import java.util.List;
 
@@ -14,6 +15,7 @@ import java.util.List;
 @Data
 public class VehicleTraceResponse implements Serializable {
 
+    @Serial
     private static final long serialVersionUID = 1L;
 
     /**
@@ -25,18 +27,5 @@ public class VehicleTraceResponse implements Serializable {
      * 车辆轨迹列表数据
      */
     private List<VehicleReturnData> dataList;
-    
-    /**
-     * 兼容方法:获取列表数据
-     * 如果 dataList 不为空则返回 dataList,否则返回包含 data 的列表
-     */
-    public List<VehicleReturnData> getDataList() {
-        if (dataList != null) {
-            return dataList;
-        }
-        if (data != null) {
-            return List.of(data);
-        }
-        return List.of();
-    }
+
 }

+ 7 - 2
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/operateService/KwcContractLogisticsService.java

@@ -1056,8 +1056,13 @@ public class KwcContractLogisticsService {
 
     public PageDataResult<QueryLogisticListResp> queryLogisticsContractListByPage(QueryLogisticListReq req) {
         log.info("分页查询物流合同参数:{}", JSON.toJSONString( req));
-        Long entId = org.apache.commons.lang3.StringUtils.isNotBlank(req.getEntId()) ?  Long.valueOf(req.getEntId()) :
-                null;
+        Long entId;
+        if (org.apache.commons.lang3.StringUtils.isNotBlank(req.getEntId())) {
+            entId = Long.valueOf(req.getEntId());
+        }else{
+            entId = LoginUserHolder.getEntId();
+        }
+
         List<Long> entIdList = Lists.newArrayList();
         if (org.apache.commons.lang3.StringUtils.isNotBlank(req.getConsignCompanyId())){
             entIdList.add(Long.valueOf(req.getConsignCompanyId()));

+ 4 - 4
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/service/KwfTruckService.java

@@ -1629,14 +1629,14 @@ public class KwfTruckService {
         
         try {
             // 使用 Feign 调用查询实时位置
-            BaseResult<VehicleTraceResponse> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
+            BaseResult<VehicleReturnData> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
             
             if (result == null || result.getCode() != HttpStatus.SUCCESS_CODE || result.getData() == null) {
                 log.warn("查询实时轨迹返回空数据, 运单号: {}", odrderNo);
                 return null;
             }
             
-            return result.getData().getData();
+            return result.getData();
         } catch (Exception e) {
             log.error("查询任务轨迹异常, 运单号: {}", odrderNo, e);
             return null;
@@ -1649,14 +1649,14 @@ public class KwfTruckService {
         
         try {
             // 使用 Feign 调用查询实时位置
-            BaseResult<VehicleTraceResponse> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
+            BaseResult<VehicleReturnData> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
             
             if (result == null || result.getCode() != HttpStatus.SUCCESS_CODE || result.getData() == null) {
                 log.warn("查询实时轨迹返回空数据, 车牌号: {}", truckId);
                 return null;
             }
             
-            return result.getData().getData();
+            return result.getData();
         } catch (Exception e) {
             log.error("查询任务轨迹异常, 车牌号: {}", truckId, e);
             return null;

+ 49 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/KwfTruckTraceController.java

@@ -8,13 +8,23 @@ import com.sckw.transport.model.param.TruckInfoReq;
 import com.sckw.transport.model.vo.CurrentTaskTraceReqVo;
 import com.sckw.transport.model.vo.KwfTruckTraceReplayVo;
 import com.sckw.transport.model.vo.TruckInfoVo;
+import com.sckw.transport.model.vo.TruckSelectVo;
+import com.sckw.transport.model.dto.TruckSelectReq;
+import com.sckw.transport.model.dto.MapVehicleQueryReq;
+import com.sckw.transport.model.vo.MapVehicleVo;
+import com.sckw.transport.model.dto.VehicleExceptionQueryReq;
+import com.sckw.transport.model.vo.VehicleExceptionVo;
+import com.sckw.transport.model.dto.GenerateTraceReq;
 import com.sckw.transport.service.kwfTruckTraceService;
+import com.sckw.transport.service.VehicleExceptionService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+
 
 /**
  * @author cxf
@@ -26,6 +36,7 @@ import org.springframework.web.bind.annotation.*;
 @Tag(name = "车辆轨迹相关接口")
 public class KwfTruckTraceController {
     private final kwfTruckTraceService kwfTruckTraceService;
+    private final VehicleExceptionService vehicleExceptionService;
 
     /**
      * 获取车辆实时位置信息
@@ -68,4 +79,41 @@ public class KwfTruckTraceController {
         return BaseResult.success( kwfTruckTraceService.findPage(req));
     }
 
-}
+    /**
+     * 车辆下拉列表(支持多选、输入匹配)
+     */
+    @PostMapping("/truckSelect")
+    @Operation(summary = "车辆下拉列表", description = "支持根据车牌号输入匹配,返回车牌号和定位状态")
+    public BaseResult<List<TruckSelectVo>> getTruckSelectList(@RequestBody TruckSelectReq req) {
+        return BaseResult.success(kwfTruckTraceService.getTruckSelectList(req));
+    }
+
+    /**
+     * 分页查询地图车辆列表
+     */
+    @PostMapping("/mapVehicleList")
+    @Operation(summary = "地图车辆列表", description = "分页查询地图中展示的进行中任务车辆信息")
+    public BaseResult<PageDataResult<MapVehicleVo>> queryMapVehicleList(@RequestBody MapVehicleQueryReq req) {
+        return BaseResult.success(kwfTruckTraceService.queryMapVehicleList(req));
+    }
+
+    /**
+     * 分页查询车辆异常图片信息
+     */
+    @PostMapping("/exceptionList")
+    @Operation(summary = "车辆异常列表", description = "根据运输企业ID、异常类型、车牌号、定位状态分页查询车辆异常图片信息")
+    public BaseResult<PageDataResult<VehicleExceptionVo>> queryExceptionList(@RequestBody VehicleExceptionQueryReq req) {
+        return BaseResult.success(vehicleExceptionService.queryExceptionList(req));
+    }
+
+    /**
+     * 生成车辆轨迹
+     */
+    @PostMapping("/generateTrace")
+    @Operation(summary = "生成车辆轨迹", description = "根据日期、运单号、车牌号、当前位置生成车辆轨迹数据")
+    public BaseResult<Void> generateTrace(@Valid @RequestBody GenerateTraceReq req) {
+        kwfTruckTraceService.generateTrace(req);
+        return BaseResult.success();
+    }
+
+}

+ 26 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/KwtWaybillOrderController.java

@@ -717,5 +717,31 @@ public class KwtWaybillOrderController {
          transportService.addLogisticOrder(req);
     }
 
+    /**
+     * 根据运单号模糊查询运单ID和运单号列表
+     *
+     * @param req 查询请求参数
+     * @return 运单列表
+     * @author system
+     * @date 2025-12-01
+     */
+    @PostMapping("/queryWaybillOrderList")
+    @Operation(summary = "根据运单号模糊查询运单ID和运单号列表")
+    public BaseResult<List<WaybillOrderSimpleVo>> queryWaybillOrderList(@RequestBody WaybillOrderQueryByNoReq req) {
+        return BaseResult.success(waybillOrderV1Service.queryWaybillOrderListByWOrderNo(req.getWOrderNo()));
+    }
+
+    /**
+     * 查询运单车辆ID和车牌号列表
+     *
+     * @return 车辆列表
+     * @author system
+     * @date 2025-12-02
+     */
+    @GetMapping("/queryTruckList")
+    @Operation(summary = "查询运单车辆ID和车牌号列表")
+    public BaseResult<List<WaybillOrderTruckVo>> queryTruckList() {
+        return BaseResult.success(waybillOrderV1Service.queryTruckList());
+    }
 
 }

+ 14 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dao/KwtVehicleExceptionMapper.java

@@ -0,0 +1,14 @@
+package com.sckw.transport.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.transport.model.KwtVehicleException;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片Mapper
+ * @date 2025-12-01
+ */
+@Mapper
+public interface KwtVehicleExceptionMapper extends BaseMapper<KwtVehicleException> {
+}

+ 145 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtVehicleException.java

@@ -0,0 +1,145 @@
+package com.sckw.transport.model;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片表
+ * @date 2025-12-01
+ */
+@Data
+@TableName("kwt_vehicle_exception")
+public class KwtVehicleException implements Serializable {
+    
+    @Serial
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 主键
+     */
+    @TableField("id")
+    private Long id;
+    
+    /**
+     * 企业ID
+     */
+    @TableField("ent_id")
+    private Long entId;
+    
+    /**
+     * 车辆ID
+     */
+    @TableField("truck_id")
+    private Long truckId;
+    
+    /**
+     * 车牌号
+     */
+    @TableField("truck_no")
+    private String truckNo;
+    
+    /**
+     * 运单号
+     */
+    @TableField("w_order_no")
+    private String wOrderNo;
+    
+    /**
+     * 司机ID
+     */
+    @TableField("driver_id")
+    private Long driverId;
+    
+    /**
+     * 司机姓名
+     */
+    @TableField("driver_name")
+    private String driverName;
+    
+    /**
+     * 司机电话
+     */
+    @TableField("driver_phone")
+    private String driverPhone;
+    
+    /**
+     * 异常类型(1-车辆偏航,2-急刹车,3-超速,4-异常停车)
+     */
+    @TableField("exception_type")
+    private Integer exceptionType;
+    
+    /**
+     * 图片URL
+     */
+    @TableField("image_url")
+    private String imageUrl;
+    
+    /**
+     * 异常时间
+     */
+    @TableField("exception_time")
+    private Date exceptionTime;
+
+    
+    /**
+     * 经度
+     */
+    @TableField("longitude")
+    private String longitude;
+    
+    /**
+     * 纬度
+     */
+    @TableField("latitude")
+    private String latitude;
+    
+    /**
+     * 位置描述
+     */
+    @TableField("location")
+    private String location;
+    
+    /**
+     * 异常详情描述
+     */
+    @TableField("description")
+    private String description;
+    
+    /**
+     * 创建人
+     */
+    @TableField("create_by")
+    private Long createBy;
+    
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private Date createTime;
+    
+    /**
+     * 更新人
+     */
+    @TableField("update_by")
+    private Long updateBy;
+    
+    /**
+     * 更新时间
+     */
+    @TableField("update_time")
+    private Date updateTime;
+    
+    /**
+     * 是否删除(0未删除,1删除)
+     */
+    @TableLogic
+    @TableField("del_flag")
+    private Integer delFlag;
+}

+ 40 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/GenerateTraceReq.java

@@ -0,0 +1,40 @@
+package com.sckw.transport.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author system
+ * @desc 生成轨迹请求
+ * @date 2025-12-02
+ */
+@Data
+@Schema(description = "生成轨迹请求")
+public class GenerateTraceReq implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "日期(格式:yyyy-MM-dd)", example = "2025-11-18", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "日期不能为空")
+    private String date;
+
+    @Schema(description = "运单号", example = "T88565469682136456969", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "运单号不能为空")
+    private String wOrderNo;
+
+    @Schema(description = "车牌号", example = "川A888528", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "车牌号不能为空")
+    private String truckNo;
+
+    @Schema(description = "当前位置(经纬度,格式:经度,纬度)", example = "104.065735,30.659462", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "当前位置不能为空")
+    private String currentLocation;
+
+    @Schema(description = "异常类型(1-车辆偏航,2-急刹车,3-超速,4-异常停车)")
+    private Integer exceptionType;
+}

+ 48 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/MapVehicleQueryReq.java

@@ -0,0 +1,48 @@
+package com.sckw.transport.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 地图车辆查询请求
+ * @author system
+ */
+@Data
+@Schema(description = "地图车辆查询请求")
+public class MapVehicleQueryReq  implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "承运单位ID")
+    private Long carrierEntId;
+
+    @Schema(description = "托运单位ID")
+    private Long consignEntId;
+
+    @Schema(description = "物流订单号")
+    private String lOrderNo;
+
+    @Schema(description = "车辆定位状态(1-在线,0-离线,null-全部)")
+    private Integer locationStatus;
+    @Schema(description = "任务状态")
+    private Integer status;
+
+    @Schema(description = "排序类型(1-按时间排序,2-耗时排序,3-异常排序)", example = "1")
+    private Integer sortType = 1;
+
+    @Schema(description = "开始日期(yyyy-MM-dd)默认当天")
+    private String startDate;
+
+    @Schema(description = "结束日期(yyyy-MM-dd)默认当天")
+    private String endDate;
+
+    @Schema(description = "页码", example = "1")
+    private Integer pageNum = 1;
+
+    @Schema(description = "每页数量", example = "20")
+    private Integer pageSize = 20;
+}

+ 32 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/TruckSelectReq.java

@@ -0,0 +1,32 @@
+package com.sckw.transport.model.dto;
+
+import com.sckw.core.web.request.PageReq;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 车辆多选下拉查询请求
+ * @author system
+ */
+@Data
+@Schema(description = "车辆多选下拉查询请求")
+public class TruckSelectReq implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+    /**
+     * 车牌号
+     */
+    @Schema(description = "车牌号(模糊匹配)")
+    private String truckNo;
+    /**
+     * 定位状态筛选
+     */
+    @Schema(description = "定位状态筛选(1-在线,0-离线,null-全部)")
+    private Integer locationStatus;
+
+}

+ 32 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/VehicleExceptionQueryReq.java

@@ -0,0 +1,32 @@
+package com.sckw.transport.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author cxf
+ * @desc 车辆异常查询请求
+ * @date 2025-12-01
+ */
+@Data
+@Schema(description = "车辆异常查询请求")
+public class VehicleExceptionQueryReq {
+    
+    @Schema(description = "运输企业ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long entId;
+    
+    @Schema(description = "异常类型(1-车辆偏航,2-急刹车,3-超速,4-异常停车)")
+    private Integer exceptionType;
+    
+    @Schema(description = "车牌号")
+    private String truckNo;
+    
+    @Schema(description = "定位状态(1-在线,0-离线)")
+    private Integer locationStatus;
+    
+    @Schema(description = "页码", example = "1")
+    private Integer pageNum = 1;
+    
+    @Schema(description = "每页数量", example = "20")
+    private Integer pageSize = 20;
+}

+ 23 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/WaybillOrderQueryByNoReq.java

@@ -0,0 +1,23 @@
+package com.sckw.transport.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author system
+ * @desc 根据运单号查询运单请求参数
+ * @date 2025-12-01
+ */
+@Data
+@Schema(description = "根据运单号查询运单请求参数")
+public class WaybillOrderQueryByNoReq implements Serializable {
+    
+    @Serial
+    private static final long serialVersionUID = 1L;
+    
+    @Schema(description = "运单号(支持模糊查询)", example = "YD202512")
+    private String wOrderNo;
+}

+ 65 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/ExceptionSortTypeEnum.java

@@ -0,0 +1,65 @@
+package com.sckw.transport.model.enuma;
+
+import lombok.Getter;
+
+/**
+ * @author cxf
+ * @desc 异常排序类型枚举
+ * @date 2025-12-01
+ */
+@Getter
+public enum ExceptionSortTypeEnum {
+    
+    /**
+     * 按时间排序(任务开始时间倒序)
+     */
+    TIME_DESC(1, "按时间排序", "exception_time DESC"),
+    
+    /**
+     * 耗时排序(按任务完成耗时倒序)
+     */
+    DURATION_DESC(2, "耗时排序", "task_duration DESC"),
+    
+    /**
+     * 异常排序(任务过程产生的异常数倒序)
+     */
+    EXCEPTION_COUNT_DESC(3, "异常排序", "exception_count DESC");
+    
+    private final Integer code;
+    private final String name;
+    private final String orderBySql;
+    
+    ExceptionSortTypeEnum(Integer code, String name, String orderBySql) {
+        this.code = code;
+        this.name = name;
+        this.orderBySql = orderBySql;
+    }
+    
+    public static String getName(Integer code) {
+        for (ExceptionSortTypeEnum entity : ExceptionSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getName();
+            }
+        }
+        return null;
+    }
+    
+    public static String getOrderBySql(Integer code) {
+        for (ExceptionSortTypeEnum entity : ExceptionSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getOrderBySql();
+            }
+        }
+        // 默认按时间倒序
+        return TIME_DESC.getOrderBySql();
+    }
+    
+    public static ExceptionSortTypeEnum getByCode(Integer code) {
+        for (ExceptionSortTypeEnum entity : ExceptionSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity;
+            }
+        }
+        return TIME_DESC;
+    }
+}

+ 53 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/MapVehicleSortTypeEnum.java

@@ -0,0 +1,53 @@
+package com.sckw.transport.model.enuma;
+
+import lombok.Getter;
+
+/**
+ * @author cxf
+ * @desc 地图车辆排序类型枚举
+ * @date 2025-12-01
+ */
+@Getter
+public enum MapVehicleSortTypeEnum {
+    
+    /**
+     * 按时间排序(任务开始时间倒序)
+     */
+    TIME_DESC(1, "按时间排序"),
+    
+    /**
+     * 耗时排序(按任务完成耗时倒序)
+     */
+    DURATION_DESC(2, "耗时排序"),
+    
+    /**
+     * 异常排序(任务过程产生的异常数倒序)
+     */
+    EXCEPTION_COUNT_DESC(3, "异常排序");
+    
+    private final Integer code;
+    private final String name;
+    
+    MapVehicleSortTypeEnum(Integer code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+    
+    public static String getName(Integer code) {
+        for (MapVehicleSortTypeEnum entity : MapVehicleSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getName();
+            }
+        }
+        return null;
+    }
+    
+    public static MapVehicleSortTypeEnum getByCode(Integer code) {
+        for (MapVehicleSortTypeEnum entity : MapVehicleSortTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity;
+            }
+        }
+        return TIME_DESC;
+    }
+}

+ 58 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/enuma/VehicleExceptionTypeEnum.java

@@ -0,0 +1,58 @@
+package com.sckw.transport.model.enuma;
+
+import lombok.Getter;
+
+/**
+ * @author AI Assistant
+ * @desc 车辆异常类型枚举
+ * @date 2025-12-01
+ */
+@Getter
+public enum VehicleExceptionTypeEnum {
+    
+    /**
+     * 车辆偏航
+     */
+    DEVIATION(1, "车辆偏航"),
+    
+    /**
+     * 急刹车
+     */
+    SUDDEN_BRAKE(2, "急刹车"),
+    
+    /**
+     * 超速
+     */
+    OVERSPEED(3, "超速"),
+    
+    /**
+     * 异常停车
+     */
+    ABNORMAL_PARKING(4, "异常停车");
+    
+    private final Integer code;
+    private final String name;
+    
+    VehicleExceptionTypeEnum(Integer code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+    
+    public static String getName(Integer code) {
+        for (VehicleExceptionTypeEnum entity : VehicleExceptionTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity.getName();
+            }
+        }
+        return null;
+    }
+    
+    public static VehicleExceptionTypeEnum getByCode(Integer code) {
+        for (VehicleExceptionTypeEnum entity : VehicleExceptionTypeEnum.values()) {
+            if (entity.getCode().equals(code)) {
+                return entity;
+            }
+        }
+        return null;
+    }
+}

+ 1 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/VehiclesTrajectoryReq.java

@@ -15,7 +15,7 @@ import java.util.Date;
 @Data
 @Valid
 public class VehiclesTrajectoryReq {
-    //private String ts;
+    private String ts;
     /**
      * 手机号
      */

+ 92 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/MapVehicleVo.java

@@ -0,0 +1,92 @@
+package com.sckw.transport.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 地图车辆展示VO
+ * @author system
+ */
+@Data
+@Schema(description = "地图车辆展示信息")
+public class MapVehicleVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "商品信息")
+    private String goodsInfo;
+
+    @Schema(description = "装货重量/任务量")
+    private String loadWeight;
+
+    @Schema(description = "定位位置")
+    private String location;
+
+    @Schema(description = "定位时间(yyyy-MM-dd HH:mm:ss)")
+    private String locationTime;
+
+    @Schema(description = "车牌号")
+    private String truckNo;
+
+    @Schema(description = "车辆ID")
+    private Long truckId;
+
+    @Schema(description = "司机姓名")
+    private String driverName;
+
+    @Schema(description = "司机手机号")
+    private String driverPhone;
+
+    @Schema(description = "运单号")
+    private String wOrderNo;
+
+    @Schema(description = "物流订单号")
+    private String lOrderNo;
+
+    @Schema(description = "托运单位")
+    private String consignUnit;
+
+    @Schema(description = "承运单位")
+    private String carrierUnit;
+
+    @Schema(description = "经度")
+    private String longitude;
+
+    @Schema(description = "纬度")
+    private String latitude;
+
+    @Schema(description = "定位状态(1-在线,0-离线)")
+    private Integer locationStatus;
+
+    @Schema(description = "定位状态描述")
+    private String locationStatusDesc;
+
+    @Schema(description = "运单状态")
+    private Integer status;
+
+    @Schema(description = "运单状态描述")
+    private String statusDesc;
+
+    @Schema(description = "装货量")
+    private BigDecimal loadAmount;
+
+    @Schema(description = "任务量")
+    private BigDecimal entrustAmount;
+
+    @Schema(description = "单位")
+    private String unit;
+
+    @Schema(description = "任务开始时间(yyyy-MM-dd HH:mm:ss)")
+    private String taskStartTime;
+
+    @Schema(description = "任务耗时(分钟)")
+    private Long taskDuration;
+
+    @Schema(description = "异常数量")
+    private Integer exceptionCount;
+}

+ 41 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/TruckSelectVo.java

@@ -0,0 +1,41 @@
+package com.sckw.transport.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 车辆下拉选项VO
+ * @author system
+ */
+@Data
+@Schema(description = "车辆下拉选项")
+public class TruckSelectVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "车牌号")
+    private String truckNo;
+
+    @Schema(description = "定位状态(0-离线,1-在线)")
+    private Integer locationStatus;
+
+    @Schema(description = "定位状态描述")
+    private String locationStatusDesc;
+
+    @Schema(description = "车辆ID")
+    private Long truckId;
+
+    @Schema(description = "司机姓名")
+    private String driverName;
+
+    @Schema(description = "司机手机号")
+    private String driverPhone;
+
+    @Schema(description = "运单状态")
+    private Integer status;
+
+    @Schema(description = "运单状态描述")
+    private String statusDesc;
+}

+ 56 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/VehicleExceptionVo.java

@@ -0,0 +1,56 @@
+package com.sckw.transport.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片信息VO
+ * @date 2025-12-01
+ */
+@Data
+@Schema(description = "车辆异常图片信息")
+public class VehicleExceptionVo {
+    
+    @Schema(description = "图片ID")
+    private Long id;
+    
+    @Schema(description = "车牌号")
+    private String truckNo;
+    
+    @Schema(description = "异常类型代码(1-车辆偏航,2-急刹车,3-超速,4-异常停车)")
+    private Integer exceptionType;
+    
+    @Schema(description = "异常类型名称")
+    private String exceptionTypeName;
+    
+    @Schema(description = "图片URL")
+    private String imageUrl;
+    
+    @Schema(description = "异常时间(yyyy-MM-dd HH:mm:ss)")
+    private String exceptionTime;
+    
+    @Schema(description = "经度")
+    private String longitude;
+    
+    @Schema(description = "纬度")
+    private String latitude;
+    
+    @Schema(description = "位置描述")
+    private String location;
+    
+    @Schema(description = "定位状态(1-在线,0-离线)")
+    private Integer locationStatus;
+    
+    @Schema(description = "定位状态描述")
+    private String locationStatusDesc;
+    
+    @Schema(description = "异常详情描述")
+    private String description;
+    
+    @Schema(description = "司机姓名")
+    private String driverName;
+    
+    @Schema(description = "司机电话")
+    private String driverPhone;
+}

+ 26 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/WaybillOrderSimpleVo.java

@@ -0,0 +1,26 @@
+package com.sckw.transport.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author system
+ * @desc 运单简单信息VO(用于下拉选择)
+ * @date 2025-12-01
+ */
+@Data
+@Schema(description = "运单简单信息VO")
+public class WaybillOrderSimpleVo implements Serializable {
+    
+    @Serial
+    private static final long serialVersionUID = 1L;
+    
+    @Schema(description = "运单ID")
+    private Long id;
+    
+    @Schema(description = "运单号")
+    private String wOrderNo;
+}

+ 23 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/vo/WaybillOrderTruckVo.java

@@ -0,0 +1,23 @@
+package com.sckw.transport.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 运单车辆信息VO
+ * @author system
+ */
+@Data
+@Schema(description = "运单车辆信息")
+public class WaybillOrderTruckVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "车辆ID")
+    private Long truckId;
+
+    @Schema(description = "车牌号")
+    private String truckNo;
+}

+ 9 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderGoodsRepository.java

@@ -52,4 +52,13 @@ public class KwtLogisticsOrderGoodsRepository extends ServiceImpl<KwtLogisticsOr
                 .in(KwtLogisticsOrderGoods::getLOrderId,logIds)
         );
     }
+
+    /**
+     * 根据物流订单ID列表查询
+     */
+    public List<KwtLogisticsOrderGoods> queryByLOrderIds(List<Long> lOrderIds) {
+        return list(Wrappers.<KwtLogisticsOrderGoods>lambdaQuery()
+                .eq(KwtLogisticsOrderGoods::getDelFlag, 0)
+                .in(KwtLogisticsOrderGoods::getLOrderId, lOrderIds));
+    }
 }

+ 7 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderRepository.java

@@ -117,4 +117,11 @@ public class KwtLogisticsOrderRepository extends ServiceImpl<KwtLogisticsOrderMa
                 .like(StringUtils.isNotBlank(orderNo),KwtLogisticsOrder::getLOrderNo, orderNo)
         );
     }
+
+    public KwtLogisticsOrder queryByLogisticOrderNo(String lOrderNo) {
+        return getOne(Wrappers.<KwtLogisticsOrder>lambdaQuery()
+                .eq(KwtLogisticsOrder::getDelFlag,0)
+                .eq(KwtLogisticsOrder::getLOrderNo, lOrderNo)
+                .last("limit 1"));
+    }
 }

+ 9 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderUnitRepository.java

@@ -53,4 +53,13 @@ public class KwtLogisticsOrderUnitRepository extends ServiceImpl<KwtLogisticsOrd
                 .eq(KwtLogisticsOrderUnit::getDelFlag,0)
                 .eq(KwtLogisticsOrderUnit::getEntId,entId));
     }
+
+    /**
+     * 根据物流订单ID列表查询
+     */
+    public List<KwtLogisticsOrderUnit> queryByLOrderIds(List<Long> lOrderIds) {
+        return list(Wrappers.<KwtLogisticsOrderUnit>lambdaQuery()
+                .eq(KwtLogisticsOrderUnit::getDelFlag, 0)
+                .in(KwtLogisticsOrderUnit::getLOrderId, lOrderIds));
+    }
 }

+ 71 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtVehicleExceptionRepository.java

@@ -0,0 +1,71 @@
+package com.sckw.transport.repository;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.transport.dao.KwtVehicleExceptionMapper;
+import com.sckw.transport.model.KwtVehicleException;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author cxf
+ * @desc 车辆异常图片Repository
+ * @date 2025-12-01
+ */
+@Repository
+public class KwtVehicleExceptionRepository extends ServiceImpl<KwtVehicleExceptionMapper, KwtVehicleException> {
+    
+    /**
+     * 分页查询车辆异常图片
+     *
+     * @param entId          企业ID
+     * @param exceptionType  异常类型
+     * @param truckNo        车牌号
+     * @param pageNum        页码
+     * @param pageSize       每页数量
+     * @return 分页结果
+     */
+    public IPage<KwtVehicleException> queryExceptionImagePage(Long entId, Integer exceptionType,
+                                                              String truckNo, int pageNum, int pageSize) {
+        return page(new Page<>(pageNum, pageSize),
+                Wrappers.<KwtVehicleException>lambdaQuery()
+                        .eq(KwtVehicleException::getDelFlag, 0)
+                        .eq(Objects.nonNull(entId), KwtVehicleException::getEntId, entId)
+                        .eq(Objects.nonNull(exceptionType), KwtVehicleException::getExceptionType, exceptionType)
+                        .like(StringUtils.isNotBlank(truckNo), KwtVehicleException::getTruckNo, truckNo)
+                        .orderByDesc(KwtVehicleException::getExceptionTime));
+    }
+    
+    /**
+     * 批量查询运单的异常数量
+     *
+     * @param wOrderNos 运单号列表
+     * @return 运单号对应的异常数量 Map
+     */
+    public Map<String, Integer> countExceptionsByWOrderNos(List<String> wOrderNos) {
+        if (wOrderNos == null || wOrderNos.isEmpty()) {
+            return Map.of();
+        }
+        
+        List<KwtVehicleException> exceptions = list(
+                Wrappers.<KwtVehicleException>lambdaQuery()
+                        .eq(KwtVehicleException::getDelFlag, 0)
+                        .in(KwtVehicleException::getWOrderNo, wOrderNos)
+        );
+        
+        // 按运单号分组统计数量
+        return exceptions.stream()
+                .filter(e -> e.getWOrderNo() != null)
+                .collect(Collectors.groupingBy(
+                        KwtVehicleException::getWOrderNo,
+                        Collectors.collectingAndThen(Collectors.counting(), Long::intValue)
+                ));
+    }
+}

+ 79 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderRepository.java

@@ -11,9 +11,11 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.sckw.core.model.enums.CarWaybillEnum;
 import com.sckw.transport.dao.KwtWaybillOrderMapper;
 import com.sckw.transport.model.KwtWaybillOrder;
+import jakarta.validation.constraints.NotBlank;
 import org.springframework.stereotype.Repository;
 
 import java.time.LocalDateTime;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
@@ -180,6 +182,27 @@ public class KwtWaybillOrderRepository extends ServiceImpl<KwtWaybillOrderMapper
                         .eq(KwtWaybillOrder::getDelFlag, 0));
     }
 
+    /**
+     * 查询车辆列表(用于下拉选择)
+     * @param truckNo 车牌号关键字(模糊匹配)
+     * @return 车辆列表(按车牌号分组去重,最多返回100条)
+     */
+    public List<KwtWaybillOrder> selectTruckListForSelect(String truckNo) {
+        LambdaQueryWrapper<KwtWaybillOrder> wrapper = Wrappers.<KwtWaybillOrder>lambdaQuery()
+                .select(KwtWaybillOrder::getTruckId,
+                        KwtWaybillOrder::getTruckNo,
+                        KwtWaybillOrder::getDriverId,
+                        KwtWaybillOrder::getDriverName,
+                        KwtWaybillOrder::getDriverPhone,
+                        KwtWaybillOrder::getStatus)
+                .eq(KwtWaybillOrder::getDelFlag, 0)
+                .like(StringUtils.isNotBlank(truckNo), KwtWaybillOrder::getTruckNo, truckNo)
+                .groupBy(KwtWaybillOrder::getTruckNo)
+                .orderByDesc(KwtWaybillOrder::getUpdateTime)
+                .last("LIMIT 100");
+        return list(wrapper);
+    }
+
 
     public List<KwtWaybillOrder> queryByEntIdAndDriverId(Long entId, Long driverId) {
         Date thirtyDaysAgo = DateUtil.beginOfDay(DateUtil.offsetDay(new Date(), -30));
@@ -191,6 +214,61 @@ public class KwtWaybillOrderRepository extends ServiceImpl<KwtWaybillOrderMapper
                         .ge(KwtWaybillOrder::getUpdateTime, thirtyDaysAgo));
     }
 
+    /**
+     * 分页查询地图车辆列表(进行中任务)
+     * @param entIds 企业ID集合
+     * @param startDate 开始日期
+     * @param endDate 结束日期
+     * @param taskingStatus 任务状态列表
+     * @param wayOrderIds 运单ID集合
+     * @param pageNum 页码
+     * @param pageSize 每页数量
+     * @return 分页结果
+     */
+    public IPage<KwtWaybillOrder> queryMapVehicleListPage(Set<Long> entIds, Date startDate, Date endDate,
+                                                           List<Integer> taskingStatus, Set<Long> wayOrderIds, int pageNum, int pageSize) {
+        return page(new Page<>(pageNum, pageSize),
+                Wrappers.<KwtWaybillOrder>lambdaQuery()
+                        .eq(KwtWaybillOrder::getDelFlag, 0)
+                        .in(CollectionUtils.isNotEmpty(entIds), KwtWaybillOrder::getEntId, entIds)
+                        .in(KwtWaybillOrder::getStatus, taskingStatus)
+                        .in(CollectionUtils.isNotEmpty(wayOrderIds), KwtWaybillOrder::getId, wayOrderIds)
+                        .ge(Objects.nonNull(startDate), KwtWaybillOrder::getCreateTime, startDate)
+                        .le(Objects.nonNull(endDate), KwtWaybillOrder::getCreateTime, endDate)
+                        .orderByDesc(KwtWaybillOrder::getUpdateTime));
+    }
+    
+    /**
+     * 根据运单号模糊查询运单ID和运单号列表
+     * @param wOrderNo 运单号关键字(模糊匹配)
+     * @return 运单列表(最多返回50条)
+     */
+    public List<KwtWaybillOrder> queryWaybillOrderListByWOrderNo(String wOrderNo) {
+        return list(Wrappers.<KwtWaybillOrder>lambdaQuery()
+                .select(KwtWaybillOrder::getId, KwtWaybillOrder::getWOrderNo)
+                .eq(KwtWaybillOrder::getDelFlag, 0)
+                .like(StringUtils.isNotBlank(wOrderNo), KwtWaybillOrder::getWOrderNo, wOrderNo)
+                .orderByDesc(KwtWaybillOrder::getCreateTime)
+                .last("LIMIT 50"));
+    }
 
+    /**
+     * 查询运单车辆ID和车牌号列表(去重)
+     * @return 车辆列表(按车牌号分组去重)
+     */
+    public List<KwtWaybillOrder> queryTruckList(Long entId ) {
+        return list(Wrappers.<KwtWaybillOrder>lambdaQuery()
+                .select(KwtWaybillOrder::getTruckId, KwtWaybillOrder::getTruckNo)
+                .eq(KwtWaybillOrder::getDelFlag, 0)
+                .eq(KwtWaybillOrder::getEntId, entId)
+                .orderByDesc(KwtWaybillOrder::getTruckId));
+    }
 
-}
+    public KwtWaybillOrder queryByWayOrderNo(String wOrderNo) {
+        return getOne(
+                Wrappers.<KwtWaybillOrder>lambdaQuery()
+                        .eq(KwtWaybillOrder::getWOrderNo, wOrderNo)
+                        .eq(KwtWaybillOrder::getDelFlag, 0)
+                        .last("limit 1"));
+    }
+}

+ 96 - 6
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtWaybillOrderV1Service.java

@@ -3,6 +3,8 @@ package com.sckw.transport.service;
 import cn.hutool.core.bean.BeanUtil;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -2204,10 +2206,10 @@ public class KwtWaybillOrderV1Service {
                     // 使用 Feign 调用查询车辆轨迹列表
                     com.sckw.transport.api.model.dto.VehicleDataDTO vehicleDataDTO1 = new com.sckw.transport.api.model.dto.VehicleDataDTO();
                     vehicleDataDTO.setWOrderNo(waybillOrder.getWOrderNo());
-                    BaseResult<VehicleTraceResponse> result = vehicleTraceClient.queryVehicleDataList(vehicleDataDTO1);
+                    BaseResult<List<com.sckw.transport.api.model.dto.VehicleReturnData>> result = vehicleTraceClient.queryVehicleDataList(vehicleDataDTO1);
                     
                     if (result != null && result.getCode()!= HttpStatus.SUCCESS_CODE && result.getData() != null) {
-                        List<com.sckw.transport.api.model.dto.VehicleReturnData> vehicleReturn = result.getData().getDataList();
+                        List<com.sckw.transport.api.model.dto.VehicleReturnData> vehicleReturn = result.getData();
                         List<VehicleRouteData> vehicleReturnData = vehicleReturn.stream().map(x->{
                             VehicleRouteData vehicleRouteData = new VehicleRouteData();
                             vehicleRouteData.setTs(x.getTs());
@@ -3021,6 +3023,9 @@ public class KwtWaybillOrderV1Service {
 
         // 查询运单相关的装卸货时间信息
         Map<String, KwtWaybillOrderTicket> subBillIdAddressIdKeyAndOrderTrackMap = getStringKwtWaybillOrderTrackMap(addressIds);
+        
+        // 查询运单节点数据,获取离场时间
+        Map<Long, KwtWaybillOrderNode> wOrderIdAndOffsiteNodeMap = getOffsiteNodeMap(wayBillOrderIds);
 
         //更具物流订单查询商品
         List<KwtLogisticsOrderGoods> logisticsOrderGoods =
@@ -3065,7 +3070,7 @@ public class KwtWaybillOrderV1Service {
                 .map(record -> getWaybillOrderResp(record, waybillOrderIdAndBillOrderMap,
                         logOrderIdUnitTypeKeyAndUnitMap, finalLogOrderIdAndGoodsIdMap,
                         kwpGoodsMap, finalSubOrderIdAddressTypeKeyAndAddressMap, subBillIdAddressIdKeyAndOrderTrackMap,
-                        finalLogIdAndOrderMap, finalDictValueAndDictResDtoMap))
+                        finalLogIdAndOrderMap, finalDictValueAndDictResDtoMap, wOrderIdAndOffsiteNodeMap))
                 .collect(Collectors.toList());
 
         return PageDataResult.of(page,resps);
@@ -3134,6 +3139,41 @@ public class KwtWaybillOrderV1Service {
         }
         return subBillIdAddressIdKeyAndOrderTrackMap;
     }
+    
+    /**
+     * 批量查询运单的离场节点
+     * @param wOrderIds 运单ID集合
+     * @return 运单ID对应的离场节点Map
+     */
+    @NotNull
+    private Map<Long, KwtWaybillOrderNode> getOffsiteNodeMap(Set<Long> wOrderIds) {
+        Map<Long, KwtWaybillOrderNode> offsiteNodeMap = Maps.newHashMap();
+        
+        if (CollectionUtils.isEmpty(wOrderIds)) {
+            return offsiteNodeMap;
+        }
+        
+        // 查询所有运单的节点数据,过滤出状态为“已离场”的节点
+        List<KwtWaybillOrderNode> nodes = kwtWaybillOrderNodeRepository.list(
+                Wrappers.<KwtWaybillOrderNode>lambdaQuery()
+                        .in(KwtWaybillOrderNode::getWOrderId, wOrderIds)
+                        .eq(KwtWaybillOrderNode::getOrderStatus, CarWaybillV1Enum.WAIT_LOADING.getCode())
+                        .orderByAsc(KwtWaybillOrderNode::getCreateTime)
+        );
+        
+        if (CollectionUtils.isNotEmpty(nodes)) {
+            // 每个运单只取最后一条离场记录
+            offsiteNodeMap = nodes.stream()
+                    .collect(Collectors.toMap(
+                            KwtWaybillOrderNode::getWOrderId,
+                            Function.identity(),
+                            (existing, replacement) -> 
+                                    existing.getCreateTime().before(replacement.getCreateTime()) ? existing : replacement
+                    ));
+        }
+        
+        return offsiteNodeMap;
+    }
 
     @NotNull
     private Set<Long> getLogOrderIds(WaybillOrderReq req) {
@@ -3238,7 +3278,8 @@ public class KwtWaybillOrderV1Service {
                                                         Map<String, KwtWaybillOrderAddress> finalSubOrderIdAddressTypeKeyAndAddressMap,
                                                         Map<String, KwtWaybillOrderTicket> finalSubBillIdAddressIdKeyAndOrderTrackMap,
                                                         Map<Long, KwtLogisticsOrder> finalLogIdAndOrderMap,
-                                                        Map<String, SysDictResDto> dictValueAndDictResDtoMap) {
+                                                        Map<String, SysDictResDto> dictValueAndDictResDtoMap,
+                                                        Map<Long, KwtWaybillOrderNode> wOrderIdAndOffsiteNodeMap) {
         WaybillOrderResp waybillOrderResp = new WaybillOrderResp();
         KwtWaybillOrder billOrder = finalWaybillOrderIdAndBillOrderMap.getOrDefault(record.getWOrderId(),
                 new KwtWaybillOrder());
@@ -3291,8 +3332,13 @@ public class KwtWaybillOrderV1Service {
                 finalSubBillIdAddressIdKeyAndOrderTrackMap.getOrDefault(record.getId() + "-" + loadingAdd.getId(),
                         new KwtWaybillOrderTicket());
         waybillOrderResp.setLoadingTime(DateUtils.format(loadTrack.getOperateTime(),DateUtils.DATE_TIME_PATTERN));
-        //todo  cxf
-        waybillOrderResp.setOffsiteTime(DateUtils.format(unloadTrack.getOperateTime(),DateUtils.DATE_TIME_PATTERN));
+        
+        // 从节点表中获取离场时间
+        KwtWaybillOrderNode offsiteNode = wOrderIdAndOffsiteNodeMap.get(record.getWOrderId());
+        if (offsiteNode != null) {
+            waybillOrderResp.setOffsiteTime(DateUtils.format(offsiteNode.getCreateTime(), DateUtils.DATE_TIME_PATTERN));
+        }
+        
         waybillOrderResp.setUnloadingTime(DateUtils.format(unloadTrack.getOperateTime(),DateUtils.DATE_TIME_PATTERN));
         if (Objects.equals(record.getStatus(), CarWaybillV1Enum.WAIT_UNLOADING.getCode())){
             waybillOrderResp.setCompleteTime(DateUtils.format(record.getUpdateTime(),DateUtils.DATE_TIME_PATTERN));
@@ -3934,4 +3980,48 @@ public class KwtWaybillOrderV1Service {
         // 暂时返回经纬度格式
         return String.format("经度:%s, 纬度:%s", lng, lat);
     }
+    
+    /**
+     * 根据运单号模糊查询运单ID和运单号列表
+     *
+     * @param wOrderNo 运单号关键字(模糊匹配)
+     * @return 运单列表
+     */
+    public List<WaybillOrderSimpleVo> queryWaybillOrderListByWOrderNo(String wOrderNo) {
+        // 查询运单数据
+        List<KwtWaybillOrder> waybillOrders = kwtWaybillOrderRepository.queryWaybillOrderListByWOrderNo(wOrderNo);
+        
+        // 转换为VO
+        return waybillOrders.stream()
+                .map(order -> {
+                    WaybillOrderSimpleVo vo = new WaybillOrderSimpleVo();
+                    vo.setId(order.getId());
+                    vo.setWOrderNo(order.getWOrderNo());
+                    return vo;
+                })
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 查询运单车辆ID和车牌号列表(去重)
+     *
+     * @return 车辆列表
+     */
+    public List<WaybillOrderTruckVo> queryTruckList() {
+        Long entId = LoginUserHolder.getEntId();
+        // 查询运单车辆数据
+        List<KwtWaybillOrder> waybillOrders = kwtWaybillOrderRepository.queryTruckList(entId);
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(waybillOrders)){
+            return List.of();
+        }
+        // 转换为VO
+        return waybillOrders.stream()
+                .map(order -> {
+                    WaybillOrderTruckVo vo = new WaybillOrderTruckVo();
+                    vo.setTruckId(order.getTruckId());
+                    vo.setTruckNo(order.getTruckNo());
+                    return vo;
+                })
+                .collect(Collectors.toList());
+    }
 }

+ 165 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/VehicleExceptionService.java

@@ -0,0 +1,165 @@
+package com.sckw.transport.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.transport.api.feign.VehicleTraceClient;
+import com.sckw.transport.api.model.dto.VehicleDataDTO;
+import com.sckw.transport.api.model.dto.VehicleReturnData;
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.transport.model.KwtVehicleException;
+import com.sckw.transport.model.dto.VehicleExceptionQueryReq;
+import com.sckw.transport.model.enuma.VehicleExceptionTypeEnum;
+import com.sckw.transport.model.vo.VehicleExceptionVo;
+import com.sckw.transport.repository.KwtVehicleExceptionRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author AI Assistant
+ * @desc 车辆异常图片Service
+ * @date 2025-12-01
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class VehicleExceptionService {
+    
+    private final KwtVehicleExceptionRepository exceptionImageRepository;
+    private final VehicleTraceClient vehicleTraceClient;
+    
+    /**
+     * 分页查询车辆异常图片信息
+     *
+     * @param req 查询请求
+     * @return 分页结果
+     */
+    public PageDataResult<VehicleExceptionVo> queryExceptionList(VehicleExceptionQueryReq req) {
+        // 分页查询异常图片数据
+        IPage<KwtVehicleException> page = exceptionImageRepository.queryExceptionImagePage(
+                req.getEntId(),
+                req.getExceptionType(),
+                req.getTruckNo(),
+                req.getPageNum(),
+                req.getPageSize()
+        );
+        
+        List<KwtVehicleException> records = page.getRecords();
+        if (CollectionUtils.isEmpty(records)) {
+            return PageDataResult.empty(req.getPageNum(), req.getPageSize());
+        }
+        
+        // 如果有定位状态筛选,需要查询车辆实时定位
+        Map<String, Integer> truckLocationStatusMap = new HashMap<>();
+        if (req.getLocationStatus() != null) {
+            List<String> truckNos = records.stream()
+                    .map(KwtVehicleException::getTruckNo)
+                    .distinct()
+                    .collect(Collectors.toList());
+            
+            truckLocationStatusMap = queryVehicleLocationStatus(truckNos);
+        }
+        
+        // 转换为VO
+        final Map<String, Integer> finalLocationStatusMap = truckLocationStatusMap;
+        List<VehicleExceptionVo> voList = records.stream()
+                .map(image -> buildVehicleExceptionVo(image, finalLocationStatusMap))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        
+        // 如果有定位状态筛选,过滤结果
+        if (req.getLocationStatus() != null) {
+            voList = voList.stream()
+                    .filter(vo -> req.getLocationStatus().equals(vo.getLocationStatus()))
+                    .collect(Collectors.toList());
+        }
+        
+        return PageDataResult.of(page, voList);
+    }
+    
+    /**
+     * 构建车辆异常图片VO
+     *
+     * @param image                异常图片实体
+     * @param locationStatusMap    定位状态Map
+     * @return VO对象
+     */
+    private VehicleExceptionVo buildVehicleExceptionVo(KwtVehicleException image,
+                                                       Map<String, Integer> locationStatusMap) {
+        VehicleExceptionVo vo = new VehicleExceptionVo();
+        
+        vo.setId(image.getId());
+        vo.setTruckNo(image.getTruckNo());
+        vo.setExceptionType(image.getExceptionType());
+        vo.setExceptionTypeName(VehicleExceptionTypeEnum.getName(image.getExceptionType()));
+        vo.setImageUrl(image.getImageUrl());
+        
+        // 格式化异常时间
+        if (image.getExceptionTime() != null) {
+            vo.setExceptionTime(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+                    .format(image.getExceptionTime()));
+        }
+        
+        vo.setLongitude(image.getLongitude());
+        vo.setLatitude(image.getLatitude());
+        vo.setLocation(image.getLocation());
+        vo.setDescription(image.getDescription());
+        vo.setDriverName(image.getDriverName());
+        vo.setDriverPhone(image.getDriverPhone());
+        
+        // 设置定位状态
+        if (locationStatusMap.containsKey(image.getTruckNo())) {
+            Integer locationStatus = locationStatusMap.get(image.getTruckNo());
+            vo.setLocationStatus(locationStatus);
+            vo.setLocationStatusDesc(locationStatus == 1 ? "在线" : "离线");
+        } else {
+            vo.setLocationStatus(0);
+            vo.setLocationStatusDesc("离线");
+        }
+        
+        return vo;
+    }
+    
+    /**
+     * 批量查询车辆定位状态
+     *
+     * @param truckNos 车牌号列表
+     * @return 车牌号与定位状态的映射
+     */
+    private Map<String, Integer> queryVehicleLocationStatus(List<String> truckNos) {
+        Map<String, Integer> locationStatusMap = new HashMap<>();
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime thirtyMinutesAgo = now.minusMinutes(30);
+        
+        for (String truckNo : truckNos) {
+            try {
+                VehicleDataDTO vehicleDataDTO = new VehicleDataDTO();
+                vehicleDataDTO.setCarNo(truckNo);
+                BaseResult<VehicleReturnData> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
+                // 默认离线
+                Integer locationStatus = 0;
+                
+                if (result != null && result.getCode() == 200 && result.getData() != null) {
+                    VehicleReturnData vehicleData = result.getData();
+                    
+                    // 判断30分钟内有定位数据为在线
+                    if (vehicleData.getTs() != null && vehicleData.getTs().isAfter(thirtyMinutesAgo)) {
+                        locationStatus = 1;
+                    }
+                }
+                
+                locationStatusMap.put(truckNo, locationStatus);
+            } catch (Exception e) {
+                log.warn("查询车辆定位状态异常, 车牌号: {}", truckNo, e);
+                locationStatusMap.put(truckNo, 0);
+            }
+        }
+        
+        return locationStatusMap;
+    }
+}

+ 667 - 16
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/kwfTruckTraceService.java

@@ -3,12 +3,17 @@ package com.sckw.transport.service;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.sckw.contract.api.RemoteContractService;
 import com.sckw.core.exception.BusinessException;
 import com.sckw.core.model.constant.UrlConstants;
 import com.sckw.core.model.enums.AddressTypeEnum;
 import com.sckw.core.model.enums.CarWaybillEnum;
+import com.sckw.core.model.enums.CarWaybillV1Enum;
 import com.sckw.core.utils.DateUtils;
 import com.sckw.core.utils.HttpUtil;
 import com.sckw.core.web.constant.CommonConstants;
@@ -19,6 +24,7 @@ import com.sckw.core.web.response.BaseResult;
 import com.sckw.fleet.api.RemoteFleetService;
 import com.sckw.fleet.api.model.vo.RDriverVo;
 import com.sckw.fleet.api.model.vo.RFleetDriverVo;
+import com.sckw.fleet.api.model.vo.RFleetVo;
 import com.sckw.fleet.api.model.vo.RTruckVo;
 import com.sckw.manage.api.RemoteManageService;
 import com.sckw.order.api.dubbo.RemoteTradeOrderAmountService;
@@ -30,26 +36,22 @@ import com.sckw.system.api.RemoteSystemService;
 import com.sckw.system.api.model.dto.res.EntCacheResDto;
 import com.sckw.system.api.model.dto.res.KwsEnterpriseResDto;
 import com.sckw.transport.common.config.UrlConfigProperties;
-import com.sckw.transport.model.KwtLogisticsOrderUnit;
-import com.sckw.transport.model.KwtWaybillOrder;
-import com.sckw.transport.model.KwtWaybillOrderAddress;
-import com.sckw.transport.model.KwtWaybillOrderSubtask;
+import com.sckw.transport.model.*;
 import com.sckw.transport.model.dto.TruckDto;
 import com.sckw.transport.api.feign.VehicleTraceClient;
 import com.sckw.transport.api.model.dto.VehicleDataDTO;
 import com.sckw.transport.api.model.dto.VehicleReturnData;
-import com.sckw.transport.api.model.vo.VehicleTraceResponse;
-import com.sckw.transport.model.param.CurrentTaskTraceReq;
-import com.sckw.transport.model.param.KwfTruckTraceReplayReq;
-import com.sckw.transport.model.param.TruckInfoReq;
-import com.sckw.transport.model.param.VehiclesTrajectoryReq;
+import com.sckw.transport.model.param.*;
+import com.sckw.transport.model.dto.GenerateTraceReq;
 import com.sckw.transport.model.vo.CurrentTaskTraceReqVo;
 import com.sckw.transport.model.vo.KwfTruckTraceReplayVo;
 import com.sckw.transport.model.vo.TruckInfoVo;
-import com.sckw.transport.repository.KwtLogisticsOrderUnitRepository;
-import com.sckw.transport.repository.KwtWaybillOrderAddressRepository;
-import com.sckw.transport.repository.KwtWaybillOrderRepository;
-import com.sckw.transport.repository.KwtWaybillOrderSubtaskRepository;
+import com.sckw.transport.model.vo.TruckSelectVo;
+import com.sckw.transport.model.dto.TruckSelectReq;
+import com.sckw.transport.model.dto.MapVehicleQueryReq;
+import com.sckw.transport.model.vo.MapVehicleVo;
+import com.sckw.transport.model.enuma.MapVehicleSortTypeEnum;
+import com.sckw.transport.repository.*;
 import com.sckw.transport.response.CollectZjxlResponse;
 import com.sckw.transport.service.zj.VehicleCollectService;
 import lombok.RequiredArgsConstructor;
@@ -64,11 +66,14 @@ import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
+import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import lombok.Data;
+
 
 /**
  * @author PC
@@ -84,6 +89,9 @@ public class kwfTruckTraceService {
     private final VehicleCollectService vehicleCollectService;
     private final KwtLogisticsOrderUnitRepository kwtLogisticsOrderUnitRepository;
     private final VehicleTraceClient vehicleTraceClient;
+    private final KwtLogisticsOrderGoodsRepository kwtLogisticsOrderGoodsRepository;
+    private final KwtLogisticsOrderRepository kwtLogisticsOrderRepository;
+    private final KwtVehicleExceptionRepository kwtVehicleExceptionRepository;
     @DubboReference(version = "1.0.0", group = "design", check = false, timeout = 8000)
     RemoteSystemService remoteSystemService;
 
@@ -536,14 +544,14 @@ public class kwfTruckTraceService {
 
         try {
             // 使用 Feign 调用查询实时位置
-            BaseResult<VehicleTraceResponse> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
+            BaseResult<VehicleReturnData> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
 
             if (result == null || result.getCode() != HttpStatus.SUCCESS_CODE || result.getData() == null) {
                 log.warn("查询实时轨迹返回空数据, 车牌号: {}", truckId);
                 return null;
             }
 
-            return result.getData().getData();
+            return result.getData();
         } catch (Exception e) {
             log.error("查询任务轨迹异常, 车牌号: {}", truckId, e);
             return null;
@@ -668,4 +676,647 @@ public class kwfTruckTraceService {
         truckDto.setTaskAddress(taskAddress);
         return truckDto;
     }
-}
+
+    /**
+     * 查询车辆下拉列表(支持多选和输入匹配)
+     * @param req 查询请求
+     * @return 车辆下拉列表
+     */
+    public List<TruckSelectVo> getTruckSelectList(TruckSelectReq req) {
+        log.info("查询车辆下拉列表参数:{}", JSON.toJSONString( req));
+        
+        // 使用 Repository 查询车辆运单数据
+        List<KwtWaybillOrder> waybillOrders = kwtWaybillOrderRepository.selectTruckListForSelect(req.getTruckNo());
+        
+        if (CollectionUtils.isEmpty(waybillOrders)) {
+            return new ArrayList<>();
+        }
+        
+        // 获取所有车牌号列表,用于查询实时位置
+        List<String> truckNos = waybillOrders.stream()
+                .map(KwtWaybillOrder::getTruckNo)
+                .distinct()
+                .collect(Collectors.toList());
+        
+        // 批量查询实时位置状态(30分钟内有数据为在线)
+        Map<String, Integer> truckLocationStatusMap = getStringIntegerMap(truckNos);
+
+        // 转换为 VO 对象并根据状态筛选
+        return waybillOrders.stream()
+                .map(order -> getTruckSelectVo(order, truckLocationStatusMap))
+                // 根据请求参数筛选定位状态
+                .filter(vo -> req.getLocationStatus() == null || vo.getLocationStatus().equals(req.getLocationStatus()))
+                .sorted(Comparator.comparing(TruckSelectVo::getLocationStatus).reversed())
+                .collect(Collectors.toList());
+    }
+
+    @NotNull
+    private Map<String, Integer> getStringIntegerMap(List<String> truckNos) {
+        Map<String, Integer> truckLocationStatusMap = new HashMap<>();
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime thirtyMinutesAgo = now.minusMinutes(30);
+
+        for (String truckNo : truckNos) {
+            try {
+                VehicleDataDTO vehicleDataDTO = new VehicleDataDTO();
+                vehicleDataDTO.setCarNo(truckNo);
+                BaseResult<VehicleReturnData> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
+
+                if (result == null || result.getCode() != HttpStatus.SUCCESS_CODE || result.getData() == null) {
+                    // 无定位数据,设置为离线
+                    truckLocationStatusMap.put(truckNo, 0);
+                    return truckLocationStatusMap;
+                }
+
+                VehicleReturnData vehicleData = result.getData();
+
+                // 判断定位时间是否在30分钟内
+                if (vehicleData.getTs() == null) {
+                    // 无GPS时间,设置为离线
+                    truckLocationStatusMap.put(truckNo, 0);
+                    return truckLocationStatusMap;
+                }
+
+                // 30分钟内为在线
+                if (vehicleData.getTs().isAfter(thirtyMinutesAgo)) {
+                    truckLocationStatusMap.put(truckNo, 1);
+                } else {
+                    truckLocationStatusMap.put(truckNo, 0);
+                }
+
+            } catch (Exception e) {
+                log.warn("查询车辆实时位置异常, 车牌号: {}", truckNo, e);
+                truckLocationStatusMap.put(truckNo, 0);
+            }
+        }
+        return truckLocationStatusMap;
+    }
+
+    @NotNull
+    private static TruckSelectVo getTruckSelectVo(KwtWaybillOrder order, Map<String, Integer> truckLocationStatusMap) {
+        TruckSelectVo vo = new TruckSelectVo();
+        vo.setTruckId(order.getTruckId());
+        vo.setTruckNo(order.getTruckNo());
+        vo.setDriverName(order.getDriverName());
+        vo.setDriverPhone(order.getDriverPhone());
+        vo.setStatus(order.getStatus());
+        vo.setStatusDesc(CarWaybillEnum.getName(order.getStatus()));
+
+        // 设置定位状态
+        Integer locationStatus = truckLocationStatusMap.getOrDefault(order.getTruckNo(), 0);
+        vo.setLocationStatus(locationStatus);
+        vo.setLocationStatusDesc(locationStatus == 1 ? "在线" : "离线");
+
+        return vo;
+    }
+
+    /**
+     * 分页查询地图车辆列表
+     * @param req 查询请求
+     * @return 分页结果
+     */
+    public PageDataResult<MapVehicleVo> queryMapVehicleList(MapVehicleQueryReq req) {
+        log.info("分页查询地图车辆列表参数:{}", req);
+        Set<Long> allEnt = getAllEnt();
+        // 处理日期参数,默认为当天
+        Date startDate = getDate(req.getStartDate());
+        Date endDate = getDate(req.getEndDate());
+
+        // 进行中任务状态:已接单(1)、到达装货点(5)、已装货(10)、已离场(15)
+        List<Integer> taskingStatus = Lists.newArrayList();
+        if (Objects.nonNull(req.getStatus())) {
+            taskingStatus.add(req.getStatus());
+        } else {
+            taskingStatus.addAll(Arrays.asList(CarWaybillV1Enum.PENDING_VEHICLE.getCode(), CarWaybillV1Enum.REFUSE_TRAFFIC.getCode(),
+                    CarWaybillV1Enum.EXIT_COMPLETED.getCode(), CarWaybillV1Enum.WAIT_LOADING.getCode()));
+        }
+        
+        // 前置条件:根据承运单位和托运单位筛选出符合条件的运单ID
+        Set<Long> wayOrderIds = getWayOrderIds(req);
+        if (CollectionUtils.isEmpty(wayOrderIds) && (StringUtils.isNotBlank(req.getLOrderNo()) || Objects.nonNull(req.getCarrierEntId()) || Objects.nonNull(req.getConsignEntId()))){
+            return PageDataResult.empty(req.getPageNum(), req.getPageSize());
+        }
+
+        // 使用 MyBatis-Plus 分页查询(如果有前置筛选条件,则添加运单ID的IN条件)
+
+        IPage<KwtWaybillOrder> page = kwtWaybillOrderRepository.queryMapVehicleListPage(allEnt,startDate, endDate, taskingStatus, wayOrderIds, req.getPageNum(),req.getPageSize() );
+        
+        List<KwtWaybillOrder> waybillOrders = page.getRecords();
+        if (CollectionUtils.isEmpty(waybillOrders)) {
+            return PageDataResult.empty(req.getPageNum(), req.getPageSize());
+        }
+        
+        // 获取运单ID列表
+        List<Long> wOrderIds = waybillOrders.stream()
+                .map(KwtWaybillOrder::getId)
+                .collect(Collectors.toList());
+        
+        // 查询子运单信息
+        List<KwtWaybillOrderSubtask> subtasks = kwtWaybillOrderSubtaskRepository.queryByWOrderIds(wOrderIds);
+        final Map<Long, KwtWaybillOrderSubtask> wOrderIdSubtaskMap;
+        if (CollectionUtils.isNotEmpty(subtasks)) {
+            wOrderIdSubtaskMap = subtasks.stream()
+                    .filter(subtask -> subtask.getWOrderId() != null)
+                    .collect(Collectors.toMap(KwtWaybillOrderSubtask::getWOrderId, Function.identity(), (k1, k2) -> k1));
+        } else {
+            wOrderIdSubtaskMap = new HashMap<>();
+        }
+        
+        // 获取物流订单ID
+        Set<Long> lOrderIds = new HashSet<>();
+        if (CollectionUtils.isNotEmpty(subtasks)) {
+            lOrderIds = subtasks.stream()
+                    .map(KwtWaybillOrderSubtask::getLOrderId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+        }
+
+        List<KwtLogisticsOrderUnit> logisticsOrderUnits = Lists.newArrayList();
+        List<KwtLogisticsOrderGoods> goodsList = Lists.newArrayList();
+        if (CollectionUtils.isNotEmpty(lOrderIds)) {
+            // 查询托运企业和承运企业
+            logisticsOrderUnits = kwtLogisticsOrderUnitRepository.queryByLOrderIds(new ArrayList<>(lOrderIds));
+            // 查询商品信息
+            goodsList = kwtLogisticsOrderGoodsRepository.queryByLOrderIds(new ArrayList<>(lOrderIds));
+        }
+
+        
+        // 按物流订单ID和单位类型分组
+        Map<String, KwtLogisticsOrderUnit> unitMap =new HashMap<>();
+        if (CollectionUtils.isNotEmpty(logisticsOrderUnits)) {
+            unitMap = logisticsOrderUnits.stream()
+                    .filter(unit -> unit.getLOrderId() != null && unit.getUnitType() != null)
+                    .collect(Collectors.toMap(
+                        unit -> unit.getLOrderId() + "_" + unit.getUnitType(),
+                        Function.identity(),
+                        (k1, k2) -> k1
+                    ));
+        }
+        
+
+         Map<Long, KwtLogisticsOrderGoods> goodsMap =new HashMap<>();
+        if (CollectionUtils.isNotEmpty(goodsList)) {
+            goodsMap = goodsList.stream()
+                    .filter(goods -> goods.getLOrderId() != null)
+                    .collect(Collectors.toMap(KwtLogisticsOrderGoods::getLOrderId, Function.identity(), (k1, k2) -> k1));
+        }
+        
+        // 获取车牌号列表,查询定位信息
+        List<String> truckNos = waybillOrders.stream()
+                .map(KwtWaybillOrder::getTruckNo)
+                .distinct()
+                .collect(Collectors.toList());
+        
+        // 批量查询定位状态和位置信息
+        Map<String, VehicleLocationInfo> locationInfoMap = queryVehicleLocationBatch(truckNos);
+        
+        // 批量查询运单轨迹数据,统计异常数量(通过alarmCode判断)
+        List<String> wOrderNos = waybillOrders.stream()
+                .map(KwtWaybillOrder::getWOrderNo)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<String, Integer> exceptionCountMap = countExceptionsByTraceData(wOrderNos);
+        
+        // 根据定位状态筛选(这个筛选保留在内存中,因为涉及外部服务调用)
+        List<KwtWaybillOrder> filteredOrders = waybillOrders;
+        if (req.getLocationStatus() != null) {
+            filteredOrders = waybillOrders.stream()
+                    .filter(order -> {
+                        VehicleLocationInfo locationInfo = locationInfoMap.get(order.getTruckNo());
+                        return locationInfo != null && req.getLocationStatus().equals(locationInfo.getLocationStatus());
+                    })
+                    .collect(Collectors.toList());
+        }
+        
+        // 转换为 VO
+        Map<String, KwtLogisticsOrderUnit> finalUnitMap = unitMap;
+        Map<Long, KwtLogisticsOrderGoods> finalGoodsMap = goodsMap;
+        List<MapVehicleVo> result = filteredOrders.stream()
+                .map(order -> buildMapVehicleVo(order, wOrderIdSubtaskMap, finalUnitMap, finalGoodsMap, locationInfoMap, exceptionCountMap))
+                .collect(Collectors.toList());
+        
+        // 根据排序类型进行排序
+        MapVehicleSortTypeEnum sortType = MapVehicleSortTypeEnum.getByCode(req.getSortType());
+        result = switch (sortType) {
+            case TIME_DESC ->
+                // 按任务开始时间倒序
+                    result.stream()
+                            .sorted(Comparator.comparing(MapVehicleVo::getTaskStartTime,
+                                    Comparator.nullsLast(Comparator.reverseOrder())))
+                            .collect(Collectors.toList());
+            case DURATION_DESC ->
+                // 按任务耗时倒序
+                    result.stream()
+                            .sorted(Comparator.comparing(MapVehicleVo::getTaskDuration,
+                                    Comparator.nullsLast(Comparator.reverseOrder())))
+                            .collect(Collectors.toList());
+            case EXCEPTION_COUNT_DESC ->
+                // 按异常数量倒序
+                    result.stream()
+                            .sorted(Comparator.comparing(MapVehicleVo::getExceptionCount,
+                                    Comparator.nullsLast(Comparator.reverseOrder())))
+                            .collect(Collectors.toList());
+        };
+        
+        // 返回分页结果(使用数据库分页的总数)
+        return PageDataResult.of(page, result);
+    }
+
+    @NotNull
+    private Set<Long> getWayOrderIds(MapVehicleQueryReq req) {
+        Set<Long> wayOrderIds = Sets.newHashSet();
+        if (req.getConsignEntId() != null || req.getCarrierEntId() != null) {
+            // 第一步:从物流订单单位表查询符合条件的物流订单ID
+            // 同时有托运和承运单位条件,将企业ID和类型组合后批量查询
+            List<Long> entIds = Arrays.asList(req.getConsignEntId(), req.getCarrierEntId());
+
+            List<KwtLogisticsOrderUnit> allUnits = kwtLogisticsOrderUnitRepository.list(
+                    Wrappers.<KwtLogisticsOrderUnit>lambdaQuery()
+                            .eq(KwtLogisticsOrderUnit::getDelFlag, 0)
+                            .in(KwtLogisticsOrderUnit::getEntId, entIds)
+                            .in(KwtLogisticsOrderUnit::getUnitType, Arrays.asList(1, 2)));
+            if (CollectionUtils.isEmpty(allUnits)) {
+                return Set.of();
+            }
+            Set<Long> lOrderIds = allUnits.stream()
+                    .map(KwtLogisticsOrderUnit::getLOrderId)
+                    .collect(Collectors.toSet());
+            if (CollectionUtils.isEmpty(lOrderIds)) {
+                return Set.of();
+            }
+
+            // 第二步:根据物流订单ID查询子运单
+            List<KwtWaybillOrderSubtask> subtasks = kwtWaybillOrderSubtaskRepository.list(
+                    Wrappers.<KwtWaybillOrderSubtask>lambdaQuery()
+                            .eq(KwtWaybillOrderSubtask::getDelFlag, 0)
+                            .in(KwtWaybillOrderSubtask::getLOrderId, lOrderIds));
+
+            if (CollectionUtils.isEmpty(subtasks)) {
+                return Set.of();
+            }
+
+            // 第三步:获取运单ID
+            wayOrderIds = subtasks.stream()
+                    .map(KwtWaybillOrderSubtask::getWOrderId)
+                    .collect(Collectors.toSet());
+
+            if (CollectionUtils.isEmpty(wayOrderIds)) {
+                return Set.of();
+            }
+        }
+        if (StringUtils.isNotBlank(req.getLOrderNo())) {
+            // 获取物流订单ID
+            KwtLogisticsOrder logOrder = kwtLogisticsOrderRepository.queryByLogisticOrderNo(req.getLOrderNo());
+            if (Objects.isNull(logOrder)) {
+                return Set.of();
+            }
+            wayOrderIds.add(logOrder.getId());
+        }
+        return wayOrderIds;
+    }
+
+    @NotNull
+    private static Date getDate(String date) {
+        Date startDate;
+        if (StringUtils.isBlank(date)) {
+            startDate = DateUtils.getStartOfDay(new Date());
+        } else {
+            try {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+                startDate = sdf.parse(date);
+                startDate = DateUtils.getStartOfDay(startDate);
+            } catch (Exception e) {
+                log.error("解析开始日期失败", e);
+                startDate = DateUtils.getStartOfDay(new Date());
+            }
+        }
+        return startDate;
+    }
+
+    /**
+     * 批量查询车辆定位信息
+     */
+    private Map<String, VehicleLocationInfo> queryVehicleLocationBatch(List<String> truckNos) {
+        Map<String, VehicleLocationInfo> locationInfoMap = new HashMap<>();
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime thirtyMinutesAgo = now.minusMinutes(30);
+        if (CollectionUtils.isEmpty(truckNos)){
+            return locationInfoMap;
+        }
+        for (String truckNo : truckNos) {
+            try {
+                VehicleDataDTO vehicleDataDTO = new VehicleDataDTO();
+                vehicleDataDTO.setCarNo(truckNo);
+                BaseResult<VehicleReturnData> result = vehicleTraceClient.queryRealTimeLocation(vehicleDataDTO);
+                
+                VehicleLocationInfo locationInfo = new VehicleLocationInfo();
+                
+                if (result != null && result.getCode() == 200 && result.getData() != null) {
+                    VehicleReturnData vehicleData = result.getData();
+                    
+                    // 判断定位状态
+                    if (vehicleData.getTs() != null && vehicleData.getTs().isAfter(thirtyMinutesAgo)) {
+                        locationInfo.setLocationStatus(1);
+                    } else {
+                        locationInfo.setLocationStatus(0);
+                    }
+                    
+                    locationInfo.setLongitude(vehicleData.getLongitude());
+                    locationInfo.setLatitude(vehicleData.getLatitude());
+                    if (vehicleData.getTs() != null) {
+                        locationInfo.setLocationTime(vehicleData.getTs().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+                    }
+                    locationInfo.setStatus(vehicleData.getStatus());
+                } else {
+                    locationInfo.setLocationStatus(0);
+                }
+
+                locationInfoMap.put(truckNo, locationInfo);
+            } catch (Exception e) {
+                log.warn("查询车辆定位信息异常, 车牌号: {}", truckNo, e);
+                VehicleLocationInfo locationInfo = new VehicleLocationInfo();
+                locationInfo.setLocationStatus(0);
+                locationInfoMap.put(truckNo, locationInfo);
+            }
+        }
+        
+        return locationInfoMap;
+    }
+    
+    /**
+     * 批量查询运单轨迹数据并统计异常数量
+     * 通过调用VehicleTraceClient#queryVehicleDataList查询轨迹,统计alarmCode不为空的数据为异常
+     *
+     * @param wOrderNos 运单号列表
+     * @return 运单号对应的异常数量 Map
+     */
+    private Map<String, Integer> countExceptionsByTraceData(List<String> wOrderNos) {
+        Map<String, Integer> exceptionCountMap = new HashMap<>();
+        
+        if (CollectionUtils.isEmpty(wOrderNos)) {
+            return exceptionCountMap;
+        }
+        
+        // 批量查询每个运单的轨迹数据
+        for (String wOrderNo : wOrderNos) {
+            try {
+                VehicleDataDTO vehicleDataDTO = new VehicleDataDTO();
+                vehicleDataDTO.setWOrderNo(wOrderNo);
+                
+                // 调用Feign接口查询轨迹列表
+                BaseResult<List<com.sckw.transport.api.model.dto.VehicleReturnData>> result = 
+                        vehicleTraceClient.queryVehicleDataList(vehicleDataDTO);
+                
+                if (result != null && result.getCode() == HttpStatus.SUCCESS_CODE && result.getData() != null) {
+                    List<com.sckw.transport.api.model.dto.VehicleReturnData> traceDataList = result.getData();
+                    
+                    // 统计alarmCode不为null且不为0的记录数量(表示有异常报警)
+                    int exceptionCount = (int) traceDataList.stream()
+                            .filter(data -> StringUtils.equals(data.getStatus(),"0"))
+                            .count();
+                    
+                    exceptionCountMap.put(wOrderNo, exceptionCount);
+                    log.debug("运单号: {}, 异常数量: {}", wOrderNo, exceptionCount);
+                } else {
+                    log.warn("查询运单轨迹数据失败或返回空数据, 运单号: {}", wOrderNo);
+                    exceptionCountMap.put(wOrderNo, 0);
+                }
+            } catch (Exception e) {
+                log.error("查询运单轨迹数据异常, 运单号: {}", wOrderNo, e);
+                exceptionCountMap.put(wOrderNo, 0);
+            }
+        }
+        
+        return exceptionCountMap;
+    }
+    
+    /**
+     * 构建地图车辆VO
+     */
+    private MapVehicleVo buildMapVehicleVo(KwtWaybillOrder order,
+                                           Map<Long, KwtWaybillOrderSubtask> subtaskMap,
+                                           Map<String, KwtLogisticsOrderUnit> unitMap,
+                                           Map<Long, KwtLogisticsOrderGoods> goodsMap,
+                                           Map<String, VehicleLocationInfo> locationInfoMap,
+                                           Map<String, Integer> exceptionCountMap) {
+        MapVehicleVo vo = new MapVehicleVo();
+        
+        // 车辆和司机信息
+        vo.setTruckNo(order.getTruckNo());
+        vo.setTruckId(order.getTruckId());
+        vo.setDriverName(order.getDriverName());
+        vo.setDriverPhone(order.getDriverPhone());
+        vo.setWOrderNo(order.getWOrderNo());
+        vo.setStatus(order.getStatus());
+        vo.setStatusDesc(CarWaybillEnum.getName(order.getStatus()));
+        
+        // 子运单信息
+        KwtWaybillOrderSubtask subtask = subtaskMap.get(order.getId());
+        if (subtask != null) {
+            vo.setLOrderNo(String.valueOf(subtask.getLOrderId()));
+            vo.setLoadAmount(subtask.getLoadAmount());
+            vo.setEntrustAmount(subtask.getEntrustAmount());
+            vo.setUnit(subtask.getUnit());
+            
+            // 装货重量/任务量
+            String loadWeight = String.format("%s%s / %s%s",
+                    subtask.getLoadAmount() != null ? subtask.getLoadAmount() : "0",
+                    subtask.getUnit() != null ? subtask.getUnit() : "",
+                    subtask.getEntrustAmount() != null ? subtask.getEntrustAmount() : "0",
+                    subtask.getUnit() != null ? subtask.getUnit() : "");
+            vo.setLoadWeight(loadWeight);
+            
+            // 托运单位
+            KwtLogisticsOrderUnit consignUnit = unitMap.get(subtask.getLOrderId() + "_1");
+            if (consignUnit != null) {
+                vo.setConsignUnit(consignUnit.getFirmName());
+            }
+            
+            // 承运单位
+            KwtLogisticsOrderUnit carrierUnit = unitMap.get(subtask.getLOrderId() + "_2");
+            if (carrierUnit != null) {
+                vo.setCarrierUnit(carrierUnit.getFirmName());
+            }
+            
+            // 商品信息
+            KwtLogisticsOrderGoods goods = goodsMap.get(subtask.getLOrderId());
+            if (goods != null) {
+                vo.setGoodsInfo(goods.getGoodsName());
+            }
+        }
+        
+        // 定位信息
+        VehicleLocationInfo locationInfo = locationInfoMap.get(order.getTruckNo());
+        if (locationInfo != null) {
+            vo.setLocationStatus(locationInfo.getLocationStatus());
+            vo.setLocationStatusDesc(locationInfo.getLocationStatus() == 1 ? "在线" : "离线");
+            vo.setLongitude(locationInfo.getLongitude());
+            vo.setLatitude(locationInfo.getLatitude());
+            vo.setLocation(locationInfo.getLocation());
+            vo.setLocationTime(locationInfo.getLocationTime());
+        } else {
+            vo.setLocationStatus(0);
+            vo.setLocationStatusDesc("离线");
+        }
+        
+        // 任务开始时间
+        vo.setTaskStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(order.getCreateTime()));
+        
+        // 计算任务耗时(分钟)
+        Date startTime =  order.getCreateTime();
+        Date endTime = new Date();
+        if (Objects.equals(order.getStatus(), CarWaybillV1Enum.WAIT_UNLOADING.getCode())){
+            endTime = order.getUpdateTime() != null ? order.getUpdateTime() : new Date();
+        }
+        long duration = (endTime.getTime() - startTime.getTime()) / (1000 * 60);
+        vo.setTaskDuration(duration);
+        
+        // 从异常表查询该运单的异常数量
+        Integer exceptionCount = exceptionCountMap.getOrDefault(order.getWOrderNo(), 0);
+        vo.setExceptionCount(exceptionCount);
+        
+        return vo;
+    }
+    
+    /**
+     * 车辆定位信息内部类
+     */
+    @Data
+    private static class VehicleLocationInfo {
+        private Integer locationStatus;
+        private String longitude;
+        private String latitude;
+        private String location;
+        private String locationTime;
+        private String status;
+    }
+
+    @NotNull
+    private Set<Long> getAllEnt() {
+        Set<Long> allEnt = Sets.newHashSet();
+        //根据企业类型查询企业
+
+        allEnt.add( LoginUserHolder.getUserId());
+        EntCacheResDto entCacheResDto = remoteSystemService.queryEntTreeById(LoginUserHolder.getUserId());
+        if (Objects.nonNull(entCacheResDto)) {
+            EntCacheResDto entCacheResDto1 = remoteSystemService.queryEntTreeById(entCacheResDto.getId());
+            List<EntCacheResDto> child = entCacheResDto1.getChild();
+            if (com.sckw.core.utils.CollectionUtils.isNotEmpty(child)) {
+                allEnt.addAll(child.stream()
+                        .map(EntCacheResDto::getId).toList());
+            }
+        }
+        return allEnt;
+    }
+
+    /**
+     * 生成车辆轨迹数据
+     * @param req 生成轨迹请求
+     */
+    public void generateTrace(GenerateTraceReq req) {
+        log.info("生成车辆轨迹参数:{}", JSON.toJSONString(req));
+        
+        // 校验当前位置格式
+        String[] location = req.getCurrentLocation().split(",");
+        if (location.length != 2) {
+            throw new BusinessException("当前位置格式错误,应为:经度,纬度");
+        }
+        
+        String longitude = location[0];
+        String latitude = location[1];
+
+        KwtWaybillOrder order = kwtWaybillOrderRepository.queryByWayOrderNo(req.getWOrderNo());
+        if (Objects.isNull(order)){
+            throw new BusinessException("运单不存在");
+        }
+        //查询子运单
+        KwtWaybillOrder subOrders = kwtWaybillOrderRepository.queryByBillOrderId(order.getLOrderId());
+        if (Objects.isNull(subOrders)){
+            throw new BusinessException("子运单不存在");
+        }
+        // 通过车牌id查询车队
+        RFleetVo fleetByTruckId = fleetService.findFleetByTruckId(order.getTruckId(), order.getEntId());
+        //查询物流订单号
+        KwtLogisticsOrder logisticsOrder = kwtLogisticsOrderRepository.queryByLogisticsOrderId(subOrders.getLOrderId());
+
+        // 构造轨迹数据
+        VehiclesTrajectoryReq vehiclesTrajectoryReq = getVehiclesTrajectoryReq(req, order, longitude, latitude, logisticsOrder, fleetByTruckId);
+
+        try {
+            // 调用数据中台保存轨迹数据
+            BaseResult<Void> result = vehicleTraceClient.saveVehicleData(vehiclesTrajectoryReq);
+            if (result.getCode() != HttpStatus.SUCCESS_CODE) {
+                log.error("保存车辆轨迹数据失败:{}", result.getMessage());
+                throw new BusinessException("生成轨迹失败:" + result.getMessage());
+            }
+            
+            // 如果有异常类型,同时保存到本地车辆异常表
+            saveException(req, order, longitude, latitude);
+
+            log.info("生成车辆轨迹成功,运单号:{},车牌号:{}", req.getWOrderNo(), req.getTruckNo());
+        } catch (Exception e) {
+            log.error("生成车辆轨迹异常:", e);
+            throw new BusinessException("生成轨迹失败:" + e.getMessage());
+        }
+
+
+    }
+
+    private void saveException(GenerateTraceReq req, KwtWaybillOrder order, String longitude, String latitude) {
+        if (Objects.nonNull(req.getExceptionType())) {
+            // 保存到车辆异常表
+            KwtVehicleException vehicleException = new KwtVehicleException();
+            vehicleException.setEntId(order.getEntId());
+            vehicleException.setTruckId(order.getTruckId());
+            vehicleException.setTruckNo(req.getTruckNo());
+            vehicleException.setWOrderNo(req.getWOrderNo());
+            vehicleException.setDriverId(order.getDriverId());
+            vehicleException.setDriverName(order.getDriverName());
+            vehicleException.setDriverPhone(order.getDriverPhone());
+            vehicleException.setExceptionType(req.getExceptionType());
+            vehicleException.setLongitude(longitude);
+            vehicleException.setLatitude(latitude);
+            vehicleException.setExceptionTime(new Date());
+            vehicleException.setCreateBy(LoginUserHolder.getUserId());
+            vehicleException.setCreateTime(new Date());
+            vehicleException.setUpdateBy(LoginUserHolder.getUserId());
+            vehicleException.setUpdateTime(new Date());
+            vehicleException.setDelFlag(0);
+
+            // 保存异常信息
+            kwtVehicleExceptionRepository.save(vehicleException);
+        }
+    }
+
+    @NotNull
+    private static VehiclesTrajectoryReq getVehiclesTrajectoryReq(GenerateTraceReq req, KwtWaybillOrder order, String longitude, String latitude, KwtLogisticsOrder logisticsOrder, RFleetVo fleetByTruckId) {
+        VehiclesTrajectoryReq vehiclesTrajectoryReq = new VehiclesTrajectoryReq();
+        vehiclesTrajectoryReq.setTs(req.getDate() + " " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
+        vehiclesTrajectoryReq.setMobile(order.getDriverPhone());
+        vehiclesTrajectoryReq.setTruckNo(req.getTruckNo());
+        vehiclesTrajectoryReq.setLongitude(longitude);
+        vehiclesTrajectoryReq.setLatitude(latitude);
+        vehiclesTrajectoryReq.setSpeed(60.0f);
+        vehiclesTrajectoryReq.setGpsStatus("60");
+        vehiclesTrajectoryReq.setDirection(30.0f);
+        vehiclesTrajectoryReq.setFuelLevel(20.0f);
+        vehiclesTrajectoryReq.setMileage("60");
+        vehiclesTrajectoryReq.setEngineTemp(40.0f);
+        vehiclesTrajectoryReq.setBatteryVoltage(30.0f);
+        vehiclesTrajectoryReq.setStatus("0");
+        if (req.getExceptionType() != null){
+            vehiclesTrajectoryReq.setStatus(String.valueOf(req.getExceptionType()));
+        }
+        vehiclesTrajectoryReq.setWOrderNo(req.getWOrderNo());
+        vehiclesTrajectoryReq.setLOrderNo(Optional.ofNullable(logisticsOrder).map(KwtLogisticsOrder::getLOrderNo).orElse( ""));
+        VehiclesTrajectoryReq.VehicleDataVO vehicleDataVO = new VehiclesTrajectoryReq.VehicleDataVO();
+        vehicleDataVO.setCarNo(req.getTruckNo());
+        if (fleetByTruckId != null){
+            vehicleDataVO.setFleetId(fleetByTruckId.getId().toString());
+            vehicleDataVO.setFleetName(fleetByTruckId.getName());
+        }
+        vehiclesTrajectoryReq.setVehicleDataVO(vehicleDataVO);
+        return vehiclesTrajectoryReq;
+    }
+}

+ 152 - 0
sql/2025/11/30/2025_11_30_cxf_creat.sql

@@ -0,0 +1,152 @@
+-- auto-generated definition
+create table kwt_logistics_order_unit
+(
+    id          bigint        not null comment '主键'
+        primary key,
+    l_order_id  bigint        not null comment '物流订单id(kwt_logistics_order的主键id)',
+    unit_type   int           not null comment '单位类型(1托运企业、2承运企业)',
+    top_ent_id  bigint        null comment '顶级企业ID',
+    ent_id      bigint        not null comment '企业id',
+    firm_name   varchar(100)  not null comment '企业名称',
+    contacts_id bigint        null comment '联系人ID',
+    contacts    varchar(40)   null comment '联系人姓名',
+    phone       varchar(40)   null comment '联系电话',
+    remark      varchar(400)  null comment '备注',
+    status      int default 0 not null comment '用户状态(0正常、1已锁)',
+    create_by   bigint        not null,
+    create_time datetime      not null,
+    update_by   bigint        not null,
+    update_time datetime      not null comment '更新时间',
+    del_flag    int default 0 not null comment '是否删除(0未删除,1删除)'
+)
+    comment '物流订单-托运企业/承运企业' row_format = DYNAMIC;
+
+-- auto-generated definition
+create table kwt_waybill_order
+(
+    id              bigint                                 not null comment '主键'
+        primary key,
+    ent_id          bigint                                 not null comment '企业id',
+    l_order_id      bigint                                 not null comment '物流订单id',
+    w_order_no      varchar(30)                            not null comment '编号',
+    type            int                                    not null comment '派车类型(1:趟次:2:循环)',
+    truck_id        bigint                                 null comment '车辆档案ID',
+    truck_no        varchar(20)                            not null comment '车牌号',
+    driver_id       bigint                                 null comment '司机id',
+    driver_name     varchar(40)                            null comment '司机姓名',
+    driver_phone    varchar(20)                            null comment '司机手机号',
+    driver_idcard   varchar(20)                            null comment '司机身份证号码',
+    remark          varchar(400)                           null comment '备注',
+    status          int      default 0                     not null comment '状态(1-接单,5-到达装货点,10-已装货,15-已离场,20-已卸货,25-已完成,30-审核驳回,99-已作废)',
+    create_by       bigint                                 not null,
+    create_time     datetime default CURRENT_TIMESTAMP     not null,
+    update_by       bigint   default -1                    not null,
+    update_time     datetime default CURRENT_TIMESTAMP     not null comment '更新时间',
+    del_flag        int      default 0                     not null comment '是否删除(0未删除,1删除)',
+    task_start_time datetime default '1000-01-01 00:00:00' not null comment '任务开始时间',
+    task_end_time   datetime default '1000-01-01 00:00:00' not null comment '任务结束时间'
+)
+    comment '车辆运单' row_format = DYNAMIC;
+
+-- auto-generated definition
+create table kwt_waybill_order_subtask
+(
+    id                 bigint                             not null comment '主键'
+        primary key,
+    ent_id             bigint                             not null comment '企业id',
+    l_order_id         bigint                             not null comment '物流订单id(kwt_logistics_order)',
+    w_order_id         bigint                             not null comment '物流订单id',
+    w_order_no         varchar(30)                        not null comment '编号',
+    unit               varchar(10)                        null comment '托量单位',
+    entrust_amount     decimal(10, 6)                     null comment '已委托量',
+    unload_amount      decimal(10, 6)                     null comment '卸货重量',
+    unload_time        datetime                           null comment '卸货时间',
+    load_amount        decimal(10, 6)                     null comment '装货重量',
+    load_time          datetime                           null comment '装货时间',
+    deficit_amount     decimal(10, 6)                     null comment '亏吨重量',
+    deficit_price      decimal(10, 4)                     null comment '亏吨扣款',
+    remark             varchar(400)                       null comment '备注',
+    status             int      default 0                 not null comment '状态(1-接单,5-到达装货点,10-已装货,15-已离场,20-已卸货,25-已完成,30-审核驳回,99-已作废)',
+    create_by          bigint                             not null,
+    create_time        datetime                           not null,
+    update_by          bigint   default -1                not null,
+    update_time        datetime default CURRENT_TIMESTAMP not null comment '更新时间',
+    del_flag           int      default 0                 not null comment '是否删除(0未删除,1删除)',
+    unload_url         varchar(255)                       null comment '卸货凭证',
+    unload_upload_time datetime                           null comment '凭证上传时间',
+    unload_operator    varchar(255)                       null comment '凭证上传操作人'
+)
+    comment '车辆运单-子运单' row_format = DYNAMIC;
+
+
+-- auto-generated definition
+create table kwt_logistics_order_goods
+(
+    id          bigint        not null comment '主键'
+        primary key,
+    l_order_id  bigint        not null comment '物流订单id',
+    l_order_no  varchar(64)   not null comment '物流订单编号',
+    goods_id    bigint        null comment '商品id',
+    goods_name  varchar(100)  null comment '商品名称',
+    goods_type  varchar(32)   null comment '商品类型',
+    sku_id      bigint        null comment 'sku',
+    remark      varchar(400)  null comment '备注',
+    status      int default 0 not null comment '用户状态(0正常、1已锁)',
+    create_by   bigint        not null,
+    create_time datetime      not null,
+    update_by   bigint        not null,
+    update_time datetime      not null comment '更新时间',
+    del_flag    int default 0 not null comment '是否删除(0未删除,1删除)'
+)
+    comment '物流订单-商品信息' row_format = DYNAMIC;
+
+-- auto-generated definition
+create table kwt_logistics_order
+(
+    id                  bigint                    not null comment '主键'
+        primary key,
+    ent_id              bigint                    not null comment '企业id',
+    t_order_id          bigint                    null comment '交易订单id(kwo_trade_order)',
+    t_order_no          varchar(30)               null comment '交易订单编号(kwo_trade_order)',
+    l_order_no          varchar(30)               not null comment '物流订单编号',
+    pid                 bigint                    null comment '分包上级物流订单id',
+    pids                varchar(100)              null comment '存放分包所有物流订单id使用“;”分隔',
+    type                varchar(10)               not null comment '物流订单类型(1 贸易订单生成物流订单、2 自建物流订单)',
+    level               varchar(255)              null comment '代表分包等级',
+    bind_status         varchar(255) charset utf8 null comment '对账绑定状态(0未绑定/解绑:1绑定)',
+    settlement_cycle    bigint                    null comment '结算周期(周结、月结、季结)',
+    billing_mode        varchar(255)              null comment '计费方式',
+    price               decimal(8, 2)             null comment '运价',
+    price_type          bigint                    null comment '运价方式(元/吨、元/车)',
+    amount              decimal(10, 6)            not null comment '已委托量(上游给他分配的量)',
+    unit                varchar(10)               not null comment '单位(吨、方、箱、件)',
+    loss                decimal(8, 2)             null comment '合理损耗',
+    loss_unit           varchar(20)               null comment '合理损耗单位(‰/kG)',
+    goods_price         decimal(11, 2)            null comment '商品价值(扣亏货值)((实装-实卸)-合理损耗)如果为负数,则为零',
+    goods_price_unit    varchar(20)               null comment '商品价值(扣亏货值)单位',
+    start_time          datetime                  null comment '计划开始时间',
+    end_time            datetime                  null comment '计划结束时间',
+    subcontract_amount  decimal(11, 6)            null comment '分包量(给下游承运商分包的量)',
+    entrust_amount      decimal(11, 6)            null comment '已委托量(自己承运,给下游司机分配的量)',
+    unload_amount       decimal(11, 6)            null comment '卸货量(自己承运总卸货量)',
+    load_amount         decimal(11, 6)            null comment '装货量(自己承运总装货量)',
+    ignore_amount       decimal(11, 6)            null comment '忽略剩余量',
+    deficit_amount      decimal(11, 6)            null comment '亏吨量(自己承运总亏吨量:装货量-卸货量)',
+    deficit_price       decimal(11, 2)            null comment '亏吨扣款(自己承运总亏吨扣款)',
+    load_time           datetime                  null comment '实际开始时间(装货)',
+    unload_time         datetime                  null comment '实际结束时间(卸货)',
+    total_take          int                       null comment '趟次(自己+下游承运总卸货量,算方式为趟次时,最终值以完结输入值为准)',
+    total_load_amount   decimal(11, 6)            null comment '装货-履约量(自己+下游承运总装货量)',
+    total_unload_amount decimal(11, 6)            null comment '卸货-履约量(自己+下游承运总卸货量)',
+    payment             bigint                    null comment '付款方式(预付款、线下支付、第三方支付)',
+    tax_rate            decimal(6)                null comment '发票税率(%)',
+    remark              varchar(200)              null comment '备注',
+    status              int default 0             not null comment '0,待签约,1待接单',
+    create_by           bigint                    not null,
+    create_time         datetime                  not null,
+    update_by           bigint                    not null,
+    update_time         datetime                  not null comment '更新时间',
+    del_flag            int default 0             not null comment '是否删除(0未删除,1删除)'
+)
+    comment '物流订单' row_format = DYNAMIC;
+

+ 29 - 0
sql/2025/12/01/2025_12_01_vehicle_exception.sql

@@ -0,0 +1,29 @@
+-- 车辆异常表
+CREATE TABLE IF NOT EXISTS `kwt_vehicle_exception` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `ent_id` bigint(20) DEFAULT '0' COMMENT '企业ID',
+  `truck_id` bigint(20) DEFAULT '0' COMMENT '车辆ID',
+  `truck_no` varchar(50) DEFAULT '' COMMENT '车牌号',
+  `w_order_no` varchar(50) DEFAULT '' COMMENT '运单号',
+  `driver_id` bigint(20) DEFAULT '0' COMMENT '司机ID',
+  `driver_name` varchar(50) DEFAULT '' COMMENT '司机姓名',
+  `driver_phone` varchar(20) DEFAULT '' COMMENT '司机电话',
+  `exception_type` int(2) DEFAULT '0' COMMENT '异常类型(1-车辆偏航,2-急刹车,3-超速,4-异常停车)',
+  `image_url` varchar(500) DEFAULT '' COMMENT '图片URL',
+  `exception_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '异常时间',
+  `longitude` varchar(50) DEFAULT '' COMMENT '经度',
+  `latitude` varchar(50) DEFAULT '' COMMENT '纬度',
+  `location` varchar(200) DEFAULT '' COMMENT '位置描述',
+  `description` varchar(500) DEFAULT '' COMMENT '异常详情描述',
+  `create_by` bigint(20) DEFAULT '0' COMMENT '创建人',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` bigint(20) DEFAULT '0' COMMENT '更新人',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` int(1) DEFAULT '0' COMMENT '是否删除(0未删除,1删除)',
+  PRIMARY KEY (`id`),
+  KEY `idx_ent_id` (`ent_id`),
+  KEY `idx_truck_no` (`truck_no`),
+  KEY `idx_w_order_no` (`w_order_no`),
+  KEY `idx_exception_type` (`exception_type`),
+  KEY `idx_exception_time` (`exception_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆异常表';