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,21 +1,14 @@
package com.ycwl.basic.service.notify;
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.repository.WechatSubscribeNotifyConfigRepository;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 微信订阅消息配置查询服务(仅负责配置解析,不包含授权/发送逻辑)
* 微信订阅消息配置查询服务(仅负责"配置解析",不包含授权/发送逻辑)
* 底层通过 Repository 实现 Redis 缓存
*
* @Author: System
* @Date: 2025/12/31
@@ -29,53 +22,53 @@ public class WechatSubscribeNotifyConfigService {
this.configRepository = configRepository;
}
/**
* 获取场景下的模板配置列表(带缓存)
*
* @param scenicId 景区ID
* @param sceneKey 场景标识
* @return 启用的模板配置列表
*/
public List<WechatSubscribeTemplateConfigEntity> listSceneTemplateConfigs(Long scenicId, String sceneKey) {
List<WechatSubscribeSceneTemplateEntity> mappings =
configRepository.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 =
configRepository.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;
return configRepository.getSceneTemplateConfigsCached(scenicId, sceneKey);
}
/**
* 获取事件下的模板配置列表(带缓存)
*
* @param scenicId 景区ID
* @param eventKey 事件标识
* @return 启用的模板配置列表
*/
public List<WechatSubscribeTemplateConfigEntity> listEventTemplateConfigs(Long scenicId, String eventKey) {
List<WechatSubscribeEventTemplateEntity> mappings =
configRepository.listEffectiveEventTemplateMappings(scenicId, eventKey);
if (CollectionUtils.isEmpty(mappings)) {
return new ArrayList<>();
}
return configRepository.getEventTemplateConfigsCached(scenicId, eventKey);
}
Set<String> templateKeys = mappings.stream()
.map(WechatSubscribeEventTemplateEntity::getTemplateKey)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<String, WechatSubscribeTemplateConfigEntity> configMap =
configRepository.getEffectiveTemplateConfigs(scenicId, templateKeys);
/**
* 清除指定场景的配置缓存
*/
public void clearSceneConfigsCache(Long scenicId, String sceneKey) {
configRepository.clearSceneTemplateConfigsCache(scenicId, sceneKey);
}
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;
/**
* 清除指定事件的配置缓存
*/
public void clearEventConfigsCache(Long scenicId, String eventKey) {
configRepository.clearEventTemplateConfigsCache(scenicId, eventKey);
}
/**
* 清除景区下所有订阅消息配置缓存
*/
public void clearAllConfigsCacheByScenic(Long scenicId) {
configRepository.clearAllConfigsCacheByScenic(scenicId);
}
/**
* 清除所有订阅消息配置缓存(全局配置变更时调用)
*/
public void clearAllConfigsCache() {
configRepository.clearAllConfigsCache();
}
}

View File

