【项目杂记】实现文章、评论、回复点赞及防刷赞

本文介绍如何通过Redis Set实现用户点赞防刷功能,涉及登录验证、点赞计数存储和实时检查,以确保每个用户只能点赞一次。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点赞这里我们分为三种情况,给文章点赞,给评论点赞,给评论的回复点赞;

public abstract class LikeTypeRole {
	// 给文章点赞
    public static final int ARTICLE_LIKE = 1;
	// 给评论点赞
    public static final int COMMENT_LIKE = 2;
	// 给回复点赞
    public static final int REPLY_LIKE = 3;
}

数据库表相应字段如下:

在这里插入图片描述

1.基本逻辑

1.1 Controller

提供点赞接口

@PutMapping("/like")
public ResponseEntity<Void> like(@RequestBody LikeDto likeDto){
    if(this.likeService.like(likeDto)){
        return ResponseEntity.noContent().build();
    }else {
        return ResponseEntity.badRequest().build();
    }
}

LikeDto 是封装的点赞的请求对象

public class LikeDto {

    /** 1.给文章点赞,2.评论点赞 3.给回复点赞*/
    private Integer type;
	/** 相应的 id */ 
    private Long toId;

    public Integer getType() {
        if(type == null){
            return LikeTypeRole.ARTICLE_LIKE;
        }
        return type;
    }
    
     public void setType(Integer type) {
        this.type = type;
    }

    public Long getToId() {
        return toId;
    }
}

1.2 Service

点赞具体逻辑

public Boolean like(LikeDto likeDto) {

    Integer type = likeDto.getType();
    if(type == LikeTypeRole.ARTICLE_LIKE ){
        // 更新文章点赞数
        this.articleDao.updateLike(likeDto.getToId());
        return true;
    }else if(type == LikeTypeRole.COMMENT_LIKE ){
     	// 更新评论点赞数
        this.commentDao.updateComLike(likeDto.getToId());
        return true;
    }else {
     	// 更新回复点赞数
        this.replyDao.updateRepLike(likeDto.getToId());
        return true;
    }

    return false;
}

1.3 Dao

这里就不把所有的 dao 层代码列出来了,只列出更新点赞数的方法及Sql(mybatis)

@Update("UPDATE article SET alike=alike+1 WHERE id=#{id}")
public void updateLike(@Param("id") Long id);

@Update("UPDATE `comment` SET com_like=com_like+1 WHERE id=#{id}")
public void updateComLike(@Param("id") Long id);

@Update("UPDATE reply SET rep_like=rep_like+1 WHERE rep_id=#{repId}")
public void updateRepLike(@Param("repId") Long repId);

上面虽然实现了最基本的点赞功能,但是根本无法防止一个用户对谋篇文章、回复、评论重复多次点赞。

2.实现防刷赞

其实防止刷赞的实现有很多方案,下面我们来演示其中一种思路。

  1. 在用户要点赞前判断用户是否登录,不登录就不能点赞

  2. 用户点赞后就将用户 id 保存到 redis 中,key 如下:

    public abstract class LikeRedisRole {
        // 文章点赞的 redis key(like:article:articleId)
        public static final String PREFIX_KEY_ARTICLE = "like:article:";
        // 评论点赞的 redis key(like:comment:commentId)
        public static final String PREFIX_KEY_COMMENT = "like:comment:";
        // 评论点赞的 redis key(like:reply:replyId)
        public static final String PREFIX_KEY_REPLY = "like:reply:";
    }
    

    我们采用的结构是 Set,因为 Set 是一个不能添加重复的元素的集合。

  3. 当用户再要点赞时,判断 redis 中相应文章、评论、回复是否有该用户的点赞记录。

2.1 登录拦截

关于 JWT 鉴权的逻辑这里就不列出了,具体可以参考我的这篇文章

@Component
/**
 * 校验当前用户是否已经登录
 */
public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Resource
    private JwtProperties jwtProperties; // 获取 cookie 信息

    private static final ThreadLocal<Long> THREAD_LOCAL = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{
        // 获取 cookie 中的 token
        String cookieValue = CookieUtils.getCookieValue(request, this.jwtProperties.getCookieName());
        // 如果请求中没有 token
        if(StringUtils.isBlank(cookieValue)){
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        // 校验 token 合法性,是否包含了 user 信息
        UserInfo user = JwtUtils.getInfoFromToken(cookieValue,jwtProperties.getPublicKey());
        if(user == null){
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        // 将 token 中保存的当前 user 的 id 放入 ThreadLocal
        THREAD_LOCAL.set(user.getId());

        return true;
    }

    public static Long getUserId(){
        return THREAD_LOCAL.get();
    }

    /**
     * 在请求处理完后删除 ThreadLocal 中保存的 id
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        THREAD_LOCAL.remove();
    }
}

然后配置拦截路径

public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private UvInterceptor uvInterceptor;
	
    public void addInterceptors(InterceptorRegistry registry){
        		// 登录拦截
                registry.addInterceptor(loginInterceptor).addPathPatterns("/user/detail")
                                                 .addPathPatterns("/like") // 点赞
                                                 .addPathPatterns("/upload/**")
                                                 .addPathPatterns("/article/post")
                                                 .addPathPatterns("/source/detail/*");
    }
}

1.2 根据用户 ID 判断该用户能否点赞

这时也要修改 LikeService 了,添加一个 doLike 方法去判断该用户机是否已经点过赞,只有没点过赞的才能可以

@Override
@Transactional
public Boolean like(LikeDto likeDto) {

    Integer type = likeDto.getType();
    if(type == LikeTypeRole.ARTICLE_LIKE ){
        // 判断能否点赞,redis key 为 like:article:articleId
        if(doLike(LikeRedisRole.PREFIX_KEY_ARTICLE + likeDto.getToId())){
            // 更新文章点赞数
            this.articleDao.updateLike(likeDto.getToId());
            return true;
        }
    }else if(type == LikeTypeRole.COMMENT_LIKE ){
        // 判断能否点赞,redis key 为 like:comment:commentId
        if(doLike(LikeRedisRole.PREFIX_KEY_COMMENT + likeDto.getToId())){
            // 更新评论点赞数
            this.commentDao.updateComLike(likeDto.getToId());
            return true;
        }
    }else {
        // 判断能否点赞,redis key 为 like:reply:replyId
        if(doLike(LikeRedisRole.PREFIX_KEY_REPLY + likeDto.getToId())){
            // 更新回复点赞数
            this.replyDao.updateRepLike(likeDto.getToId());
            return true;
        }
    }

    return false;
}

/**
 * 根据用户ID判断当前用户机能否点赞,
 */
private boolean doLike(String key){
    // 如果 redis 中没有该文章、评论、回复的记录,可以点赞
    if(!this.redisTemplate.hasKey(key)){
        Long userId = LoginInterceptor.getUserId();
        // 将当前用户ID放到 redis
        this.redisTemplate.opsForSet().add(key,String.valueOf(userId));
        return true;
     // 如果 redis 中已经有该文章、评论、回复的点赞记录
    }else {
        // 判断当前用户是否点过赞
        if(this.redisTemplate.opsForSet().isMember(key,String.valueOf(LoginInterceptor.getUserId()))){
                return false; // 点过赞就返回 false
            }else{
            	// 没点过赞就将该用户 ID 放到 redis,然后返回 true
                this.redisTemplate.opsForSet().add(key,String.valueOf(LoginInterceptor.getUserId()));
                return true;
            }
    }
}

之后我们就可以业务的实际体量,选择是将 redis 中的点赞情况定时落库,还是索性全部都持久化到 redis。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A minor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值