使用aop校验重复提交

背景

前后端分离的项目会经常产生重复提交的数据,前端做了各种防重提交的方法,比如提交后置灰,但偶尔还是会有重复的数据。这里提供一个后端防重复提交的方案。

主要思路

对接口入参进行校验,如果单位时间内提交的参数一样,就表示是重复提交,后面的请求直接返回

具体实现

1、新建一个注解

     作用的标识接口需要防重复提交。

    参数second,禁止时长,表示该时间内禁止再提交,默认是1秒,可以针对接口自定义。

    参数value是对防重接口起名,需要至少在类中是唯一的。

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 Resubmit {

    /**
     * 全局唯一的名称
     * @return  名称
     */
    String value() default "";

    /**
     * 禁止提交时长 单位秒
     * @return 时长
     */
    int second() default 1;
}

2、防重主要实现

主要使用了spring aop和分布式锁来实现防重校验,分布式锁不在本次的讨论范围内,具体实现可以根据各自的方式,本人是使用redis来实现的。

    private static final String PRIVATE_KEY = "resubmit";
    @Autowired
    private LockUtil lockUtil;

    @Pointcut("@annotation(com.ydj.annotation.Resubmit)")
    public void cutService(){

    }

    @Around("cutService()")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable{
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        String className = joinPoint.getTarget().getClass().getName();

        Resubmit resubmit =  method.getAnnotation(Resubmit.class);

        //获取接口入参
        Object requestBodyArg = getRequestBodyParam(method.getParameterAnnotations(), joinPoint.getArgs());

        if (null == requestBodyArg) {
            //如果没有入参有 requestBody注解的, 暂不支持重复提交校验
            return joinPoint.proceed(joinPoint.getArgs());
        }

        //禁止重复提交时间
        int second = resubmit.second();

        //获取分布式锁key
        String key = getKey(className, methodName, resubmit.value(), requestBodyArg);
        if (!lockUtil.tryGetDistributedLock(key, UUID.randomUUID().toString(), second)) {
            throw new BizException("禁止重复提交");
        }
        return joinPoint.proceed(joinPoint.getArgs());
    }

3、获取接口参数

我们系统中的提交接口一般使用的是post application/json方式,如果有其它方式这边需要进行修改

 /**
 * 找到RequestBody的参数
 * @param annotations 方法参数注解
 * @param arguments 参数
 * @return 带有RequestBody的参数
 */
 private Object getRequestBodyParam(Annotation[][] annotations ,  Object[] arguments){
     for (int i = 0; i < annotations.length; i++) {
         Annotation[] argAnnotations = annotations[i];
         for (Annotation argAnnotation : argAnnotations) {
            if (argAnnotation instanceof RequestBody) {
                 return arguments[i];
             }
         }
     }
     return null;
 }

4、获取分布式锁的key

入参按key1=value1&key2=value2这样的形式,并且key是自然排序的。

urlParams 拼上resubmitValue是为了防止不同的接口参数是一样的,再在前面拼上className和methodName是做进一步保证不会串到其它接口

最后再对urlParams进行base64编码用作分布式锁的key。

   /**
     * 获取分布式锁的key
     * @param className 类名
     * @param methodName 方法名
     * @param resubmitValue 防重提交value
     * @param requestBodyArg 参数
     * @return key
     */
    private String getKey(String className, String methodName, String resubmitValue, Object requestBodyArg){
        Map<String, Object> paramMap = JSON.parseObject(JSON.toJSONString(requestBodyArg), new TypeReference<Map<String, Object>>(){});
        List<String> paramList=new ArrayList<>(paramMap.size());

        for (Map.Entry<String, Object> entry:paramMap.entrySet()){
            if (null == entry.getValue() || StringUtils.isBlank(String.valueOf(entry.getValue()))){
                continue;
            }
            paramList.add(entry.getKey()+"="+entry.getValue());
        }
        Collections.sort(paramList);
        String urlParams = className + ":" + methodName + ":" + resubmitValue + ":" + String.join("&", paramList);
        try {
            return RSAUtils.sign(urlParams, PRIVATE_KEY, "UTF_8");
        } catch (CodecSignException e) {
            return urlParams;
        }
    }

5、用法

用法很简单了,只需要在防重提交的接口上加上@Resubmit注解就行

    @Resubmit("testResubmit")
    @RequestMapping(value = "/testResubmit", method = RequestMethod.POST)
    public String testResubmit(@RequestBody ResubmitReq resubmitReq){
        return JSONObject.toJSONString(resubmitReq);
    }

总结

以上代码已经经过本人测试过,如有问题可以联系我改正,谢谢。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值