diff --git a/src/main/java/com/yolo/keyborad/listener/ThemeCacheInitializer.java b/src/main/java/com/yolo/keyborad/listener/ThemeCacheInitializer.java new file mode 100644 index 0000000..32562e7 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/listener/ThemeCacheInitializer.java @@ -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 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 stylesList = themeStylesService.lambdaQuery() + .eq(KeyboardThemeStyles::getDeleted, false) + .list(); + + List 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 allThemes = themesService.lambdaQuery() + .eq(KeyboardThemes::getDeleted, false) + .eq(KeyboardThemes::getThemeStatus, true) + .orderByAsc(KeyboardThemes::getSort) + .list(); + + // 转换为VO(不设置购买状态,缓存的是公共数据) + List 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> themesByStyle = allThemesVO.stream() + .collect(Collectors.groupingBy(KeyboardThemesRespVO::getThemeStyle)); + + // 按风格缓存主题 + for (Map.Entry> entry : themesByStyle.entrySet()) { + Long styleId = entry.getKey(); + List 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("已清除主题相关缓存"); + } +} diff --git a/src/main/java/com/yolo/keyborad/service/impl/KeyboardThemesServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/KeyboardThemesServiceImpl.java index f5d2562..c9b8589 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/KeyboardThemesServiceImpl.java +++ b/src/main/java/com/yolo/keyborad/service/impl/KeyboardThemesServiceImpl.java @@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapp import com.yolo.keyborad.model.entity.KeyboardThemePurchase; import com.yolo.keyborad.service.KeyboardThemePurchaseService; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.List; @@ -20,19 +22,27 @@ import com.yolo.keyborad.service.KeyboardThemesService; * @author: ziin * @date: 2025/12/9 14:51 */ - + @Service +@Slf4j public class KeyboardThemesServiceImpl extends ServiceImpl implements KeyboardThemesService { + private static final String THEME_STYLE_KEY = "theme:style:"; + private static final String THEME_ALL_KEY = "theme:style:all"; + @Resource @Lazy // 延迟加载,打破循环依赖 private KeyboardThemePurchaseService purchaseService; + @Resource(name = "objectRedisTemplate") + private RedisTemplate redisTemplate; + /** * 根据风格查询主题列表 *

查询规则:

*
    + *
  • 优先从Redis缓存读取主题列表
  • *
  • 当themeStyle为9999时,查询所有主题并按排序字段升序排列
  • *
  • 其他情况下,查询指定风格的主题
  • *
  • 查询结果均过滤已删除和未启用的主题
  • @@ -44,23 +54,44 @@ public class KeyboardThemesServiceImpl extends ServiceImpl selectThemesByStyle(Long themeStyle, Long userId) { - // 根据风格参数查询主题列表 - List themesList; - if (themeStyle == 9999) { - // 查询所有主题,按排序字段升序 - themesList = this.lambdaQuery() - .eq(KeyboardThemes::getDeleted, false) - .eq(KeyboardThemes::getThemeStatus, true) - .orderByAsc(KeyboardThemes::getSort) - .list(); - } else { - // 查询指定风格的主题 - themesList = this.lambdaQuery() - .eq(KeyboardThemes::getDeleted, false) - .eq(KeyboardThemes::getThemeStatus, true) - .eq(KeyboardThemes::getThemeStyle, themeStyle) - .list(); + // 尝试从Redis缓存读取 + String cacheKey = themeStyle == 9999 ? THEME_ALL_KEY : THEME_STYLE_KEY + themeStyle; + List themesList = null; + + try { + Object cached = redisTemplate.opsForValue().get(cacheKey); + if (cached != null) { + themesList = (List) cached; + log.debug("从缓存读取风格{}的主题列表,共{}个", themeStyle, themesList.size()); + } + } catch (Exception e) { + log.warn("读取主题缓存失败,将从数据库查询", e); + } + + // 缓存未命中,从数据库查询 + if (themesList == null) { + List 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集合 @@ -72,7 +103,7 @@ public class KeyboardThemesServiceImpl extends ServiceImpl { KeyboardThemesRespVO vo = BeanUtil.copyProperties(theme, KeyboardThemesRespVO.class); vo.setIsPurchased(purchasedThemeIds.contains(theme.getId()));