基于数据库
1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE IF NOT EXISTS `liked_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
`user_id` bigint NOT NULL COMMENT '用户id',
`biz_id` bigint NOT NULL COMMENT '点赞的业务id',
`biz_type` VARCHAR(16) NOT NULL COMMENT '点赞的业务类型',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_biz_user` (`biz_id`,`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='点赞记录表';

controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.orchids.likepointbyset.web.controller;

import com.orchids.likepointbyset.web.domain.dto.LikeRecordFormDTO;
import com.orchids.likepointbyset.web.domain.po.LikedRecord;
import com.orchids.likepointbyset.web.domain.result.Result;
import com.orchids.likepointbyset.web.domain.vo.LikedRecordVo;
import com.orchids.likepointbyset.web.service.ILikedRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @ Author qwh
* @ Date 2024/7/5 19:14
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/likes")
@Api(tags = "基于数据库的点赞点赞业务相关接口")
public class LikedRecordController {
private final ILikedRecordService likedRecordService;
@PostMapping("points")
@ApiOperation("点赞或取消点赞")
public Result<LikedRecordVo> addLIkeRecord(@Validated @RequestBody LikeRecordFormDTO recordDTO){

LikedRecordVo record = likedRecordService.addLIkeRecord(recordDTO);
return Result.ok(record);

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.orchids.likepointbyset.web.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.orchids.likepointbyset.web.domain.dto.LikeRecordFormDTO;
import com.orchids.likepointbyset.web.domain.po.LikedRecord;
import com.orchids.likepointbyset.web.domain.result.Result;
import com.orchids.likepointbyset.web.domain.vo.LikedRecordVo;

/**
* <p>
* 点赞记录表 服务类
* </p>
*
* @author nullpointer
* @since 2024-07-05
*/
public interface ILikedRecordService extends IService<LikedRecord> {


LikedRecordVo addLIkeRecord(LikeRecordFormDTO recordDTO);
}

service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.orchids.likepointbyset.web.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.orchids.likepointbyset.web.domain.dto.LikeRecordFormDTO;
import com.orchids.likepointbyset.web.domain.po.LikedRecord;
import com.orchids.likepointbyset.web.domain.vo.LikedRecordVo;
import com.orchids.likepointbyset.web.exception.SignException;
import com.orchids.likepointbyset.web.mapper.LikedRecordMapper;
import com.orchids.likepointbyset.web.service.ILikedRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
* <p>
* 点赞记录表 服务实现类
* </p>
*
* @author nullpointer
* @since 2024-07-05
*/
@Service
public class LikedRecordServiceImpl extends ServiceImpl<LikedRecordMapper, LikedRecord> implements ILikedRecordService {

@Override
public LikedRecordVo addLIkeRecord(LikeRecordFormDTO recordDTO) {
//判断是否是点赞
//是点赞在点赞记录表添加点赞记录 取消点赞就把点赞记录删除
//true //true
boolean success = recordDTO.getLiked()?like(recordDTO):unlike(recordDTO);
if (!success){
throw new SignException("亲!业务失败了 >︿< 马上好",500);
}
//保存成功或者取消成功
//重新计算总点赞次数
Long likePoints = lambdaQuery().eq(LikedRecord::getBizId, recordDTO.getBizId()).count();
LikedRecordVo result = new LikedRecordVo();
result.setBizId(recordDTO.getBizId());
result.setLikeCount(likePoints.intValue());
return result;
}


private boolean like(LikeRecordFormDTO recordDTO) {
//假设用户Id为
Long userId = 138888L;
//查询是否有点赞记录如果有直接退出没有就添加点赞记录
Long count = lambdaQuery()
.eq(LikedRecord::getUserId, userId)
.eq(LikedRecord::getBizId, recordDTO.getBizId()).count();
if (count>0){
return false;
}
//添加记录
LikedRecord record = new LikedRecord();
record.setUserId(userId);
record.setBizId(recordDTO.getBizId());
record.setBizType(recordDTO.getBizType());
boolean save = save(record);
return save;
}
private boolean unlike(LikeRecordFormDTO recordDTO) {
//假设用户Id为
Long userId = 138888L;
//删除点记录
LambdaQueryWrapper<LikedRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LikedRecord::getUserId,userId).eq(LikedRecord::getBizId,recordDTO.getBizId());
boolean remove = remove(wrapper);
return remove;
}
}

测试结果

屏幕截图 2024-07-05 204016.png
屏幕截图 2024-07-05 204039.png
屏幕截图 2024-07-05 204046.png
屏幕截图 2024-07-05 204056.png
屏幕截图 2024-07-05 204103.png

基于redis set
使用redis set 先缓存点赞数据然后使用定时任务批量保存点赞数据 key 为业务id value 为点赞用户的id

使用set中的 size方法统计一条评论的点赞总数
使用zset对每一条评论的点赞数进行排序
配合定时任务定时更新评论区点赞数
image.pngcontroller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.orchids.likepointbyset.web.controller;

import com.orchids.likepointbyset.web.domain.dto.LikeRecordFormDTO;
import com.orchids.likepointbyset.web.domain.po.LikedRecord;
import com.orchids.likepointbyset.web.domain.result.Result;
import com.orchids.likepointbyset.web.domain.vo.LikedRecordVo;
import com.orchids.likepointbyset.web.service.ILikedRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @ Author qwh
* @ Date 2024/7/5 19:14
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/likes")
@Api(tags = "基于数据库的点赞点赞业务相关接口")
public class LikedRecordController {
private final ILikedRecordService likedRecordService;
@PostMapping("points")
@ApiOperation("点赞或取消点赞")
public Result<LikedRecordVo> addLIkeRecord(@Validated @RequestBody LikeRecordFormDTO recordDTO){

LikedRecordVo record = likedRecordService.addLIkeRecord(recordDTO);
return Result.ok(record);

}
}

service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.orchids.likepointbyset.web.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.orchids.likepointbyset.web.domain.dto.LikeRecordFormDTO;
import com.orchids.likepointbyset.web.domain.po.LikedRecord;
import com.orchids.likepointbyset.web.domain.result.Result;
import com.orchids.likepointbyset.web.domain.vo.LikedRecordVo;

/**
* <p>
* 点赞记录表 服务类
* </p>
*
* @author nullpointer
* @since 2024-07-05
*/
public interface ILikedRecordService extends IService<LikedRecord> {


LikedRecordVo addLIkeRecord(LikeRecordFormDTO recordDTO);

/**
* 用于发送消息将redis中的数据取出并统计
* @param bizType
* @param maxBizSize
*/

void readLikedTimesAndSendMessage(String bizType, int maxBizSize);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.orchids.likepointbyset.web.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.orchids.likepointbyset.web.domain.dto.LikeRecordFormDTO;
import com.orchids.likepointbyset.web.domain.dto.LikedTimesDTO;
import com.orchids.likepointbyset.web.domain.po.LikedRecord;
import com.orchids.likepointbyset.web.domain.vo.LikedRecordVo;
import com.orchids.likepointbyset.web.exception.SignException;
import com.orchids.likepointbyset.web.mapper.LikedRecordMapper;
import com.orchids.likepointbyset.web.service.ILikedRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Set;

/**
* @ Author qwh
* @ Date 2024/7/5 21:07
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class LikedRecordRedisServiceImpl extends ServiceImpl<LikedRecordMapper, LikedRecord> implements ILikedRecordService {
private final StringRedisTemplate redisTemplate;
private final String LIKE_SET_BIZ = "like:set:biz";
private final String LIKE_SET_BIZ_SUM = "like:set:biz:type";
private final String LIKE_RECORD_EXCHANGE = "like.record.topic";
private final String LIKED_TIMES_KEY_TEMPLATE = "qa.times.changed";
private final RabbitTemplate rabbitTemplate;

/**
* 对一条评论的点赞一个视频的点赞 一个评论有许多条评论 一个页面哟许多视频 在评论区可以根据 点赞数量进行排序 就是是高赞评论 或者高质量视频
* @param recordDTO
* @return
*/
@Override
public LikedRecordVo addLIkeRecord(LikeRecordFormDTO recordDTO) {
// 1.基于前端的参数,判断是执行点赞还是取消点赞
boolean success = recordDTO.getLiked() ? like(recordDTO) : unlike(recordDTO);
if (!success){
throw new SignException("亲!业务失败了 >︿< 马上好",500);
}
//计算点赞总数
Long likeCount = redisTemplate.opsForSet().size(LIKE_SET_BIZ + recordDTO.getBizId());
if (likeCount == null){
throw new SignException("亲 该评论还未被点赞过急需 您的点赞(✿◡‿◡)",203);
}
//将该评论总数缓存总数到redis //todo 后序可以对该评论区的热评进行排序
Boolean add = redisTemplate.opsForZSet().add(LIKE_SET_BIZ_SUM +":"+ recordDTO.getBizType(), recordDTO.getBizId().toString(), likeCount);
//返回点赞数
LikedRecordVo result = new LikedRecordVo();
result.setBizId(recordDTO.getBizId());
result.setLikeCount(likeCount.intValue());
return result;
}

@Override
public void readLikedTimesAndSendMessage(String bizType, int maxBizSize) {
//读取redis中的该评论的点赞总数
String key = LIKE_SET_BIZ_SUM + bizType;
//从redis中取出30条评论 点赞数由小到大
Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet().popMin(key, maxBizSize);
if (CollectionUtils.isEmpty(tuples)){
return;
}
//数据转换
ArrayList<LikedTimesDTO> list = new ArrayList<>(tuples.size());
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
LikedTimesDTO dto = new LikedTimesDTO();
String bizId = tuple.getValue();
Double likeCount = tuple.getScore();
if (bizId==null||likeCount==null){
continue;
}
dto.setBizId(Long.valueOf(bizId));
dto.setLikedCounts(likeCount.intValue());
list.add(dto);
}
log.error("评论区点赞数据{}",list);
if (!CollectionUtils.isEmpty(list)) {
//发送MQ消息 将评论区中的点赞数据保存到数据库
rabbitTemplate.convertAndSend(LIKE_RECORD_EXCHANGE, LIKED_TIMES_KEY_TEMPLATE, list);
//由评论区业务监听这个消息 监听后更新评论区数据
//todo 由评论区消费者消费这些消息
}
};

private boolean like(LikeRecordFormDTO recordDTO) {
//假设用户Id为
Long userId = 138888L;
//获取key
String key = LIKE_SET_BIZ + recordDTO.getBizId();
Long result = redisTemplate.opsForSet().add(key, userId.toString());
//点赞成功
return result !=null && result > 0;
}

private boolean unlike(LikeRecordFormDTO recordDTO) {
//假设用户Id为
Long userId = 138888L;
//获取key
String key = LIKE_SET_BIZ + recordDTO.getBizId();
Long result = redisTemplate.opsForSet().remove(key, userId.toString());
//点赞成功
return result !=null && result > 0;
}

}

其他类
  1. 定时任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package com.orchids.likepointbyset.web.task;

    import com.orchids.likepointbyset.web.service.ILikedRecordService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;

    import java.util.Arrays;
    import java.util.List;


    /**
    * @ Author qwh
    * @ Date 2024/7/5 21:50
    */
    @Component
    @RequiredArgsConstructor
    public class TimesCheckLikedTask {
    private final ILikedRecordService recordService;
    //这个理解为视频唯一ID
    private static final List<String> BIZ_TYPES = Arrays.asList("QA","NOTE");
    private static final int MAX_BIZ_SIZE = 30;

    //每20秒保存批量保存一次数据到数据库
    @Scheduled(cron = "5 * * * * *")
    public void TimesCheckLiked(){
    for (String bizType : BIZ_TYPES) {
    recordService.readLikedTimesAndSendMessage(bizType, MAX_BIZ_SIZE);
    }
    }
    }

  2. 启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.orchids.likepointbyset;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.scheduling.annotation.EnableScheduling;

    @EnableScheduling
    @SpringBootApplication
    @ComponentScan(basePackages = "com.orchids.likepointbyset.web")
    public class LikepointbyzsetApplication {

    public static void main(String[] args) {
    SpringApplication.run(LikepointbyzsetApplication.class, args);
    }

    }

    测试因为要写其他业务就算了 思想理解了 就行