Ver Fonte

perf: 直播首页接口查询缓存

zqqqqqq há 1 mês atrás
pai
commit
62d0deb12c

+ 3 - 0
fs-common/src/main/java/com/fs/common/constant/LiveKeysConstant.java

@@ -9,4 +9,7 @@ public class LiveKeysConstant {
     public static final String TOTAL_VIEWS_KEY = "live:total:views:";  //累计观看人次
     public static final String MAX_ONLINE_USERS_KEY = "live:max:online:"; //最大在线人数
     public static final String ONLINE_USERS_KEY = "live:online:users:";  //当前在线人数
+
+    public static final String LIVE_HOME_PAGE_LIST = "live:homePage:list"; //直播列表数据
+    public static final Integer LIVE_HOME_PAGE_LIST_EXPIRE = 300; //首页缓存过期时间
 }

+ 20 - 0
fs-common/src/main/java/com/fs/common/core/page/PageRequest.java

@@ -0,0 +1,20 @@
+package com.fs.common.core.page;
+
+import lombok.Getter;
+import lombok.Setter;
+
+
+@Setter
+@Getter
+public class PageRequest {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前页
+     */
+    private int currentPage = 1;
+    /**
+     * 每页结果数
+     */
+    private int pageSize = 10;
+}

+ 295 - 0
fs-common/src/main/java/com/fs/common/core/redis/RedisUtil.java

@@ -0,0 +1,295 @@
+package com.fs.common.core.redis;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Redis 工具类
+ * 提供对 Redis 各种数据结构的操作封装
+ */
+@Component
+public class RedisUtil {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 缓存基本对象
+     *
+     * @param key 缓存键
+     * @param value 缓存值
+     */
+    public void set(String key, Object value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 缓存基本对象(带过期时间)
+     *
+     * @param key 缓存键
+     * @param value 缓存值
+     * @param timeout 过期时间
+     * @param timeUnit 时间单位
+     */
+    public void set(String key, Object value, long timeout, TimeUnit timeUnit) {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 获取缓存对象
+     *
+     * @param key 缓存键
+     * @return 缓存值
+     */
+    public Object get(String key) {
+        return redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 删除单个对象
+     *
+     * @param key 缓存键
+     * @return 是否删除成功
+     */
+    public boolean delete(String key) {
+        return Boolean.TRUE.equals(redisTemplate.delete(key));
+    }
+
+    /**
+     * 删除多个对象
+     *
+     * @param keys 缓存键集合
+     * @return 删除的对象数量
+     */
+    public long delete(Collection<String> keys) {
+        return redisTemplate.delete(keys);
+    }
+
+    /**
+     * 判断是否存在指定键
+     *
+     * @param key 缓存键
+     * @return 是否存在
+     */
+    public boolean hasKey(String key) {
+        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
+    }
+
+    /**
+     * 设置过期时间
+     *
+     * @param key 缓存键
+     * @param timeout 过期时间
+     * @param unit 时间单位
+     * @return 是否设置成功
+     */
+    public boolean expire(String key, long timeout, TimeUnit unit) {
+        return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));
+    }
+
+    /**
+     * 获取过期时间
+     *
+     * @param key 缓存键
+     * @return 剩余过期时间(秒)
+     */
+    public long getExpire(String key) {
+        return redisTemplate.getExpire(key);
+    }
+
+    // ======================== List 操作 ========================
+
+    /**
+     * 向列表右侧添加元素
+     *
+     * @param key 列表键
+     * @param value 元素值
+     * @return 列表长度
+     */
+    public long listRightPush(String key, Object value) {
+        return redisTemplate.opsForList().rightPush(key, value);
+    }
+
+    /**
+     * 向列表左侧弹出元素
+     *
+     * @param key 列表键
+     * @return 元素值
+     */
+    public Object listLeftPop(String key) {
+        return redisTemplate.opsForList().leftPop(key);
+    }
+
+    /**
+     * 获取列表指定范围的元素
+     *
+     * @param key 列表键
+     * @param start 起始位置
+     * @param end 结束位置
+     * @return 元素列表
+     */
+    public List<Object> listRange(String key, long start, long end) {
+        return redisTemplate.opsForList().range(key, start, end);
+    }
+
+    // ======================== Set 操作 ========================
+
+    /**
+     * 向集合添加元素
+     *
+     * @param key 集合键
+     * @param values 元素值
+     * @return 添加成功的元素数量
+     */
+    public long setAdd(String key, Object... values) {
+        return redisTemplate.opsForSet().add(key, values);
+    }
+
+    /**
+     * 获取集合所有元素
+     *
+     * @param key 集合键
+     * @return 元素集合
+     */
+    public Set<Object> setMembers(String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 判断元素是否在集合中
+     *
+     * @param key 集合键
+     * @param value 元素值
+     * @return 是否存在
+     */
+    public boolean setIsMember(String key, Object value) {
+        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));
+    }
+
+    // ======================== Hash 操作 ========================
+
+    /**
+     * 向哈希表中添加键值对
+     *
+     * @param key 哈希表键
+     * @param hashKey 哈希键
+     * @param value 哈希值
+     */
+    public void hashPut(String key, String hashKey, Object value) {
+        redisTemplate.opsForHash().put(key, hashKey, value);
+    }
+
+    /**
+     * 获取哈希表中的值
+     *
+     * @param key 哈希表键
+     * @param hashKey 哈希键
+     * @return 哈希值
+     */
+    public Object hashGet(String key, String hashKey) {
+        return redisTemplate.opsForHash().get(key, hashKey);
+    }
+
+    /**
+     * 获取哈希表中所有键值对
+     *
+     * @param key 哈希表键
+     * @return 键值对映射
+     */
+    public Map<Object, Object> hashEntries(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    // ======================== ZSet 操作 ========================
+
+    /**
+     * 向有序集合添加元素
+     *
+     * @param key 有序集合键
+     * @param value 元素值
+     * @param score 分数
+     * @return 是否添加成功
+     */
+    public boolean zSetAdd(String key, Object value, double score) {
+        return Boolean.TRUE.equals(redisTemplate.opsForZSet().add(key, value, score));
+    }
+
+    /**
+     * 升序获取有序集合指定范围的元素
+     *
+     * @param key 有序集合键
+     * @param start 起始位置
+     * @param end 结束位置
+     * @return 元素集合
+     */
+    public Set<Object> zSetRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().range(key, start, end);
+    }
+
+    /**
+     * 降序获取有序集合指定范围的元素
+     *
+     * @param key 有序集合键
+     * @param start 起始位置
+     * @param end 结束位置
+     * @return 元素集合
+     */
+    public Set<Object> zSetReverseRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().reverseRange(key, start, end);
+    }
+
+    /**
+     * 获取有序集合元素数量
+     *
+     * @param key 有序集合键
+     * @return 元素集合数量
+     */
+    public Long zSetSize(String key) {
+        return redisTemplate.opsForZSet().size(key);
+    }
+
+    // ======================== 字符串专用操作 ========================
+
+    /**
+     * 设置字符串值
+     *
+     * @param key 键
+     * @param value 值
+     */
+    public void setString(String key, String value) {
+        stringRedisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 设置字符串值(带过期时间)
+     *
+     * @param key 键
+     * @param value 值
+     * @param timeout 过期时间
+     * @param timeUnit 时间单位
+     */
+    public void setString(String key, String value, long timeout, TimeUnit timeUnit) {
+        stringRedisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 获取字符串值
+     *
+     * @param key 键
+     * @return 字符串值
+     */
+    public String getString(String key) {
+        return stringRedisTemplate.opsForValue().get(key);
+    }
+}

+ 35 - 0
fs-company/src/main/java/com/fs/core/aspectj/LiveControllerAspect.java

@@ -0,0 +1,35 @@
+package com.fs.core.aspectj;
+
+import com.fs.live.service.ILiveService;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class LiveControllerAspect {
+
+    private static final Logger logger = LoggerFactory.getLogger(LiveControllerAspect.class);
+
+    @Autowired
+    private ILiveService liveService;
+
+
+    @Pointcut("execution(* com.fs.company.controller.live.LiveController.*(..))")
+    public void liveControllerMethods() {
+        // 切入点定义
+    }
+
+    /**
+     * 在LiveController的方法执行结束后执行
+     */
+    @AfterReturning(pointcut = "liveControllerMethods()", returning = "result")
+    public void afterLiveControllerMethodExecution(Object result) {
+        logger.info("后台直播间列表变更");
+        liveService.asyncToCache();
+    }
+}

+ 3 - 0
fs-service-system/src/main/java/com/fs/live/domain/Live.java

@@ -10,6 +10,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.time.LocalDateTime;
+import java.util.Date;
 
 /**
  * 直播对象 live
@@ -113,4 +114,6 @@ public class   Live extends BaseEntity {
 
     /** 直播审核状态,销售端修改后需要总后台审核 0未审核 1已审核*/
     private Integer isAudit;
+    /** 创建时间 */
+    private Date createTime;
 }

