切面实现下单请求防重提交功能(自定义注释@repeatSubmit)

这篇具有很好参考价值的文章主要介绍了切面实现下单请求防重提交功能(自定义注释@repeatSubmit)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

该切面功能适用场景

  • 下单请求多次提交,导致生成多个相同的订单

解决方案

  • 前端解决:限制点击下单按钮为1次后失效。不足:用户体验下降,能绕过前端

  • 后端解决:防重提交切面解决,自定义注释实现该功能(如下)

    • 步骤:
      • 自定义注释类RepeatSubmit
      • 创建切面并有该注释绑定,在切面类实现防重提交功能:
        • 方式一:引入redission进行加锁5秒,原理redis的setAbsent
        • 方式二:将token存入redis中,下单成功删除token,下单前需要调用获取token接口才能成功下单(类似于加锁,和方式一原理相同)
  • RepeatSubmit文章来源地址https://www.toymoban.com/news/detail-633652.html

/**
 * 自定义防重提交
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /**
     * 防重提交类型。  方法、令牌
     */
    enum Type {PARAM, TOKEN}

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

    /**
     * 加锁过期时间,默认5秒
     * @return
     */
    long lockTime() default 5;

}

  • 自定义切面类
/**
 * 定义一个切面类
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 定义 @Pointcut注解表达式,
     * 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这)
     */
    @Pointcut("@annotation(repeatSubmit)")
    public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {

    }

    /**
     * 环绕通知, 围绕着方法执行
     *
     * @param joinPoint
     * @param repeatSubmit
     * @return
     * @throws Throwable
     * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     */
    @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 ipAddr = CommonUtil.getIpAddr(request);

            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

            Method method = methodSignature.getMethod();
            String className = method.getDeclaringClass().getName();
            String key = "order-server:repeat_submit"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ipAddr, className, method, accountNo));

            // 加锁
            //res = redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS);
            RLock lock = redissonClient.getLock(key);

            // 尝试加锁,最多等待2秒,上锁以后5秒自动解锁 [lockTime默认为5s, 可以自定义]
            res = lock.tryLock(2, lockTime, TimeUnit.SECONDS);

        } else if (type.equalsIgnoreCase(RepeatSubmit.Type.TOKEN.name())) {
            //方式二,令牌形式防重提交
            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
             * 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;
    }
}

  • RedissionConfiguration配置类(用于加锁)
@Configuration
public class RedissionConfiguration {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private String redisPort;


    @Value("${spring.redis.password}")
    private String redisPwd;


    /**
     * 配置分布式锁的redisson
     * @return
     */
    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();

        //单机方式
        config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+redisHost+":"+redisPort);

        //集群
        //config.useClusterServers().addNodeAddress("redis://192.31.21.1:6379","redis://192.31.21.2:6379")

        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }

    /**
     * 集群模式
     * 备注:可以用"rediss://"来启用SSL连接
     */
    /*@Bean
    public RedissonClient redissonClusterClient() {
        Config config = new Config();
        config.useClusterServers().setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
              .addNodeAddress("redis://127.0.0.1:7000")
              .addNodeAddress("redis://127.0.0.1:7002");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }*/

}

  • 使用说明:在下单接口标注@RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)
  • 或者@RepeatSubmit(limitType = RepeatSubmit.Type.PARAM)
    /**
     * 下单前获取令牌,用于防重提交
     * @return
     */
    @GetMapping("token")
    public JsonData getOrderToken() {

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

        String token = CommonUtil.getStringNumRandom(32);

        String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, token);
        // token 过期时间30分钟
        redisTemplate.opsForValue().set(key, String.valueOf(Thread.currentThread().getId()), 30, TimeUnit.MINUTES);

        return JsonData.buildSuccess(token);
    } 	


	@PostMapping("confirm")
    @RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)
    public void confirmOrder(@RequestBody ConfirmOrderRequest orderRequest, HttpServletResponse response) {
        // TODO 下单业务
    }

