feat(theme): 新增主题列表Redis缓存机制

为提升查询性能,在KeyboardThemesServiceImpl中集成RedisTemplate,优先从缓存读取主题列表;新增ThemeCacheInitializer用于应用启动时预热缓存。
This commit is contained in:
2025-12-29 15:13:50 +08:00
parent be921e144f
commit c38f62c3c1
2 changed files with 199 additions and 18 deletions

View File

@@ -0,0 +1,150 @@
package com.yolo.keyborad.listener;
import cn.hutool.core.bean.BeanUtil;
import com.yolo.keyborad.model.entity.KeyboardThemeStyles;
import com.yolo.keyborad.model.entity.KeyboardThemes;
import com.yolo.keyborad.model.vo.themes.KeyboardThemeStylesRespVO;
import com.yolo.keyborad.model.vo.themes.KeyboardThemesRespVO;
import com.yolo.keyborad.service.KeyboardThemeStylesService;
import com.yolo.keyborad.service.KeyboardThemesService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 主题缓存初始化器
* 在应用启动时按风格将主题缓存到Redis
*/
@Component
@Slf4j
public class ThemeCacheInitializer implements ApplicationRunner {
/**
* 主题按风格分组的缓存key前缀
*/
private static final String THEME_STYLE_KEY = "theme:style:";
/**
* 所有风格列表的缓存key
*/
private static final String THEME_STYLES_KEY = "theme:styles";
/**
* 所有主题列表的缓存key风格ID=9999表示全部
*/
private static final String THEME_ALL_KEY = "theme:style:all";
/**
* 缓存过期时间(天)
*/
private static final long CACHE_EXPIRE_DAYS = 1;
@Resource
private KeyboardThemesService themesService;
@Resource
private KeyboardThemeStylesService themeStylesService;
@Resource(name = "objectRedisTemplate")
private RedisTemplate<String, Object> redisTemplate;
@Override
public void run(ApplicationArguments args) {
try {
log.info("开始缓存主题数据到Redis...");
// 1. 缓存所有风格列表
cacheAllStyles();
// 2. 按风格分组缓存主题
cacheThemesByStyle();
log.info("主题数据缓存完成");
} catch (Exception e) {
log.error("缓存主题数据失败", e);
}
}
/**
* 缓存所有风格列表
*/
private void cacheAllStyles() {
List<KeyboardThemeStyles> stylesList = themeStylesService.lambdaQuery()
.eq(KeyboardThemeStyles::getDeleted, false)
.list();
List<KeyboardThemeStylesRespVO> stylesVOList = BeanUtil.copyToList(stylesList, KeyboardThemeStylesRespVO.class);
redisTemplate.opsForValue().set(THEME_STYLES_KEY, stylesVOList, CACHE_EXPIRE_DAYS, TimeUnit.DAYS);
log.info("已缓存 {} 种主题风格", stylesVOList.size());
}
/**
* 按风格分组缓存主题
*/
private void cacheThemesByStyle() {
// 查询所有有效主题
List<KeyboardThemes> allThemes = themesService.lambdaQuery()
.eq(KeyboardThemes::getDeleted, false)
.eq(KeyboardThemes::getThemeStatus, true)
.orderByAsc(KeyboardThemes::getSort)
.list();
// 转换为VO不设置购买状态缓存的是公共数据
List<KeyboardThemesRespVO> allThemesVO = allThemes.stream()
.map(theme -> BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class))
.collect(Collectors.toList());
// 缓存所有主题风格ID=all
redisTemplate.opsForValue().set(THEME_ALL_KEY, allThemesVO, CACHE_EXPIRE_DAYS, TimeUnit.DAYS);
log.info("已缓存所有主题,共 {} 个", allThemesVO.size());
// 按风格分组
Map<Long, List<KeyboardThemesRespVO>> themesByStyle = allThemesVO.stream()
.collect(Collectors.groupingBy(KeyboardThemesRespVO::getThemeStyle));
// 按风格缓存主题
for (Map.Entry<Long, List<KeyboardThemesRespVO>> entry : themesByStyle.entrySet()) {
Long styleId = entry.getKey();
List<KeyboardThemesRespVO> themes = entry.getValue();
String key = THEME_STYLE_KEY + styleId;
redisTemplate.opsForValue().set(key, themes, CACHE_EXPIRE_DAYS, TimeUnit.DAYS);
log.info("已缓存风格ID={} 的主题,共 {} 个", styleId, themes.size());
}
}
/**
* 手动刷新缓存(可通过接口调用)
*/
public void refreshCache() {
log.info("手动刷新主题缓存...");
clearCache();
cacheAllStyles();
cacheThemesByStyle();
log.info("主题缓存刷新完成");
}
/**
* 清除主题相关缓存
*/
public void clearCache() {
// 删除风格列表缓存
redisTemplate.delete(THEME_STYLES_KEY);
redisTemplate.delete(THEME_ALL_KEY);
// 删除所有风格下的主题缓存
var keys = redisTemplate.keys(THEME_STYLE_KEY + "*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
log.info("已清除主题相关缓存");
}
}

View File

