0.前言
本文基于Springboot利用Redis實(shí)現(xiàn)點(diǎn)贊功能的緩存和定時(shí)持久化接口。
用戶對(duì)瀏覽內(nèi)容進(jìn)行【點(diǎn)贊/取贊】,并發(fā)送【點(diǎn)贊/取贊】請(qǐng)求到后端,這些信息先存入Redis中緩存,再每隔兩小時(shí)將Redis中的內(nèi)容直接寫入數(shù)據(jù)庫(kù)持久化存儲(chǔ)。
1.項(xiàng)目目錄結(jié)構(gòu)

2.Redis緩存點(diǎn)贊消息
1.設(shè)計(jì)思路
用戶點(diǎn)贊一條數(shù)據(jù),設(shè)置狀態(tài)為0,并且更新被點(diǎn)贊內(nèi)容的likeCount+1
用戶取消點(diǎn)贊一條數(shù)據(jù),設(shè)置狀態(tài)為1,并且更新被點(diǎn)贊內(nèi)容的likeCount+0
1.1 Redis鍵值對(duì)設(shè)計(jì)
選用Hash(散列)存儲(chǔ)
點(diǎn)贊信息
-
key: (String) 瀏覽信息id和點(diǎn)贊用戶id拼接而成, 分隔符為::
-
value: (HashMap) 存儲(chǔ)點(diǎn)贊狀態(tài)(0: 點(diǎn)贊 1:取消點(diǎn)贊)和更新時(shí)間的時(shí)間戳
-
key: "瀏覽信息id::點(diǎn)贊用戶id" value:
{status: Integer, updateTime: long}
點(diǎn)贊數(shù)量
-
key: (String) 瀏覽信息id
-
value: (Integer) 點(diǎn)贊數(shù)量


1.2 點(diǎn)贊

-
用戶點(diǎn)贊信息,發(fā)送點(diǎn)贊請(qǐng)求到后端
-
后端判斷該點(diǎn)贊信息在Redis中的狀態(tài)
-
Redis不進(jìn)行存儲(chǔ),并提醒前端重復(fù)存儲(chǔ)。
-
更新/新增點(diǎn)贊信息
-
更新/新增點(diǎn)贊量
-
【不存在(沒(méi)有對(duì)應(yīng)key) 】|| 【取消點(diǎn)贊(即取出的status為1)】
-
【點(diǎn)贊(即取出的status為0,此時(shí)相當(dāng)于重復(fù)點(diǎn)贊行為)】
-
一次點(diǎn)贊請(qǐng)求完畢
1.3 取消點(diǎn)贊

