Springboot AOP接口防刷、防重复提交

日常开发项目中,经常会要求部分接口做防刷、防重复提交的拦截,比如:获取验证码(前端亦可加入倒计时实现,60秒不能再次获取),下单接口 (网络原因或其他什么原因,有时候点击下单支付,感觉没反应 连点了n次, 导致几秒内相同一个东西支付下单了多笔,被银行风控的情况)等等,所以有些接口我们有必要做防重复提交的拦截,今天就来简单聊聊这个话题.

首当其冲肯定是先引入AOP依赖,maven为例 pom.xml

<!-- aop依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- JSON依赖 -->
<dependency>
   <groupId>net.sf.json-lib</groupId>
   <artifactId>json-lib</artifactId>
   <version>2.4</version>
   <classifier>jdk15</classifier>
</dependency>
<!-- redis依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

有了AOP的支持 接下来我们进行自定义注解 NoRepeatSubmit



import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)//作用于方法上
@Retention(RetentionPolicy.RUNTIME)//运行时
public @interface NoRepeatSubmit {

    /**
     * 设置请求锁定时间  默认5秒
     */
    int lockTime() default 5000;
    /**
     * 当发生重复提交时候默认返回的错误信息
     */
    String errMsg() default "重复提交,请 second 秒后重试";
}
RepeatSubmitAspect切面
package com.karo.unicorn.aspect;

import com.karo.unicorn.annotation.NoRepeatSubmit;
import com.karo.unicorn.common.result.ResultAPI;
import com.karo.unicorn.utils.ObjectUtil;
import com.karo.unicorn.utils.http.HttpContextUtils;
import com.karo.unicorn.utils.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * @ClassName RepeatSubmitAspect
 * @Description TODO :
 * @Author :Panguaxe
 * @Date 2020-05-26 16:08
 * @Version V1.0
 */
@Slf4j
@Aspect
@Component
public class RepeatSubmitAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointcut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("@annotation(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        log.warn("AOP防重复提交设置的加锁时间:{}", noRepeatSubmit.lockTime());
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 请求的方法参数值  POST的JSON请求request.getParameter()是获取不到值的
        Object[] args = joinPoint.getArgs();
        JSONObject requestParams = ObjectUtil.isNotBlank(args) ? JSONObject.fromObject(args[0]) : null;
        log.warn("AOP防重复提交[" + request.getRequestURI() + "]接口的请求参数:{}",requestParams);
        // 此处可以用token或者JSessionId  TODO 或者使用用户ID作为标识
        //String token = request.getHeader("Authorization");//根据自己业务替换为你的唯一标识
        //String token = request.getHeader("token");//根据自己业务替换为你的唯一标识
        String key = JSONUtil.getValNetsf(requestParams, "userId") + request.getServletPath();
        log.warn("AOP防重复提交,加锁Key[" + key + "]:{}",requestParams);
        boolean isSuccess = tryLock(key, noRepeatSubmit.lockTime());
        log.warn("AOP防重复提交,是否放行:{}",isSuccess ? "放行" : "拦截重复提交");
        if (!isSuccess) {// 获取锁失败,认为是重复提交的请求
            return new ResultAPI().error(noRepeatSubmit.errMsg().replace("second",String.valueOf(noRepeatSubmit.lockTime()/1000)),"");
        }
        // 获取锁成功, 执行进程
        Object result;
        try {
            result = joinPoint.proceed();
        } finally {
            redisTemplate.delete(key);// 解锁
        }
        return result;
    }

    /**
     * @MethodName: tryLock
     * @Param: [key --- key值, lockSeconds  时长]
     * @Return: boolean       是否获取到
     * @Author: Panguaxe
     * @Date: 2020-05-26 16:13
     * @Description: TODO          最终加强分布式锁
     */
    private boolean tryLock(String key, int lockSeconds) {
        //lambda表达式
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis() + lockSeconds + 1;
            log.warn("失效时间:{}", expireAt);
            Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(expireAt).getBytes());
            if (acquire) {
                return true;
            }
            byte[] value = connection.get(key.getBytes());
            if (Objects.nonNull(value) && value.length > 0) {
                if (Long.parseLong(new String(value)) < System.currentTimeMillis()) {
                    // 如果锁已经过期
                    byte[] oldValue = connection.getSet(key.getBytes(),String.valueOf(System.currentTimeMillis() + lockSeconds + 1).getBytes());
                    // 防止死锁
                    return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                }
            }
            return false;
        });
    }
}

其中ResultAPI为统一返回结果

使用示例:

    //@Validated({Update.class,Create.class}) //也可以注释掉 此注解为个人测试自定义参数校验注解用
    @NoRepeatSubmit(lockTime = 3000)//3000即3秒内不能重复提交
    @ExceptionHandler(value = Exception.class)//此处可注释掉 统一异常处理
    @PostMapping("noRepeatSubmit")
    public APIResult noRepeatSubmit(@RequestBody @Validated({Update.class,Create.class})  UserInfo userInfo){
        log.warn("请求参数:{}", JSON.toJSONString(userInfo));
        APIResult result = new APIResult();
        try {
            result.success(userInfo);//因为仅测试防重复提交拦截  不做业务处理 直接请求参数返回 仅验证3秒内同一用户 同一接口不能重复提交
        }catch (Exception e){
            result.systemError(e.getMessage());
        }
        return result;
    }

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值