Merge remote-tracking branch 'origin/main'

This commit is contained in:
attiya 2025-12-05 09:14:48 +08:00
commit 4f3c914bea
6 changed files with 125 additions and 16 deletions

View File

@ -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;
@ -34,6 +35,8 @@ public class SafeOrderExpirationListener {
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);
}
}
/**
* 安全释放锁
*/
@ -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;
}

View File

@ -13,7 +13,7 @@ import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 车辆相关配置 实体类
* 车辆调度配置 实体类
*
* @author yanglei
* @since 2025-12-03 16:18

View File

@ -18,7 +18,7 @@ public interface EbikeDispatchConfigurationService extends IService<EbikeDispatc
*
* @return 配置项
*/
EbikeDispatchConfiguration getConfigurationByOperationId();
EbikeDispatchConfiguration getConfigurationByOperationId(Long operationId);
/**
* 配置项保存

View File

@ -12,10 +12,7 @@ import com.cdzy.operations.model.dto.EbikeUserBikeInfo;
import com.cdzy.operations.model.dto.EbikeUserLockDto;
import com.cdzy.operations.model.entity.*;
import com.cdzy.operations.model.vo.*;
import com.cdzy.operations.service.EbikeBikeInfoService;
import com.cdzy.operations.service.EbikeEcuInfoService;
import com.cdzy.operations.service.EbikeInventoryRecordService;
import com.cdzy.operations.service.EbikeInventoryService;
import com.cdzy.operations.service.*;
import com.cdzy.operations.utils.RedisUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
@ -35,6 +32,7 @@ import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static com.cdzy.operations.model.entity.table.EbikeBatteryInfoTableDef.EBIKE_BATTERY_INFO;
import static com.cdzy.operations.model.entity.table.EbikeBikeInfoTableDef.EBIKE_BIKE_INFO;
@ -96,6 +94,9 @@ public class EbikeBikeInfoServiceImpl extends ServiceImpl<EbikeBikeInfoMapper, E
@Resource
private EbikeBikeOrderMapper orderMapper;
@Resource
private EbikeDispatchConfigurationService dispatchConfigurationService;
@Override
@Transactional
public void bind(EbikeBikeBindVo bindVo) {
@ -466,6 +467,10 @@ public class EbikeBikeInfoServiceImpl extends ServiceImpl<EbikeBikeInfoMapper, E
ebikeEcuInfoService.lock(ebikeEcuInfo);
EbikeDispatchConfiguration configuration = dispatchConfigurationService.getConfigurationByOperationId(info.getOperatorId());
redisUtil.saveNoDocument(bikeCode, "2", configuration.getDispatchDuration(), TimeUnit.HOURS);
this.mapper.update(info);
return EbikeUserLockDto.builder()

View File

@ -10,6 +10,7 @@ import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import static com.cdzy.operations.model.entity.table.EbikeDispatchConfigurationTableDef.EBIKE_DISPATCH_CONFIGURATION;
@ -24,9 +25,10 @@ import static com.cdzy.operations.model.entity.table.EbikeDispatchConfigurationT
public class EbikeDispatchConfigurationServiceImpl extends ServiceImpl<EbikeDispatchConfigurationMapper, EbikeDispatchConfiguration> 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);
}

View File

@ -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专用存储中控信息
*/