core拦截重复提交数据_重复请求怎么办?

本文介绍了在开发中如何通过AOP和Redis来解决接口重复请求的问题,包括自定义注解、AOP切面处理以及分布式锁的实现,确保同一时间只有一个请求能执行特定接口,防止数据混乱和资源浪费。
摘要由CSDN通过智能技术生成

Web开发中,重复请求的问题是不能避免的。

具体问题要具体分析,我们先来看一下,重复请求有几种。

  1. 首先有一个接口,假设有一个人手速很快,服务器响应都跟不上他的手速,请求过来后,这个人没有休息,以非人的速度再次点击,服务器在一瞬间收到了很多同样的请求,一时间服务器不知所措...
  2. 假如有一个发短信验证码的接口,又有一个手速和之前那个不相上下的人,而这个接口又没有拦截或者只有前端拦截,而这个人绕过了你的拦截,然后短信就嗖嗖嗖的发出去,钱也哗哗哗的流出去。
  3. 还是有一个接口,只不过这个接口同时有很多人请求,按理说每个用户的调用是相互隔离的,不会有问题,可是如果他们操作同一张表,即在数据操作上有交集,这时数据就会变的乱七八糟。

其实一提到重复请求,大家肯定都能想到Redis,一般都用Redis来作为分布式锁,来处理重复请求等等。

大概的步骤如下

  1. 一进到接口里,先查一下Redis里有没有指定key的数据
  2. 如果有那就直接返回提示信息,没有的话,就在Redis放个数据,然后执行具体的业务操作
  3. 执行完毕,删除Redis中这个key的数据。

这个key一般都是根据业务来设定的。

升级一下

Redis遇到AOPAnnotation会发生什么?

其实上面那样的方式我也用了很久,也没有觉得哪里不对。如果一个接口要用分布式锁,我就按照上面的写一套,甚至为了偷懒,我在Idea自定义了一个Live Template

程序员就应该有讨厌重复劳动的高贵品质,重复劳动就是浪费生命。

首先

Java中,注解最好用了,需要加锁,加一个注解就好了,多完美。所以先定义一个注解。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Lock {

    /**
     * @return 锁的名称
     */
    String name() default "";

    /**
     * @return 锁定时间,默认3秒
     */
    int value() default 3;

    /**
     * @return 是否强制模式
     * true 一次请求后,必须经过锁定时间后才可访问
     * false 方法执行完后会自动删除锁,即方法执行完就可以再次访问
     */
    boolean hard() default false;

    /**
     * @return 是否是分布式锁
     * true 分布式锁 所有用户同一把锁,即key相同
     * false 每个用户锁不同,防止单个用户重复提交(提交过快 , 如点击按钮太快)
     */
    boolean distributed() default true;
}

想法很美好,现实还是需要自己努力。有了注解,我们要对使用了注解的方法进行处理。

AOP

其实一开始我想到的是拦截器,可是最后还是觉得AOP的环绕增强最适合。

@Slf4j
@Aspect
@Component
public class RepeatRequestAspect {

    @Resource
    private RedisUtil redisUtil;

    /**
     * 切面
     */
    @Pointcut("@annotation(top.lww0511.redislock.annotation.Lock)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) {
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        String ipAddr = IPUtils.getIpAddr(request);
        String servletPath = request.getServletPath();

        Method method = ((MethodSignature) point.getSignature()).getMethod();
        Lock lock = method.getAnnotation(Lock.class);
        int lockTime = lock.value();
        boolean hard = lock.hard();
        boolean distributed = lock.distributed();
        String lockKey = RedisKey.REQUEST_PREFIX + servletPath + (distributed ? "" : ("_" + ipAddr));
        lockKey = StringUtils.isEmpty(lock.name()) ? lockKey : RedisKey.REQUEST_PREFIX + lock.name() + (distributed ? "" : ("_" + ipAddr));
        log.info("RepeatRequestAspect_around_lockTime:{}, hard:{}, lockKey:{}", lockTime, hard, lockKey);
        String value = redisUtil.getValue(lockKey);
        if (!StringUtils.isEmpty(value)) {
            Assert.isTrue(false, "操作太频繁了,请休息一会再操作!");
        }
        redisUtil.setValue(lockKey, lockKey, lockTime, TimeUnit.SECONDS);
        Object proceed = null;
        try {
            proceed = point.proceed();
        } catch (Throwable throwable) {
            log.error("RepeatRequestAspect_around_throwable:{}", throwable);
        } finally {
            if (!hard) {
                redisUtil.remove(lockKey);
            }
        }
        return proceed;
    }
}

没有复杂的代码,就是对所有加了Lock注解的方法加强,获取注解的值,比如锁定时间,是否强制模式,是否是分布式锁。

  • 强制模式通俗讲就是,假如我设置锁定时间10秒,那么必须10秒后才能访问,就算提前执行完了也不能访问。不会自动删除作为锁的Redis中的数据。
  • 分布式锁就简单易懂了,设置为分布式锁,那么同一方法的key是相同的,即一个方法同一时间只能一个用户访问,否则只是单纯的防御那些手速超快的强人。
  • 至于注解里的name(锁的名称),其实没什么用,就是任性...

使用方式

要配置Redis

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0

加入依赖

<dependency>
    <groupId>top.lww0511groupId>
    <artifactId>redis-lockartifactId>
    <version>1.0.2version>
dependency>

在方法上添加注解 Lock

@Lock(value = 10, distributed = false, hard = true)
@GetMapping(value = "/hi", name = "log")
public HttpResult hello() {
    return HttpResult.success("Hello");
}
  • @Lock:默认的,表示分布式,锁定时间3秒,非强制模式
  • @Lock(value = 10, distributed = false, hard = true):表示不是分布式的,锁定时间10秒,且是强制模式。

其实不是特殊需求,一般一个@Lock就够了。

效果

非分布式绑定了IP,并且是强制模式,虽然执行完了,方法还是不能访问的。
93e987cd3bc3bc5de8a81d7bd09e0d63.png
ba5b3df975eb307acaa346af4acde5f0.png
当只有一个@Lock

没有绑定IP,且虽然锁定时间3秒,但是方法执行完毕,自动释放了。63c312f7feaff7d34ea890005911ac38.png

d71199615cf4423cef2808e9e493aea8.png

最后

其中还有一些工具类,全局异常拦截器等等,还有META-INF/spring.factories

详细代码可以看GitHub
地址:https://github.com/LerDer/redis-lock

这个已经发布到Maven中心仓库了,所以直接添加依赖就可以使用了。不需要自己打包发布到私服。

  • 坐标

<dependency>
    <groupId>top.lww0511groupId>
    <artifactId>redis-lockartifactId>
    <version>1.0.2version>
dependency>

欢迎大家关注我的公众号,共同学习,一起进步。加油?

12f090894ba7a5feb260e9dceecead8c.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值