SpringBoot项目中防止表单重复提交的两种方法(自定义注解解决API接口幂等设计和重定向)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: SpringBoot项目中防止表单重复提交的两种方法(自定义注解解决API接口幂等设计和重定向)

1,什么是幂等?

用户对于同一操作发起的一次请求或者多次请求的结果是一致的。

2,场景

比如添加用户的接口,在提交时由于网络波动或其他原因没有及时响应,用户可能会误以为没有点到提交按钮,会再次进行提交或连续点击提交按钮,这就会导致同一用户在数据库中保存了好几条,这当然是不符合我们预期的。

3,实现原理:

  • 自定义防止重复提交标记(@RepeatSubmit)。
  • 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
  • 新增Aspect切入点,为@RepeatSubmitAspect加入切入点。
  • 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
  • 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。

3,AOP拦截

@Aspect
@Component
public class NoRepeatAspect {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Pointcut("@annotation(com.sunline.project.aop.NoRepeat) || @within(com.sunline.project.aop.NoRepeat)")
    public void pointCut(){}
    @Around("pointCut()")
    private Object around(ProceedingJoinPoint point) {
        try {
            // 获取当前用户的token
            Subject currStaff = SecurityUtils.getSubject();
            UserVo user = (UserVo) currStaff.getPrincipal();
            
            String token = user.getToken();
            StaticLog.info("检测是否重复提交");
            StaticLog.info("point:{}", point);
            // 获取当前请求的方法名
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            String name = method.getName();
            StaticLog.info("token:{},======methodName:{}", token, name);
            if (redisTemplate.hasKey(token+name)) {
                StaticLog.error("监测到重复提交>>>>>>>>>>");
                return ResponseData.fail(ResponseCode.FAIL_CODE, "请勿重复提交");
            }
            // 获取注解
            NoRepeat annotation = method.getAnnotation(NoRepeat.class);
            Long timeout = annotation.timeOut();
            // 此处我用token和请求方法名为key存入redis中,有效期为timeout 时间, 也可以使用ip地址做为key
            redisTemplate.opsForValue().set(token+name, token+name, timeout, TimeUnit.SECONDS);
            return point.proceed();
        } catch (Throwable throwable) {
            StaticLog.error("=====>>>>>>操作失败:{}", throwable);
            return ResponseData.fail(ResponseCode.FAIL_CODE, "操作失败");
        }
    }
}

4,加注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoRepeat {
    // 默认失效时间
    long timeOut() default 10;
}

5,测试:

@NoRepeat(timeOut = 5)

timeOut默认时间为10s

@NoRepeat(timeOut = 5)
    @GetMapping(value = "/test")
    @ApiOperation(value = "测试幂等注解")
    @SysLogs("测试幂等注解")
    @ApiImplicitParam(paramType = "header", name = "Authorization", value = "身份认证Token")
    public ResponseData<T> testIdempotent() {
        try {
            System.err.println(new Date());
            return ResponseData.ok(ResponseCode.SUCCESS_CODE, "操作成功");
        } catch (Exception e) {
            StaticLog.error("操作失败:{}", e);
            return ResponseData.ok(ResponseCode.FAIL_CODE, "操作失败");
        }
    }

6,重定向方法

  • 问题提出:
    造成表单重复提交的原因是当我们刷新浏览器的时候,浏览器会发送上一次提交的请求。由于上一次提交的请求方式为post,刷新浏览器就会重新发送这个post请求,造成表单重复提交。
  • 解决办法:

将请求当前页面的方式由请求转发改为重定向到当前页面即可。

  • 举例:

编写一个处理登录请求的controller,登录成功就转到dashboard.html,登录失败则跳转到登录页面login.html重新登录。

注:dashboard.html和login.htm都是templates包下的。

@Controller
public class LoginController {
    @PostMapping("/user/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Map<String,Object> map
    ){
        if (StringUtils.isEmpty(username) && "123456".equals(password)){
            return "dashboard";
        }else {
            map.put("msg","用户名或密码错误");
            return "login";
        }
    }
}

上边这段代码是不正确的,会造成表单重复提交。

当我们输入正确账号密码时就会return “dashboard”;就会经过视图解析器转发到了dashboard.html页面,这样当我们浏览器中刷新是就会造成重复提交。

所以我们不能return “dashboard”,而要重定向到dashboard.html。

注意:重定向到templates包下的资源要经过视图解析器处理,而重定向默认是不会经过视图解析器的,所以我们要先编写一个视图映射。

