feat(theme): 新增主题列表Redis缓存机制
为提升查询性能,在KeyboardThemesServiceImpl中集成RedisTemplate,优先从缓存读取主题列表;新增ThemeCacheInitializer用于应用启动时预热缓存。
This commit is contained in:
@@ -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("已清除主题相关缓存");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -20,19 +22,27 @@ import com.yolo.keyborad.service.KeyboardThemesService;
|
|||||||
* @author: ziin
|
* @author: ziin
|
||||||
* @date: 2025/12/9 14:51
|
* @date: 2025/12/9 14:51
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@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,23 +54,44 @@ 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;
|
||||||
if (themeStyle == 9999) {
|
List<KeyboardThemesRespVO> themesList = null;
|
||||||
// 查询所有主题,按排序字段升序
|
|
||||||
themesList = this.lambdaQuery()
|
try {
|
||||||
.eq(KeyboardThemes::getDeleted, false)
|
Object cached = redisTemplate.opsForValue().get(cacheKey);
|
||||||
.eq(KeyboardThemes::getThemeStatus, true)
|
if (cached != null) {
|
||||||
.orderByAsc(KeyboardThemes::getSort)
|
themesList = (List<KeyboardThemesRespVO>) cached;
|
||||||
.list();
|
log.debug("从缓存读取风格{}的主题列表,共{}个", themeStyle, themesList.size());
|
||||||
} else {
|
}
|
||||||
// 查询指定风格的主题
|
} catch (Exception e) {
|
||||||
themesList = this.lambdaQuery()
|
log.warn("读取主题缓存失败,将从数据库查询", e);
|
||||||
.eq(KeyboardThemes::getDeleted, false)
|
}
|
||||||
.eq(KeyboardThemes::getThemeStatus, true)
|
|
||||||
.eq(KeyboardThemes::getThemeStyle, themeStyle)
|
// 缓存未命中,从数据库查询
|
||||||
.list();
|
if (themesList == null) {
|
||||||
|
List<KeyboardThemes> themesFromDb;
|
||||||
|
if (themeStyle == 9999) {
|
||||||
|
// 查询所有主题,按排序字段升序
|
||||||
|
themesFromDb = this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemes::getDeleted, false)
|
||||||
|
.eq(KeyboardThemes::getThemeStatus, true)
|
||||||
|
.orderByAsc(KeyboardThemes::getSort)
|
||||||
|
.list();
|
||||||
|
} else {
|
||||||
|
// 查询指定风格的主题
|
||||||
|
themesFromDb = this.lambdaQuery()
|
||||||
|
.eq(KeyboardThemes::getDeleted, false)
|
||||||
|
.eq(KeyboardThemes::getThemeStatus, true)
|
||||||
|
.eq(KeyboardThemes::getThemeStyle, themeStyle)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
themesList = themesFromDb.stream()
|
||||||
|
.map(theme -> BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
log.debug("从数据库读取风格{}的主题列表,共{}个", themeStyle, themesList.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询用户已购买的主题ID集合
|
// 查询用户已购买的主题ID集合
|
||||||
@@ -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()));
|
||||||
|
|||||||
Reference in New Issue
Block a user