通过注解开发来实现下单接口的防重提交

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 通过注解开发来实现下单接口的防重提交

公众号merlinsea


  • 下单流程设计
  • 首先需要生成token,这个token作为唯一标识这个用户下的这笔订单
  • 其次携带token和订单信息创建订单并完成支付功能。


640.jpg


  • 如何设计才能更加解耦呢?
  • 第5步的过程可以通过注解切面来实现判断是否存在token的步骤下单支付的流程解耦,通过拦截器将用户携带token和订单信息的请求拦截下来,判断是否携带了token,如果是且验证成功则放行,否则不放行。


640.jpg


  • 代码实现
  • 编写自定义注解
  • 这里的自定义注解的核心作用是【标识】哪个方法需要注入切面类,其验证的类型分为方法参数类型和token类型,即有该自定义注解的方法就需要验证request-token。

         
/**
 * 自定义防重提交注解,用于标识是这次request的类型是方法参数还是token类型
 * @author lianglin
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
    /**
     * 防重提交类型:
     * 1、方法参数类型 : key=ip+method+param
     * 2、token类型:key=accountNo+token
     */
    enum Type {
        PARAM,
        TOKEN
    }
    /**
     * 默认防重提交是方法参数
     * @return
     */
    Type limitType() default Type.PARAM;
    /**
     * 加锁过期时间是5秒
     * @return
     */
    long LockTime() default 5;
}


  • 编写切面类【核心重点】
  • 切面类的核心作用就是配合自定义注解@RepeatSubmit,在有自定义注解的地方注入这个切面方法并执行
  • @PointCut代表切入点
  • @Around代表核心切入逻辑
/**
 * 定义切面类
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 定义 @Pointcut注解表达式,
     *  方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这)
     *  方式二:execution:一般用于指定方法的执行
     * 
     * @param repeatSubmit
     */
    @Pointcut("@annotation(repeatSubmit)")
    public void pointcutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
    }
    /**
     * 环绕通知, 围绕着方法执行
     * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     *
     * 方式一:单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以
     * 方式二:用@Pointcut和@Around联合注解也可以(我们采用这个)
     *
     *
     * 两种方式
     * 方式一:加锁 固定时间内不能重复提交
     * <p>
     * 方式二:先请求获取token,这边再删除token,删除成功则是第一次提交
     *
     * @param joinPoint
     * @param noRepeatSubmit
     * @return
     * @throws Throwable
     * 所有@RepeatSubmit注解的方法都添加该环绕通知
     */
    @Around("pointcutNoRepeatSubmit(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit noRepeatSubmit) throws Throwable {
        //获取request请求
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        boolean res=false;
        String type = noRepeatSubmit.limitType().name();
        if (type.equals(RepeatSubmit.Type.PARAM.name())) {
            //方式一方法参数            TODO
        } else {
            //方式二,令牌形式
            String requestToken = request.getHeader("request-token");
            if (StringUtils.isBlank(requestToken)) {
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
            }
            LoginUser loginUser = LoginInterceptor.threadLocal.get();
            //"order:submit:%s:%s"
            String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getAccountNo(),requestToken);
            /**
             * 提交表单的token key
             * 方式一:不用lua脚本获取再判断,之前是因为 key组成是 order:submit:accountNo, value是对应的token,所以需要先获取值,再判断
             * 方式二:可以直接key是 order:submit:accountNo:token,然后直接删除成功则完成
             */
            res = stringRedisTemplate.delete(key);
        }
        if (!res) {
            //删除失败,说明redis中没有这个token
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
        }
        System.out.println("目标方法执行前");
        //被自定义注解标识的方法继续执行
        Object object = joinPoint.proceed();
        System.out.println("目标方法执行后");
        return object;
    }
}


  • 注解方式解决防重提交问题的测试
  • 业务controller层测试接口
  • 这个接口用@RepeatSubmit()标识,spring就会在执行这个方法前先执行切面类中的around逻辑代码,根据around逻辑代码的执行结果来判断是否需要继续执行testRequestToken代码。
/**
 * 测试下单token的获取
 * @return
 */
