高并发下如何避免重复提交表单?一线 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机制实现服务端防重复提交的统一解决方案。

✅ 简单易用

✅ 分布式友好

✅ 拓展性强

✅ 性能高效

相关推荐
晴殇i3 分钟前
3 分钟掌握图片懒加载核心技术:面试攻略
前端·面试·trae
天天摸鱼的java工程师14 分钟前
如何实现一个红包系统,支持并发抢红包?
后端
稳妥API14 分钟前
Gemini 2.5 Pro vs Flash API:正式版对比选择指南,深度解析性能与成本平衡 - API易-帮助中心
后端
深栈解码19 分钟前
OpenIM 源码深度解析系列(十一):群聊系统架构与业务设计
后端
前端小巷子21 分钟前
跨标签页通信(四):SharedWorker
前端·面试·浏览器
trow23 分钟前
Spring 手写简易IOC容器
后端·spring
山城小辣椒23 分钟前
spring-cloud-gateway使用websocket出现Max frame length of 65536 has been exceeded
后端·websocket·spring cloud
天天摸鱼的java工程师26 分钟前
谈谈你对 AQS(AbstractQueuedSynchronizer)的理解?
后端
鸡窝头on26 分钟前
Spring Boot 多 Profile 配置详解
spring boot·后端
风之旅人27 分钟前
开发必备"节假日接口"
java·后端·开源