package cn.van.business.util; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.van.business.model.jd.OrderRow; import cn.van.business.model.pl.Comment; import cn.van.business.model.wx.SuperAdmin; import cn.van.business.repository.CommentRepository; import cn.van.business.repository.OrderRowRepository; import cn.van.business.util.jdReq.*; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.util.DateUtils; import com.jd.open.api.sdk.DefaultJdClient; import com.jd.open.api.sdk.JdClient; import com.jd.open.api.sdk.domain.kplunion.OrderService.request.query.OrderRowReq; import com.jd.open.api.sdk.domain.kplunion.OrderService.response.query.OrderRowResp; import com.jd.open.api.sdk.request.kplunion.UnionOpenOrderRowQueryRequest; import com.jd.open.api.sdk.response.kplunion.UnionOpenOrderRowQueryResponse; import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; import static cn.van.business.util.JDUtil.DATE_TIME_FORMATTER; import static cn.van.business.util.WXUtil.super_admins; /** * @author Leo * @version 1.0 * @create 2025/3/16 17:01 * @description: */ @Component public class JDScheduleJob { private static final Logger logger = LoggerFactory.getLogger(JDScheduleJob.class); private static final String SERVER_URL = "https://api.jd.com/routerjson"; //accessToken private static final String ACCESS_TOKEN = ""; // 标记是否拉取过小时的订单,空订单会set 一个 tag,避免重复拉取 private static final String JD_REFRESH_TAG = "jd:refresh:tag:"; private final StringRedisTemplate redisTemplate; private final OrderRowRepository orderRowRepository; private final OrderUtil orderUtil; private final JDUtil jdUtil; private final CommentRepository commentRepository; private final WXUtil wxUtil; @Getter @Value("${isRunning.wx}") private String isRunning_wx; @Getter @Value("${isRunning.jd}") private String isRunning_jd; // 构造函数中注入StringRedisTemplate @Autowired public JDScheduleJob(WXUtil wxUtil, StringRedisTemplate redisTemplate, OrderRowRepository orderRowRepository, OrderUtil orderUtil, JDUtil jdUtil, CommentRepository commentRepository) { this.redisTemplate = redisTemplate; this.orderRowRepository = orderRowRepository; this.orderUtil = orderUtil; this.jdUtil = jdUtil; this.commentRepository = commentRepository; this.wxUtil = wxUtil; } /** * 将 响应参数转化为 OrderRow,并返回 */ private OrderRow createOrderRow(OrderRowResp orderRowResp) { OrderRow orderRow = new OrderRow(); orderRow.setOrderId(orderRowResp.getOrderId()); orderRow.setSkuId(orderRowResp.getSkuId()); orderRow.setSkuName(orderRowResp.getSkuName()); orderRow.setItemId(orderRowResp.getItemId()); orderRow.setSkuNum(orderRowResp.getSkuNum()); orderRow.setPrice(orderRowResp.getPrice()); orderRow.setActualCosPrice(orderRowResp.getActualCosPrice()); orderRow.setActualFee(orderRowResp.getActualFee()); orderRow.setEstimateCosPrice(orderRowResp.getEstimateCosPrice()); orderRow.setEstimateFee(orderRowResp.getEstimateFee()); orderRow.setSubSideRate(orderRowResp.getSubSideRate()); orderRow.setSubsidyRate(orderRowResp.getSubsidyRate()); orderRow.setCommissionRate(orderRowResp.getCommissionRate()); orderRow.setFinalRate(orderRowResp.getFinalRate()); orderRow.setOrderTime(DateUtils.parseDate(orderRowResp.getOrderTime())); orderRow.setFinishTime(DateUtils.parseDate(orderRowResp.getFinishTime())); orderRow.setOrderTag(orderRowResp.getOrderTag()); orderRow.setOrderEmt(orderRowResp.getOrderEmt()); orderRow.setUnionId(orderRowResp.getUnionId()); orderRow.setUnionRole(orderRowResp.getUnionRole()); orderRow.setUnionAlias(orderRowResp.getUnionAlias()); orderRow.setUnionTag(orderRowResp.getUnionTag()); orderRow.setTraceType(orderRowResp.getTraceType()); orderRow.setValidCode(orderRowResp.getValidCode()); orderRow.setPayMonth(orderRowResp.getPayMonth()); orderRow.setSiteId(orderRowResp.getSiteId()); orderRow.setParentId(orderRowResp.getParentId()); //GoodsInfo goodsInfo = orderRowResp.getGoodsInfo(); //GoodsInfoVO goodsInfoVO = new GoodsInfoVO(); //goodsInfoVO.setShopId(String.valueOf(goodsInfo.getShopId())); //goodsInfoVO.setShopName(goodsInfo.getShopName()); //goodsInfoVO.setOwner(goodsInfo.getOwner()); //goodsInfoVO.setProductId(String.valueOf(goodsInfo.getProductId())); //goodsInfoVO.setImageUrl(goodsInfo.getImageUrl()); //orderRow.setGoodsInfo(goodsInfoVO); orderRow.setCallerItemId(orderRowResp.getCallerItemId()); orderRow.setPid(orderRowResp.getPid()); orderRow.setCid1(orderRowResp.getCid1()); orderRow.setCid2(orderRowResp.getCid2()); orderRow.setCid3(orderRowResp.getCid3()); orderRow.setChannelId(orderRowResp.getChannelId()); orderRow.setProPriceAmount(orderRowResp.getProPriceAmount()); orderRow.setSkuFrozenNum(orderRowResp.getSkuFrozenNum()); orderRow.setSkuReturnNum(orderRowResp.getSkuReturnNum()); orderRow.setSkuTag(orderRowResp.getSkuTag()); orderRow.setPositionId(orderRowResp.getPositionId()); orderRow.setPopId(orderRowResp.getPopId()); orderRow.setRid(orderRowResp.getRid()); orderRow.setPlus(orderRowResp.getPlus()); orderRow.setCpActId(orderRowResp.getCpActId()); orderRow.setGiftCouponKey(orderRowResp.getGiftCouponKey()); orderRow.setModifyTime(new Date()); orderRow.setSign(orderRowResp.getSign()); orderRow.setBalanceExt(orderRowResp.getBalanceExt()); orderRow.setExpressStatus(orderRowResp.getExpressStatus()); orderRow.setExt1(orderRowResp.getExt1()); orderRow.setSubUnionId(orderRowResp.getSubUnionId()); orderRow.setGiftCouponOcsAmount(orderRowResp.getGiftCouponOcsAmount()); orderRow.setTraceType(orderRowResp.getTraceType()); orderRow.setExpressStatus(orderRowResp.getExpressStatus()); orderRow.setTraceType(orderRowResp.getTraceType()); orderRow.setId(orderRowResp.getId()); orderRow.setValidCode(orderRowResp.getValidCode()); orderRow.setExpressStatus(orderRowResp.getExpressStatus()); orderRow.setTraceType(orderRowResp.getTraceType()); return orderRow; } /** * 根据指定的日期时间拉取订单 * * @param startTime 开始时间 * isRealTime 是否是实时订单 是的话不会判断是否拉取过 * page 分页页码 * isRealTime 是否是分钟级订单 分钟的每次加10分钟,小时每小时加1小时 */ public UnionOpenOrderRowQueryResponse fetchOrdersForDateTime(LocalDateTime startTime, boolean isRealTime, Integer page, boolean isMinutes, String appKey, String secretKey) { LocalDateTime endTime = isMinutes ? startTime.plusMinutes(10) : startTime.plusHours(1); String hourMinuteTag = isRealTime ? "minute" : "hour"; //String oldTimeTag = JD_REFRESH_TAG + startTime.format(DATE_TIME_FORMATTER); String newTimeTag = JD_REFRESH_TAG + appKey + ":" + startTime.format(DATE_TIME_FORMATTER); HashOperations hashOps = redisTemplate.opsForHash(); // 检查这个小时或分钟是否已经被处理过 if (hashOps.hasKey(newTimeTag, hourMinuteTag)) { // 0007需要暴力拉取 if (!isMinutes && !isRealTime) { return null; } } // 调用 API 以拉取订单 try { UnionOpenOrderRowQueryResponse unionOpenOrderRowQueryResponse = getUnionOpenOrderRowQueryResponse(startTime, endTime, page, appKey, secretKey); // 历史的订单才进行标记为已拉取,小时分钟的都进行拉取并且标记 if (!isRealTime) { // 只有没有订单的才进行标记为已拉取 if (unionOpenOrderRowQueryResponse != null && unionOpenOrderRowQueryResponse.getQueryResult() != null && unionOpenOrderRowQueryResponse.getQueryResult().getData() == null) { hashOps.put(newTimeTag, hourMinuteTag, "done"); logger.info(" 账号 {} -- 没有订单 -- 开始时间:{} --- 结束时间:{}", appKey.substring(appKey.length() - 4), startTime.format(DATE_TIME_FORMATTER), endTime.format(DATE_TIME_FORMATTER)); } } // 打印方法调用和开始结束时间 //if (isRealTime && (LocalDateTime.now().getMinute() % 10 == 0)) { // logger.debug(" {} --- 拉取订单, 分钟还是秒 {} , 开始时间:{} --- 结束时间:{}", appKey.substring(appKey.length() - 4), hourMinuteTag, startTime.format(DATE_TIME_FORMATTER), endTime.format(DATE_TIME_FORMATTER)); //} return unionOpenOrderRowQueryResponse; } catch (Exception e) { return null; } } // 响应校验方法 private boolean isValidResponse(UnionOpenOrderRowQueryResponse response) { return response != null && response.getQueryResult() != null && response.getQueryResult().getCode() == 200 && response.getQueryResult().getData() != null; } // 订单处理方法 private void processOrderResponse(UnionOpenOrderRowQueryResponse response, SuperAdmin admin) { Arrays.stream(response.getQueryResult().getData()).parallel().map(this::createOrderRow).forEach(orderRowRepository::save); } public int fetchOrders(OrderFetchStrategy strategy, String appKey, String secretKey) { TimeRange range = strategy.calculateRange(LocalDateTime.now()); int count = 0; // 复用原有的抓取逻辑 LocalDateTime current = range.getStart(); while (!current.isAfter(range.getEnd())) { // 调用分页抓取API... Integer pageIndex = 1; boolean hasMore = true; while (hasMore) { try { // 30-60 天 ,非实时,非分钟 UnionOpenOrderRowQueryResponse response = fetchOrdersForDateTime(current, strategy.isRealTime(), pageIndex, false, appKey, secretKey); if (response != null && response.getQueryResult() != null) { if (response.getQueryResult().getCode() == 200) { OrderRowResp[] orderRowResps = response.getQueryResult().getData(); if (orderRowResps != null) { for (OrderRowResp orderRowResp : orderRowResps) { if (orderRowResp != null) { // Check each orderRowResp is not null OrderRow orderRow = createOrderRow(orderRowResp); // Ensure orderRow is not null after creation orderRowRepository.save(orderRow); count++; } } } hasMore = Boolean.TRUE.equals(response.getQueryResult().getHasMore()); } else { hasMore = false; } } else { hasMore = false; } } catch (Exception e) { hasMore = false; // Optionally break out of the while loop if required } if (hasMore) pageIndex++; } current = current.plusHours(1); } return count; } /** * 实时刷新最近10分钟的订单(Resilience4j限流集成) */ @Scheduled(cron = "0 * * * * ?") public void fetchLatestOrder() { if (isRunning_jd.equals("true")) { LocalDateTime now = LocalDateTime.now(); LocalDateTime startTime = now.minusMinutes(10).withSecond(0).withNano(0); super_admins.values().parallelStream().forEach(admin -> { if (Util.isAnyEmpty(admin.getAppKey(), admin.getSecretKey())) return; try { UnionOpenOrderRowQueryResponse response = fetchOrdersForDateTime(startTime, true, 1, true, admin.getAppKey(), admin.getSecretKey()); if (isValidResponse(response)) { processOrderResponse(response, admin); } } catch (JDUtil.RateLimitExceededException e) { logger.warn("[限流] {} 请求频率受限", admin.getName()); } catch (Exception e) { logger.error("{} 订单抓取异常: {}", admin.getName(), e.getMessage()); } }); } } /** * 扫描订单发送到微信 * 每分钟的30秒执行一次 */ @Scheduled(cron = "3 * * * * ?") public void sendOrderToWx() { if (isRunning_wx.equals("true")) { //long start = System.currentTimeMillis(); int[] validCodes = {-1}; // 只要三个月的,更多的也刷新不出来的 Date threeMonthsAgo = Date.from(LocalDateTime.now().minusMonths(3).atZone(ZoneId.systemDefault()).toInstant()); List orderRows = orderRowRepository.findByValidCodeNotInAndOrderTimeAfter(validCodes, threeMonthsAgo); for (OrderRow orderRow : orderRows) { orderUtil.orderToWx(orderRow, true, false); } //logger.info("扫描订单发送到微信耗时:{} ms, 订单数:{} ", System.currentTimeMillis() - start, orderRows.size()); } } /** * 一天拉取三次 30天到60天前的订单 */ @Scheduled(cron = "0 0 */4 * * ?") public void fetchHistoricalOrders3090() { if (isRunning_jd.equals("true")) { try { OrderFetchStrategy strategy = new Days3090Strategy(); for (SuperAdmin admin : super_admins.values()) { try { if (Util.isAnyEmpty(admin.getAppKey(), admin.getSecretKey())) { continue; } int count = fetchOrders(strategy, admin.getAppKey(), admin.getSecretKey()); logger.info("账号{} 3090订单拉取完成,新增{}条", admin.getName(), count); } catch (Exception e) { logger.error("账号 {} 拉取异常: {}", admin.getName(), e.getMessage()); } } } catch (Exception ex) { logger.error("策略执行异常", ex); } } } /** * 一天拉取6次 14天到30天前的订单 */ @Scheduled(cron = "0 0 * * * ?") public void fetchHistoricalOrders1430() { if (isRunning_jd.equals("true")) { try { OrderFetchStrategy strategy = new Days1430Strategy(); // 需补充Days1430Strategy实现 for (SuperAdmin admin : super_admins.values()) { try { if (Util.isAnyEmpty(admin.getAppKey(), admin.getSecretKey())) { continue; } int count = fetchOrders(strategy, admin.getAppKey(), admin.getSecretKey()); logger.info("账号{} 1430订单拉取完成,新增{}条", admin.getName(), count); } catch (Exception e) { logger.error("账号 {} 拉取异常: {}", admin.getName(), e.getMessage()); } } } catch (Exception ex) { logger.error("1430策略执行异常", ex); } } } /** * 每10分钟拉取07-14天的订单 */ @Scheduled(cron = "0 0 * * * ?") public void fetchHistoricalOrders0714() { if (isRunning_jd.equals("true")) { try { OrderFetchStrategy strategy = new Days0714Strategy(); super_admins.values().parallelStream().forEach(admin -> { if (Util.isAnyEmpty(admin.getAppKey(), admin.getSecretKey())) return; try { int count = fetchOrders(strategy, admin.getAppKey(), admin.getSecretKey()); logger.info("账号{} 0714订单拉取完成,新增{}条", admin.getName(), count); } catch (Exception e) { logger.error("账号 {} 0714拉取异常: {}", admin.getName(), e.getMessage()); } }); } catch (Exception ex) { logger.error("0714策略执行异常", ex); } } } @Scheduled(cron = "30 */10 * * * ?") public void fetchHistoricalOrders0007() { if (isRunning_jd.equals("true")) { try { OrderFetchStrategy strategy = new Days0007Strategy(); super_admins.values().parallelStream().forEach(admin -> { if (Util.isAnyEmpty(admin.getAppKey(), admin.getSecretKey())) return; try { int count = fetchOrders(strategy, admin.getAppKey(), admin.getSecretKey()); logger.info("账号{} 0007订单拉取完成,新增{}条", admin.getName(), count); } catch (JDUtil.RateLimitExceededException e) { logger.warn("[限流] {} 0007请求受限", admin.getName()); } catch (Exception e) { logger.error("账号{}0007拉取异常: {}", admin.getName(), e.getMessage()); } }); } catch (Exception ex) { logger.error("0007策略执行异常", ex); } } } /** * 获取订单列表 * * @param start 开始时间 * @param end 结束时间 * @return * @throws Exception */ public UnionOpenOrderRowQueryResponse getUnionOpenOrderRowQueryResponse(LocalDateTime start, LocalDateTime end, Integer pageIndex, String appKey, String secretKey) throws Exception { String startTime = start.format(DATE_TIME_FORMATTER); String endTime = end.format(DATE_TIME_FORMATTER); // 模拟 API 调用 //System.out.println("调用API - 从 " + startTime // + " 到 " + endTime); // 实际的 API 调用逻辑应在这里进行 JdClient client = new DefaultJdClient(SERVER_URL, ACCESS_TOKEN, appKey, secretKey); UnionOpenOrderRowQueryRequest request = new UnionOpenOrderRowQueryRequest(); OrderRowReq orderReq = new OrderRowReq(); orderReq.setPageIndex(pageIndex); orderReq.setPageSize(200); orderReq.setStartTime(startTime); orderReq.setEndTime(endTime); orderReq.setType(1); request.setOrderReq(orderReq); request.setVersion("1.0"); request.setSignmethod("md5"); // 时间戳,格式为yyyy-MM-dd HH:mm:ss,时区为GMT+8。API服务端允许客户端请求最大时间误差为10分钟 Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); request.setTimestamp(simpleDateFormat.format(date)); return client.execute(request); } //@Scheduled(cron = "0 0 8-20 * * ?") // 每天从 8:00 到 20:00,每小时执行一次 public void fetchPL() { logger.info("开始执行fetchPL任务"); // 设置每天最多执行 3 次 Set executedHours = getExecutedHoursFromRedis(); // 从 Redis 获取已执行的小时数 LocalDateTime now = LocalDateTime.now(); String currentHour = String.valueOf(now.getHour()); // 如果今天已经执行了3次,则跳过 if (executedHours.size() >= 2) { logger.info("今天已经执行了2次,跳过本次任务"); return; } // 随机决定是否执行本次任务(例如 50% 概率) if (new Random().nextBoolean()) { logger.info("执行fetchPL任务"); // 执行任务逻辑 executeFetchPL(); // 记录该小时已执行 executedHours.add(currentHour); saveExecutedHoursToRedis(executedHours); // 存入 Redis } } private void executeFetchPL() { HashMap productTypeMap = jdUtil.getProductTypeMap(); int usedCommentCount; int canUseComentCount; int addCommentCount; for (Map.Entry entry : productTypeMap.entrySet()) { // 随机睡眠1-5分钟 int sleepTime = new Random().nextInt(3000) + 60; try { Thread.sleep(sleepTime * 1000); } catch (InterruptedException e) { logger.error("线程中断", e); } String product_id = entry.getKey(); List availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1); List usedComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 0); canUseComentCount = availableComments.size(); usedCommentCount = usedComments.size(); if (canUseComentCount > 5) { logger.info("商品{} 评论可用数量大于5:{}", product_id, canUseComentCount); return; } try { String fetchUrl = "http://192.168.8.6:5000/fetch_comments?product_id=" + product_id; // 用hutool发起post请求 HttpResponse response = HttpRequest.post(fetchUrl).timeout(60000).execute(); logger.info("fetchUrl: {}", fetchUrl); // code = 200 表示成功,-200 表示失败 if (response.getStatus() == 200) { // ✅ 关键修改:重新从数据库中查询,而不是使用内存中的 fetchedComments availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1); if (!availableComments.isEmpty()) { addCommentCount = availableComments.size() - canUseComentCount; logger.info("自动刷新并且获取评论成功"); logger.info("型号{} 总评论数量 {} 可用数量 {} 新增评论数量:{}", entry.getValue(), availableComments.size() + usedCommentCount, canUseComentCount, addCommentCount); } } else if (response.getStatus() == -200) { return; } } catch (Exception e) { logger.error("调用外部接口获取评论失败", e); return; } } } private Set getExecutedHoursFromRedis() { String key = "fetchPL:executedHours"; HashOperations hashOps = redisTemplate.opsForHash(); return hashOps.entries(key).keySet(); } private void saveExecutedHoursToRedis(Set hours) { String key = "fetchPL:executedHours"; HashOperations hashOps = redisTemplate.opsForHash(); hashOps.putAll(key, hours.stream().collect(Collectors.toMap(h -> h, h -> "1"))); } @Scheduled(cron = "0 0 0 * * ?") public void checkGiftCouponsExpiry() { Set keys = redisTemplate.keys("gift_coupon:*"); if (keys.isEmpty()) return; for (String key : keys) { Map entries = redisTemplate.opsForHash().entries(key); if (entries.isEmpty()) continue; for (Map.Entry entry : entries.entrySet()) { String giftJson = (String) entry.getValue(); JSONObject gift = JSON.parseObject(giftJson); String giftKey = gift.getString("giftKey"); String skuName = gift.getString("skuName"); String owner = gift.getString("owner"); LocalDateTime expireTime = LocalDateTime.parse(gift.getString("expireTime"), DateTimeFormatter.ISO_DATE_TIME); boolean isAboutToExpire = false; if ("g".equals(owner)) { // 自营:当天过期 isAboutToExpire = !expireTime.isAfter(LocalDateTime.now().with(LocalTime.MAX)); } else if ("p".equals(owner)) { // POP:7天后过期,提前一天提醒 isAboutToExpire = ChronoUnit.HOURS.between(LocalDateTime.now(), expireTime) <= 24; } if (isAboutToExpire) { String message = String.format("[礼金提醒]\n商品:%s\n礼金Key:%s\n类型:%s\n将在 %s 过期", skuName, giftKey, "g".equals(owner) ? "自营" : "POP", expireTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))); wxUtil.sendTextMessage(WXUtil.default_super_admin_wxid, message, 1, "bot", false); redisTemplate.delete(key); } } } } /** * 清理三个月前的Redis hash数据 * 修复了时间解析异常的问题 */ @Scheduled(cron = "0 45 11 * * ?") // 每月1日的凌晨3点执行 public void cleanOldRedisHashData() { try { // 获取三个月前的时间 LocalDateTime threeMonthsAgo = LocalDateTime.now().minusMonths(3); // 获取所有以JD_REFRESH_TAG开头的键 Set keys = redisTemplate.keys(JD_REFRESH_TAG + "*"); if (keys != null && !keys.isEmpty()) { for (String key : keys) { try { // 提取时间部分,处理两种格式: // 1. jd:refresh:tag:hash值:2025-02-02 16:00:00 // 2. jd:refresh:tag:2024-11-30 09:26:00 String timePart; // 检查是否包含hash值(32位十六进制字符串) if (key.matches("jd:refresh:tag:[a-f0-9]{32}:[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}")) { // 格式:jd:refresh:tag:hash值:时间 timePart = key.substring(key.lastIndexOf(":") + 1); } else if (key.matches("jd:refresh:tag:[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}")) { // 格式:jd:refresh:tag:时间 timePart = key.substring("jd:refresh:tag:".length()); } else { logger.warn("无法识别Redis键格式:{}", key); continue; } LocalDateTime time; try { // 解析为完整的时间格式 yyyy-MM-dd HH:mm:ss time = LocalDateTime.parse(timePart, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } catch (DateTimeParseException e) { logger.warn("无法解析Redis键时间:{},时间部分:{}", key, timePart); continue; } // 检查是否在三个月前 if (time.isBefore(threeMonthsAgo)) { redisTemplate.delete(key); logger.info("已删除过期的Redis键:{}", key); } } catch (Exception e) { logger.warn("解析Redis键时间失败:{}", key, e); } } } } catch (Exception e) { logger.error("清理Redis hash数据时发生错误", e); } } }