使用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);
     }
}

然后定时保存数据库中即可。

posted @ 2022-02-18 14:31  背着泰山找黄河  阅读(2041)  评论(1编辑  收藏  举报