使用Redis實現點贊功能


參考1 參考2 參考3 參考4 redis命令

以上是參考文章,以下是個人總結,可能沒有以上總結的好,僅做自我復盤。

點贊操作比較頻繁,而且比較隨意,所以數據變更很快,如果用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);
     }
}

然后定時保存數據庫中即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM