当前位置: 首页 > news >正文

java自定义注解防重提交

很多场景下,接口需要做防重提交处理,比如下单,用户第一次提交后不能连续提交,从而避免接口被攻击;

1、java 注解介绍

Annotation(注解) 从JDK 1.5开始, Java增加了对元数据(MetaData)的⽀持,也就 是 Annotation(注解)。 注解其实就是代码⾥的特殊标记,它⽤于替代配置⽂件 常⻅的很多 @Override、@Deprecated等 

什么是元注解 注解的注解,⽐如当我们需要⾃定义注解时 会需要⼀些元注解

(meta-annotation),如@Target和 @Retention

java内置4种元注解

@Target 表示该注解⽤于什么地⽅

ElementType.CONSTRUCTOR ⽤在构造器

ElementType.FIELD ⽤于描述域-属性上

ElementType.METHOD ⽤在⽅法上

ElementType.TYPE ⽤在类或接⼝上

ElementType.PACKAGE ⽤于描述包

@Retention 表示在什么级别保存该注解信息

RetentionPolicy.SOURCE 保留到源码上

RetentionPolicy.CLASS 保留到字节码上

RetentionPolicy.RUNTIME 保留到虚拟机运⾏时(最多, 可通过反射获取)

@Documented 将此注解包含在 javadoc 中

@Inherited 是否允许⼦类继承⽗类中的注解

@interface ⽤来声明⼀个注解,可以通过default来声明参数的默认值 ⾃定义注解时,⾃动继承了java.lang.annotation.Annotation 接⼝ 通过反射可以获取⾃定义注解 

以下代码自定义了一个注解


/**
 * 自定义防重提交注解
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {

    // 设置这个自顶一个注解里面的几个属性,即我们写注解的时候后面括号配置的参数


    /**
     * 防重提交,支持两种,一个方法参数,一个是令牌
     */
    enum  Type {PARAM,TOKEN }

    /**
     * 默认防重提交是方法参数
     * @return
     */
    Type limitType() default Type.PARAM;

    /**
     * 加锁过期时间,默认是 5s
     *     比如通过redis的key来校验是否重复提交,
     *     这个5s就是设置的key的过期时间
     * @return
     */
    long lockTime() default 5;
}

2、AOP切面编程

注解具体的效果,通过是基于AOP 切面编程来实现;注解标记的位置,作为切面编程的切入点

横切关注点: 对哪些⽅法进⾏拦截,拦截后怎么处理,这些就叫横切关 注点 ⽐如 权限认证、⽇志、事物

通知 Advice :在特定的切⼊点上执⾏的增强处理 做啥? ⽐如你需要记录⽇志,控制事务 ,提前编写好通⽤ 的模块,需要的地⽅直接调⽤ ⽐如防复提交判断逻辑 类型

@Before前置通知 在执⾏⽬标⽅法之前运⾏

@After后置通知 在⽬标⽅法运⾏结束之后

@AfterReturning返回通知 在⽬标⽅法正常返回值后运⾏

@AfterThrowing异常通知 在⽬标⽅法出现异常后运⾏

@Around环绕通知 在⽬标⽅法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,

像事务,⽇志等都是环绕通知,注意编 程中核⼼是⼀个ProceedingJoinPoint,

需要⼿动执 ⾏ joinPoint.procced()

连接点 JointPoint :要⽤通知的地⽅,业务流程在运⾏过程中需要插⼊切⾯的 具体位置, ⼀般是⽅法的调⽤前后,全部⽅法都可以是连接点 

spring 的AOP 执行顺序:

5.0 之后

3、环绕通知中实现防重提交功能

防重提交的⽅式 :

token令牌⽅式

ip+类+⽅法⽅式



/**
 * 定义切面类
 */

@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