-
用戶取消點(diǎn)贊信息,發(fā)送取消點(diǎn)贊請(qǐng)求到后端
-
后端判斷該點(diǎn)贊信息在Redis中的狀態(tài)
-
更新/新增點(diǎn)贊信息
-
更新/新增點(diǎn)贊量
-
Redis不進(jìn)行存儲(chǔ),并提醒前端重復(fù)存儲(chǔ)。
-
更新/新增點(diǎn)贊信息
-
增加0條內(nèi)容點(diǎn)贊量
-
【不存在(沒(méi)有對(duì)應(yīng)key) 】
-
【取消點(diǎn)贊(即取出的status為1,此時(shí)相當(dāng)于重復(fù)取消點(diǎn)贊行為)】
-
【點(diǎn)贊(即取出的status為0)】
-
一次取消點(diǎn)贊請(qǐng)求完畢
2.核心代碼實(shí)現(xiàn)
2.1 Redis封裝
具體實(shí)現(xiàn)參考該博客,不在贅述。
-
https://www.cnblogs.com/caizhaokai/p/11037610.html
2.2 工具類
1.時(shí)間戳轉(zhuǎn)化為L(zhǎng)ocalDateTime
importjava.time.Instant;
importjava.time.LocalDateTime;
importjava.time.ZoneId;
/**
*工具類:將時(shí)間戳轉(zhuǎn)化為L(zhǎng)ocalDateTime
*主要是因?yàn)閞edis不好存儲(chǔ)LocalDateTime,存儲(chǔ)timestamp方便一點(diǎn),而且格式可以隨意改變
*/
publicclassLocalDateTimeConvertUtil{
publicstaticLocalDateTimegetDateTimeOfTimestamp(longtimestamp){
Instantinstant=Instant.ofEpochMilli(timestamp);
ZoneIdzone=ZoneId.systemDefault();
returnLocalDateTime.ofInstant(instant,zone);
}
}
2.RedisKey處理類
publicclassRedisKeyUtils{
/**
*
保存用戶點(diǎn)贊內(nèi)容數(shù)據(jù)的key
*@date2021/9/2614:44
*/
publicstaticfinalStringMAP_KEY_USER_LIKED="MAP_USER_LIKED";
/**
*
保存內(nèi)容被點(diǎn)贊數(shù)量的key
*@date2021/9/2614:44
*/
publicstaticfinalStringMAP_KEY_USER_LIKED_COUNT="MAP_USER_LIKED_COUNT";
/**
*拼接被點(diǎn)贊的內(nèi)容id和點(diǎn)贊的人的id作為key。格式222222::333333
*@paraminfoId被點(diǎn)贊的內(nèi)容id
*@paramlikeUserId點(diǎn)贊的人的id
*@return
*/
publicstaticStringgetLikedKey(StringinfoId,StringlikeUserId){
returninfoId+
"::"+
likeUserId;
}
}
2.3 DTO
//UserLikesDTO.java
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassUserLikesDTO{
privateStringinfoId;
privateStringlikeUserId;
privateIntegerstatus;
privateLocalDateTimeupdateTime;
}
//UserLikeCountDTO.java
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassUserLikeCountDTO{
privateStringinfoId;
privateIntegerlikeCount;
}
2.4 Service
-
interface
//RedisService.java
importcom.csu.edu.redisLikeDemo.domain.DTO.UserLikeCountDTO;
importcom.csu.edu.redisLikeDemo.domain.DTO.UserLikesDTO;
importjava.util.List;
/**
*負(fù)責(zé)將數(shù)據(jù)寫入Redis緩存
*/
publicinterfaceRedisService{
/**
*獲取點(diǎn)贊狀態(tài)
*@paraminfoId
*@paramlikeUserId
*/
IntegergetLikeStatus(StringinfoId,StringlikeUserId);
/**
*點(diǎn)贊。狀態(tài)為1
*@paraminfoId
*@paramlikeUserId
*/
voidsaveLiked2Redis(StringinfoId,StringlikeUserId);
/**
*取消點(diǎn)贊。將狀態(tài)改變?yōu)?
*@paraminfoId
*@paramlikeUserId
*/
voidunlikeFromRedis(StringinfoId,StringlikeUserId);
/**
*從Redis中刪除一條點(diǎn)贊數(shù)據(jù)
*@paraminfoId
*@paramlikeUserId
*/
voiddeleteLikedFromRedis(StringinfoId,StringlikeUserId);
/**
*該內(nèi)容的點(diǎn)贊數(shù)變化Δdelta
*@paraminfoId
*/
voidin_decrementLikedCount(StringinfoId,Integerdelta);
/**
*獲取Redis中存儲(chǔ)的所有點(diǎn)贊數(shù)據(jù)
*@return
*/
ListgetLikedDataFromRedis();
/**
*獲取Redis中存儲(chǔ)的所有點(diǎn)贊數(shù)量
*@return
*/
ListgetLikedCountFromRedis();
}
-
implement
importcom.csu.edu.redisLikeDemo.common.CONSTANT;
importcom.csu.edu.redisLikeDemo.domain.DTO.UserLikeCountDTO;
importcom.csu.edu.redisLikeDemo.domain.DTO.UserLikesDTO;
importcom.csu.edu.redisLikeDemo.service.RedisService;
importcom.csu.edu.redisLikeDemo.util.LocalDateTimeConvertUtil;
importcom.csu.edu.redisLikeDemo.util.RedisKeyUtils;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.data.redis.core.Cursor;
importorg.springframework.data.redis.core.HashOperations;
importorg.springframework.data.redis.core.ScanOptions;
importorg.springframework.stereotype.Service;
importjava.time.LocalDateTime;
importjava.util.ArrayList;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;
@Service("redisService")
@Slf4j
publicclassRedisServiceImplimplementsRedisService{
@Autowired
privateHashOperationsredisHash;//RedisHash
@Override
publicIntegergetLikeStatus(StringinfoId,StringlikeUserId){
if(redisHash.hasKey(RedisKeyUtils.MAP_KEY_USER_LIKED,RedisKeyUtils.getLikedKey(infoId,likeUserId))){
HashMapmap=(HashMap)redisHash.get(RedisKeyUtils.MAP_KEY_USER_LIKED,RedisKeyUtils.getLikedKey(infoId,likeUserId));
return(Integer)map.get("status");
}
returnCONSTANT.LikedStatusEum.NOT_EXIST.getCode();
}
@Override
publicvoidsaveLiked2Redis(StringinfoId,StringlikeUserId){
//生成key
Stringkey=RedisKeyUtils.getLikedKey(infoId,likeUserId);
//封裝value喜歡狀態(tài)更新時(shí)間
HashMapmap=newHashMap<>();
map.put("status",CONSTANT.LikedStatusEum.LIKE.getCode());
map.put("updateTime",System.currentTimeMillis());
redisHash.put(RedisKeyUtils.MAP_KEY_USER_LIKED,key,map);
}
@Override
publicvoidunlikeFromRedis(StringinfoId,StringlikeUserId){
//生成key
Stringkey=RedisKeyUtils.getLikedKey(infoId,likeUserId);
//封裝value喜歡狀態(tài)更新時(shí)間
HashMapmap=newHashMap<>();
map.put("status",CONSTANT.LikedStatusEum.UNLIKE.getCode());
map.put("updateTime",System.currentTimeMillis());//存入當(dāng)前時(shí)間戳
redisHash.put(RedisKeyUtils.MAP_KEY_USER_LIKED,key,map);
}
@Override
publicvoiddeleteLikedFromRedis(StringinfoId,StringlikeUserId){
Stringkey=RedisKeyUtils.getLikedKey(infoId,likeUserId);
redisHash.delete(RedisKeyUtils.MAP_KEY_USER_LIKED,key);
}
@Override
publicvoidin_decrementLikedCount(StringinfoId,Integerdelta){
redisHash.increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,infoId,delta);
}
@Override
publicListgetLikedDataFromRedis(){
//scan讀取數(shù)據(jù),比key匹配優(yōu)雅
Cursor>cursor=redisHash.scan(RedisKeyUtils.MAP_KEY_USER_LIKED,ScanOptions.NONE);
Listlist=newArrayList<>();
while(cursor.hasNext()){
Map.Entryentry=cursor.next();
Stringkey=(String)entry.getKey();
//分離出infoId,likedPostId,解析value
String[]split=key.split("::");
StringinfoId=split[0];
StringlikeUserId=split[1];
HashMapmap=(HashMap)entry.getValue();
Integerstatus=(Integer)map.get("status");
longupdateTimeStamp=(long)map.get("updateTime");
LocalDateTimeupdateTime=LocalDateTimeConvertUtil.getDateTimeOfTimestamp(updateTimeStamp);//時(shí)間戳轉(zhuǎn)為L(zhǎng)ocalDateTime
//組裝成UserLike對(duì)象
UserLikesDTOuserLikesDTO=newUserLikesDTO(infoId,likeUserId,status,updateTime);
list.add(userLikesDTO);
//存到list后從Redis中清理緩存
redisHash.delete(RedisKeyUtils.MAP_KEY_USER_LIKED,key);
}
returnlist;
}
@Override
publicListgetLikedCountFromRedis(){
//scan讀取數(shù)據(jù),比key匹配優(yōu)雅
Cursor>cursor=redisHash.scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,ScanOptions.NONE);
Listlist=newArrayList<>();
while(cursor.hasNext()){
Map.Entrymap=cursor.next();
//將點(diǎn)贊數(shù)量存儲(chǔ)在LikedCountDT
Stringkey=(String)map.getKey();
UserLikeCountDTOdto=newUserLikeCountDTO(key,(Integer)map.getValue());
list.add(dto);
//從Redis中刪除這條記錄
redisHash.delete(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT,key);
}
returnlist;
}
}
2.5 controller & API
-
controller
importcom.csu.edu.redisLikeDemo.common.CommonResponse;
importcom.csu.edu.redisLikeDemo.service.LikeService;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.web.bind.annotation.PostMapping;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test/")
publicclassLikeController{
@Autowired
privateLikeServicelikeService;
@PostMapping("like")
publicCommonResponse