JAVA项目点赞功能如何实现?如何利用缓存优化?如何防止刷赞?

news2024/11/22 20:33:21

- 普通的点赞如何实现? -

    每个人都见过点赞功能,大家想实现一个点赞功能也简单,比如一个简单的文章点赞逻辑如下:

    首先需要建个表,记录下点赞人的id,被点赞文章的id,点赞状态三个关键因素即可,需要的话可以把被点赞文章的作者id也冗余进来

冗余一个作者id字段这样子可以避免一个连表查询操作,在较大数据量下的时候,冗余字段带来的内存消耗的性价比是远远高于连表查询时带来的时间消耗的性价比的

    然后点赞的时候前端可以直接做一个状态的更改,无需等待后端结果返回,毕竟这是一个对数据精确性要求没那么高的功能

首先需要两张数据库表:

article文章表

article_user_thumb文章用户点赞关系表,用于记录哪个用户点赞了哪个文章:

后端逻辑的实现

查询数据库article表给已有点赞数进行+1操作

问题:

注意,这里可能有线程安全性问题,实际上这是一个并发问题,只要在并发的情况下就会出现问题,我们知道Spring Mvc是基于servlet的,servlet在接收到用户请求后会从线程池中拿一个线程分配给它,每个请求都是一个单独的线程。试想一下,如果A线程在执行完查询操作后,发现没有记录,随后由于CPU调度,把控制权让了出去,然后B线程执行查询,也发现没有记录,这时候A和B线程都会执行保存并商品点赞数加1这个操作,导致数据不正确。

问题解决:

(其实上述问题主要的出现的原因在于将查询和加1操作进行了分步,而没有做到原子操作)

那么我们可以通过以下代码 / SQL实现一个原子的累加操作:

productService.update(new UpdateWrapper<article>().lambda()
                    .setSql("like_count = like_count + 1")
                    .eq(Article::getAuthorId, authorId));

= UPDATE article SET like_count = like_count + 1 WHERE author_id = ?;

因为在MySQL中执行DML操作的时候会自动上一个行级锁(前提是条件字段是被索引修饰),这样子可以保证其的SQL原子性

 新增article_user_thumb表的一条数据进行关联


缓存性能优化

为了避免频繁地访问数据库,可以使用缓存技术Redis,将点赞量存储在缓存中。每次用户点赞时,首先将点赞量从缓存中读取,然后对其进行修改(该两部分操作使用lua脚本实现,保证整体操作的原子性),最后再将修改后的点赞量写回缓存。设置定时任务异步的将redis中的数据持久化到mysql

 至于后端的处理逻辑,可以根据(点赞人的id,被点赞文章的id,点赞状态)三个字段,直接存到redis,做一个快速的读写,之后再异步保存到数据库中做一个备份即可(如果后续需要在别的地方如列表页查询,甚至可以直接在这里写到索引中)

总之的原则就是,数据的快速读写,允许一定时间的误差(比如我点完赞非常快速的刷新页面发现还是未点赞状态,再次刷新的时候就是已点赞状态这种现象,是可以容忍的,虽然它也几乎不可能出现)

相关的数据都可以存到redis,比如对某文章的点赞数

某个用户对某篇文章的点赞关系

 

还有一些逻辑可以做判断优化,比如可以通过redis中的数据判断用户是否重复点赞,如果是的话就没必要再访问一次数据库了 


防止刷赞

防刷策略:

为了防止刷赞,可以实施以下几种策略:

  • 限制点赞频率:可以限制用户在一定时间内的点赞次数,例如,每分钟或每小时点赞次数的限制。
  • IP限制:可以限制同一IP地址下的点赞操作次数或频率,以防止刷赞行为。
  • 用户认证:确保只有已登录的用户才能进行点赞操作,减少匿名用户的刷赞可能性。
  • 验证码验证:在点赞操作前,要求用户进行验证码验证,以确保每个点赞请求都是由真实用户发起的。

自定义注解实现综合防刷策略

可能不同的产品对此的方案也不一样,如果可以在网关做限流那是最好的,但是有些具体的业务在网关配置还是不太方便的

    下面给大家一个简单使用的防止刷赞的限流方法

首先定义一个注解@Limit

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Limit {
 
 
    /**
     * 资源的名字
     *
     * @return String
     */
    String name() default "";
 
 
    /**
     * 资源的key
     *
     * @return String
     */
    String key() default "";
 
 
    /**
     * Key的prefix
     *
     * @return String
     */
    String prefix() default "";
 
 
    /**
     * 给定的时间段
     * 单位秒
     *
     * @return int
     */
    int period();
 
 
    /**
     * 最多的访问限制次数
     *
     * @return int
     */
    int count();
 
 
    /**
     * 类型
     *
     * @return LimitType
     */
    LimitType limitType() default LimitType.CUSTOMER;
 
 
    // 限流方式,默认根据方法名methodName限流
    enum LimitType {
        /**
         * 自定义key
         */
        CUSTOMER,
        /**
         * 根据请求者IP
         */
        IP
    }
}

