feat(payment): 支付与退款后清除景区统计缓存

- 在支付成功、取消、退款回调后增加缓存删除逻辑
- 新增 `invalidateStatisticsCache` 方法用于删除 Redis 缓存
- 定时任务中统计景区数据后也调用缓存清除方法
- 调整景区统计任务时间并扩展统计周期为近7天
- 增强定时任务日志记录和异常处理机制
This commit is contained in:
2025-10-31 13:46:17 +08:00
parent 785de52780
commit 631d5c175f
2 changed files with 76 additions and 14 deletions

View File

@@ -30,6 +30,7 @@ import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.utils.SnowFlakeUtil; import com.ycwl.basic.utils.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@@ -60,6 +61,8 @@ public class WxPayServiceImpl implements WxPayService {
private OrderMapper orderMapper; private OrderMapper orderMapper;
@Autowired @Autowired
private ScenicService scenicService; private ScenicService scenicService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override @Override
public Map<String, Object> createOrder(Long scenicId, WXPayOrderReqVO req) { public Map<String, Object> createOrder(Long scenicId, WXPayOrderReqVO req) {
@@ -100,10 +103,14 @@ public class WxPayServiceImpl implements WxPayService {
long orderId = Long.parseLong(callbackResponse.getOrderNo()); long orderId = Long.parseLong(callbackResponse.getOrderNo());
if (callbackResponse.isPay()) { if (callbackResponse.isPay()) {
orderBiz.paidOrder(orderId); orderBiz.paidOrder(orderId);
// 支付成功后删除统计缓存,确保下次查询获取最新数据
invalidateStatisticsCache(scenicId);
} else if (callbackResponse.isCancel()) { } else if (callbackResponse.isCancel()) {
orderBiz.cancelOrder(orderId); orderBiz.cancelOrder(orderId);
} else if (callbackResponse.isRefund()) { } else if (callbackResponse.isRefund()) {
orderBiz.refundOrder(orderId); orderBiz.refundOrder(orderId);
// 退款后删除统计缓存
invalidateStatisticsCache(scenicId);
} }
}); });
} catch (Exception e) { } catch (Exception e) {
@@ -165,6 +172,10 @@ public class WxPayServiceImpl implements WxPayService {
statisticsRecordAddReq.setScenicId(order.getScenicId()); statisticsRecordAddReq.setScenicId(order.getScenicId());
statisticsRecordAddReq.setMorphId(orderId); statisticsRecordAddReq.setMorphId(orderId);
statisticsMapper.addStatisticsRecord(statisticsRecordAddReq); statisticsMapper.addStatisticsRecord(statisticsRecordAddReq);
// 退款成功后删除统计缓存,确保下次查询获取最新数据
invalidateStatisticsCache(scenicId);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
log.error("微信退款回调失败!", e); log.error("微信退款回调失败!", e);
@@ -180,4 +191,15 @@ public class WxPayServiceImpl implements WxPayService {
.setOrderNo(orderId); .setOrderNo(orderId);
scenicPayAdapter.cancelOrder(request); scenicPayAdapter.cancelOrder(request);
} }
/**
* 删除景区统计缓存
* 在支付或退款回调后调用,确保下次查询时重新计算统计数据
* @param scenicId 景区ID
*/
private void invalidateStatisticsCache(Long scenicId) {
String redisKey = "statistics:tmp_cache:" + scenicId;
Boolean deleted = redisTemplate.delete(redisKey);
log.info("[缓存删除] 景区 {} 的统计缓存删除结果: {}", scenicId, deleted);
}
} }

View File

