649 lines
28 KiB
Java
649 lines
28 KiB
Java
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<String, String, String> 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<OrderRow> 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<String> 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<String, String> productTypeMap = jdUtil.getProductTypeMap();
|
||
int usedCommentCount;
|
||
int canUseComentCount;
|
||
int addCommentCount;
|
||
for (Map.Entry<String, String> 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<Comment> availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1);
|
||
List<Comment> 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<String> getExecutedHoursFromRedis() {
|
||
String key = "fetchPL:executedHours";
|
||
HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
|
||
return hashOps.entries(key).keySet();
|
||
}
|
||
|
||
private void saveExecutedHoursToRedis(Set<String> hours) {
|
||
String key = "fetchPL:executedHours";
|
||
HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
|
||
hashOps.putAll(key, hours.stream().collect(Collectors.toMap(h -> h, h -> "1")));
|
||
}
|
||
|
||
@Scheduled(cron = "0 0 0 * * ?")
|
||
public void checkGiftCouponsExpiry() {
|
||
Set<String> keys = redisTemplate.keys("gift_coupon:*");
|
||
if (keys.isEmpty()) return;
|
||
|
||
for (String key : keys) {
|
||
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
|
||
if (entries.isEmpty()) continue;
|
||
|
||
for (Map.Entry<Object, Object> 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<String> 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);
|
||
}
|
||
}
|
||
|
||
}
|