diff --git a/ebike-operations/src/main/java/com/cdzy/operations/component/SafeOrderExpirationListener.java b/ebike-operations/src/main/java/com/cdzy/operations/component/SafeOrderExpirationListener.java index 61a3fdf..c3d7383 100644 --- a/ebike-operations/src/main/java/com/cdzy/operations/component/SafeOrderExpirationListener.java +++ b/ebike-operations/src/main/java/com/cdzy/operations/component/SafeOrderExpirationListener.java @@ -10,6 +10,7 @@ import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; + import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -18,22 +19,24 @@ import static com.cdzy.operations.model.entity.table.EbikeBikeOrderTableDef.EBIK @Component @Slf4j public class SafeOrderExpirationListener { - + @Resource private RedisUtil redisUtil; - + @Resource private ServiceInstanceManager instanceManager; @Resource private EbikeBikeOrderService orderService; - + // Redis键常量 private static final String ORDER_KEY_PREFIX = "bike:dispatch:lock:"; private static final long LOCK_WAIT_TIME = 5L; // 获取锁等待时间(秒) - + + private static final String CODE_KEY_PREFIX = "bike:no:lock:"; + /** * 处理Redis键过期事件 */ @@ -55,6 +58,23 @@ public class SafeOrderExpirationListener { return null; }); } + // 判断是否为车辆多长时间无单生成调度工单,过期则创建调度工单 + if (expiredKey.startsWith(RedisUtil.BIKE_NO_DOCUMENT_PREFIX)) { + // 提取车辆编号 + String bikeCode = extractBikeCodeFromKey(expiredKey); + if (bikeCode == null) { + return; + } + + log.info("实例 {} 接收到工单过期事件: bikeCode={}", + instanceManager.getInstanceId(), bikeCode); + + // 异步处理,避免阻塞监听线程 + CompletableFuture.runAsync(() -> processExpiredNoDocumentSafely(bikeCode)).exceptionally(e -> { + log.error("车辆多长时间无单生成调度工单异常: bikeCode={}", bikeCode, e); + return null; + }); + } } /** @@ -89,6 +109,33 @@ public class SafeOrderExpirationListener { } } + private void processExpiredNoDocumentSafely(String bikeCode) { + String instanceId = instanceManager.getInstanceId(); + String lockKey = CODE_KEY_PREFIX + bikeCode; + + // 使用RedisUtil的分布式锁功能 + boolean locked = redisUtil.tryNoDocumentLock(lockKey, instanceId, LOCK_WAIT_TIME, TimeUnit.SECONDS); + + if (!locked) { + log.debug("实例 {} 获取锁失败,其他实例正在处理: bikeCode={}", instanceId, bikeCode); + return; + } + log.info("实例 {} 开始生成调度工单: bikeCode={}", instanceId, bikeCode); + try { + + // 执行实际的生成调度工单逻辑 + processCreateDispatchOrderBusiness(bikeCode); + + log.info("实例 {} 完成生成调度工单处理: bikeCode={}", instanceId, bikeCode); + + } catch (Exception e) { + log.error("生成调度工单业务逻辑异常: bikeCode={}", bikeCode, e); + } finally { + // 安全释放锁:先检查锁是否还是自己的 + releaseLockSafely(lockKey, instanceId); + } + } + /** * 安全释放锁 */ @@ -103,13 +150,13 @@ public class SafeOrderExpirationListener { log.error("释放锁异常: lockKey={}", lockKey, e); } } - + /** * 实际的业务处理逻辑 */ private void processExpiredOrderBusiness(String orderId) { try { - //处理订单,该订单为到期未有用户骑行的调度订单 + //处理订单,该订单为到期未有用户骑行的调度订单 QueryWrapper query = QueryWrapper.create() .where(EBIKE_BIKE_ORDER.ORDER_ID.eq(orderId)) .where(EBIKE_BIKE_ORDER.ORDER_TYPE.eq(BikeOrderType.DISPATCH)) @@ -124,13 +171,36 @@ public class SafeOrderExpirationListener { } } - + /** + * 实际的业务处理逻辑 + */ + private void processCreateDispatchOrderBusiness(String bikeCode) { + try { + // 生成调度工单 + orderService.createDispatchSwapOrder(bikeCode); + } catch (Exception e) { + log.error("生成调度工单业务异常: orderId={}", bikeCode, e); + throw e; + } + } + + /** * 从Redis键中提取订单ID */ private String extractOrderIdFromKey(String redisKey) { if (redisKey.startsWith(RedisUtil.BIKE_DISPATCH_ORDER_PREFIX)) { - return redisKey.substring(ORDER_KEY_PREFIX.length()); + return redisKey.substring(RedisUtil.BIKE_DISPATCH_ORDER_PREFIX.length()); + } + return null; + } + + /** + * 从Redis键中提取车辆编号 + */ + private String extractBikeCodeFromKey(String redisKey) { + if (redisKey.startsWith(RedisUtil.BIKE_NO_DOCUMENT_PREFIX)) { + return redisKey.substring(RedisUtil.BIKE_NO_DOCUMENT_PREFIX.length()); } return null; } diff --git a/ebike-operations/src/main/java/com/cdzy/operations/model/entity/EbikeDispatchConfiguration.java b/ebike-operations/src/main/java/com/cdzy/operations/model/entity/EbikeDispatchConfiguration.java index fb0ecdb..c20e6a2 100644 --- a/ebike-operations/src/main/java/com/cdzy/operations/model/entity/EbikeDispatchConfiguration.java +++ b/ebike-operations/src/main/java/com/cdzy/operations/model/entity/EbikeDispatchConfiguration.java @@ -13,7 +13,7 @@ import java.io.Serializable; import java.time.LocalDateTime; /** - * 车辆相关配置 实体类 + * 车辆调度配置 实体类 * * @author yanglei * @since 2025-12-03 16:18 diff --git a/ebike-operations/src/main/java/com/cdzy/operations/service/EbikeDispatchConfigurationService.java b/ebike-operations/src/main/java/com/cdzy/operations/service/EbikeDispatchConfigurationService.java index df6268b..98510b6 100644 --- a/ebike-operations/src/main/java/com/cdzy/operations/service/EbikeDispatchConfigurationService.java +++ b/ebike-operations/src/main/java/com/cdzy/operations/service/EbikeDispatchConfigurationService.java @@ -18,7 +18,7 @@ public interface EbikeDispatchConfigurationService extends IService implements EbikeDispatchConfigurationService { @Override - public EbikeDispatchConfiguration getConfigurationByOperationId() { + public EbikeDispatchConfiguration getConfigurationByOperationId(Long operationId) { QueryWrapper queryWrapper = QueryWrapper.create() - .select(EBIKE_DISPATCH_CONFIGURATION.ALL_COLUMNS); + .select(EBIKE_DISPATCH_CONFIGURATION.ALL_COLUMNS) + .and(EBIKE_DISPATCH_CONFIGURATION.OPERATOR_ID.eq(operationId, Objects.nonNull(operationId))); return this.mapper.selectOneByQuery(queryWrapper); } diff --git a/ebike-operations/src/main/java/com/cdzy/operations/utils/RedisUtil.java b/ebike-operations/src/main/java/com/cdzy/operations/utils/RedisUtil.java index edb7f4b..c130e2c 100644 --- a/ebike-operations/src/main/java/com/cdzy/operations/utils/RedisUtil.java +++ b/ebike-operations/src/main/java/com/cdzy/operations/utils/RedisUtil.java @@ -41,6 +41,9 @@ public class RedisUtil { // 车辆信息相关 public static final String BIKE_ECU_PREFIX = "bike:ecu:"; + // 车辆多长时间无单生成调度工单 + public static final String BIKE_NO_DOCUMENT_PREFIX = "bike:no:document:"; + /** * 错误消息常量 @@ -476,6 +479,35 @@ public class RedisUtil { return delete(Database.DB2, lockKey); } + + /** + * 数据库2专用:存储 车辆多长时间无单生成调度工单 并设置过期时间 + */ + public void saveNoDocument(String bikeCode, Object orderData, long timeout, TimeUnit unit) { + set(Database.DB2, BIKE_NO_DOCUMENT_PREFIX + bikeCode, orderData, timeout, unit); + } + + /** + * 数据库2专用:存储 辆多长时间无单生成调度工单 并设置过期时间 + */ + public void deleteNoDocument(String bikeCode) { + delete(Database.DB2, BIKE_NO_DOCUMENT_PREFIX + bikeCode); + } + + /** + * 数据库2专用:分布式锁(针对调度任务) + */ + public Boolean tryNoDocumentLock(String lockKey, Object value, long timeout, TimeUnit unit) { + return getRedisTemplate(Database.DB2).opsForValue().setIfAbsent(lockKey, value, timeout, unit); + } + + /** + * 数据库2专用:释放调度锁 + */ + public Boolean releaseNoDocumentLock(String lockKey) { + return delete(Database.DB2, lockKey); + } + /** * 数据库2专用:存储中控信息 */