@@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapp
import com.yolo.keyborad.model.entity.KeyboardThemePurchase; import com.yolo.keyborad.model.entity.KeyboardThemePurchase;
import com.yolo.keyborad.service.KeyboardThemePurchaseService; import com.yolo.keyborad.service.KeyboardThemePurchaseService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
@@ -22,17 +24,25 @@ import com.yolo.keyborad.service.KeyboardThemesService;
*/ */
@Service @Service
@Slf4j
public class KeyboardThemesServiceImpl extends ServiceImpl<KeyboardThemesMapper, KeyboardThemes> implements KeyboardThemesService { public class KeyboardThemesServiceImpl extends ServiceImpl<KeyboardThemesMapper, KeyboardThemes> implements KeyboardThemesService {
private static final String THEME_STYLE_KEY = "theme:style:";
private static final String THEME_ALL_KEY = "theme:style:all";
@Resource @Resource
@Lazy // 延迟加载,打破循环依赖 @Lazy // 延迟加载,打破循环依赖
private KeyboardThemePurchaseService purchaseService; private KeyboardThemePurchaseService purchaseService;
@Resource(name = "objectRedisTemplate")
private RedisTemplate<String, Object> redisTemplate;
/** /**
* 根据风格查询主题列表 * 根据风格查询主题列表
* <p>查询规则:</p> * <p>查询规则:</p>
* <ul> * <ul>
* <li>优先从Redis缓存读取主题列表</li>
* <li>当themeStyle为9999时查询所有主题并按排序字段升序排列</li> * <li>当themeStyle为9999时查询所有主题并按排序字段升序排列</li>
* <li>其他情况下,查询指定风格的主题</li> * <li>其他情况下,查询指定风格的主题</li>
* <li>查询结果均过滤已删除和未启用的主题</li> * <li>查询结果均过滤已删除和未启用的主题</li>
@@ -44,24 +54,45 @@ public class KeyboardThemesServiceImpl extends ServiceImpl<KeyboardThemesMapper,
* @return 主题列表,包含主题详情和购买状态 * @return 主题列表,包含主题详情和购买状态
*/ */
@Override @Override
@SuppressWarnings("unchecked")
public List<KeyboardThemesRespVO> selectThemesByStyle(Long themeStyle, Long userId) { public List<KeyboardThemesRespVO> selectThemesByStyle(Long themeStyle, Long userId) {
// 根据风格参数查询主题列表 // 尝试从Redis缓存读取
List<KeyboardThemes> themesList; String cacheKey = themeStyle == 9999 ? THEME_ALL_KEY : THEME_STYLE_KEY + themeStyle;
List<KeyboardThemesRespVO> themesList = null;
try {
Object cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
themesList = (List<KeyboardThemesRespVO>) cached;
log.debug("从缓存读取风格{}的主题列表,共{}个", themeStyle, themesList.size());
}
} catch (Exception e) {
log.warn("读取主题缓存失败,将从数据库查询", e);
}
// 缓存未命中,从数据库查询
if (themesList == null) {
List<KeyboardThemes> themesFromDb;
if (themeStyle == 9999) { if (themeStyle == 9999) {
// 查询所有主题,按排序字段升序 // 查询所有主题,按排序字段升序
themesList = this.lambdaQuery() themesFromDb = this.lambdaQuery()
.eq(KeyboardThemes::getDeleted, false) .eq(KeyboardThemes::getDeleted, false)
.eq(KeyboardThemes::getThemeStatus, true) .eq(KeyboardThemes::getThemeStatus, true)
.orderByAsc(KeyboardThemes::getSort) .orderByAsc(KeyboardThemes::getSort)
.list(); .list();
} else { } else {
// 查询指定风格的主题 // 查询指定风格的主题
themesList = this.lambdaQuery() themesFromDb = this.lambdaQuery()
.eq(KeyboardThemes::getDeleted, false) .eq(KeyboardThemes::getDeleted, false)
.eq(KeyboardThemes::getThemeStatus, true) .eq(KeyboardThemes::getThemeStatus, true)
.eq(KeyboardThemes::getThemeStyle, themeStyle) .eq(KeyboardThemes::getThemeStyle, themeStyle)
.list(); .list();
} }
themesList = themesFromDb.stream()
.map(theme -> BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class))
.collect(Collectors.toList());
log.debug("从数据库读取风格{}的主题列表,共{}个", themeStyle, themesList.size());
}
// 查询用户已购买的主题ID集合 // 查询用户已购买的主题ID集合
Set<Long> purchasedThemeIds = purchaseService.lambdaQuery() Set<Long> purchasedThemeIds = purchaseService.lambdaQuery()
@@ -72,7 +103,7 @@ public class KeyboardThemesServiceImpl extends ServiceImpl<KeyboardThemesMapper,
.map(KeyboardThemePurchase::getThemeId) .map(KeyboardThemePurchase::getThemeId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 转换为VO并设置购买状态 // 设置购买状态并返回
return themesList.stream().map(theme -> { return themesList.stream().map(theme -> {
KeyboardThemesRespVO vo = BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class); KeyboardThemesRespVO vo = BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class);
vo.setIsPurchased(purchasedThemeIds.contains(theme.getId())); vo.setIsPurchased(purchasedThemeIds.contains(theme.getId()));