diff --git a/src/main/java/com/ycwl/basic/model/pc/notify/entity/WechatSubscribeTemplateConfigEntity.java b/src/main/java/com/ycwl/basic/model/pc/notify/entity/WechatSubscribeTemplateConfigEntity.java index 0b7968fb..3db180f1 100644 --- a/src/main/java/com/ycwl/basic/model/pc/notify/entity/WechatSubscribeTemplateConfigEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/notify/entity/WechatSubscribeTemplateConfigEntity.java @@ -1,5 +1,6 @@ package com.ycwl.basic.model.pc.notify.entity; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -64,6 +65,13 @@ public class WechatSubscribeTemplateConfigEntity { */ private String description; + /** + * 去重窗口(秒),0表示永久去重,小于0表示不去重,大于0表示窗口期去重 + * 来自 wechat_subscribe_event_template 表,仅在事件触发时有效 + */ + @TableField(exist = false) + private Integer dedupSeconds; + private Date createTime; private Date updateTime; diff --git a/src/main/java/com/ycwl/basic/repository/WechatSubscribeNotifyConfigRepository.java b/src/main/java/com/ycwl/basic/repository/WechatSubscribeNotifyConfigRepository.java index b768ba4f..6dbaa6d6 100644 --- a/src/main/java/com/ycwl/basic/repository/WechatSubscribeNotifyConfigRepository.java +++ b/src/main/java/com/ycwl/basic/repository/WechatSubscribeNotifyConfigRepository.java @@ -428,6 +428,8 @@ public class WechatSubscribeNotifyConfigRepository { if (cfg == null || !Objects.equals(cfg.getEnabled(), 1)) { continue; } + // 复制去重配置到模板实体中 + cfg.setDedupSeconds(mapping.getDedupSeconds()); result.add(cfg); } return result; diff --git a/src/main/java/com/ycwl/basic/service/notify/WechatSubscribeNotifyTriggerService.java b/src/main/java/com/ycwl/basic/service/notify/WechatSubscribeNotifyTriggerService.java index 39a70bef..504a2469 100644 --- a/src/main/java/com/ycwl/basic/service/notify/WechatSubscribeNotifyTriggerService.java +++ b/src/main/java/com/ycwl/basic/service/notify/WechatSubscribeNotifyTriggerService.java @@ -14,6 +14,7 @@ import com.ycwl.basic.utils.NotificationAuthUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.dao.DuplicateKeyException; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; @@ -23,6 +24,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,15 +50,18 @@ public class WechatSubscribeNotifyTriggerService { private final WechatSubscribeSendLogMapper sendLogMapper; private final NotificationAuthUtils notificationAuthUtils; private final ZtMessageProducerService ztMessageProducerService; + private final StringRedisTemplate redisTemplate; public WechatSubscribeNotifyTriggerService(WechatSubscribeNotifyConfigService configService, WechatSubscribeSendLogMapper sendLogMapper, NotificationAuthUtils notificationAuthUtils, - ZtMessageProducerService ztMessageProducerService) { + ZtMessageProducerService ztMessageProducerService, + StringRedisTemplate redisTemplate) { this.configService = configService; this.sendLogMapper = sendLogMapper; this.notificationAuthUtils = notificationAuthUtils; this.ztMessageProducerService = ztMessageProducerService; + this.redisTemplate = redisTemplate; } /** @@ -91,8 +97,32 @@ public class WechatSubscribeNotifyTriggerService { continue; } - String idempotencyKey = buildIdempotencyKey(eventKey, cfg.getTemplateKey(), request); - WechatSubscribeSendLogEntity sendLog = buildInitLog(idempotencyKey, eventKey, cfg, request); + // 计算基础幂等键 + String baseHash = buildIdempotencyKey(eventKey, cfg.getTemplateKey(), request); + String dbIdempotencyKey = baseHash; + Integer dedupSeconds = cfg.getDedupSeconds(); + + if (dedupSeconds != null && dedupSeconds != 0) { + // 非永久去重场景 + if (dedupSeconds < 0) { + // 不去重:强制生成新的幂等键 + dbIdempotencyKey = baseHash + "_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8); + } else { + // 窗口期去重:依赖 Redis 检查 + String redisKey = "wechat:subscribe:dedup:" + baseHash; + Boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", dedupSeconds, TimeUnit.SECONDS); + if (Boolean.FALSE.equals(success)) { + // 窗口期内已发送 + skippedCount++; + continue; + } + // 允许发送,但需要新的 DB 键以记录日志(因为表中 idempotency_key 唯一) + dbIdempotencyKey = baseHash + "_" + UUID.randomUUID().toString().replace("-", "").substring(0, 8); + } + } + // else: dedupSeconds == 0 或 null,使用 baseHash 作为 DB 键,利用 DB 唯一索引实现永久去重 + + WechatSubscribeSendLogEntity sendLog = buildInitLog(dbIdempotencyKey, eventKey, cfg, request); if (!tryInsertSendLog(sendLog)) { skippedCount++; continue;