|
|
@@ -1,16 +1,23 @@
|
|
|
package com.fs.live.service.impl;
|
|
|
|
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.fs.common.core.redis.RedisUtil;
|
|
|
import com.fs.common.utils.DateUtils;
|
|
|
import com.fs.live.domain.LiveMsg;
|
|
|
import com.fs.live.mapper.LiveMsgMapper;
|
|
|
import com.fs.live.service.ILiveMsgService;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.util.Collections;
|
|
|
import java.util.Date;
|
|
|
import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
/**
|
|
|
* 直播讨论Service业务层处理
|
|
|
@@ -21,10 +28,29 @@ import java.util.List;
|
|
|
@Service
|
|
|
public class LiveMsgServiceImpl implements ILiveMsgService
|
|
|
{
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(LiveMsgServiceImpl.class);
|
|
|
+
|
|
|
@Autowired
|
|
|
private LiveMsgMapper liveMsgMapper;
|
|
|
@Autowired
|
|
|
private LiveDataServiceImpl liveDataService;
|
|
|
+ @Autowired
|
|
|
+ private RedisUtil redisUtil;
|
|
|
+ @Autowired
|
|
|
+ private RedisTemplate<String, Object> redisTemplate;
|
|
|
+
|
|
|
+ /** 直播间消息缓存key前缀 */
|
|
|
+ private static final String LIVE_MSG_CACHE_KEY_PREFIX = "live:msg:list:";
|
|
|
+ /** 直播间消息更新时间key前缀 */
|
|
|
+ private static final String LIVE_MSG_UPDATE_TIME_KEY_PREFIX = "live:msg:update:time:";
|
|
|
+ /** 锁key前缀 */
|
|
|
+ private static final String LIVE_MSG_LOCK_KEY_PREFIX = "live:msg:lock:";
|
|
|
+ /** 消息更新时间阈值(秒) */
|
|
|
+ private static final long UPDATE_TIME_THRESHOLD = 5;
|
|
|
+ /** 消息缓存过期时间(小时) */
|
|
|
+ private static final long MSG_CACHE_EXPIRE_HOURS = 24;
|
|
|
+ /** 更新时间缓存过期时间(秒) */
|
|
|
+ private static final long UPDATE_TIME_CACHE_EXPIRE_SECONDS = 5;
|
|
|
|
|
|
/**
|
|
|
* 查询直播讨论
|
|
|
@@ -47,7 +73,125 @@ public class LiveMsgServiceImpl implements ILiveMsgService
|
|
|
@Override
|
|
|
public List<LiveMsg> selectLiveMsgList(LiveMsg liveMsg)
|
|
|
{
|
|
|
- return liveMsgMapper.selectLiveMsgList(liveMsg);
|
|
|
+ // 如果liveId为空,直接查询数据库
|
|
|
+ if (liveMsg == null || liveMsg.getLiveId() == null) {
|
|
|
+ return liveMsgMapper.selectLiveMsgList(liveMsg);
|
|
|
+ }
|
|
|
+
|
|
|
+ Long liveId = liveMsg.getLiveId();
|
|
|
+ String cacheKey = LIVE_MSG_CACHE_KEY_PREFIX + liveId;
|
|
|
+ String updateTimeKey = LIVE_MSG_UPDATE_TIME_KEY_PREFIX + liveId;
|
|
|
+ String lockKey = LIVE_MSG_LOCK_KEY_PREFIX + liveId;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 先查redis消息时间
|
|
|
+ String updateTimeStr = redisUtil.getString(updateTimeKey);
|
|
|
+ long currentTime = System.currentTimeMillis();
|
|
|
+ boolean needUpdate = false;
|
|
|
+
|
|
|
+ if (updateTimeStr == null || updateTimeStr.isEmpty()) {
|
|
|
+ // 如果更新时间不存在,需要更新
|
|
|
+ needUpdate = true;
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ long lastUpdateTime = Long.parseLong(updateTimeStr);
|
|
|
+ long timeDiff = (currentTime - lastUpdateTime) / 1000; // 转换为秒
|
|
|
+ if (timeDiff >= UPDATE_TIME_THRESHOLD) {
|
|
|
+ // 如果时间超过了5s,需要更新
|
|
|
+ needUpdate = true;
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("解析更新时间失败,liveId: {}, updateTimeStr: {}", liveId, updateTimeStr);
|
|
|
+ needUpdate = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 如果时间超过了5s,那么就拿锁进行更新
|
|
|
+ if (needUpdate) {
|
|
|
+ // 使用Redis SETNX实现分布式锁
|
|
|
+ boolean lockAcquired = tryLock(lockKey, 10);
|
|
|
+ if (lockAcquired) {
|
|
|
+ try {
|
|
|
+ // 3. 更新缓存中的直播间消息,设置过期时间24小时
|
|
|
+ List<LiveMsg> msgList = liveMsgMapper.selectLiveMsgList(liveMsg);
|
|
|
+ if (msgList != null) {
|
|
|
+ String msgListJson = JSON.toJSONString(msgList);
|
|
|
+ redisUtil.setString(cacheKey, msgListJson, MSG_CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 设置redis消息时间为5s后
|
|
|
+ long nextUpdateTime = currentTime + (UPDATE_TIME_CACHE_EXPIRE_SECONDS * 1000);
|
|
|
+ redisUtil.setString(updateTimeKey, String.valueOf(nextUpdateTime),
|
|
|
+ UPDATE_TIME_CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ return msgList != null ? msgList : Collections.emptyList();
|
|
|
+ } finally {
|
|
|
+ releaseLock(lockKey);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 5. 如果没拿到锁,直接返回缓存中的直播间消息
|
|
|
+ return getCachedMsgList(cacheKey);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 时间未超过5s,直接返回缓存
|
|
|
+ return getCachedMsgList(cacheKey);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询直播间消息列表异常,liveId: {}", liveId, e);
|
|
|
+ // 异常情况下,直接查询数据库
|
|
|
+ return liveMsgMapper.selectLiveMsgList(liveMsg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从缓存获取消息列表
|
|
|
+ */
|
|
|
+ private List<LiveMsg> getCachedMsgList(String cacheKey) {
|
|
|
+ try {
|
|
|
+ String msgListJson = redisUtil.getString(cacheKey);
|
|
|
+ if (msgListJson != null && !msgListJson.isEmpty()) {
|
|
|
+ List<LiveMsg> msgList = JSON.parseArray(msgListJson, LiveMsg.class);
|
|
|
+ return msgList != null ? msgList : Collections.emptyList();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("从缓存获取消息列表失败,cacheKey: {}", cacheKey, e);
|
|
|
+ }
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 尝试获取分布式锁(使用SETNX)
|
|
|
+ * @param lockKey 锁的key
|
|
|
+ * @param expireSeconds 锁的过期时间(秒)
|
|
|
+ * @return 是否获取成功
|
|
|
+ */
|
|
|
+ private boolean tryLock(String lockKey, long expireSeconds) {
|
|
|
+ try {
|
|
|
+ Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", expireSeconds, TimeUnit.SECONDS);
|
|
|
+ return Boolean.TRUE.equals(result);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取锁失败,lockKey: {}", lockKey, e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 释放分布式锁
|
|
|
+ * @param lockKey 锁的key
|
|
|
+ */
|
|
|
+ private void releaseLock(String lockKey) {
|
|
|
+ try {
|
|
|
+ // 使用Lua脚本确保只删除自己设置的锁
|
|
|
+ String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
|
|
+ "return redis.call('del', KEYS[1]) " +
|
|
|
+ "else return 0 end";
|
|
|
+ DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
|
|
+ script.setScriptText(luaScript);
|
|
|
+ script.setResultType(Long.class);
|
|
|
+ redisTemplate.execute(script, Collections.singletonList(lockKey), "1");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("释放锁失败,lockKey: {}", lockKey, e);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|