feat(notify): 添加批量查询用户授权余额功能

- 新增批量查询用户授权余额接口 /api/mobile/notify/auth/batch-remaining
- 实现批量检查用户对多个模板的授权记录功能
- 添加景区所有场景及模板列表查询接口并支持缓存
- 优化授权记录查询性能,使用批量查询替代逐个查询
- 新增批量查询请求对象 BatchRemainingCountReq 和响应对象 WechatSubscribeAllScenesResp
- 在数据层添加批量查询用户授权记录的 SQL 映射
- 实现缓存管理机制,支持所有场景模板配置的缓存读写与清理
This commit is contained in:
2026-01-10 17:30:48 +08:00
parent 02f1392355
commit c9cc90c842
9 changed files with 316 additions and 15 deletions

View File

@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.ycwl.basic.mapper.WechatSubscribeEventTemplateMapper;
import com.ycwl.basic.mapper.WechatSubscribeSceneTemplateMapper;
import com.ycwl.basic.mapper.WechatSubscribeTemplateConfigMapper;
import com.ycwl.basic.model.mobile.notify.resp.WechatSubscribeAllScenesResp;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeEventTemplateEntity;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeSceneTemplateEntity;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeTemplateConfigEntity;
@@ -58,6 +59,11 @@ public class WechatSubscribeNotifyConfigRepository {
*/
private static final String EVENT_TEMPLATE_SCENIC_PREFIX = "wechat:subscribe:event:configs:%s:*";
/**
* 景区所有场景及模板缓存KEY
*/
private static final String ALL_SCENES_TEMPLATES_KEY = "wechat:subscribe:all-scenes:configs:%s";
/**
* 缓存过期时间(小时)
*/
@@ -208,6 +214,98 @@ public class WechatSubscribeNotifyConfigRepository {
return null;
}
/**
* 获取景区下所有启用的场景Key列表(去重)
* 包含默认配置(scenicId=0)和景区特定配置
*
* @param scenicId 景区ID
* @return 去重后的场景Key列表
*/
public List<String> listAllSceneKeys(Long scenicId) {
Objects.requireNonNull(scenicId, "scenicId is null");
List<Long> scenicIds = List.of(DEFAULT_SCENIC_ID, scenicId);
QueryWrapper<WechatSubscribeSceneTemplateEntity> wrapper = new QueryWrapper<>();
wrapper.in("scenic_id", scenicIds)
.eq("enabled", 1)
.select("DISTINCT scene_key");
List<WechatSubscribeSceneTemplateEntity> rows = sceneTemplateMapper.selectList(wrapper);
return rows.stream()
.map(WechatSubscribeSceneTemplateEntity::getSceneKey)
.filter(Objects::nonNull)
.distinct()
.sorted()
.collect(Collectors.toList());
}
/**
* 获取景区下所有场景及其模板列表(带缓存,不含用户授权信息)
*
* @param scenicId 景区ID
* @return 所有场景及模板配置
*/
public WechatSubscribeAllScenesResp getAllScenesWithTemplatesCached(Long scenicId) {
Objects.requireNonNull(scenicId, "scenicId is null");
String cacheKey = String.format(ALL_SCENES_TEMPLATES_KEY, scenicId);
// 1. 尝试从缓存读取
Boolean hasKey = redisTemplate.hasKey(cacheKey);
if (Boolean.TRUE.equals(hasKey)) {
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
if (cacheValue != null) {
log.debug("从缓存读取所有场景模板配置: scenicId={}", scenicId);
return JacksonUtil.parseObject(cacheValue, WechatSubscribeAllScenesResp.class);
}
}
// 2. 从数据库加载
WechatSubscribeAllScenesResp resp = loadAllScenesWithTemplates(scenicId);
// 3. 写入缓存
String json = JacksonUtil.toJSONString(resp);
redisTemplate.opsForValue().set(cacheKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
log.debug("所有场景模板配置缓存写入: scenicId={}, sceneCount={}",
scenicId, resp.getScenes() != null ? resp.getScenes().size() : 0);
return resp;
}
/**
* 加载所有场景及其模板配置(内部方法)
*/
private WechatSubscribeAllScenesResp loadAllScenesWithTemplates(Long scenicId) {
List<String> sceneKeys = listAllSceneKeys(scenicId);
WechatSubscribeAllScenesResp resp = new WechatSubscribeAllScenesResp();
resp.setScenicId(scenicId);
List<WechatSubscribeAllScenesResp.SceneWithTemplates> scenes = new ArrayList<>();
for (String sceneKey : sceneKeys) {
List<WechatSubscribeTemplateConfigEntity> configs = getSceneTemplateConfigsCached(scenicId, sceneKey);
WechatSubscribeAllScenesResp.SceneWithTemplates sceneWithTemplates = new WechatSubscribeAllScenesResp.SceneWithTemplates();
sceneWithTemplates.setSceneKey(sceneKey);
List<WechatSubscribeAllScenesResp.StaticTemplateInfo> templates = new ArrayList<>();
for (WechatSubscribeTemplateConfigEntity cfg : configs) {
if (cfg == null || StringUtils.isBlank(cfg.getWechatTemplateId())) {
continue;
}
WechatSubscribeAllScenesResp.StaticTemplateInfo info = new WechatSubscribeAllScenesResp.StaticTemplateInfo();
info.setTemplateKey(cfg.getTemplateKey());
info.setWechatTemplateId(cfg.getWechatTemplateId());
info.setTitle(StringUtils.isNotBlank(cfg.getTitleTemplate()) ? cfg.getTitleTemplate() : cfg.getTemplateKey());
info.setDescription(cfg.getDescription());
templates.add(info);
}
sceneWithTemplates.setTemplates(templates);
scenes.add(sceneWithTemplates);
}
resp.setScenes(scenes);
return resp;
}
// ==================== 带缓存的配置查询方法 ====================
/**
@@ -386,6 +484,10 @@ public class WechatSubscribeNotifyConfigRepository {
String eventPattern = String.format(EVENT_TEMPLATE_SCENIC_PREFIX, scenicId);
deleteByPattern(eventPattern);
// 清除所有场景模板缓存
String allScenesKey = String.format(ALL_SCENES_TEMPLATES_KEY, scenicId);
redisTemplate.delete(allScenesKey);
log.info("清除景区所有订阅消息配置缓存: scenicId={}", scenicId);
}
@@ -396,6 +498,7 @@ public class WechatSubscribeNotifyConfigRepository {
public void clearAllConfigsCache() {
deleteByPattern("wechat:subscribe:scene:configs:*");
deleteByPattern("wechat:subscribe:event:configs:*");
deleteByPattern("wechat:subscribe:all-scenes:configs:*");
log.warn("清除所有订阅消息配置缓存");
}