@@ -11,8 +11,10 @@ import com.ycwl.basic.model.pc.scenicDeviceStats.entity.ScenicDeviceStatsEntity;
import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.mobile.AppStatisticsService; import com.ycwl.basic.service.mobile.AppStatisticsService;
import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -20,6 +22,7 @@ import org.springframework.stereotype.Component;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@Slf4j
@Component @Component
@EnableScheduling @EnableScheduling
@Profile("prod") @Profile("prod")
@@ -32,6 +35,8 @@ public class ScenicStatsTask {
private AppStatisticsService statisticsService; private AppStatisticsService statisticsService;
@Autowired @Autowired
private ScenicRepository scenicRepository; private ScenicRepository scenicRepository;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Scheduled(cron = "0 1 0 * * *") @Scheduled(cron = "0 1 0 * * *")
public void countDeviceStats() { public void countDeviceStats() {
@@ -55,23 +60,58 @@ public class ScenicStatsTask {
}); });
} }
} }
@Scheduled(cron = "0 20 0 * * *") @Scheduled(cron = "0 0 2 * * *")
public void countScenicStats() { public void countScenicStats() {
Date yesterdayStart = DateUtil.beginOfDay(DateUtil.yesterday()); log.info("开始执行景区统计任务,统计前7天至昨天的数据");
Date yesterdayEnd = DateUtil.endOfDay(yesterdayStart);
// 获取所有景区列表
ScenicReqQuery query = new ScenicReqQuery(); ScenicReqQuery query = new ScenicReqQuery();
query.setPageSize(1000); query.setPageSize(1000);
List<ScenicV2DTO> scenicList = scenicRepository.list(query); List<ScenicV2DTO> scenicList = scenicRepository.list(query);
// 循环统计前7天(至昨天)的数据
for (int daysAgo = 1; daysAgo <= 7; daysAgo++) {
Date targetDate = DateUtil.offsetDay(new Date(), -daysAgo);
Date startTime = DateUtil.beginOfDay(targetDate);
Date endTime = DateUtil.endOfDay(targetDate);
log.info("正在统计第{}天前的数据,日期: {}", daysAgo, DateUtil.formatDate(startTime));
scenicList.forEach((scenic) -> { scenicList.forEach((scenic) -> {
try {
CommonQueryReq commonQueryReq = new CommonQueryReq(); CommonQueryReq commonQueryReq = new CommonQueryReq();
Long scenicId = Long.valueOf(scenic.getId()); Long scenicId = Long.valueOf(scenic.getId());
commonQueryReq.setScenicId(scenicId); commonQueryReq.setScenicId(scenicId);
commonQueryReq.setStartTime(yesterdayStart); commonQueryReq.setStartTime(startTime);
commonQueryReq.setEndTime(yesterdayEnd); commonQueryReq.setEndTime(endTime);
commonQueryReq.setRealtime(true); commonQueryReq.setRealtime(true);
// 执行统计查询
ApiResponse<AppStatisticsFunnelVO> resp = statisticsService.userConversionFunnel(commonQueryReq); ApiResponse<AppStatisticsFunnelVO> resp = statisticsService.userConversionFunnel(commonQueryReq);
AppStatisticsFunnelVO data = resp.getData(); AppStatisticsFunnelVO data = resp.getData();
statisticsMapper.insertStat(scenicId, yesterdayStart, data);
// 写入数据库(REPLACE INTO 会自动更新已存在的记录)
statisticsMapper.insertStat(scenicId, startTime, data);
// 删除该景区的缓存,确保下次查询时获取最新数据
invalidateStatisticsCache(scenicId);
} catch (Exception e) {
log.error("统计景区 {} 在日期 {} 的数据时发生错误", scenic.getId(), DateUtil.formatDate(startTime), e);
}
}); });
log.info("第{}天前的数据统计完成,日期: {}", daysAgo, DateUtil.formatDate(startTime));
}
log.info("景区统计任务执行完成");
}
/**
* 删除景区统计缓存
* @param scenicId 景区ID
*/
private void invalidateStatisticsCache(Long scenicId) {
String redisKey = "statistics:tmp_cache:" + scenicId;
redisTemplate.delete(redisKey);
} }
} }