+ 5 - 0
fs-service-system/src/main/java/com/fs/live/service/ILiveService.java

@@ -161,4 +161,9 @@ public interface ILiveService
     void updateBatchById(List<Live> list);
 
     void updateStatusAndTimeBatchById(List<Live> list);
+
+    /**
+     * 将直播间数据加入缓存
+     */
+    List<Live> asyncToCache();
 }

+ 23 - 0
fs-service-system/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -1,10 +1,13 @@
 package com.fs.live.service.impl;
 
+import cn.hutool.core.thread.ThreadUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 
+import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.core.redis.RedisUtil;
 import com.fs.common.exception.BaseException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
@@ -34,6 +37,7 @@ import com.fs.common.utils.sign.Md5Utils;
 import java.io.IOException;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -76,6 +80,8 @@ public class LiveServiceImpl implements ILiveService
     @Autowired
     private LiveMapper baseMapper;
 
+    @Autowired
+    private RedisUtil redisUtil;
     /**
      * 查询直播
      *
@@ -126,6 +132,23 @@ public class LiveServiceImpl implements ILiveService
         baseMapper.updateStatusAndTimeBatchById(list);
     }
 
+    @Override
+    public List<Live> asyncToCache() {
+        // 同步直播间数据到缓存
+        List<Live> list = liveList();
+        log.info("开始同步直播间数据到缓存,共{}条数据", list.size());
+        ThreadUtil.execute(()->{
+            // 清空原有的 ZSet 数据
+            redisUtil.delete(LiveKeysConstant.LIVE_HOME_PAGE_LIST);
+            for (Live live : list) {
+                redisUtil.zSetAdd(LiveKeysConstant.LIVE_HOME_PAGE_LIST, JSON.toJSONString(live), live.getCreateTime().getTime());
+                redisUtil.expire(LiveKeysConstant.LIVE_HOME_PAGE_LIST, LiveKeysConstant.LIVE_HOME_PAGE_LIST_EXPIRE, TimeUnit.SECONDS);
+            }
+            log.info("直播间数据同步到缓存完成");
+        });
+        return list;
+    }
+
     /**
      * 查询企业直播
      * @param liveId            直播ID

+ 9 - 4
fs-user-app/src/main/java/com/fs/app/controller/LiveController.java

@@ -2,6 +2,7 @@ package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.app.annotation.Login;
+import com.fs.app.facade.LiveFacadeService;
 import com.fs.app.vo.LiveInfoVo;
 import com.fs.app.vo.LiveVo;
 import com.fs.app.websocket.bean.SendMsgVo;
@@ -30,6 +31,7 @@ import lombok.AllArgsConstructor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import com.fs.common.core.page.PageRequest;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -62,6 +64,8 @@ public class LiveController extends AppBaseController {
 
 	@Autowired
 	private ILiveVideoService videoService;
+	@Autowired
+	private LiveFacadeService liveFacadeService;
 
 
 
@@ -102,19 +106,20 @@ public class LiveController extends AppBaseController {
 		return R.ok().put("data", liveVo).put("storeId", storeId);
 	}
 
-	@Login
+	// @Login
 	@ApiOperation("直播间列表")
 	@GetMapping("/liveList")
 	@ApiResponse(code = 200, message = "", response = LiveInfoVo.class)
-	public R liveList(Live live) {
-		PageHelper.startPage(Integer.parseInt(ServletUtils.getParameter("pageNum")) ,Integer.parseInt(ServletUtils.getParameter("pageSize")) );
+	public R liveList(PageRequest pageRequest) {
+/*		PageHelper.startPage(Integer.parseInt(ServletUtils.getParameter("pageNum")) ,Integer.parseInt(ServletUtils.getParameter("pageSize")) );
 		try {
 			List<Live> list = liveService.liveList();
 			PageInfo<Live> result = new PageInfo<>(list);
 			return R.ok().put("data", result);
 		} catch (Exception e) {
 			return R.error("操作异常");
-		}
+		}*/
+		return liveFacadeService.liveList(pageRequest);
 	}
 
 

