Procházet zdrojové kódy

优化pc端司机管理功能

donglang před 1 měsícem
rodič
revize
6ec8514f1d

+ 1 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/common/enums/enums/ErrorCodeEnum.java

@@ -32,6 +32,7 @@ public enum ErrorCodeEnum {
     ILLEGAL_PARAM("300002", "非法参数"),
     TOKEN_EXPIRED("300003", "令牌已过期"),
     TOKEN_INVALID("300004", "令牌无效"),
+    REPEAT_SUBMIT("300005", "重复提交"),
 
     // ====================== 数据存储错误(40000~49999)======================
     DATA_SAVE_FAIL("400000", "数据保存失败"),

+ 15 - 1
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/controller/KwfDriverController.java

@@ -35,8 +35,8 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * @desc 司机
  * @author zk
+ * @desc 司机
  * @date 2023/7/6 0006
  */
 @RestController
@@ -348,4 +348,18 @@ public class KwfDriverController {
         return BaseResult.success();
     }
 
+
+    /**
+     * @desc 保存车库
+     * @author zk
+     * @date 2023/7/6
+     **/
+    @PostMapping("/saveAssociatedTruck")
+    public BaseResult saveAssociatedTruck(@RequestBody SaveAssociatedTruckParam param) {
+
+        driverService.saveAssociatedTruck(param);
+        return BaseResult.success();
+    }
+
+
 }

+ 55 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/dto/SaveAssociatedTruckParam.java

@@ -0,0 +1,55 @@
+package com.sckw.fleet.model.dto;
+
+import com.sckw.core.model.page.PageRequest;
+import com.sckw.core.web.request.PageReq;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author zk
+ * @desc 车辆信息
+ * @date 2023/7/12 0012
+ */
+@Data
+public class SaveAssociatedTruckParam  implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = -183438702190878123L;
+
+    /**
+     * 司机id
+     */
+    @NotBlank(message = "司机id不能空!")
+    private Long driverId;
+
+    /**
+     * 车辆信息
+     */
+    @NotNull(message = "车辆信息不能空!")
+    private List<AssociatedTruckVO> associatedTruckVO;
+
+
+    @Data
+    public class AssociatedTruckVO{
+
+        /**
+         * 车辆号
+         */
+        @NotBlank(message = "车辆号不能空!")
+        private String truckNo;
+
+        /**
+         * 轴数id
+         */
+        @NotNull(message = "轴数id不能空!")
+        private Long truckAxleId;
+    }
+
+
+}

+ 62 - 17
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/service/KwfDriverService.java

@@ -19,6 +19,7 @@ import com.sckw.core.model.file.FileInfo;
 import com.sckw.core.utils.*;
 import com.sckw.core.web.constant.HttpStatus;
 import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.core.web.response.BaseResult;
 import com.sckw.core.web.response.HttpResult;
 import com.sckw.excel.easyexcel.ExcelImportListener;
 import com.sckw.excel.utils.ExcelUtil;
