高并发下如何避免重复提交表单?一线 Java 工程师的实战经验分享

高并发下如何避免重复提交表单?一线 Java 工程师的实战经验分享

引言:

"为什么用户点击一次提交,系统却生成了两笔订单?"

"为什么我加了锁,还是出现了重复支付?"

"为什么并发一上来,接口就乱套了?"

如果你在开发高并发系统、秒杀活动、支付接口、订单系统时碰到这种问题,那这篇文章你一定不能错过。作为一名从事 Java 后端开发 8 年的工程师,我将从实际业务出发,教你如何在高并发场景下彻底解决重复提交表单的问题


🧭 一、业务背景与问题描述

1.1 重复提交的真实场景

在日常开发中,以下场景都可能导致重复提交:

  • 用户点击"提交订单"按钮多次(网络慢、页面卡顿)
  • 前端因网络异常自动重试请求
  • 支付系统中用户刷新页面重复支付
  • 后端服务处理较慢,用户误以为没提交成功

这些问题非常常见,却极具"杀伤力":轻则用户体验差,重则导致资金损失、库存异常、数据错乱


🔍 二、重复提交的常见原因分析

原因 场景说明
表单未限制点击 用户多次点击提交按钮
接口无幂等控制 多次请求生成多个订单
分布式部署无状态 Session状态无法共享
中间件重试机制 Nginx、API Gateway 自动重试

🎯 三、如何解决重复提交问题?

原则:前端防抖 + 服务端幂等性保证

虽然前端可以做一些防抖处理,但服务端控制才是最后的防线 。尤其在高并发系统中,服务端必须保证:相同请求不会被处理两次

常见解决方案对比:

方案 优点 缺点
数据库唯一约束 简单可靠 侵入性强,可能影响性能
接口幂等ID 解耦,灵活 需要调用方配合
Token机制(一次性Token) 轻量、独立、通用 依赖缓存系统
Redis分布式锁 精准控制 实现复杂、需注意死锁

✅ 四、最佳实践:Token机制防重复提交(基于Redis)

原理说明:

  1. 每次表单提交前,客户端先从服务器获取一个唯一 Token。

  2. 服务端将 Token 存入 Redis,设置过期时间。

  3. 客户端提交表单时,携带该 Token。

  4. 服务端校验 Token 是否存在:

    • 存在 ➝ 删除 Token ➝ 执行业务逻辑;
    • 不存在 ➝ 拒绝请求(说明是重复提交)。

🧪 五、代码实战:Spring Boot + Redis 实现一次性Token机制

5.1 获取Token接口

less 复制代码
@RestController
@RequestMapping("/api/token")
public class TokenController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 生成一次性Token(有效期5分钟)
     */
    @GetMapping("/generate")
    public ResponseEntity<String> generateToken() {
        String token = UUID.randomUUID().toString();
        String key = "form:token:" + token;

        // 存入Redis,设置5分钟过期
        redisTemplate.opsForValue().set(key, "1", 5, TimeUnit.MINUTES);

        return ResponseEntity.ok(token);
    }
}

5.2 表单提交接口

less 复制代码
@RestController
@RequestMapping("/api/order")
public class OrderController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 提交订单接口。使用Token防止重复提交
     */
    @PostMapping("/submit")
    public ResponseEntity<String> submitOrder(@RequestBody OrderRequest request) {
        String tokenKey = "form:token:" + request.getToken();

        // 使用 Redis 的 delete 操作作为原子校验
        Boolean success = redisTemplate.delete(tokenKey);

        if (Boolean.FALSE.equals(success)) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                    .body("请勿重复提交!");
        }

        // TODO: 执行核心业务逻辑(如创建订单、扣减库存)
        // 这里应该保证业务也具有幂等性(如幂等ID)

        return ResponseEntity.ok("订单提交成功");
    }
}

5.3 请求参数对象

arduino 复制代码
@Data
public class OrderRequest {
    private String token;
    private String productId;
    private int quantity;
}

🧠 六、关键技术细节说明

  • Token使用一次即删除,防止重复使用
  • Redis支持原子操作,高效线程安全
  • 可扩展性强:支持多业务共用
  • 无状态设计,适用于分布式部署

⚙️ 七、进阶优化建议

7.1 拦截器统一处理Token验证

使用Spring的HandlerInterceptor统一拦截提交请求,避免每个Controller重复代码。

vbnet 复制代码
@Component
public class TokenInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getHeader("X-Form-Token");
        if (StringUtils.isEmpty(token)) {
            response.setStatus(400);
            response.getWriter().write("缺少Token");
            return false;
        }

        String key = "form:token:" + token;

        Boolean success = redisTemplate.delete(key);
        if (Boolean.FALSE.equals(success)) {
            response.setStatus(400);
            response.getWriter().write("请勿重复提交!");
            return false;
        }

        return true;
    }
}

配置WebMvcConfigurer注册拦截器:

typescript 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private TokenInterceptor tokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/api/order/**");
    }
}

7.2 Token粒度控制

  • 按照用户维度+Tokenform:token:{userId}:{token}
  • 按照业务维度划分:不同业务使用不同前缀,避免冲突

7.3 Token安全性设计

  • 可使用JWT或签名机制加密Token,防止伪造
  • 可结合用户会话Token进行验证

📌 八、总结

重复提交表单在系统低并发时可能只是个小问题,但一旦并发上来,就可能造成灾难级后果

本篇文章结合项目实战经验,从业务痛点到技术方案,详细讲解了如何通过Redis + Token机制实现服务端防重复提交的统一解决方案。

✅ 简单易用

✅ 分布式友好

✅ 拓展性强

✅ 性能高效

相关推荐
IT_陈寒4 分钟前
Python 3.12 新特性实战:5个让你的代码效率提升50%的技巧!🔥
前端·人工智能·后端
Apifox5 分钟前
Apifox 8 月更新|新增测试用例、支持自定义请求示例代码、提升导入/导出 OpenAPI/Swagger 数据的兼容性
前端·后端·测试
我们从未走散9 分钟前
设计模式学习笔记-----抽象责任链模式
java·笔记·学习·设计模式·责任链模式
风飘百里16 分钟前
Go语言DDD架构的务实之路
后端·架构
郭庆汝17 分钟前
GraphRAG——v0.3.5版本
后端·python·flask
轻松Ai享生活25 分钟前
Linux Swap 详解 (2) - 配置与优化
后端
xiguolangzi27 分钟前
springBoot3 生成订单号
后端
二十雨辰29 分钟前
[TG开发]部署机器人
java·web3
zjjuejin32 分钟前
告别低效:我用这招,让Go和Java项目的依赖管理又快又稳
java
我不是星海1 小时前
RabbitMQ基础入门实战
java·开发语言