它可以支持自定义key、ip限流以及根据方法名三种方式进行接口访问频次限流

    然后是注解拦截内容的处理逻辑,这部分代码太长就不贴了,贴一下主要逻辑

主要的逻辑方法

@Around("execution(public * *(..)) && @annotation(com.luhui.utils.annotation.Limit)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        String[] paramNames = signature.getParameterNames();
        Stream<?> stream = ArrayUtils.isEmpty(pjp.getArgs()) ? Stream.empty() : Arrays.stream(pjp.getArgs());
        List<Object> paramValues = stream
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        Limit limitAnnotation = method.getAnnotation(Limit.class);
        Limit.LimitType limitType = limitAnnotation.limitType();
        String key;
        int limitPeriod = limitAnnotation.period();
        int limitCount = limitAnnotation.count();
        switch (limitType) {
            case IP:
                key = getIpAddress();
                break;
            case CUSTOMER:
                key = getLimitKeyValue(limitAnnotation.key(), paramNames, paramValues);
                break;
            default:
                key = StringUtils.upperCase(method.getName());
        }
        ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));
        try {
            String luaScript = buildLuaScript();
            RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
            Number count = redisTemplate.execute(redisScript, keys, String.valueOf(limitCount), String.valueOf(limitPeriod));
            logger.info("Access try count is {} for name={} and key = {}", count, limitAnnotation.name(), key);
            if (count != null && count.intValue() <= limitCount) {
                return pjp.proceed();
            } else {
                return ApiResponse.fail("访问太频繁,请稍后再试");
            }
        } catch (Throwable e) {
            if (e instanceof RuntimeException) {
                throw new RuntimeException(e.getLocalizedMessage());
            }
            throw new RuntimeException("server exception");
        }
    }

 具体的频次控制是通过redis的lua表达式来实现的。

    使用起来也很方便,比如我要限制点赞接口5分钟内不能超过10次,直接在接口上加注解即可

@Limit(key = "uid;assetsId", period = 300, count = 10, name="like", prefix = "limit_")

    最后就可以实现啦!

大家如果觉得有帮助,欢迎关注我!你们的关注就是我的动力,有什么好的建议也可以留言

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1071876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【modprobe_path】RWCTF2022-Digging-into-kernel-2

启动脚本&#xff1a; qemu-system-x86_64 \-kernel bzImage \-initrd rootfs.cpio \-append "consolettyS0 root/dev/ram rdinit/sbin/init quiet kaslr" \-cpu kvm64,smep,smap \-monitor null \--nographic \-s 开启了 smep、smap、kaslr保护。 程序分析 单独创…

ceph版本和Ceph的CSI驱动程序

ceph版本和Ceph的CSI驱动程序 ceph查看ceph版本Ceph的CSI驱动程序 ceph ceph版本和Ceph的CSI驱动程序 查看ceph版本 官网ceph-releases-index Ceph的CSI驱动程序 Ceph的CSI驱动程序 https://github.com/ceph/ceph-csi

Docker项目部署lnmp+wordpress

一.项目环境 公司在实际的生产环境中&#xff0c;需要使用Docker 技术在一台主机上创建LNMP服务并运行Wordpress网站平台。然后对此服务进行相关的性能调优和管理工作。 1.1 环境描述 主机 操作系统 IP地址 主要软件 Docker C…

HTTP长连接实现原理

1. HTTP长连接和短连接的定义 HTTP长连接 浏览器向服务器进行一次HTTP会话访问后&#xff0c;并不会直接关闭这个连接&#xff0c;而是会默认保持一段时间&#xff0c;那么下一次浏览器继续访问的时候就会再次利用到这个连接。在HTTP/1.1版本中&#xff0c;默认的连接都是长连…

《Python 自动化办公应用大全》书籍推荐(包邮送书五本)

前言 随着科技的快速发展和智能化办公的需求增加&#xff0c;Python自动化办公成为了一种趋势。Python作为一种高级编程语言&#xff0c;具有简单易学、功能强大和开放源代码等优势&#xff0c;可以帮助我们更高效地完成日常办公任务。 Python自动化办公还可以帮助我们实现更…

数据结构-二叉查找树(BST)

二叉查找树 需要满足这些规则&#xff1a; 左子节点小于父节点右子节点大于父节点 查找的效率 非常好&#xff0c;每次都能根据大小去舍弃另一半的分支&#xff0c;极大的减少的比对次数 具体的性能&#xff0c;取决于树的层数和平衡程度。 BST树的节点 struct Node {No…

HTML5+CSSday4综合案例二——banner效果

bannerCSS展示图&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wi…

Unity实现设计模式——策略模式

Unity实现设计模式——策略模式 策略模式是一种定义一些列算法的方法&#xff0c;这些所有的算法都是完成相同的工作&#xff0c;只是实现不同。它可以通过相同的方式调用所有的算法&#xff0c;减少各种算法类与使用算法类之间的耦合。 策略模式的 Strategy 类层次为 Contex…

