产品需求:需要在某个帖子或者博主内容下进行评论或回复的点赞实现。
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());
}
}
}
点赞完成。