以上是參考文章,以下是個人總結,可能沒有以上總結的好,僅做自我復盤。
點贊操作比較頻繁,而且比較隨意,所以數據變更很快,如果用mysql,會對mysql產生很大的壓力,於是決定使用Redis,防止數據丟失,所以會定期將數據持久化同步到mysql中。
然后開始進行分析,點贊功能需要保存的數據都有哪些,比如給某個新聞點贊,那么需要保存的數據需要有該新聞的id(topicId)、點贊的用戶id(fromUid)、點贊的狀態(status)。
通過研究Redis的基本數據類型,最終覺得使用hash進行保存,定義一個點贊key(AGREE),所以最后的數據結構如下
127.0.0.1:6379> hset AGREE 1::2 0
將被點贊id和點贊用戶進行拼接作為一個字段,最后一個0代表狀態,0代表未點贊,1代表一點贊,在存入之前進行判斷,進行相應的保存。
大概的流程圖如下:
具體實現代碼:
RedisKeyUtils
package org.jeecg.modules.common.util; /** * @Author: qiaochengqiang * @Date: 2022/2/17 * @Description: 根據一定規則生成key **/ public class RedisKeyUtils { //保存用戶點贊數據的key public static final String MAP_KEY_USER_LIKED = "MAP_USER_LIKED"; //保存用戶被點贊數量的key public static final String MAP_KEY_USER_LIKED_COUNT = "MAP_USER_LIKED_COUNT"; /** * 拼接被點贊的用戶id和點贊的人的id作為key。格式 222222::333333::主題id * @param topicId likedUserId 被點贊的人id == topicId * @param fromUid likedPostId 點贊的人的id == fromUid * @return */ //* @param likedUserId 被點贊的人id == topicId //* @param likedPostId 點贊的人的id == fromUid public static String getLikedKey(String topicId, String fromUid){ StringBuilder builder = new StringBuilder(); builder.append(topicId); builder.append("::"); builder.append(fromUid); return builder.toString(); } }
LikedStatusEnum
package org.jeecg.modules.common.util; import lombok.Getter; /** * @Author: qiaochengqiang * @Date: 2022/2/17 * @Description: 用戶點贊狀態 **/ @Getter public enum LikedStatusEnum { LIKE(1, "點贊"), UNLIKE(0, "取消點贊/未點贊"), ; private Integer code; private String msg; LikedStatusEnum(Integer code, String msg) { this.code = code; this.msg = msg; } }
CommonAgree
package org.jeecg.modules.agree.entity; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.util.Date; import java.math.BigDecimal; import java.util.Map; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import com.fasterxml.jackson.annotation.JsonFormat; import org.springframework.format.annotation.DateTimeFormat; import org.jeecgframework.poi.excel.annotation.Excel; import org.jeecg.common.aspect.annotation.Dict; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; /** * @Description: 點贊表 * @Author: jeecg-boot * @Date: 2022-02-15 * @Version: V1.0 */ @Data @TableName("common_agree") @Accessors(chain = true) @EqualsAndHashCode(callSuper = false) @ApiModel(value="common_agree對象", description="點贊表") public class CommonAgree implements Serializable { private static final long serialVersionUID = 1L; /**主鍵*/ @TableId(type = IdType.ASSIGN_UUID) @ApiModelProperty(value = "主鍵") private java.lang.String id; /**創建人*/ @ApiModelProperty(value = "創建人") private java.lang.String createBy; /**創建日期*/ @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") @ApiModelProperty(value = "創建日期") private java.util.Date createTime; /**更新人*/ @ApiModelProperty(value = "更新人") private java.lang.String updateBy; /**更新日期*/ @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") @ApiModelProperty(value = "更新日期") private java.util.Date updateTime; /**所屬部門*/ @ApiModelProperty(value = "所屬部門") private java.lang.String sysOrgCode; /**系統id*/ @Excel(name = "系統id", width = 15) @ApiModelProperty(value = "系統id") private java.lang.String sysCode; /**租戶id*/ @Excel(name = "租戶id", width = 15) @ApiModelProperty(value = "租戶id") private java.lang.String tenantId; /**基礎flag*/ @Excel(name = "基礎flag", width = 15) @ApiModelProperty(value = "基礎flag") private java.lang.String baseFlag; /**點贊用戶*/ @Excel(name = "點贊用戶", width = 15) @ApiModelProperty(value = "點贊用戶") private java.lang.String fromUid; /**被點贊的評論或回復id*/ @Excel(name = "被點贊的評論或回復id", width = 15) @ApiModelProperty(value = "被點贊的評論或回復id") private java.lang.String topicId; /**被點贊類型*/ @Excel(name = "被點贊類型", width = 15) @ApiModelProperty(value = "被點贊類型") private java.lang.String topicType; /**被點贊用戶*/ @Excel(name = "被點贊用戶", width = 15) @ApiModelProperty(value = "被點贊用戶") private java.lang.String toUid; /**點贊狀態*/ @Excel(name = "點贊狀態", width = 15) @ApiModelProperty(value = "點贊狀態") private java.lang.Integer status; @TableField(exist = false) public Map<String,Integer> map; public CommonAgree() { } public CommonAgree(String topicId, String toUid, Integer status) { this.topicId = topicId; this.toUid = toUid; this.status = status; } }
RedisService
package org.jeecg.modules.common.service; import org.jeecg.modules.agree.entity.CommonAgree; import java.util.List; public interface RedisService { /** * 點贊。狀態為1 * @param topicId * @param fromUserId */ void saveLiked2Redis(String topicId, String fromUserId); /** * 取消點贊。將狀態改變為0 * @param topicId * @param fromUserId */ void unlikeFromRedis(String topicId, String fromUserId); /** * 從Redis中刪除一條點贊數據 * @param topicId * @param fromUserId */ void deleteLikedFromRedis(String topicId, String fromUserId); /** * 該用戶的點贊數加1 * @param toUserId */ void incrementLikedCount(String toUserId); /** * 該用戶的點贊數減1 * @param toUserId */ void decrementLikedCount(String toUserId); /** * 獲取Redis中存儲的所有點贊數據 * @return */ List<CommonAgree> getLikedDataFromRedis(); /** * 獲取redis中的存儲判斷是否已經點贊 * @return 可以根據key 字段 判斷是否已經點贊 而不用拿到所有 然后進行遍歷 */ CommonAgree checkIsAgreeFromRedis(String topidId,String fromUserId); /** * 根據主題id獲取所有的點贊數 * @return */ List<CommonAgree> getAgreeFromRedisByTopicId(String topidId); /** * 根據用戶id獲取所有的點贊數 * @return */ List<CommonAgree> getAgreeFromRedisByFromUserId(String fromUserId); }
RedisServiceImpl
package org.jeecg.modules.common.service.impl; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jeecg.modules.agree.entity.CommonAgree; import org.jeecg.modules.common.service.RedisService; import org.jeecg.modules.common.util.LikedStatusEnum; import org.jeecg.modules.common.util.RedisKeyUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Service @Slf4j public class RedisServiceImpl implements RedisService { @Autowired RedisTemplate redisTemplate; @Override public void saveLiked2Redis(String topicId, String fromUserId) { String key = RedisKeyUtils.getLikedKey(topicId, fromUserId); //用戶點贊,存儲的鍵為:topicId::fromUId,對應的值為 1 redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.LIKE.getCode()); } @Override public void unlikeFromRedis(String topicId, String fromUserId) { String key = RedisKeyUtils.getLikedKey(topicId, fromUserId); //用戶點贊,存儲的鍵為:topicId::fromUId,對應的值為 0 redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.UNLIKE.getCode()); } @Override public void deleteLikedFromRedis(String topicId, String fromUserId) { String key = RedisKeyUtils.getLikedKey(topicId, fromUserId); redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key); } @Override public void incrementLikedCount(String toUserId) { redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, toUserId, 1); } @Override public void decrementLikedCount(String toUserId) { redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, toUserId, -1); } @Override public List<CommonAgree> getLikedDataFromRedis() { Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE); List<CommonAgree> list = new ArrayList<>(); while (cursor.hasNext()){ Map.Entry<Object, Object> entry = cursor.next(); String key = (String) entry.getKey(); //分離出 toUserId,fromUserId String[] split = key.split("::"); //被點贊用戶 String topicId = split[0]; String fromUserId = split[1]; Integer value = (Integer) entry.getValue(); //組裝成 CommonAgree 對象 CommonAgree userLike = new CommonAgree(topicId, fromUserId, value); list.add(userLike); //存到 list 后從 Redis 中刪除 redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key); } return list; } @Override public CommonAgree checkIsAgreeFromRedis(String topicId, String fromUserId) { //可以根據key 字段 判斷是否已經點贊 而不用拿到所有 然后進行遍歷 String smallKey = topicId+"::"+fromUserId; CommonAgree commonAgree = new CommonAgree(); Integer value = (Integer) redisTemplate.opsForHash().get(RedisKeyUtils.MAP_KEY_USER_LIKED,smallKey); if (value != null){ //如果能夠查詢到 則將查詢到的數據直接進行賦值即可 commonAgree.setStatus(value); }else{ //redis 如果沒有 則認為是未點贊 commonAgree.setStatus(LikedStatusEnum.UNLIKE.getCode()); } return commonAgree; } @Override public List<CommonAgree> getAgreeFromRedisByTopicId(String topicId) { List<CommonAgree> commonAgrees = new ArrayList<>(); Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE); while (cursor.hasNext()){ CommonAgree commonAgree = new CommonAgree(); Map.Entry<Object, Object> entry = cursor.next(); String key = (String) entry.getKey(); //分離出 topicId,toUserId String[] split = key.split("::"); //被點贊主題 String topicIdRedis = split[0]; String fromUserIdRedis = split[1]; Integer value = (Integer) entry.getValue(); //如果主題id 點贊用戶id 以及 狀態為1 則代表已經點贊 if (StringUtils.equals(topicId,topicIdRedis)){ commonAgree.setTopicId(topicIdRedis); commonAgree.setFromUid(fromUserIdRedis); commonAgree.setStatus(value); commonAgrees.add(commonAgree); } } return commonAgrees; } @Override public List<CommonAgree> getAgreeFromRedisByFromUserId(String fromUserId) { List<CommonAgree> commonAgrees = new ArrayList<>(); Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE); while (cursor.hasNext()){ CommonAgree commonAgree = new CommonAgree(); Map.Entry<Object, Object> entry = cursor.next(); String key = (String) entry.getKey(); //分離出 topicId,toUserId String[] split = key.split("::"); //被點贊主題 String topicIdRedis = split[0]; String fromUserIdRedis = split[1]; Integer value = (Integer) entry.getValue(); //如果主題id 點贊用戶id 以及 狀態為1 則代表已經點贊 if (StringUtils.equals(fromUserId,fromUserIdRedis)){ commonAgree.setTopicId(topicIdRedis); commonAgree.setFromUid(fromUserIdRedis); commonAgree.setStatus(value); commonAgrees.add(commonAgree); } } return commonAgrees; } }
ICommonAgreeService
package org.jeecg.modules.agree.service; import org.jeecg.modules.agree.entity.CommonAgree; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; /** * @Description: 點贊表 * @Author: jeecg-boot * @Date: 2022-02-15 * @Version: V1.0 */ public interface ICommonAgreeService extends IService<CommonAgree> { /** * @Author: qiaochengqiang * @Date: 2022/2/17 * @Description: 通過點贊人和被點贊人查詢是否存在點贊記錄 **/ CommonAgree getByTopicIdAndFromUserId(String topicId, String fromUserId); /** * @Author: qiaochengqiang * @Date: 2022/2/17 * @Description: 通過點贊人和被點贊人查詢是否存在點贊記錄 **/ List<CommonAgree> getAllByTopicIdOrFromUserId(String topicId, String fromUserId); /** * @Author: qiaochengqiang * @Date: 2022/2/17 * @Description: 定時任務 將Redis里的點贊數據存入數據庫中 **/ void transLikedFromRedis2DB(); }
CommonAgreeServiceImpl
package org.jeecg.modules.agree.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.apache.commons.lang3.StringUtils; import org.jeecg.modules.agree.entity.CommonAgree; import org.jeecg.modules.agree.mapper.CommonAgreeMapper; import org.jeecg.modules.agree.service.ICommonAgreeService; import org.jeecg.modules.common.service.RedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import java.util.List; /** * @Description: 點贊表 * @Author: jeecg-boot * @Date: 2022-02-15 * @Version: V1.0 */ @Service public class CommonAgreeServiceImpl extends ServiceImpl<CommonAgreeMapper, CommonAgree> implements ICommonAgreeService { @Autowired RedisService redisService; @Override public CommonAgree getByTopicIdAndFromUserId(String topicId, String fromUserId) { QueryWrapper<CommonAgree> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(CommonAgree::getFromUid,fromUserId); queryWrapper.lambda().eq(CommonAgree::getTopicId,topicId); return getOne(queryWrapper); } @Override public List<CommonAgree> getAllByTopicIdOrFromUserId(String topicId, String fromUserId) { QueryWrapper<CommonAgree> queryWrapper = new QueryWrapper<>(); if (topicId != null && StringUtils.isNotEmpty(topicId)){ queryWrapper.lambda().eq(CommonAgree::getTopicId,topicId); } if (fromUserId != null && StringUtils.isNotEmpty(fromUserId)){ queryWrapper.lambda().eq(CommonAgree::getFromUid,fromUserId); } return list(queryWrapper); } @Override public void transLikedFromRedis2DB() { //數據同步的時候是否需要查詢所有數據 只同步當天的是否可以 List<CommonAgree> list = redisService.getLikedDataFromRedis(); for (CommonAgree commonAgree : list) { CommonAgree cg = getByTopicIdAndFromUserId(commonAgree.getTopicId(), commonAgree.getFromUid()); if (cg == null){ //沒有記錄,直接存入 save(commonAgree); }else{ //有記錄,需要更新 cg.setStatus(commonAgree.getStatus()); save(cg); } } } }
CommonAgreeController
package org.jeecg.modules.agree.controller; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.jeecg.common.api.vo.Result; import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.util.oConvertUtils; import org.jeecg.modules.agree.entity.CommonAgree; import org.jeecg.modules.agree.service.ICommonAgreeService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.extern.slf4j.Slf4j; import org.jeecg.modules.common.service.RedisService; import org.jeecg.modules.common.util.LikedStatusEnum; import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.entity.ExportParams; import org.jeecgframework.poi.excel.entity.ImportParams; import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; import org.jeecg.common.system.base.controller.JeecgController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.servlet.ModelAndView; import com.alibaba.fastjson.JSON; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.jeecg.common.aspect.annotation.AutoLog; /** * @Description: 點贊表 * @Author: jeecg-boot * @Date: 2022-02-15 * @Version: V1.0 */ @Api(tags="點贊表") @RestController @RequestMapping("/common/commonAgree") @Slf4j public class CommonAgreeController extends JeecgController<CommonAgree, ICommonAgreeService> { @Autowired private ICommonAgreeService commonAgreeService; @Autowired private RedisService redisService; /** * 添加 * * @param commonAgree * @return */ @AutoLog(value = "點贊表-添加") @ApiOperation(value="點贊表-添加", notes="點贊表-添加") @PostMapping(value = "/add") public Result<?> add(@RequestBody CommonAgree commonAgree) { //點贊分兩個步驟 首先保存到redis 定時計划保存到數據庫中 //將數據保存到 redis 保存是會根據狀態判斷是點贊還是取消點贊 Integer status = commonAgree.getStatus(); //如果狀態為null 或者狀態為0 則屬於點贊 if (status == null || LikedStatusEnum.UNLIKE.getCode() == status){ redisService.saveLiked2Redis(commonAgree.getTopicId(), commonAgree.getFromUid()); }else{ //取消點贊 redisService.unlikeFromRedis(commonAgree.getTopicId(), commonAgree.getFromUid()); } return Result.OK("點贊成功!"); } /** * @Author: qiaochengqiang * @Date: 2022/2/17 * @Description: 根據主題id和登錄用戶id查詢是否已經點贊 **/ @AutoLog(value = "點贊表-通過id查詢") @ApiOperation(value="點贊表-通過用戶id和主題id查詢", notes="點贊表-通過用戶id和主題id查詢") @GetMapping(value = "/queryAgree") public Result<?> queryAgree(@RequestParam(name="fromUid",required=true) String fromUid, @RequestParam(name="topicId",required=true) String topicId) { CommonAgree commonAgree = new CommonAgree(); commonAgree = redisService.checkIsAgreeFromRedis(topicId, fromUid); return Result.OK(commonAgree); } /** * @Author: qiaochengqiang * @Date: 2022/2/17 * @Description: 通過點贊id 被點贊主體id 以及被點贊用戶id 進行查詢 * 統計一共被點了多少贊 * 統計一共點了多少贊 **/ @AutoLog(value = "點贊表-通過id查詢") @ApiOperation(value="點贊表-通過id查詢", notes="點贊表-通過id查詢") @GetMapping(value = "/queryAgreeByTopicIdOrFromUserId") public Result<?> queryAgreeByTopicIdOrFromUserId(@RequestParam(name="topicId",required=false) String topicId, @RequestParam(name="fromUserId",required=false) String fromUserId) { //按照主題id 查詢統計總數 if (StringUtils.isEmpty(topicId) && StringUtils.isEmpty(fromUserId)){ return Result.error("參數不能都為空!"); } if (StringUtils.isNotEmpty(topicId) && StringUtils.isNotEmpty(fromUserId)){ return Result.error("參數不能都存在!"); } List<CommonAgree> commonAgrees = new ArrayList<>(); if (StringUtils.isNotEmpty(topicId)){ //查詢redis List<CommonAgree> agreeFromRedisByTopicId = redisService.getAgreeFromRedisByTopicId(topicId); commonAgrees.addAll(agreeFromRedisByTopicId); } //按照用戶 查詢一共點贊多少 給誰點贊 if (StringUtils.isNotEmpty(fromUserId)){ List<CommonAgree> agreeFromRedisByFromUserId = redisService.getAgreeFromRedisByFromUserId(fromUserId); commonAgrees.addAll(agreeFromRedisByFromUserId); } return Result.OK(commonAgrees); }
}
然后定時保存數據庫中即可。