@@ -334,7 +335,6 @@ public class KwfDriverService {
         //查询司机关联车辆信息
         KwfTruckReport truckReport = truckReportRepository.getOne(Wrappers.<KwfTruckReport>lambdaQuery()
                 .eq(BaseModel::getDelFlag, 0)
-                .eq(BaseModel::getStatus, 0)
                 .eq(KwfTruckReport::getDriverId, driverId)
                 .eq(KwfTruckReport::getEntId, entId));
         if (truckReport == null) {
@@ -1236,22 +1236,22 @@ public class KwfDriverService {
      * @return
      */
     private List<KwfDriverAssociatedTruck> checkKwfDriverAssociatedTracks(AssociatedTrackAddRequest request) {
-        CapacityTruckParam param = new CapacityTruckParam();
-        param.setTruckNo(request.getTruckNo());
-        param.setEntId(request.getEntId());
-        //查询企业下的车辆信息
-        List<KwfTruck> kwfTrucks = truckRepository.queryTruckByEntId(request.getEntId());
-        if (CollectionUtils.isEmpty(kwfTrucks)) {
-            throw new BusinessPlatfromException(ErrorCodeEnum.TRUCK_NOT_FOUND, "您所在的企业暂无该车辆,请与企业管理人员联系");
-        }
-        //校验输入的车牌和轴数
-        KwfTruck matchedTruck = kwfTrucks.stream()
-                .filter(truck ->
-                        Objects.equals(request.getTruckNo(), truck.getTruckNo()) &&
-                                Objects.equals(String.valueOf(request.getTruckAxleId()), truck.getCarAxis()))
-                .findFirst().orElse(null);
-        if (matchedTruck == null) {
-            throw new BusinessPlatfromException(ErrorCodeEnum.TRUCK_NOT_FOUND, "您所在的企业暂无该车辆,请与企业管理人员联系");
+        int systemType = LoginUserHolder.getSystemType();
+        if (systemType == SystemTypeEnum.DRIVER.getCode()) {
+            //查询企业下的车辆信息
+            List<KwfTruck> kwfTrucks = truckRepository.queryTruckByEntId(request.getEntId());
+            if (CollectionUtils.isEmpty(kwfTrucks)) {
+                throw new BusinessPlatfromException(ErrorCodeEnum.TRUCK_NOT_FOUND, "您所在的企业暂无该车辆,请与企业管理人员联系");
+            }
+            //校验输入的车牌和轴数
+            KwfTruck matchedTruck = kwfTrucks.stream()
+                    .filter(truck ->
+                            Objects.equals(request.getTruckNo(), truck.getTruckNo()) &&
+                                    Objects.equals(String.valueOf(request.getTruckAxleId()), truck.getCarAxis()))
+                    .findFirst().orElse(null);
+            if (matchedTruck == null) {
+                throw new BusinessPlatfromException(ErrorCodeEnum.TRUCK_NOT_FOUND, "您所在的企业暂无该车辆,请与企业管理人员联系");
+            }
         }
         //校验车辆是否已存在
         List<KwfDriverAssociatedTruck> associatedTrackList = driverAssociatedTruckRepository.queryByEntIdAndDriverId(request.getEntId(), request.getDriverId());
@@ -1262,6 +1262,9 @@ public class KwfDriverService {
         }
 
         //校验新增车辆是否有未完成运单
+        CapacityTruckParam param = new CapacityTruckParam();
+        param.setTruckNo(request.getTruckNo());
+        param.setEntId(request.getEntId());
         List<RWaybillOrderVo> waybillVos = transportRemoteService.queryWaybillOrder(param);
         if (CollectionUtils.isNotEmpty(waybillVos)) {
             //首次关联不能新增
@@ -1381,5 +1384,47 @@ public class KwfDriverService {
 
     }
 
+    /**
+     * @param params 参数
+     * @desc 保存车库
+     * @author zk
+     * @date 2023/7/6
+     **/
+    public void saveAssociatedTruck(SaveAssociatedTruckParam param) {
+        log.info("新增司机关联车辆信息:{}", JSON.toJSONString(param));
+        Long entId = LoginUserHolder.getEntId();
+        List<String> truckNos = param.getAssociatedTruckVO().stream().map(item -> item.getTruckNo()) .distinct().collect(Collectors.toList());
+        //校验车辆是否已存在
+        List<KwfDriverAssociatedTruck> associatedTrackList = driverAssociatedTruckRepository.queryByEntIdAndDriverId(entId, param.getDriverId());
+        //已经关联的车牌
+        Set<String> existingTruckNos = associatedTrackList.stream()
+                .map(KwfDriverAssociatedTruck::getTruckNo)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        // 获取所有重复的车辆号
+        List<String> duplicateTruckNos = truckNos.stream()
+                .filter(existingTruckNos::contains)
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isNotEmpty(duplicateTruckNos)) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.ASSOCIATED_TRUCK_EXIST, "司机已关联车牌[" + JSON.toJSONString(duplicateTruckNos) + "],无需重复添加");
+        }
+
+        for (SaveAssociatedTruckParam.AssociatedTruckVO vo : param.getAssociatedTruckVO()) {
+            //设置关联状态(首次关联设为当前车辆,并将原有当前车辆置为非当前)
+            KwfDriverAssociatedTruck associatedTrack = new KwfDriverAssociatedTruck();
+            associatedTrack.setEntId(entId);
+            associatedTrack.setDriverId(param.getDriverId());
+            associatedTrack.setTruckNo(vo.getTruckNo());
+            associatedTrack.setTruckAxleId(vo.getTruckAxleId());
+            associatedTrack.setCreateUser(param.getDriverId());
+            //首次关联默认设为当前车辆;
+            associatedTrack.setStatus(associatedTrackList.isEmpty() ? Global.YES : Global.NO);
+            driverAssociatedTruckRepository.save(associatedTrack);
+        }
+        log.info("新增司机关联车辆成功!");
+    }
+
 
 }

+ 23 - 1
sckw-modules/sckw-system/src/main/resources/bootstrap.yml

@@ -10,4 +10,26 @@ spring:
 #    active: test
   main:
     allow-bean-definition-overriding: true
-    allow-circular-references: true
+    allow-circular-references: true
+
+# 车辆轨迹服务配置
+vehicle:
+  trace:
+    api:
+      base-url: ${VEHICLE_TRACE_API_URL:http://localhost:8080}
+
+# Feign 配置
+feign:
+  client:
+    config:
+      default:
+        connect-timeout: 5000
+        read-timeout: 10000
+      vehicle-trace-service:
+        connect-timeout: 5000
+        read-timeout: 10000
+        logger-level: full
+  httpclient:
+    enabled: true
+    max-connections: 200
+    max-connections-per-route: 50

+ 10 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderRepository.java

@@ -271,4 +271,14 @@ public class KwtWaybillOrderRepository extends ServiceImpl<KwtWaybillOrderMapper
                         .eq(KwtWaybillOrder::getDelFlag, 0)
                         .last("limit 1"));
     }
+
+    public KwtWaybillOrder findOneByTruckNo(Long logOrderId, String truckNo, Long entId ) {
+        return getOne(Wrappers.<KwtWaybillOrder>lambdaQuery()
+                .eq(KwtWaybillOrder::getDelFlag,0)
+                .eq(KwtWaybillOrder::getTruckNo,truckNo)
+                .eq(KwtWaybillOrder::getEntId, entId)
+                .eq(KwtWaybillOrder::getStatus, 1)
+                .orderByDesc(KwtWaybillOrder::getId)
+                .last("limit 1"));
+    }
 }

