以上是参考文章,以下是个人总结,可能没有以上总结的好,仅做自我复盘。
点赞操作比较频繁,而且比较随意,所以数据变更很快,如果用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);
}
}
然后定时保存数据库中即可。