微信黑名单在哪里找出来?掌握4个步骤即可!

微信的黑名单功能可以帮助用户过滤掉一些不友好的联系人&#xff0c;从而在一定程度上限制与这些联系人的互动。在使用微信的过程中&#xff0c;如果不想被一些陌生人或者恶意用户骚扰&#xff0c;那么可以通过将这些人拉入黑名单来阻断联系。 但是如果是和熟人吵架&#xff0…

SpringBoot 如何使用 Micrometer 进行度量和监控

使用Micrometer进行度量和监控Spring Boot应用程序 在构建和维护现代应用程序时&#xff0c;度量和监控是至关重要的&#xff0c;它们可以帮助您了解应用程序的性能、稳定性和可用性。Spring Boot提供了集成Micrometer的功能&#xff0c;使得度量和监控变得非常容易。本文将介…

设计模式 - 访问者模式

目录 一. 前言 二. 实现 三. 优缺点 一. 前言 访问者模式&#xff0c;即在不改变聚合对象内元素的前提下&#xff0c;为聚合对象内每个元素提供多种访问方式&#xff0c;即聚合对象内的每个元素都有多个访问者对象。访问者模式主要解决稳定的数据结构和易变元素的操作之间的…

DCE/RPC协议详解之-数据包请求响应过程

在windows的域环境中有非常多的协议和服务是基于DCE/RPC协议进行实现的,例如NETLOGON,LSA,SAMR,DSSETUP等。因此在 windows的环境下会大量的遇到DCE/RPC协议,因此有必要对该协议有一个初步的了解,这样的话在遇到对应的数据包,则能够比较清楚的还原数据包中发生了什么。本…

长沙旅行见闻实用帖

最近趁着假期&#xff0c;来了趟长沙游&#xff0c;还是有很多值得记录下来的。 众所周知&#xff0c;长沙市是湖南省的省会城市&#xff0c;也是国务院批复确定的长江中游地区重要的中心城市和长株潭城市群中心城市。它位于湖南省中部偏东&#xff0c;湘江下游&#xff0c;辖6…

常见弯道输送机有哪些

提到弯道输送机您可能首先想到的就是弯道滚筒线&#xff0c;其实除了滚筒线之外&#xff0c;也有一些其他线体可以做弯道&#xff0c;下面就为您总结了4种常见的弯道输送机。 1、弯道皮带线&#xff1a;即线体转弯处设计成皮带输送机&#xff0c;这种形式的转弯设计可以实现不同…

Xcode 15 编译出错问题解决

正常升级xcode 15以后发现原来没有出现报错的代码&#xff0c;现在出现了编译错误。&#xff08;如果没有出现请忽略&#xff09;下面教你如何解决这个问题。 1、pod update更新cocoapods&#xff0c;因为其根据xcode15做了很多的更新&#xff0c;保证cocoapods是最新的。 千…

深入理解PKI

安全始终是网络通信的核心议题&#xff0c;PKI提供了一组标准的网络安全组件&#xff0c;可以为通信双方提供加密、完整性保护、认证等安全基础设施。原文: Public Key Infrastructure (PKI) Jacek DylagUnsplash 由于用户名和密码不足以验证用户的身份&#xff0c;因此PKI(公钥…

数据结构 | (二) List

什么是 List 在集合框架中&#xff0c; List 是一个接口&#xff0c;继承自 Collection 。 Collection 也是一个接口 &#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下所示&#xff1a; Iterable 也是一个接口&#xff0c;表示实现该接口的类是可以逐个…

iPhone15手机拓展坞方案,支持手机快充+传输数据功能

手机拓展坞的组合有何意义&#xff1f;首先是数据存储场景&#xff0c;借助拓展坞扩展出的接口&#xff0c;可以连接U盘、移动硬盘等采用USB接口的设备&#xff0c;实现大文件的快速存储或者流转&#xff1b;其次是图片、视频的读取场景&#xff0c;想要读取相机、无人机SD/TF存…

阿里云ESS弹性伸缩的实例配置以及伸缩组规则配置

文章目录 1.配置伸缩组的实例来源信息1.1.创建伸缩组实例来源配置属性1.2.查看创建的伸缩来源配置信息 2.配置伸缩组的触发规则2.1.创建伸缩规则2.2.创建扩展实例的伸缩规则2.3.创建缩减实例的伸缩规则2.4.扩展缩减规则添加完成 1.配置伸缩组的实例来源信息 伸缩组的属性已经配…

自监督DINO论文笔记

论文名称&#xff1a;Emerging Properties in Self-Supervised Vision Transformers 发表时间&#xff1a;CVPR2021 作者及组织&#xff1a; Facebook AI Research GitHub&#xff1a;https://github.com/facebookresearch/dino/tree/main 问题与贡献 作者认为self-supervise…