+ 110 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/app/WaybillOrderService.java

@@ -54,12 +54,15 @@ import org.apache.dubbo.config.annotation.DubboReference;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -105,6 +108,19 @@ public class WaybillOrderService {
 
     private final KwtWaybillOrderV1Service waybillOrderV1Service;
 
+    // 注入RedisTemplate用于分布式锁
+    @Resource
+    private RedisTemplate<String, String> redisTemplate;
+
+    // 分布式锁相关常量
+    private static final String TAKING_ORDER_LOCK_PREFIX = "transport:taking_order:lock:";
+    // 锁超时时间30秒
+    private static final long LOCK_EXPIRE_SECONDS = 30;
+    // 锁等待时间500毫秒
+    private static final long LOCK_WAIT_MILLIS = 500;
+    // 锁重试间隔100毫秒
+    private static final long LOCK_RETRY_INTERVAL = 100;
+
     //载重任务量浮动吨数
     private static final BigDecimal TWO_TONS = new BigDecimal("2");
     //载重任务量计算比例
@@ -834,7 +850,100 @@ public class WaybillOrderService {
      */
     @Transactional(rollbackFor = Exception.class)
     public OrderTakingResp orderTaking(OrderCirculateTakingQueryParam param) {
-        return takingOrderHandler.handler(param);
+        // 1. 幂等性校验:检查是否已经存在该车辆针对该物流订单的有效运单
+        checkIdempotent(param);
+
+        // 2. 构建分布式锁Key(物流订单ID+车牌号 唯一标识)
+        String lockKey = TAKING_ORDER_LOCK_PREFIX + param.getLogOrderId() + "_" + param.getTruckNo();
+        String requestId = UUID.randomUUID().toString();
+        boolean lockAcquired = false;
+
+        try {
+            // 3. 获取分布式锁(带重试机制)
+            lockAcquired = acquireLock(lockKey, requestId, LOCK_EXPIRE_SECONDS, LOCK_WAIT_MILLIS);
+            if (!lockAcquired) {
+                log.warn("接单请求获取分布式锁失败,可能存在重复提交,param:{}", JSON.toJSONString(param));
+                throw new BusinessPlatfromException(ErrorCodeEnum.REPEAT_SUBMIT, "当前接单请求正在处理中,请稍后再试");
+            }
+
+            // 4. 再次幂等性校验(防止锁等待期间已经创建运单)
+            checkIdempotent(param);
+
+            // 6. 执行核心接单逻辑
+            return takingOrderHandler.handler(param);
+
+        } finally {
+            // 7. 释放分布式锁(只有获取锁的请求才能释放)
+            if (lockAcquired) {
+                releaseLock(lockKey, requestId);
+                log.info("释放接单分布式锁成功,lockKey:{}, requestId:{}", lockKey, requestId);
+            }
+        }
+    }
+
+    /**
+     * 幂等性校验:检查是否已经存在该车辆针对该物流订单的有效运单
+     * @param param 接单参数
+     */
+    private void checkIdempotent(OrderCirculateTakingQueryParam param) {
+        // 查询该车辆针对该物流订单是否已有有效运单
+        KwtWaybillOrder existingOrder = waybillOrderRepository.findOneByTruckNo(param.getLogOrderId(), param.getTruckNo(), param.getEntId());
+
+        if (existingOrder != null) {
+            log.warn("重复接单校验失败,已存在有效运单,logOrderId:{}, truckNo:{}, waybillOrderId:{}",
+                    param.getLogOrderId(), param.getTruckNo(), existingOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.REPEAT_SUBMIT,
+                    String.format("该车辆[%s]已针对该物流订单接单,运单ID:%s", param.getTruckNo(), existingOrder.getId()));
+        }
+    }
+
+    /**
+     * 获取分布式锁(带重试机制)
+     * @param lockKey       锁Key
+     * @param requestId     请求ID(保证只有自己能释放锁)
+     * @param expireSeconds 锁过期时间(秒)
+     * @param waitMillis    最大等待时间(毫秒)
+     * @return 是否获取成功
+     */
+    private boolean acquireLock(String lockKey, String requestId, long expireSeconds, long waitMillis) {
+        long startTime = System.currentTimeMillis();
+        while (System.currentTimeMillis() - startTime < waitMillis) {
+            // 使用SET NX EX命令获取锁(原子操作)
+            Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireSeconds, TimeUnit.SECONDS);
+            if (Boolean.TRUE.equals(success)) {
+                log.info("获取接单分布式锁成功,lockKey:{}, requestId:{}", lockKey, requestId);
+                return true;
+            }
+
+            // 未获取到锁,短暂休眠后重试
+            try {
+                Thread.sleep(LOCK_RETRY_INTERVAL);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.error("获取锁时线程中断", e);
+                return false;
+            }
+        }
+
+        log.warn("获取接单分布式锁超时,lockKey:{}, waitMillis:{}", lockKey, waitMillis);
+        return false;
+    }
+
+    /**
+     * 释放分布式锁(使用Lua脚本保证原子性)
+     * @param lockKey   锁Key
+     * @param requestId 请求ID
+     */
+    private void releaseLock(String lockKey, String requestId) {
+        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+        redisScript.setScriptText(luaScript);
+        redisScript.setResultType(Long.class);
+
+        Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
+        if (result == null || result == 0) {
+            log.warn("释放接单分布式锁失败,可能锁已过期或被其他请求释放,lockKey:{}, requestId:{}", lockKey, requestId);
+        }
     }
 
     /**