//    @Autowired
//    private RedissonClient redissonClient;
    /**
     * 定义切入点
     *    方法一: 可以通过pointcut表达式来实现
     *
     *    方法而: 此处使用自定义注解来实现,即在有repeatsubmit 注解的地方,就是切入点
     */

    @Pointcut("@annotation(repeatSubmit)")
    public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit){

    }

    /**
     * 环绕通知, 围绕着方法执行
     *
     * @param joinPoint
     * @param
     * @return
     * @throws Throwable
     * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     * <p>
     * 方式一:单用 @Around("execution(* net.cloud.controller.*.*(..))")可以
     * 方式二:用@Pointcut和@Around联合注解也可以(我们采用这个)
     * <p>
     * <p>
     * 两种方式
     * 方式一:加锁 固定时间内不能重复提交
     * <p>
     * 方式二:先请求获取token,这边再删除token,删除成功则是第一次提交
     */
    @Around("pointCutNoRepeatSubmit(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        //用于记录成功或者失败
        boolean res = false;


        //防重提交类型
        String type = repeatSubmit.limitType().name();
        if(type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())){
            //方式一,参数形式防重提交
            long lockTime = repeatSubmit.lockTime();
            String ipAdr = CommonUtil.getIpAddr(request);
            MethodSignature methodSignature =  (MethodSignature)joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            String className = method.getDeclaringClass().getName();
            String key ="order-service:repeat_submit:" + CommonUtil.MD5( String.format("%s-%s-%s-%s", ipAdr,className,method,accountNo));
            // 加锁 todo
             res =  redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS);

//            RLock lock = redissonClient.getLock(key);
//            // lock.lock(); 这种方式没有返回值
//
//            // 尝试加锁,最多等待2s,上锁以后 locktime 以后自动解锁
//            res = lock.tryLock(2,lockTime,TimeUnit.SECONDS);


        }else {
            //方式二,令牌形式防重提交;这种方式的令牌是在之前设置进了header里面 --getOrderToken

            String requestToken = request.getHeader("request-token");
            if(StringUtils.isBlank(requestToken)){
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
            }

            String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY,accountNo,requestToken);

            /**
             * 提交表单的token key
             * 方式一:不用lua脚本获取再判断,之前是因为 key组成是 order:submit:accountNo, value是对应的token,所以需要先获取值,再判断
             * 方式二:可以直接key是 order:submit:accountNo:token,然后直接删除成功则完成
             */
            res = redisTemplate.delete(key);

        }

        if(!res){
          log.error("请求重复");
          return null;
        }


        log.info("环绕通知执行前");

        Object obj = joinPoint.proceed();

        log.info("环绕通知执行后");

        return obj;

    }
}

相关文章:

  • 4-Arm PEG-Aldehyde,4ARM-PEG-CHO,四臂-聚乙二醇-醛基修饰蛋白质用试剂
  • C语言预处理、宏定义
  • Flink 成长之路专栏 - 导读目录
  • 软考高级系统架构设计师系列论文五十:论SOA在企业集成架构设计中的应用
  • spring boot企业网站设计与实现毕业设计源码211750
  • springboot基于JavaWeb的疫苗接种管理系统-JAVA.JSP【数据库设计、毕业设计、源码、开题报告】
  • vue组件间传值的六种方法
  • 2022牛客杭电多校dp题汇总
  • 记一次内网靶场渗透测试
  • 案例分析重点知识 变更文档配置收尾
  • Tomcat基本使用以及项目部署。
  • 编译redis5.0.4报错/usr/bin/ld: cannot find -latomic
  • 项目 - AES对称加密算法加密和解密设备联动码
  • ROS2在ROS1 的基础的改进点
  • “对症下药”很重要——u盘数据恢复的四种方法
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • Android单元测试 - 几个重要问题
  • CentOS7简单部署NFS
  • HomeBrew常规使用教程
  • Java的Interrupt与线程中断
  • JS变量作用域
  • Python_网络编程
  • react 代码优化(一) ——事件处理
  • 关于字符编码你应该知道的事情
  • 猴子数据域名防封接口降低小说被封的风险
  • 前端面试题总结
  • 我建了一个叫Hello World的项目
  • 消息队列系列二(IOT中消息队列的应用)
  • 一道闭包题引发的思考
  • 06-01 点餐小程序前台界面搭建
  • Android开发者必备:推荐一款助力开发的开源APP
  • gunicorn工作原理
  • ​2020 年大前端技术趋势解读
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • #ifdef 的技巧用法
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (待修改)PyG安装步骤
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (十八)三元表达式和列表解析
  • (四)库存超卖案例实战——优化redis分布式锁
  • (算法)前K大的和
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • .jks文件(JAVA KeyStore)
  • .net Application的目录
  • .Net Core缓存组件(MemoryCache)源码解析
  • .NET 服务 ServiceController
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [145] 二叉树的后序遍历 js
  • [30期] 我的学习方法
  • [Android]RecyclerView添加HeaderView出现宽度问题
  • [bzoj4240] 有趣的家庭菜园