基于数据库
表
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
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);}
}
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
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 {@Overridepublic LikedRecordVo addLIkeRecord(LikeRecordFormDTO recordDTO) {//判断是否是点赞//是点赞在点赞记录表添加点赞记录 取消点赞就把点赞记录删除//true //trueboolean 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;}
}
测试结果
基于redis set
使用redis set 先缓存点赞数据然后使用定时任务批量保存点赞数据 key 为业务id value 为点赞用户的id
使用set中的 size方法统计一条评论的点赞总数
使用zset对每一条评论的点赞数进行排序
配合定时任务定时更新评论区点赞数
controller
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
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);
}
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*/@Overridepublic 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;}@Overridepublic 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;//获取keyString 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;//获取keyString key = LIKE_SET_BIZ + recordDTO.getBizId();Long result = redisTemplate.opsForSet().remove(key, userId.toString());//点赞成功return result !=null && result > 0;}}
其他类
- 定时任务
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;//这个理解为视频唯一IDprivate 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);}}
}
- 启动类
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);}}