java实现评论或回复点赞功能(Springboot+Redis+RocketMQ+Mybatis-plus+Redisson锁)

3 篇文章 0 订阅
1 篇文章 0 订阅

产品需求:需要在某个帖子或者博主内容下进行评论或回复的点赞实现。

1.添加项目依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot</artifactId>
    <version>2.0.3</version>
    <scope>compile</scope>
</dependency>
<!-- redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.7.5</version>
</dependency>
<!-- redisson -->

2.配置application.yml

server:
  port: 9797
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    name: ******
    url: jdbc:mysql://**.***.***.***:3306/test-r-t?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: *****
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 5
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1
      idle-timeout: 30000
      pool-name: **********
      auto-commit: true
      maximum-pool-size: 15
      connection-init-sql: SET NAMES utf8mb4
  redis:
    host: **.***.***.***
    port: 6379
    password: *****
    database: 0
    timeout: 3600ms
    lettuce:
      shutdown-timeout: 5000ms
      pool:
        max-active: 15
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
rocketmq:
  name-server:**.***.***.***:9876
  producer:
    group: my-group
    max-message-size: 4194304
    retry-times-when-send-failed: 2
    retry-next-server: true
    retry-times-when-send-async-failed: 0


3.点赞Controller

/**
     * 壁纸评论回复 - 点赞
     * @param data 点赞对象参数
     * @return
     */
    @RestResult
    @RequestMapping(value="/commentLike", method = {RequestMethod.POST})
    public ResultMessage commentLike(@Valid @RequestBody WallpaperCommentLike data) {
        ResultMessage result = new ResultMessage(true, "200", "壁纸评论回复 - 点赞成功!");
        try {
            long no = commentLikeService.commentLike(data);
            result.setData(no);
        } catch (Exception e) {
            result = ResultUtil.pageMsg(e,"壁纸评论回复 - 点赞失败!");
        }
        return result;
    }

4.评论回复点赞表对象WallpaperCommentLike


import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 社交模块 - 评论回复点赞表
 *
 * @author Tom
 * @date 2020-07-30
 */
@Data
@Accessors(chain = true)
@TableName("wall_paper_comment_like")
public class WallpaperCommentLike extends BaseModel implements Serializable {

    /**
     * 点赞人ID
     */
    @NotNull(message = "点赞人ID不能为空")
    @JsonProperty(value = "peopleId")
    private Integer peopleId;

    /**
     * 评论ID
     */
    @NotNull(message = "评论ID不能为空")
    @JsonProperty(value = "commentId")
    private Integer commentId;

    /**
     * 壁纸ID
     */
    @NotNull(message = "壁纸ID不能为空")
    @JsonProperty(value = "paperId")
    private Integer paperId;

    /**
     * 点赞时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonProperty(value = "likeTime")
    private LocalDateTime likeTime;

    /**
     * 取消点赞时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonProperty(value = "cancelTime")
    private LocalDateTime cancelTime;

    /**
     * 状态 1 点赞 0 取消点赞
     */
    @NotNull(message = "状态(1点赞 0取消点赞)不能为空")
    @JsonProperty(value = "likeType")
    private Integer likeType;

}

5.点赞接口

import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;
import java.util.Map;

/**
 * 社交模块 - 壁纸评论回复点赞业务接口
 *
 * @author Tom
 * @date 2020-07-30
 */
public interface WallpaperCommentLikeService extends IService<WallpaperCommentLike> {

    /**
     * 壁纸评论回复 - 点赞
     * @param model 点赞对象参数
     */
    long commentLike(WallpaperCommentLike model);

    /**
     * 获取当前壁纸的指定评论或回复的点赞数量
     * @param model 点赞对象参数
     * @return
     */
    long getLikeSum(WallpaperCommentLike model);

    /**
     * 获取点赞最高的评论Map
     * @param paperId 壁纸ID
     * @param commentIds 评论ID集合
     * @param topNo 需要获取的评论数量(Top)
     * @param loginUserId 登录用户ID
     * @return
     */
    Map<Integer, BaseModel> getBestLike(Integer paperId, List<Integer> commentIds, int topNo,Integer loginUserId);
}

6.点赞接口实现类


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Service;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;


/**
 * 社交模块 - 壁纸评论回复点赞实现类
 *
 * @author Tom
 * @date 2020-07-30
 */