+ 8 - 0
fs-user-app/src/main/java/com/fs/app/facade/LiveFacadeService.java

@@ -0,0 +1,8 @@
+package com.fs.app.facade;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.PageRequest;
+
+public interface LiveFacadeService {
+    R liveList(PageRequest pageRequest);
+}

+ 63 - 0
fs-user-app/src/main/java/com/fs/app/facade/impl/LiveFacadeServiceImpl.java

@@ -0,0 +1,63 @@
+package com.fs.app.facade.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.alibaba.fastjson.JSON;
+import com.fs.app.facade.LiveFacadeService;
+import com.fs.common.constant.LiveKeysConstant;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.PageRequest;
+import com.fs.common.core.redis.RedisUtil;
+import com.fs.live.domain.Live;
+import com.fs.live.service.ILiveService;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Service
+public class LiveFacadeServiceImpl implements LiveFacadeService {
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Autowired
+    private ILiveService liveService;
+
+    @Override
+    public R liveList(PageRequest pageRequest) {
+        int start = (pageRequest.getCurrentPage() - 1) * pageRequest.getPageSize();
+        int end = pageRequest.getCurrentPage() * pageRequest.getPageSize() - 1;
+
+        List<Live> lives = null;
+        // 查询缓存中的直播列表数据
+        Set<Object> liveList = redisUtil.zSetReverseRange(
+                LiveKeysConstant.LIVE_HOME_PAGE_LIST,
+                start,
+                end
+        );
+        if (CollUtil.isNotEmpty(liveList)) {
+            lives = liveList.stream()
+                    .map(String.class::cast)
+                    .map(json -> JSON.parseObject(json, Live.class))
+                    .collect(Collectors.toList());
+            PageInfo<Live> result = new PageInfo<>(lives);
+            result.setTotal(redisUtil.zSetSize(LiveKeysConstant.LIVE_HOME_PAGE_LIST));
+            return R.ok().put("data", result);
+        } else {
+            lives = liveService.asyncToCache();
+
+            // 对结果进行分页处理
+            List<Live> pageLives = lives.stream()
+                    .skip(start)
+                    .limit(pageRequest.getPageSize())
+                    .collect(Collectors.toList());
+
+            PageInfo<Live> result = new PageInfo<>(pageLives);
+            result.setTotal(lives.size());
+            return R.ok().put("data", result);
+        }
+    }
+}