perf(notify): 优化微信订阅消息配置查询性能

- 为微信订阅消息配置接口添加 Redis 缓存支持
- 在 WechatSubscribeNotifyConfigRepository 中实现缓存读写和清除机制
- 修改 Controller 层接口添加 @IgnoreToken 注解支持匿名访问
- 优化查询逻辑,添加 memberId 为空时的提前返回处理
- 在管理服务中添加缓存清除逻辑,确保配置变更时缓存同步更新
- 实现批量缓存清除功能,支持按景区和全局范围清除缓存
This commit is contained in:
2026-01-07 17:28:51 +08:00
parent 3291371dd7
commit e896f58d82
4 changed files with 403 additions and 98 deletions

View File

@@ -1,44 +1,81 @@
package com.ycwl.basic.repository;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.pc.notify.entity.WechatSubscribeEventTemplateEntity;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeSceneTemplateEntity;
import com.ycwl.basic.model.pc.notify.entity.WechatSubscribeTemplateConfigEntity;
import com.ycwl.basic.utils.JacksonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 微信订阅消息配置仓库(scenic 覆盖:scenic_id=具体值 > scenic_id=0)
* 支持 Redis 缓存,减少数据库查询
*
* @Author: System
* @Date: 2025/12/31
*/
@Slf4j
@Component
public class WechatSubscribeNotifyConfigRepository {
private static final long DEFAULT_SCENIC_ID = 0L;
/**
* 场景模板配置缓存KEY
*/
private static final String SCENE_TEMPLATE_CONFIGS_KEY = "wechat:subscribe:scene:configs:%s:%s";
/**
* 事件模板配置缓存KEY
*/
private static final String EVENT_TEMPLATE_CONFIGS_KEY = "wechat:subscribe:event:configs:%s:%s";
/**
* 景区所有场景缓存KEY前缀(用于批量清除)
*/
private static final String SCENE_TEMPLATE_SCENIC_PREFIX = "wechat:subscribe:scene:configs:%s:*";
/**
* 景区所有事件缓存KEY前缀(用于批量清除)
*/
private static final String EVENT_TEMPLATE_SCENIC_PREFIX = "wechat:subscribe:event:configs:%s:*";
/**
* 缓存过期时间(小时)
*/
private static final long CACHE_EXPIRE_HOURS = 1;
private final WechatSubscribeTemplateConfigMapper templateConfigMapper;
private final WechatSubscribeSceneTemplateMapper sceneTemplateMapper;
private final WechatSubscribeEventTemplateMapper eventTemplateMapper;
private final RedisTemplate<String, String> redisTemplate;
public WechatSubscribeNotifyConfigRepository(WechatSubscribeTemplateConfigMapper templateConfigMapper,
WechatSubscribeSceneTemplateMapper sceneTemplateMapper,
WechatSubscribeEventTemplateMapper eventTemplateMapper) {
WechatSubscribeEventTemplateMapper eventTemplateMapper,
RedisTemplate<String, String> redisTemplate) {
this.templateConfigMapper = templateConfigMapper;
this.sceneTemplateMapper = sceneTemplateMapper;
this.eventTemplateMapper = eventTemplateMapper;
this.redisTemplate = redisTemplate;
}
public List<WechatSubscribeSceneTemplateEntity> listEffectiveSceneTemplateMappings(Long scenicId, String sceneKey) {
@@ -170,4 +207,210 @@ public class WechatSubscribeNotifyConfigRepository {
}
return null;
}
// ==================== 带缓存的配置查询方法 ====================
/**
* 获取场景模板配置列表(带缓存)
* 缓存最终的配置列表,避免重复查询和处理覆盖逻辑
*
* @param scenicId 景区ID
* @param sceneKey 场景标识
* @return 启用的模板配置列表
*/
public List<WechatSubscribeTemplateConfigEntity> getSceneTemplateConfigsCached(Long scenicId, String sceneKey) {
Objects.requireNonNull(scenicId, "scenicId is null");
if (StringUtils.isBlank(sceneKey)) {
throw new IllegalArgumentException("sceneKey is blank");
}
String cacheKey = String.format(SCENE_TEMPLATE_CONFIGS_KEY, scenicId, sceneKey);
// 1. 尝试从缓存读取
Boolean hasKey = redisTemplate.hasKey(cacheKey);
if (Boolean.TRUE.equals(hasKey)) {
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
if (cacheValue != null) {
log.debug("从缓存读取场景模板配置: scenicId={}, sceneKey={}", scenicId, sceneKey);
return JacksonUtil.parseObject(cacheValue, new TypeReference<List<WechatSubscribeTemplateConfigEntity>>() {});
}
}
// 2. 从数据库查询
List<WechatSubscribeTemplateConfigEntity> configs = loadSceneTemplateConfigs(scenicId, sceneKey);
// 3. 写入缓存
String json = JacksonUtil.toJSONString(configs);
redisTemplate.opsForValue().set(cacheKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
log.debug("场景模板配置缓存写入: scenicId={}, sceneKey={}, count={}", scenicId, sceneKey, configs.size());
return configs;
}
/**
* 获取事件模板配置列表(带缓存)
*
* @param scenicId 景区ID
* @param eventKey 事件标识
* @return 启用的模板配置列表
*/
public List<WechatSubscribeTemplateConfigEntity> getEventTemplateConfigsCached(Long scenicId, String eventKey) {
Objects.requireNonNull(scenicId, "scenicId is null");
if (StringUtils.isBlank(eventKey)) {
throw new IllegalArgumentException("eventKey is blank");
}
String cacheKey = String.format(EVENT_TEMPLATE_CONFIGS_KEY, scenicId, eventKey);
// 1. 尝试从缓存读取
Boolean hasKey = redisTemplate.hasKey(cacheKey);
if (Boolean.TRUE.equals(hasKey)) {
String cacheValue = redisTemplate.opsForValue().get(cacheKey);
if (cacheValue != null) {
log.debug("从缓存读取事件模板配置: scenicId={}, eventKey={}", scenicId, eventKey);
return JacksonUtil.parseObject(cacheValue, new TypeReference<List<WechatSubscribeTemplateConfigEntity>>() {});
}
}
// 2. 从数据库查询
List<WechatSubscribeTemplateConfigEntity> configs = loadEventTemplateConfigs(scenicId, eventKey);
// 3. 写入缓存
String json = JacksonUtil.toJSONString(configs);
redisTemplate.opsForValue().set(cacheKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
log.debug("事件模板配置缓存写入: scenicId={}, eventKey={}, count={}", scenicId, eventKey, configs.size());
return configs;
}
/**
* 加载场景模板配置(内部方法,处理覆盖逻辑)
*/
private List<WechatSubscribeTemplateConfigEntity> loadSceneTemplateConfigs(Long scenicId, String sceneKey) {
List<WechatSubscribeSceneTemplateEntity> mappings = listEffectiveSceneTemplateMappings(scenicId, sceneKey);
if (CollectionUtils.isEmpty(mappings)) {
return new ArrayList<>();
}
Set<String> templateKeys = mappings.stream()
.map(WechatSubscribeSceneTemplateEntity::getTemplateKey)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<String, WechatSubscribeTemplateConfigEntity> configMap = getEffectiveTemplateConfigs(scenicId, templateKeys);
List<WechatSubscribeTemplateConfigEntity> result = new ArrayList<>();
for (WechatSubscribeSceneTemplateEntity mapping : mappings) {
WechatSubscribeTemplateConfigEntity cfg = configMap.get(mapping.getTemplateKey());
if (cfg == null || !Objects.equals(cfg.getEnabled(), 1)) {
continue;
}
result.add(cfg);
}
return result;
}
/**
* 加载事件模板配置(内部方法,处理覆盖逻辑)
*/
private List<WechatSubscribeTemplateConfigEntity> loadEventTemplateConfigs(Long scenicId, String eventKey) {
List<WechatSubscribeEventTemplateEntity> mappings = listEffectiveEventTemplateMappings(scenicId, eventKey);
if (CollectionUtils.isEmpty(mappings)) {
return new ArrayList<>();
}
Set<String> templateKeys = mappings.stream()
.map(WechatSubscribeEventTemplateEntity::getTemplateKey)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<String, WechatSubscribeTemplateConfigEntity> configMap = getEffectiveTemplateConfigs(scenicId, templateKeys);
List<WechatSubscribeTemplateConfigEntity> result = new ArrayList<>();
for (WechatSubscribeEventTemplateEntity mapping : mappings) {
WechatSubscribeTemplateConfigEntity cfg = configMap.get(mapping.getTemplateKey());
if (cfg == null || !Objects.equals(cfg.getEnabled(), 1)) {
continue;
}
result.add(cfg);
}
return result;
}
// ==================== 缓存清除方法 ====================
/**
* 清除指定场景的模板配置缓存
*
* @param scenicId 景区ID
* @param sceneKey 场景标识
*/
public void clearSceneTemplateConfigsCache(Long scenicId, String sceneKey) {
if (scenicId == null || StringUtils.isBlank(sceneKey)) {
return;
}
String cacheKey = String.format(SCENE_TEMPLATE_CONFIGS_KEY, scenicId, sceneKey);
redisTemplate.delete(cacheKey);
log.debug("清除场景模板配置缓存: scenicId={}, sceneKey={}", scenicId, sceneKey);
}
/**
* 清除指定事件的模板配置缓存
*
* @param scenicId 景区ID
* @param eventKey 事件标识
*/
public void clearEventTemplateConfigsCache(Long scenicId, String eventKey) {
if (scenicId == null || StringUtils.isBlank(eventKey)) {
return;
}
String cacheKey = String.format(EVENT_TEMPLATE_CONFIGS_KEY, scenicId, eventKey);
redisTemplate.delete(cacheKey);
log.debug("清除事件模板配置缓存: scenicId={}, eventKey={}", scenicId, eventKey);
}
/**
* 清除景区下所有订阅消息配置缓存
* 在景区配置变更时调用
*
* @param scenicId 景区ID
*/
public void clearAllConfigsCacheByScenic(Long scenicId) {
if (scenicId == null) {
return;
}
// 清除场景配置缓存
String scenePattern = String.format(SCENE_TEMPLATE_SCENIC_PREFIX, scenicId);
deleteByPattern(scenePattern);
// 清除事件配置缓存
String eventPattern = String.format(EVENT_TEMPLATE_SCENIC_PREFIX, scenicId);
deleteByPattern(eventPattern);
log.info("清除景区所有订阅消息配置缓存: scenicId={}", scenicId);
}
/**
* 清除所有订阅消息配置缓存
* 在全局配置变更时调用(如默认配置修改)
*/
public void clearAllConfigsCache() {
deleteByPattern("wechat:subscribe:scene:configs:*");
deleteByPattern("wechat:subscribe:event:configs:*");
log.warn("清除所有订阅消息配置缓存");
}
/**
* 根据模式删除缓存
*/
private void deleteByPattern(String pattern) {
try {
var keys = redisTemplate.keys(pattern);
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
log.debug("删除缓存: pattern={}, count={}", pattern, keys.size());
}
} catch (Exception e) {
log.error("删除缓存失败: pattern={}", pattern, e);
}
}
}