|
|
@@ -0,0 +1,69 @@
|
|
|
+package com.etotem.cfc.task;
|
|
|
+
|
|
|
+import com.etotem.cfc.service.CouponService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.Date;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 过期优惠券物理清理任务
|
|
|
+ *
|
|
|
+ * <p>清理口径:只清理「券实例 {@code family_coupon.status = 'AVAILABLE'} 且其券模板
|
|
|
+ * {@code coupon.valid_until} 早于当前时间」的记录,把这些券实例的 status 更新为 {@code EXPIRED}。
|
|
|
+ * 券模板 valid_until 为 NULL(永久有效)的券不视为过期,不会被清理。</p>
|
|
|
+ *
|
|
|
+ * <p>不会触碰 {@code status = 'RESERVED'}(下单占用中)的券实例——RESERVED 券的过期处理不在本任务范围内,
|
|
|
+ * 由下单/超时取消链路({@code OrderTimeoutCancelTask} 归还 AVAILABLE、支付后置 USED)自行负责。</p>
|
|
|
+ *
|
|
|
+ * <p>分批执行:每批最多 {@link #BATCH_SIZE} 条,逐批独立事务({@link CouponService#expireOverdueAvailableCoupons}),
|
|
|
+ * 避免长事务/大事务锁表;每批打印处理条数,任务结束时汇总输出总处理条数。</p>
|
|
|
+ *
|
|
|
+ * <p>调度:每天 03:30 低峰执行一次({@code @EnableScheduling} 已在 {@code CfcApplication} 开启)。</p>
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class CouponExpireCleanupTask {
|
|
|
+
|
|
|
+ /** 每批处理条数:控制单条 UPDATE 的影响行数与事务时长 */
|
|
|
+ private static final int BATCH_SIZE = 500;
|
|
|
+
|
|
|
+ /** 单次任务最多执行批数(500 * 200 = 10 万条),防止历史积压过大时任务长时间占用数据库 */
|
|
|
+ private static final int MAX_BATCHES = 200;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private CouponService couponService;
|
|
|
+
|
|
|
+ @Scheduled(cron = "0 30 3 * * ?")
|
|
|
+ public void cleanupExpiredCoupons() {
|
|
|
+ log.info("开始执行过期优惠券物理清理任务: batchSize={}, maxBatches={}", BATCH_SIZE, MAX_BATCHES);
|
|
|
+ long start = System.currentTimeMillis();
|
|
|
+ int total = 0;
|
|
|
+ int batches = 0;
|
|
|
+ try {
|
|
|
+ Date now = new Date();
|
|
|
+ while (batches < MAX_BATCHES) {
|
|
|
+ int updated = couponService.expireOverdueAvailableCoupons(now, BATCH_SIZE);
|
|
|
+ if (updated <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ batches++;
|
|
|
+ total += updated;
|
|
|
+ log.info("过期优惠券清理: 第{}批处理{}条,累计{}条", batches, updated, total);
|
|
|
+ if (updated < BATCH_SIZE) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (batches >= MAX_BATCHES) {
|
|
|
+ log.warn("过期优惠券清理达到单次最大批数{}(本次已处理{}条),剩余记录留待下次任务执行",
|
|
|
+ MAX_BATCHES, total);
|
|
|
+ }
|
|
|
+ log.info("过期优惠券物理清理任务执行完成: 总处理{}条, 批次={}, 耗时={}ms",
|
|
|
+ total, batches, System.currentTimeMillis() - start);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("过期优惠券物理清理任务执行失败: 已处理{}条, 批次={}", total, batches, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|