@Service
@Component
@Slf4j
public class WallpaperCommentLikeServiceImpl extends ServiceImpl<WallpaperCommentLikeMapper, WallpaperCommentLike> implements WallpaperCommentLikeService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 精彩评论点赞数最少需要满足数量
     */
    private static final int LIKE_SMALL_NO = 30;

    @Override
    public long commentLike(WallpaperCommentLike model) {
        String likeKey = ResultUtil.getLikeKey(model.getPaperId().toString(),model.getCommentId().toString());
        /**使用Redisson加锁*/
        LockUtil.tryLock(likeKey + model.getPeopleId());
        log.info("----> 使用Redisson加锁成功");
        try {
            /**在点赞集合中添加当前操作用户的peopleId(即当前用户点赞后,被点赞用户的like集合中就会加上一个点赞的用户信息)*/
            if(!redisUtil.sHasKey(likeKey,model.getPeopleId())){
                redisUtil.sSet(likeKey,model.getPeopleId());
            }
            //返回点赞数量
            return redisUtil.sGetSetSize(likeKey);
        } finally {
            /**使用Redisson释放锁*/
            LockUtil.unlock(likeKey + model.getPeopleId());
            log.info("----> 使用Redisson释放锁成功");
            new Thread(()->{
                rocketMQTemplate.convertAndSend("pc:commentLike", model);
            }).start();
        }
    }

    /**
     * 获取当前壁纸的指定评论或回复的点赞数量
     * @param model 点赞对象参数
     * @return
     */
    @Override
    public long getLikeSum(WallpaperCommentLike model) {
        long num = 0;
        try{
            String likeKey = ResultUtil.getLikeKey(model.getPaperId().toString(),model.getCommentId().toString());
            num = redisUtil.sGetSetSize(likeKey);
            if(redisUtil.sHasKey(likeKey,model.getPeopleId())){
                model.setIsLike(1);
            }else{
                model.setIsLike(2);
            }
        }catch(Exception e){
            e.printStackTrace();
            /**redis出现异常,则从数据库中取数据,然后将数据重新写入redis中*/
            QueryWrapper<WallpaperCommentLike> qw = new QueryWrapper<WallpaperCommentLike>()
                    .eq("paper_id",model.getPaperId())
                    .eq("comment_id",model.getCommentId())
                    .eq("like_type",1);
            List<WallpaperCommentLike> list = this.list(qw);
            List<Integer> pIds = list.stream().map(WallpaperCommentLike::getPeopleId).collect(Collectors.toList());
            num = list.size();
            if(pIds.contains(model.getPeopleId())){
                model.setIsLike(1);
            }else{
                model.setIsLike(2);
            }
            new Thread(()->{
                if(list != null && list.size() > 0){
                    for(WallpaperCommentLike ls : list){
                        String likeKey = ResultUtil.getLikeKey(ls.getPaperId().toString(),ls.getCommentId().toString());
                        if(!redisUtil.sHasKey(likeKey,ls.getPeopleId())){
                            redisUtil.sSet(likeKey,ls.getPeopleId());
                        }
                    }
                }
            }).start();
        }
        return num;
    }

    /**
     * 获取点赞最高的评论Map
     * @param paperId 壁纸ID
     * @param commentIds 评论ID集合
     * @param topNo 需要获取的评论数量(Top)
     * @param loginUserId 登录用户ID
     * @return
     */
    @Override
    public Map<Integer, BaseModel> getBestLike(Integer paperId, List<Integer> commentIds, int topNo,Integer loginUserId) {
        Map<Integer,BaseModel> result = new LinkedHashMap<>();
        if(commentIds != null && commentIds.size() > 0){
            try{
                Map<Integer,BaseModel> map = new HashMap<>();
                for(Integer id : commentIds){
                    BaseModel model = new BaseModel();
                    String likeKey = ResultUtil.getLikeKey(paperId.toString(),id.toString());
                    if(redisUtil.sHasKey(likeKey,loginUserId)){
                        model.setIsLike(1);
                    }else{
                        model.setIsLike(2);
                    }
                    map.put(id,model.setLikeCount(redisUtil.sGetSetSize(likeKey)));
                }
                if(map.size() < topNo){
                    topNo = map.size();
                }
                if(topNo != 0){
                    List<Integer> ls = map.entrySet().stream()
                            .sorted((Map.Entry<Integer,BaseModel> e1, Map.Entry<Integer,BaseModel> e2) -> new Long(e2.getValue().getLikeCount()).intValue() - new Long(e1.getValue().getLikeCount()).intValue())
                            .map(entry -> entry.getKey()).collect(Collectors.toList())
                            .subList(0, topNo);
                    if(ls != null && ls.size() > 0){
                        for(Integer i : ls){
                            if(map.get(i).getLikeCount() >= LIKE_SMALL_NO){
                                result.put(i,map.get(i));
                            }
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        return result;
    }
}

7.ResultUtil工具类

import lombok.extern.slf4j.Slf4j;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @description:将抛出异常进行封装,返回ResultMessage
 *
 * @author Tom
 * @date 2020-07-16
 */
@Slf4j
public class ResultUtil {

	/**
	 * 回复内容 "昵称" 前缀
	 */
	private static final String PREFIX_STR = "回复@";

	/**
	 * 回复内容 "昵称" 后缀
	 */
	private static final String SUFFIX_STR = ":";

	/**
	 * 字符串连接符(用于拼接点赞缓存的key值)
	 */
	private static final String CONNECT_STR = "-";

	/**
	 * 封装返回异常返回参数
	 * @param e 异常类
	 * @param msg 自定义提示信息
	 * @return
	 */
	public static ResultMessage pageMsg(Exception e,String msg){
		ResultMessage result = new ResultMessage(false, "500", "");
		if(e instanceof SocialException){
			SocialException ex = (SocialException)e;
			result.setMessage(ex.getMsg());
		}else{
			e.printStackTrace();
			log.error(msg, e.getMessage());
			result.setMessage(msg);
		}
		return result;
	}

	/**
	 * 截取以"回复@"开头,以:结尾的字符串返回
	 * @param str 待截取字符串
	 * @return
	 */
	public static String getIntercept(String str){
		String res = null;
		String regex = PREFIX_STR + "(.*?)" + SUFFIX_STR;
		Pattern p = Pattern.compile(regex);
		Matcher m = p.matcher(str);
		while(m.find()){
			res = m.group(1);
		}
		return res;
	}

	/**
	 * 评论回复中的回复内容封装(将特殊前缀 + 评论人ID + 特殊后缀,加入到评论开头)
	 * @param model 对象信息
	 * @param id 评论人ID
	 */
	public static void addContentId(WallpaperComment model,String id){
		StringBuffer sb = new StringBuffer(model.getContent());
		sb.insert(0,PREFIX_STR + id + SUFFIX_STR);
		model.setContent(sb.toString());
	}

	/**
	 * 将评论回复中的回复内容前缀中的评论人ID,转换为评论人昵称
	 * @param model 对象信息
	 * @param id 评论人ID
	 * @param name 评论人名称
	 */
	public static void rollNikeName(WallpaperComment model,String id,String name){
		String oldPrefix = PREFIX_STR + id + SUFFIX_STR;
		String newPrefix = PREFIX_STR + name + SUFFIX_STR;
		model.setContent(model.getContent().replace(oldPrefix,newPrefix));
	}

	/**
	 * 返回点赞的缓存key
	 * @param paperId 壁纸ID
	 * @param commentId 评论表ID
	 * @return
	 */
	public static String getLikeKey(String paperId,String commentId){
		StringBuffer sb = new StringBuffer();
		sb.append(RedisTypeEnum.COMMENT_LIKE.getCode());
		sb.append(CONNECT_STR);
		sb.append(paperId);
		sb.append(CONNECT_STR);
		sb.append(commentId);
		return sb.toString();
	}

	/**
	 * 返回反对的缓存key
	 * @param paperId 壁纸ID
	 * @param commentId 评论表ID
	 * @return
	 */
	public static String getDisLikeKey(String paperId,String commentId){
		StringBuffer sb = new StringBuffer();
		sb.append(RedisTypeEnum.COMMENT_DISLIKE.getCode());
		sb.append(CONNECT_STR);
		sb.append(paperId);
		sb.append(CONNECT_STR);
		sb.append(commentId);
		return sb.toString();
	}

}

8.从RocketMQ中接受pc端点赞或反对消息写入数据库

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
 * 从RocketMQ中接受pc端点赞或反对消息
 * 1. 记录接收的消息,并写入日志,使消息具备追溯性
 * 2. 将格式转换成功的消息,写入数据库
 * 3. 如果处理异常,记录异常消息,使异常消息具备追溯性
 */
@Slf4j
@Service
@RocketMQMessageListener(topic = "pc", consumerGroup = "wallpaper-comment", selectorExpression = "commentLike")
public class WallPaperCommentLikeListener implements RocketMQListener<MessageExt>{

    @Autowired
    private WallpaperCommentLikeService wallpaperCommentLikeService;

    /**
     * 消息推送 - 点赞或者反对都调用此方法即可
     * @param messageExt
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void onMessage(MessageExt messageExt) {
        log.info("-------receive message wallpaper-comment UNIQ_KEY:{}, message_body:{}",messageExt.getProperties().get("UNIQ_KEY"), new String(messageExt.getBody()));
        try {
            WallpaperCommentLike like = JSONObject.parseObject(new String(messageExt.getBody()), WallpaperCommentLike.class);
            if(like.getLikeType().intValue() == 1){
                like.setLikeTime(LocalDateTime.now());
            }
            if(like.getLikeType().intValue() == 0){
                like.setCancelTime(LocalDateTime.now());
            }
            QueryWrapper<WallpaperCommentLike> qw = new QueryWrapper<>();
            qw.eq("people_id",like.getPeopleId());
            qw.eq("comment_id",like.getCommentId());
            qw.eq("paper_id",like.getPaperId());
            WallpaperCommentLike one = wallpaperCommentLikeService.getOne(qw);
            if(one == null){
                wallpaperCommentLikeService.save(like);
            }else{
                if(one.getLikeType().intValue() != like.getLikeType().intValue()){
                    wallpaperCommentLikeService.update(like,qw);
                }
            }
            log.info("-------消费成功 message wallpaper-comment UNIQ_KEY:{}, message_body:{}",messageExt.getProperties().get("UNIQ_KEY"), new String(messageExt.getBody()));
        } catch (Exception e) {
            e.printStackTrace();
            log.error("--------consumer message wallpaper-comment UNIO_KEY:{}, error: {}",messageExt.getProperties().get("UNIO_KEY"),e.getMessage());
        }

    }
}

点赞完成。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值