到了这里,关于切面实现下单请求防重提交功能(自定义注释@repeatSubmit)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • flask 与 小程序 下单提交 订单列表展示

    params: JSON.parse(e.data) JSON.parse()是JavaScript中的一个方法,用于将一个JSON字符串解析为对应的JavaScript对象。它接受一个JSON字符串作为参数,并返回一个JavaScript对象。 在你提供的引用中,JSON.parse()的用法如下: JSON.stringify()是JavaScript中的另一个方法,用于将一个JavaScript对象转换

    2024年01月21日
    浏览(30)
  • JetBrains全家桶:如何自定义实现类TODO注释?

    TODO注释大家应该都用过,在注释开头打上TODO的话,软件下方的TODO选项卡里就可以自动筛选出你打了TODO的注释,你可以点击里面对应的注释来实现快速跳转。 jetbrains全家桶(如Pycharm、IntelliJ idea等)基本都默认支持这一特殊的注释,那如何自定义属于自己的TODO注释呢? 效果

    2024年02月15日
    浏览(29)
  • vue2,使用element中的Upload 上传文件,自定义上传http-request上传,上传附件支持多选,多个文件只发送一次请求,代码里有注释

    复制直接使用,组件根据multiple是否多选来返回附件内容,支持多选就返回数据附件,则返回一个附件对象。

    2024年02月10日
    浏览(41)
  • 防重放、防篡改攻击的实现(Java版)

    1、重放攻击:请求被攻击者获取,并重新发送给认证服务器,从而达到认证通过的目的。 2、篡改攻击:请求被攻击者获取,修改请求数据后,并重新发送给认证

    2024年01月17日
    浏览(46)
  • Qt之QTableView自定义排序/过滤(QSortFilterProxyModel实现,含源码+注释)

    本文过滤条件为行索引取余2等于0时返回true,且从下图中可以看到,奇偶行是各自挨在一起的。 下图添加两列条件(当前数据大于当前列条件才返回true,且多个列条件为且关系);下方添加条件分别为,”0列,条件值50“,”2列条件值40“,综合下来为0列值大于50且2列值大

    2024年02月05日
    浏览(28)
  • 05、SpringCloud -- 秒杀按钮、秒杀请求流程(各种请求到后台的判断、减库存、下单数据和次数保存)

    需求:点击抢购的按钮,在未规定的时间段内不可点击 代码实现: vue的JS实现: 在vue的js中 , prop 是父组件用来传递数据的一个自定义属性 点击立即秒杀后,从前端发送请求到后端接受的一系列判断 前端 携带数据发送到后端 后端 Seckill-api domain 添加两个实体类,秒杀订单信

    2024年02月08日
    浏览(22)
  • Python : 使用python实现学生管理系统的功能,详细注释

    学生描述:姓名、年龄、成绩 学生管理系统功能:添加学生信息、删除学生信息、根据姓名修改学生信息、根据姓名查询学生信息、显示所有学生信息、退出系统 1. 将每一个学生的信息放一个元组中,再把元组添加到列表中 2. 元组 键值对儿  {name: value,  age: value,  score: v

    2024年02月11日
    浏览(27)
  • SpringBoot对接微信小程序支付功能开发(一,下单功能)

    1,接入前准备: 接入模式选择直连模式; 申请小程序,得到APPID,并开通微信支付; 申请微信商户号,得到mchid,并绑定APPID; 配置商户API key,下载并配置商户证书,根据微信官方文档操作:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml 上面都配置完之后会得到:小

    2024年02月10日
    浏览(48)
  • 【接口测试】POST请求提交数据的三种方式及Postman实现

      POST请求是HTPP协议中一种常用的请求方法,它的使用场景是向客户端向服务器提交数据,比如登录、注册、添加等场景。另一种常用的请求方法是GET,它的使用场景是向服务器获取数据。 当前,POST请求提交数据的编码方式有三种: application/x-www-form-urlencoded multipart/form-dat

    2024年02月10日
    浏览(40)
  • Java | 使用切面AOP拦截并修改Controller接口请求参数

    关注common wx: CodingTechWork   在开发过程中,会有一些需求将controller层的一些方法入参进行全量转换,最容易想到的可能是在调用下层service方法时,调用公共的方法进行入参转换,这时带来的唯一问题就是代码不雅观,比较冗余。那还有什么方法可以更优雅的解决这个问题

    2024年01月24日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包