@GetMapping("test")
@RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)
public JsonData testRequestToken(){
    return JsonData.buildSuccess("测试成功");
}
  • 测试结果
  • 不携带request-token的情况
  • 结论:下单之前必须先获取下单token,否则校验失败

640.jpg

  • 第一次携带request-token的情况
  • 结论:携带了下单token且第一次使用这个token才起作用

640.jpg

  • 第二次携带同一个token的情况  
  • 结论:多次使用同一个下单token会导致校验失败的,因此避免了重复下单的问题

640.jpg

  • 总结一下自定义注解的实现逻辑
  • 自定义注解,需要指定这个注解作用在哪些地方 即@Target的功能
  • 定义切面类
  • 通过@Aspect告诉spring这个是一个切面类并交给spring ioc容器管理。
  • 配合自定义注解指明该切面逻辑怎么执行
  • @Around是核心执行逻辑。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
支付系统34----支付成功异步通知,处理重复通知,我们在我们程序当中找到处理订单的processOrder方法,我们要在更新订单状态和记录日志之前,先处理重复通知
支付系统34----支付成功异步通知,处理重复通知,我们在我们程序当中找到处理订单的processOrder方法,我们要在更新订单状态和记录日志之前,先处理重复通知
|
4天前
|
前端开发 Java
支付系统20-----支付宝支付-----统一收单下单并支付页面接口----定义controller,跨域注解,统一收单下单并支付页面接口的创建,打印日志的注解
支付系统20-----支付宝支付-----统一收单下单并支付页面接口----定义controller,跨域注解,统一收单下单并支付页面接口的创建,打印日志的注解
|
20天前
|
前端开发 JavaScript UED
如何防止接口重复提交?
本文讨论了前端如何防止接口重复提交的问题。主要方法包括:1) 禁用提交按钮,用户点击后立即禁用并显示加载状态,请求完成后恢复;2) 使用防抖或节流技术限制请求发送的频率;3) 生成请求标识符,后端检查已处理过的请求;4) 利用状态管理库(如Redux, Vuex)跟踪请求状态,避免重复提交;5) 接口锁定,通过变量记录请求状态,防止并发请求。这些策略可单独或组合使用,以确保请求的准确性和系统稳定性。
|
2月前
|
NoSQL Java API
SpringBoot项目中防止表单重复提交的两种方法(自定义注解解决API接口幂等设计和重定向)
SpringBoot项目中防止表单重复提交的两种方法(自定义注解解决API接口幂等设计和重定向)
187 0
|
11月前
|
Java API 数据库
一张思维导图带你学会使用SpringBoot异步任务实现下单校验库存
一张思维导图带你学会使用SpringBoot异步任务实现下单校验库存
128 0
|
12月前
|
存储 NoSQL Redis
下单接口防重提交问题
下单接口防重提交问题
|
12月前
|
存储 监控 安全
转账通缩功能开发实例源码规则解析
转账通缩功能开发实例源码规则解析
|
NoSQL Java 数据库
java接口防重提交如何处理
举一个最简单的例子:日常开发中crud在业务系统中普遍存在,在服务端没有做任何处理,客户端没有做节流、防抖等限流操作时,同一秒一个用户点了两次新增按钮,导致数据库中存在同样两条数据,其结果可想而知,同理修改、删除同样的道理;查询本身具有幂等性,但是在同一秒钟同样的操作,查询多次和一次,有区别吗?区别大了去了,不谈用户体验如何,光是网络开销、流量占用、带给服务器的压力等等,生产中一点小的问题,如何不及时处理,可能会引发灾难性bug。
|
SQL 负载均衡 NoSQL
【防止重复下单】分布式系统接口幂等性实现方案
【防止重复下单】分布式系统接口幂等性实现方案
1409 0
【防止重复下单】分布式系统接口幂等性实现方案
|
消息中间件 Dubbo 测试技术
Rest 方式测试支付下单和支付回调|学习笔记
快速学习 Rest 方式测试支付下单和支付回调
179 0
Rest 方式测试支付下单和支付回调|学习笔记