package com.xiaopeng.vehicleConvoy.service.impl; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto; import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandEnum; import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandVo; import com.xiaopeng.common.core.exception.base.BaseException; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.vehicleConvoy.service.AsyncVehicleStateService; import com.xiaopeng.vehicleConvoy.util.VehicleControlUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.io.Serializable; import java.time.LocalDateTime; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 异步车辆状态查询服务实现(使用超时工具类简化版) */ @Slf4j @Service public class AsyncVehicleStateServiceImpl implements AsyncVehicleStateService { @Resource private VehicleControlUtil vehicleControlUtil; @Resource private RedisService redisService; @Value("${vehicle.state.query.timeout.seconds:2}") private int queryTimeoutSeconds = 2; // 查询超时时间,默认2秒 // 用于异步调度的线程池 private ScheduledExecutorService scheduler; // 缓存键前缀 private static final String STATE_QUERY_PREFIX = "vehicle_state_query:"; private static final String STATE_RESULT_PREFIX = "vehicle_state_result:"; @PostConstruct public void init() { // 初始化调度器,使用较少的线程数以节省资源 this.scheduler = Executors.newScheduledThreadPool(2); } @PreDestroy public void destroy() { if (scheduler != null && !scheduler.isShutdown()) { scheduler.shutdown(); try { if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { scheduler.shutdownNow(); } } catch (InterruptedException e) { scheduler.shutdownNow(); Thread.currentThread().interrupt(); } } } @Override public CompletableFuture queryVehicleStateAsync(String uid, String vin) { log.info("开始异步查询车辆状态: uid={}, vin={}", uid, vin); try { // 1. 创建车辆控制指令 VehiclesCommandVo commandVo = new VehiclesCommandVo(); commandVo.setVin(vin); commandVo.setCommandCode(VehiclesCommandEnum.STATUS_REPORT_NOW.getCommand()); // 2. 下发指令获取消息ID String msgId = vehicleControlUtil.vehiclesCommand(commandVo, Long.valueOf(uid)); if (StrUtil.isEmpty(msgId)) { throw new BaseException("获取消息ID失败: " + msgId); } log.info("下发STATUS_REPORT_NOW指令成功: uid={}, vin={}, msgId={}", uid, vin, msgId); // 3. 创建CompletableFuture CompletableFuture future = new CompletableFuture<>(); // 4. 存储查询信息到Redis StateQueryInfo queryInfo = new StateQueryInfo(); queryInfo.setUid(uid); queryInfo.setVin(vin); queryInfo.setMsgId(msgId); queryInfo.setCreateTime(LocalDateTime.now()); String cacheKey = STATE_QUERY_PREFIX + msgId; redisService.setCacheObject(cacheKey, queryInfo, 300L, TimeUnit.SECONDS); // 5. 启动轮询任务检查Redis结果 startPollingTask(msgId, vin, future); return future; } catch (Exception e) { log.error("异步查询车辆状态失败: uid={}, vin={}", uid, vin, e); CompletableFuture errorFuture = new CompletableFuture<>(); errorFuture.completeExceptionally(e); return errorFuture; } } /** * 启动轮询任务检查Redis中的结果(高性能版本) */ private void startPollingTask(String msgId, String vin, CompletableFuture future) { String resultKey = STATE_RESULT_PREFIX + msgId; String cacheKey = STATE_QUERY_PREFIX + msgId; // 500ms后第一次检查 scheduler.schedule(() -> { if (future.isDone()) { return; // 如果Future已完成,直接返回 } try { VehicleTemperatureDto result = redisService.getCacheObject(resultKey); if (result != null) { log.info("500ms轮询获取到结果: msgId={}", msgId); result.setVin(vin); future.complete(result); cleanupResources(msgId, cacheKey, resultKey); return; } // 如果第一次没有结果,安排第二次检查(1秒后) scheduler.schedule(() -> { if (future.isDone()) { return; // 如果Future已完成,直接返回 } try { VehicleTemperatureDto result2 = redisService.getCacheObject(resultKey); if (result2 != null) { log.info("1s轮询获取到结果: msgId={}", msgId); result2.setVin(vin); future.complete(result2); } else { // 超时处理 future.completeExceptionally(new RuntimeException("车辆状态查询超时," + queryTimeoutSeconds + "秒内未收到回调")); } cleanupResources(msgId, cacheKey, resultKey); } catch (Exception e) { log.error("第二次轮询检查Redis异常: msgId={}", msgId, e); if (!future.isDone()) { future.completeExceptionally(e); } cleanupResources(msgId, cacheKey, resultKey); } }, 500, TimeUnit.MILLISECONDS); // 再等500ms,总共1秒 } catch (Exception e) { log.error("第一次轮询检查Redis异常: msgId={}", msgId, e); if (!future.isDone()) { future.completeExceptionally(e); } cleanupResources(msgId, cacheKey, resultKey); } }, 500, TimeUnit.MILLISECONDS); } /** * 清理资源 */ private void cleanupResources(String msgId, String cacheKey, String resultKey) { try { redisService.deleteObject(cacheKey); redisService.deleteObject(resultKey); log.debug("清理资源完成: msgId={}", msgId); } catch (Exception e) { log.error("清理资源异常: msgId={}", msgId, e); } } @Override public void handleStateCallback(String msgId, VehicleTemperatureDto stateData) { if (StrUtil.isEmpty(msgId)) { log.warn("处理状态回调失败: msgId为空"); return; } log.info("收到车辆状态查询回调: msgId={}, data={}", msgId, JSON.toJSONString(stateData)); try { // 获取查询信息 String cacheKey = STATE_QUERY_PREFIX + msgId; StateQueryInfo queryInfo = redisService.getCacheObject(cacheKey); if (queryInfo != null && stateData != null) { // 设置VIN信息 stateData.setVin(queryInfo.getVin()); // 直接将结果保存到Redis String resultKey = STATE_RESULT_PREFIX + msgId; redisService.setCacheObject(resultKey, stateData, 300L, TimeUnit.SECONDS); log.info("回调结果已保存到Redis: msgId={}", msgId); } else { log.warn("未找到对应的查询信息或回调数据为空: msgId={}", msgId); } } catch (Exception e) { log.error("处理车辆状态回调异常: msgId={}", msgId, e); } } @Override public void cancelStateQuery(String msgId) { if (StrUtil.isEmpty(msgId)) { return; } log.info("取消车辆状态查询: msgId={}", msgId); // 清理Redis缓存 redisService.deleteObject(STATE_QUERY_PREFIX + msgId); redisService.deleteObject(STATE_RESULT_PREFIX + msgId); } @Data public static class StateQueryInfo implements Serializable { private String uid; private String vin; private String msgId; private LocalDateTime createTime; } }