@@ -19,6 +19,7 @@ import com.ycwl.basic.model.pc.notify.req.WechatSubscribeSceneTemplateSaveReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeSendLogPageReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeTemplateConfigPageReq;
import com.ycwl.basic.model.pc.notify.req.WechatSubscribeTemplateConfigSaveReq;
import com.ycwl.basic.service.notify.WechatSubscribeNotifyConfigService;
import com.ycwl.basic.service.pc.WechatSubscribeNotifyAdminService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JacksonUtil;
@@ -47,15 +48,18 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
private final WechatSubscribeSceneTemplateMapper sceneTemplateMapper;
private final WechatSubscribeEventTemplateMapper eventTemplateMapper;
private final WechatSubscribeSendLogMapper sendLogMapper;
private final WechatSubscribeNotifyConfigService configService;
public WechatSubscribeNotifyAdminServiceImpl(WechatSubscribeTemplateConfigMapper templateConfigMapper,
WechatSubscribeSceneTemplateMapper sceneTemplateMapper,
WechatSubscribeEventTemplateMapper eventTemplateMapper,
WechatSubscribeSendLogMapper sendLogMapper) {
WechatSubscribeSendLogMapper sendLogMapper,
WechatSubscribeNotifyConfigService configService) {
this.templateConfigMapper = templateConfigMapper;
this.sceneTemplateMapper = sceneTemplateMapper;
this.eventTemplateMapper = eventTemplateMapper;
this.sendLogMapper = sendLogMapper;
this.configService = configService;
}
@Override
@@ -123,29 +127,33 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
entity.setDescription(StringUtils.trimToNull(req.getDescription()));
entity.setUpdateTime(new Date());
boolean success;
if (req.getId() != null) {
WechatSubscribeTemplateConfigEntity existing = templateConfigMapper.selectById(req.getId());
if (existing == null) {
return ApiResponse.fail("记录不存在");
}
entity.setId(req.getId());
int updated = templateConfigMapper.updateById(entity);
return ApiResponse.success(updated > 0);
success = templateConfigMapper.updateById(entity) > 0;
} else {
// upsert by (template_key, scenic_id)
WechatSubscribeTemplateConfigEntity existing = templateConfigMapper.selectOne(new QueryWrapper<WechatSubscribeTemplateConfigEntity>()
.eq("template_key", entity.getTemplateKey())
.eq("scenic_id", entity.getScenicId()));
if (existing != null) {
entity.setId(existing.getId());
success = templateConfigMapper.updateById(entity) > 0;
} else {
entity.setCreateTime(new Date());
success = templateConfigMapper.insert(entity) > 0;
}
}
// upsert by (template_key, scenic_id)
WechatSubscribeTemplateConfigEntity existing = templateConfigMapper.selectOne(new QueryWrapper<WechatSubscribeTemplateConfigEntity>()
.eq("template_key", entity.getTemplateKey())
.eq("scenic_id", entity.getScenicId()));
if (existing != null) {
entity.setId(existing.getId());
int updated = templateConfigMapper.updateById(entity);
return ApiResponse.success(updated > 0);
// 清除缓存:模板配置变更影响场景和事件查询
if (success) {
clearConfigCacheByScenic(req.getScenicId());
}
entity.setCreateTime(new Date());
int inserted = templateConfigMapper.insert(entity);
return ApiResponse.success(inserted > 0);
return ApiResponse.success(success);
} catch (DuplicateKeyException e) {
return ApiResponse.fail("保存失败:唯一键冲突(templateKey+scenicId已存在)");
} catch (Exception e) {
@@ -160,7 +168,17 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
if (id == null) {
return ApiResponse.fail("id不能为空");
}
return ApiResponse.success(templateConfigMapper.deleteById(id) > 0);
// 先查询获取 scenicId 用于清除缓存
WechatSubscribeTemplateConfigEntity existing = templateConfigMapper.selectById(id);
if (existing == null) {
return ApiResponse.success(false);
}
boolean success = templateConfigMapper.deleteById(id) > 0;
// 清除缓存
if (success) {
clearConfigCacheByScenic(existing.getScenicId());
}
return ApiResponse.success(success);
} catch (Exception e) {
log.error("订阅消息|模板配置删除失败 id={}", id, e);
return ApiResponse.fail("模板配置删除失败: " + e.getMessage());
@@ -228,29 +246,33 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
entity.setSortOrder(Objects.requireNonNullElse(req.getSortOrder(), 0));
entity.setUpdateTime(new Date());
boolean success;
if (req.getId() != null) {
WechatSubscribeSceneTemplateEntity existing = sceneTemplateMapper.selectById(req.getId());
if (existing == null) {
return ApiResponse.fail("记录不存在");
}
entity.setId(req.getId());
int updated = sceneTemplateMapper.updateById(entity);
return ApiResponse.success(updated > 0);
success = sceneTemplateMapper.updateById(entity) > 0;
} else {
WechatSubscribeSceneTemplateEntity existing = sceneTemplateMapper.selectOne(new QueryWrapper<WechatSubscribeSceneTemplateEntity>()
.eq("scene_key", entity.getSceneKey())
.eq("template_key", entity.getTemplateKey())
.eq("scenic_id", entity.getScenicId()));
if (existing != null) {
entity.setId(existing.getId());
success = sceneTemplateMapper.updateById(entity) > 0;
} else {
entity.setCreateTime(new Date());
success = sceneTemplateMapper.insert(entity) > 0;
}
}
WechatSubscribeSceneTemplateEntity existing = sceneTemplateMapper.selectOne(new QueryWrapper<WechatSubscribeSceneTemplateEntity>()
.eq("scene_key", entity.getSceneKey())
.eq("template_key", entity.getTemplateKey())
.eq("scenic_id", entity.getScenicId()));
if (existing != null) {
entity.setId(existing.getId());
int updated = sceneTemplateMapper.updateById(entity);
return ApiResponse.success(updated > 0);
// 清除场景配置缓存
if (success) {
configService.clearSceneConfigsCache(req.getScenicId(), req.getSceneKey().trim());
}
entity.setCreateTime(new Date());
int inserted = sceneTemplateMapper.insert(entity);
return ApiResponse.success(inserted > 0);
return ApiResponse.success(success);
} catch (DuplicateKeyException e) {
return ApiResponse.fail("保存失败:唯一键冲突(sceneKey+templateKey+scenicId已存在)");
} catch (Exception e) {
@@ -265,7 +287,17 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
if (id == null) {
return ApiResponse.fail("id不能为空");
}
return ApiResponse.success(sceneTemplateMapper.deleteById(id) > 0);
// 先查询获取 scenicId 和 sceneKey 用于清除缓存
WechatSubscribeSceneTemplateEntity existing = sceneTemplateMapper.selectById(id);
if (existing == null) {
return ApiResponse.success(false);
}
boolean success = sceneTemplateMapper.deleteById(id) > 0;
// 清除场景配置缓存
if (success) {
configService.clearSceneConfigsCache(existing.getScenicId(), existing.getSceneKey());
}
return ApiResponse.success(success);
} catch (Exception e) {
log.error("订阅消息|场景映射删除失败 id={}", id, e);
return ApiResponse.fail("场景映射删除失败: " + e.getMessage());
@@ -335,29 +367,33 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
entity.setDedupSeconds(Objects.requireNonNullElse(req.getDedupSeconds(), 0));
entity.setUpdateTime(new Date());
boolean success;
if (req.getId() != null) {
WechatSubscribeEventTemplateEntity existing = eventTemplateMapper.selectById(req.getId());
if (existing == null) {
return ApiResponse.fail("记录不存在");
}
entity.setId(req.getId());
int updated = eventTemplateMapper.updateById(entity);
return ApiResponse.success(updated > 0);
success = eventTemplateMapper.updateById(entity) > 0;
} else {
WechatSubscribeEventTemplateEntity existing = eventTemplateMapper.selectOne(new QueryWrapper<WechatSubscribeEventTemplateEntity>()
.eq("event_key", entity.getEventKey())
.eq("template_key", entity.getTemplateKey())
.eq("scenic_id", entity.getScenicId()));
if (existing != null) {
entity.setId(existing.getId());
success = eventTemplateMapper.updateById(entity) > 0;
} else {
entity.setCreateTime(new Date());
success = eventTemplateMapper.insert(entity) > 0;
}
}
WechatSubscribeEventTemplateEntity existing = eventTemplateMapper.selectOne(new QueryWrapper<WechatSubscribeEventTemplateEntity>()
.eq("event_key", entity.getEventKey())
.eq("template_key", entity.getTemplateKey())
.eq("scenic_id", entity.getScenicId()));
if (existing != null) {
entity.setId(existing.getId());
int updated = eventTemplateMapper.updateById(entity);
return ApiResponse.success(updated > 0);
// 清除事件配置缓存
if (success) {
configService.clearEventConfigsCache(req.getScenicId(), req.getEventKey().trim());
}
entity.setCreateTime(new Date());
int inserted = eventTemplateMapper.insert(entity);
return ApiResponse.success(inserted > 0);
return ApiResponse.success(success);
} catch (DuplicateKeyException e) {
return ApiResponse.fail("保存失败:唯一键冲突(eventKey+templateKey+scenicId已存在)");
} catch (Exception e) {
@@ -372,7 +408,17 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
if (id == null) {
return ApiResponse.fail("id不能为空");
}
return ApiResponse.success(eventTemplateMapper.deleteById(id) > 0);
// 先查询获取 scenicId 和 eventKey 用于清除缓存
WechatSubscribeEventTemplateEntity existing = eventTemplateMapper.selectById(id);
if (existing == null) {
return ApiResponse.success(false);
}
boolean success = eventTemplateMapper.deleteById(id) > 0;
// 清除事件配置缓存
if (success) {
configService.clearEventConfigsCache(existing.getScenicId(), existing.getEventKey());
}
return ApiResponse.success(success);
} catch (Exception e) {
log.error("订阅消息|事件映射删除失败 id={}", id, e);
return ApiResponse.fail("事件映射删除失败: " + e.getMessage());
@@ -529,5 +575,23 @@ public class WechatSubscribeNotifyAdminServiceImpl implements WechatSubscribeNot
}
return null;
}
/**
* 清除景区相关的配置缓存
* 模板配置变更会影响场景和事件的查询结果,需要清除该景区下所有缓存
*
* @param scenicId 景区ID
*/
private void clearConfigCacheByScenic(Long scenicId) {
if (scenicId == null) {
return;
}
// scenicId=0 表示默认配置,变更时需要清除所有缓存
if (scenicId == 0L) {
configService.clearAllConfigsCache();
} else {
configService.clearAllConfigsCacheByScenic(scenicId);
}
}
}