编写视图映射:

@Configuration
public class MyMvcConfig implements WebMvcConfigurer{
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/main.html").setViewName("dashboard");
    }
}

将转发修改为重定向:代码标红位置

@Controller
public class LoginController {
    @PostMapping("/user/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Map<String,Object> map
    ){
        if (!StringUtils.isEmpty(username) && "123456".equals(password)){
            return "redirect:/main.html";
        }else {
            map.put("msg","用户名或密码错误");
            return "login";
        }
    }
}
相关实践学习
基于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
目录
相关文章
|
1月前
|
JSON 数据挖掘 API
1688API最新指南:商品详情接口接入与应用
本指南介绍1688商品详情接口的接入与应用,该接口可获取商品标题、价格、规格、库存等详细信息,适用于电商平台开发、数据分析等场景。接口通过商品唯一标识查询,支持HTTP GET/POST请求,返回JSON格式数据,助力开发者高效利用1688海量商品资源。
|
1月前
|
JSON 数据挖掘 API
京东API接口最新指南:店铺所有商品接口的接入与使用
本文介绍京东店铺商品数据接口的应用与功能。通过该接口,商家可自动化获取店铺内所有商品的详细信息,包括基本信息、销售数据及库存状态等,为营销策略制定提供数据支持。此接口采用HTTP请求(GET/POST),需携带店铺ID和授权令牌等参数,返回JSON格式数据,便于解析处理。这对于电商运营、数据分析及竞品研究具有重要价值。
|
2月前
|
存储 供应链 监控
1688商品数据实战:API搜索接口开发与供应链分析应用
本文详细介绍了如何通过1688开放API实现商品数据的获取与应用,涵盖接入准备、签名流程、数据解析存储及商业化场景。开发者可完成智能选品、价格监控和供应商评级等功能,同时提供代码示例与问题解决方案,确保法律合规与数据安全。适合企业开发者快速构建供应链管理系统。
|
1月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
150 5
|
1月前
|
Java Spring 容器
两种Spring Boot 项目启动自动执行方法的实现方式
在Spring Boot项目启动后执行特定代码的实际应用场景中,可通过实现`ApplicationRunner`或`CommandLineRunner`接口完成初始化操作,如系统常量或配置加载。两者均支持通过`@Order`注解控制执行顺序,值越小优先级越高。区别在于参数接收方式:`CommandLineRunner`使用字符串数组,而`ApplicationRunner`采用`ApplicationArguments`对象。注意,`@Order`仅影响Bean执行顺序,不影响加载顺序。
|
1月前
|
JSON API 开发者
京东API最新指南:商品视频接口接入与应用
在电商领域,商品视频能有效提升销售业绩。京东商品视频接口助力开发者获取商品视频信息(播放链接、时长、格式、封面图等),通过 HTTP GET/POST 请求返回 JSON 数据,便于集成到各类应用中,优化展示效果与用户体验。本指南详解接口接入与使用方法。
|
2月前
|
Java 测试技术 Spring
SpringBoot+@Async注解一起用,速度提升
本文介绍了异步调用在高并发Web应用性能优化中的重要性,对比了同步与异步调用的区别。同步调用按顺序执行,每一步需等待上一步完成;而异步调用无需等待,可提升效率。通过Spring Boot示例,使用@Async注解实现异步任务,并借助Future对象处理异步回调,有效减少程序运行时间。
|
3月前
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
129 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
|
3月前
|
监控 供应链 搜索推荐
亚马逊商品详情接口(亚马逊 API 系列)
亚马逊作为全球最大的电商平台之一,提供了丰富的商品资源。开发者和电商从业者可通过亚马逊商品详情接口获取商品的描述、价格、评论、排名等数据,对市场分析、竞品研究、价格监控及业务优化具有重要价值。接口基于MWS服务,支持HTTP/HTTPS协议,需注册并获得API权限。Python示例展示了如何使用mws库调用接口获取商品详情。应用场景包括价格监控、市场调研、智能选品、用户推荐和库存管理等,助力电商运营和决策。
240 23
|
2月前
|
机器学习/深度学习 JSON 算法
淘宝拍立淘按图搜索API接口系列的应用与数据解析
淘宝拍立淘按图搜索API接口是阿里巴巴旗下淘宝平台提供的一项基于图像识别技术的创新服务。以下是对该接口系列的应用与数据解析的详细分析