feat(notification): 添加微信订阅消息配置管理及幂等授权功能

- 新增微信订阅消息配置管理控制器,支持模板、场景、事件映射配置
- 实现用户通知授权服务的幂等控制,避免前端重试导致授权次数虚增
- 添加微信订阅消息发送日志记录,用于幂等与排障
- 新增视频生成完成时的订阅消息触发功能
- 实现场景模板查询接口,返回用户授权余额信息
- 添加模板V2相关数据表映射器和实体类
- 集成微信订阅消息触发服务到任务完成流程中
This commit is contained in:
2026-01-01 17:53:59 +08:00
parent 81dc2f1b86
commit f1a2958251
61 changed files with 3655 additions and 9 deletions

View File

@@ -41,7 +41,8 @@ public class UserNotificationAuthController {
@PostMapping("/record")
public ApiResponse<NotificationAuthRecordResp> recordAuthorization(
@RequestBody NotificationAuthRecordReq req) {
log.debug("记录用户通知授权: templateIds={}, scenicId={}", req.getTemplateIds(), req.getScenicId());
log.debug("记录用户通知授权: templateIds={}, scenicId={}, requestId={}",
req.getTemplateIds(), req.getScenicId(), req.getRequestId());
try {
// 获取当前用户ID
@@ -50,7 +51,7 @@ public class UserNotificationAuthController {
// 调用批量授权记录方法
List<UserNotificationAuthorizationService.AuthorizationRecord> records =
userNotificationAuthorizationService.batchRecordAuthorization(
memberId, req.getTemplateIds(), req.getScenicId());
memberId, req.getTemplateIds(), req.getScenicId(), req.getRequestId());
NotificationAuthRecordResp resp = new NotificationAuthRecordResp();
@@ -187,4 +188,4 @@ public class UserNotificationAuthController {
return ApiResponse.fail("获取授权信息失败: " + e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,86 @@
package com.ycwl.basic.controller.mobile.notify;
import com.ycwl.basic.model.mobile.notify.resp.WechatSubscribeSceneTemplatesResp;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeTemplateConfigEntity;
import com.ycwl.basic.service.notify.WechatSubscribeNotifyConfigService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import com.ycwl.basic.utils.NotificationAuthUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 微信小程序订阅消息:场景模板查询(移动端API)
*
* @Author: System
* @Date: 2025/12/31
*/
@RestController
@RequestMapping("/api/mobile/notify/subscribe")
@Slf4j
public class WechatSubscribeNotifyController {
private final WechatSubscribeNotifyConfigService configService;
private final NotificationAuthUtils notificationAuthUtils;
public WechatSubscribeNotifyController(WechatSubscribeNotifyConfigService configService,
NotificationAuthUtils notificationAuthUtils) {
this.configService = configService;
this.notificationAuthUtils = notificationAuthUtils;
}
/**
* 获取“场景”下可申请授权的模板列表(支持按 scenicId 覆盖模板ID/开关/文案)
*/
@GetMapping("/scenic/{scenicId}/scenes/{sceneKey}/templates")
public ApiResponse<WechatSubscribeSceneTemplatesResp> listSceneTemplates(@PathVariable("scenicId") Long scenicId,
@PathVariable("sceneKey") String sceneKey) {
if (scenicId == null) {
return ApiResponse.fail("scenicId不能为空");
}
if (StringUtils.isBlank(sceneKey)) {
return ApiResponse.fail("sceneKey不能为空");
}
Long memberId = JwtTokenUtil.getWorker().getUserId();
List<WechatSubscribeTemplateConfigEntity> configs = configService.listSceneTemplateConfigs(scenicId, sceneKey);
WechatSubscribeSceneTemplatesResp resp = new WechatSubscribeSceneTemplatesResp();
resp.setScenicId(scenicId);
resp.setSceneKey(sceneKey);
List<WechatSubscribeSceneTemplatesResp.TemplateInfo> templates = new ArrayList<>();
for (WechatSubscribeTemplateConfigEntity cfg : configs) {
if (cfg == null || StringUtils.isBlank(cfg.getWechatTemplateId())) {
continue;
}
String title = StringUtils.isNotBlank(cfg.getTitleTemplate())
? cfg.getTitleTemplate()
: cfg.getTemplateKey();
int remaining = notificationAuthUtils.getRemainingCount(memberId, cfg.getWechatTemplateId(), scenicId);
WechatSubscribeSceneTemplatesResp.TemplateInfo info = new WechatSubscribeSceneTemplatesResp.TemplateInfo();
info.setTemplateKey(cfg.getTemplateKey());
info.setWechatTemplateId(cfg.getWechatTemplateId());
info.setTitle(title);
info.setDescription(cfg.getDescription());
info.setRemainingCount(remaining);
info.setHasAuthorization(remaining > 0);
templates.add(info);
}
resp.setTemplates(templates);
log.debug("场景模板查询: scenicId={}, sceneKey={}, memberId={}, templateCount={}",
scenicId, sceneKey, memberId, Objects.requireNonNullElse(templates.size(), 0));
return ApiResponse.success(resp);
}
}