|
|
@@ -0,0 +1,250 @@
|
|
|
+package com.sckw.transport.service.app;
|
|
|
+
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.sckw.core.common.enums.enums.ErrorCodeEnum;
|
|
|
+import com.sckw.core.exception.BusinessPlatfromException;
|
|
|
+import com.sckw.core.model.enums.ForkliftStatusEnum;
|
|
|
+import com.sckw.core.utils.CollectionUtils;
|
|
|
+import com.sckw.core.web.response.result.PageDataResult;
|
|
|
+import com.sckw.transport.model.KwtForkliftWaybillOrder;
|
|
|
+import com.sckw.transport.model.KwtWaybillOrder;
|
|
|
+import com.sckw.transport.model.param.forklift.reponse.ForkliftOrderResp;
|
|
|
+import com.sckw.transport.model.param.forklift.request.ForkliftOrderQueryParam;
|
|
|
+import com.sckw.transport.model.param.forklift.request.ForkliftOrderTakingParam;
|
|
|
+import com.sckw.transport.repository.KwtForkliftWaybillOrderRepository;
|
|
|
+import com.sckw.transport.repository.KwtWaybillOrderRepository;
|
|
|
+import jakarta.annotation.Resource;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Author: donglang
|
|
|
+ * Time: 2026-01-05
|
|
|
+ * Des: 铲车司机Service
|
|
|
+ * Version: 1.0
|
|
|
+ */
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class ForkliftOrderService {
|
|
|
+
|
|
|
+ private final KwtWaybillOrderRepository waybillOrderRepository;
|
|
|
+
|
|
|
+ private final KwtForkliftWaybillOrderRepository forkliftWaybillOrderRepository;
|
|
|
+
|
|
|
+
|
|
|
+ // 注入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;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分页查询铲车运单
|
|
|
+ * @param param
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public PageDataResult<ForkliftOrderResp> pageQueryForkliftOrder(ForkliftOrderQueryParam param) {
|
|
|
+ log.info("查询铲车司机的物流运单:{}", JSON.toJSONString(param));
|
|
|
+ //查询到底装货点的运单
|
|
|
+ Page<KwtWaybillOrder> pageByStatus = waybillOrderRepository.findPageByStatus(param.getPageNum(), param.getPageSize());
|
|
|
+ List<KwtWaybillOrder> records = pageByStatus.getRecords();
|
|
|
+ if (CollectionUtils.isEmpty(records)) {
|
|
|
+ return PageDataResult.empty(param.getPageNum(), param.getPageSize());
|
|
|
+ }
|
|
|
+ List<ForkliftOrderResp> forkliftOrderList = new ArrayList<>();
|
|
|
+ for (KwtWaybillOrder record : records) {
|
|
|
+ ForkliftOrderResp forklift = new ForkliftOrderResp();
|
|
|
+ forklift.setDriverId(record.getDriverId());
|
|
|
+ forklift.setDriverName(record.getDriverName());
|
|
|
+ forkliftOrderList.add(forklift);
|
|
|
+ }
|
|
|
+ return PageDataResult.success(param.getPageNum(), param.getPageSize(), pageByStatus.getTotal(), forkliftOrderList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 铲车接单
|
|
|
+ * @param param
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public void forkliftOrderTaking(ForkliftOrderTakingParam param) {
|
|
|
+ log.info("铲车接单:{}", JSON.toJSONString(param));
|
|
|
+ //校验运单状态
|
|
|
+ KwtWaybillOrder waybillOrder = checkWOrderStatus(param);
|
|
|
+
|
|
|
+ // 1. 幂等性校验
|
|
|
+ checkIdempotent(param);
|
|
|
+
|
|
|
+ // 2. 构建分布式锁Key(物流运单ID+铲车司机id 唯一标识)
|
|
|
+ String lockKey = TAKING_ORDER_LOCK_PREFIX + param.getWaybillOrderId() + "_" + param.getDriverId();
|
|
|
+ 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);
|
|
|
+
|
|
|
+ // 5. 执行接单
|
|
|
+ createForkliftWaybillOrder(param, waybillOrder);
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ // 6. 释放分布式锁
|
|
|
+ if (lockAcquired) {
|
|
|
+ releaseLock(lockKey, requestId);
|
|
|
+ log.info("释放接单分布式锁成功,lockKey:{}, requestId:{}", lockKey, requestId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验运单状态
|
|
|
+ * @param param
|
|
|
+ */
|
|
|
+ private KwtWaybillOrder checkWOrderStatus(ForkliftOrderTakingParam param) {
|
|
|
+ //校验
|
|
|
+ KwtWaybillOrder waybillOrder = waybillOrderRepository.getById(param.getWaybillOrderId());
|
|
|
+ if (waybillOrder == null) {
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.WAYBILL_ORDER_STATUS_ERROR, "当前运单不是可接单状态!");
|
|
|
+ }
|
|
|
+ return waybillOrder;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 幂等性校验:检查是否已经存在该铲车针对该物流运单的有效运单
|
|
|
+ * @param param 接单参数
|
|
|
+ */
|
|
|
+ private void checkIdempotent(ForkliftOrderTakingParam param) {
|
|
|
+ // 查询该物流运单+铲车司机id下是否已有接单状态运单
|
|
|
+ KwtForkliftWaybillOrder existingOrder = forkliftWaybillOrderRepository.queryForkliftWaybillOrderByWOrderId(param.getWaybillOrderId(), param.getDriverId());
|
|
|
+ if (existingOrder != null) {
|
|
|
+ log.warn("重复接单校验失败,已存在有效铲车订单,fLogOrderId:{}, driverId:{}, waybillOrderId:{}",
|
|
|
+ existingOrder.getId(), param.getDriverId(), param.getWaybillOrderId());
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.REPEAT_SUBMIT,
|
|
|
+ String.format("该铲车[%s]已针对该物流运单接单,订单ID:%s", existingOrder.getDriverId(), 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建铲车订单
|
|
|
+ * @param param
|
|
|
+ * @param waybillOrder
|
|
|
+ */
|
|
|
+ private void createForkliftWaybillOrder(ForkliftOrderTakingParam param, KwtWaybillOrder waybillOrder) {
|
|
|
+ log.info("生成铲车订单,入参参数:{}", JSON.toJSONString(param));
|
|
|
+ KwtForkliftWaybillOrder forkliftWaybillOrder = new KwtForkliftWaybillOrder();
|
|
|
+ forkliftWaybillOrder.setFOrderNo("W" + System.currentTimeMillis());
|
|
|
+ forkliftWaybillOrder.setEntId(null);
|
|
|
+ forkliftWaybillOrder.setLOrderId(waybillOrder.getLOrderId());
|
|
|
+ forkliftWaybillOrder.setLoadingType(param.getLoadingType());
|
|
|
+ forkliftWaybillOrder.setStatus(ForkliftStatusEnum.ORDER_TAKING.getCode());
|
|
|
+ forkliftWaybillOrder.setDriverName(null);
|
|
|
+ forkliftWaybillOrder.setDriverPhone(null);
|
|
|
+ forkliftWaybillOrder.setFinishTime(new Date());
|
|
|
+ forkliftWaybillOrder.setCreateUser(null);
|
|
|
+ forkliftWaybillOrder.setUpdateUser(null);
|
|
|
+ forkliftWaybillOrderRepository.save(forkliftWaybillOrder);
|
|
|
+ log.info("创建铲车订单成功,订单ID:{}", waybillOrder.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 铲车取消接单
|
|
|
+ * @param param
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public PageDataResult<ForkliftOrderResp> cancelForkliftOrder(ForkliftOrderQueryParam param) {
|
|
|
+ log.info("查询铲车司机的物流运单:{}", JSON.toJSONString(param));
|
|
|
+ //查询到底装货点的运单
|
|
|
+ Page<KwtWaybillOrder> pageByStatus = waybillOrderRepository.findPageByStatus(param.getPageNum(), param.getPageSize());
|
|
|
+ List<KwtWaybillOrder> records = pageByStatus.getRecords();
|
|
|
+ if (CollectionUtils.isEmpty(records)) {
|
|
|
+ return PageDataResult.empty(param.getPageNum(), param.getPageSize());
|
|
|
+ }
|
|
|
+ List<ForkliftOrderResp> forkliftOrderList = new ArrayList<>();
|
|
|
+ for (KwtWaybillOrder record : records) {
|
|
|
+ ForkliftOrderResp forklift = new ForkliftOrderResp();
|
|
|
+ forklift.setDriverId(record.getDriverId());
|
|
|
+ forklift.setDriverName(record.getDriverName());
|
|
|
+ forkliftOrderList.add(forklift);
|
|
|
+ }
|
|
|
+ return PageDataResult.success(param.getPageNum(), param.getPageSize(), pageByStatus.getTotal(), forkliftOrderList);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|