如何创造一个网站牛商网做网站要多少钱
2026/2/17 4:05:57 网站建设 项目流程
如何创造一个网站,牛商网做网站要多少钱,在网站用什么做页面布局,俄罗斯外贸常用网站前言 当你的用户疯狂点击提交按钮时#xff0c;你的系统准备好迎接这场“连击风暴”了吗#xff1f; 在电商系统的实战中#xff0c;我见过太多因重复提交导致的资损事故——用户一次点击#xff0c;系统却创建了多个订单#xff0c;导致库存错乱、用户重复支付、客服投…前言当你的用户疯狂点击提交按钮时你的系统准备好迎接这场“连击风暴”了吗在电商系统的实战中我见过太多因重复提交导致的资损事故——用户一次点击系统却创建了多个订单导致库存错乱、用户重复支付、客服投诉爆棚。有些小伙伴在工作中可能遇到过这样的场景大促期间用户反馈“明明只点了一次为什么扣了两次款”开发同学查了半天日志发现同一个用户请求在毫秒级内真的到达了服务器两次。今天这篇文章就跟大家聊聊高并发下防止重复提交订单希望对你会有所帮助。01 为什么会重复提交在深入解决方案前我们必须搞清楚重复提交是如何发生的。常见的场景有用户无意识重复点击网络延迟时用户心急多次点击提交按钮前端防抖失效前端做了防抖处理但被绕过或配置不当网络超时重试请求超时后客户端或网关自动重试恶意攻击竞争对手或黑客故意重复提交后端处理超时第一个请求处理慢客户端以为失败又发一次来看一个典型的用户操作流程以及其中可能发生重复的各个环节从图中可以看到从用户点击到订单落库几乎每个环节都可能成为重复提交的“案发现场”。下面我们就针对这些环节层层布防。02 第一道防线前端防抖与按钮控制这是最直观、成本最低的防护措施。原则是在用户交互层面尽量减少无效请求。2.1 按钮状态控制// 前端防抖实现示例Vue Element UI template el-button :loadingsubmitting :disabledsubmitting clickhandleSubmitOrder {{ submitting ? 提交中... : 提交订单 }} /el-button /template script export default { data() { return { submitting: false, submitToken: null // 用于标识当前提交的token } }, methods: { async handleSubmitOrder() { if (this.submitting) { this.$message.warning(正在提交请勿重复点击) return } this.submitting true try { // 生成唯一token用于后端幂等性校验 this.submitToken this.generateSubmitToken() const result await this.$api.order.submit({ orderData: this.orderData, submitToken: this.submitToken }) this.$message.success(订单提交成功) this.$router.push(/order/detail/${result.orderId}) } catch (error) { this.$message.error(提交失败: ${error.message}) this.submitting false // 失败后重置状态 } }, generateSubmitToken() { // 生成唯一标识可以用UUID或时间戳随机数 return order_submit_${Date.now()}_${Math.random().toString(36).substr(2, 9)} } } } /script2.2 请求防抖与拦截// 使用axios拦截器实现请求防抖 import axios fromaxios // 存储正在进行的请求 const pendingRequests newMap() // 生成请求key const generateReqKey (config) { const { method, url, params, data } config return [method, url, JSON.stringify(params), JSON.stringify(data)].join() } // 请求拦截器 axios.interceptors.request.use(config { const key generateReqKey(config) if (pendingRequests.has(key)) { // 请求已存在取消当前请求 config.cancelToken new axios.CancelToken(cancel { cancel(重复请求已被拦截: ${key}) }) } else { // 新请求添加到pending中 pendingRequests.set(key, config) } return config }) // 响应拦截器 axios.interceptors.response.use( response { const key generateReqKey(response.config) pendingRequests.delete(key) return response }, error { if (axios.isCancel(error)) { console.log(请求被取消:, error.message) returnPromise.reject(error) } // 错误处理完成后也要从pending中移除 if (error.config) { const key generateReqKey(error.config) pendingRequests.delete(key) } returnPromise.reject(error) } )前端防护小结优点实现简单能拦截大部分用户无意识的重复点击缺点可被绕过如直接调用API、禁用JS、使用Postman等工具结论前端防护是必要但不充分的措施绝不能作为唯一防线03 第二道防线后端接口幂等性设计幂等性是解决重复提交的核心理念。所谓幂等就是同一个操作执行多次的结果与执行一次的结果相同。3.1 什么是幂等性对于订单提交接口幂等无论调用1次还是N次都只创建一个订单非幂等调用N次可能创建N个订单3.2 基于Token的幂等实现这是最常用的幂等实现方案流程如下客户端在提交前先向后端申请一个唯一Token提交订单时携带此Token服务端检查Token是否已使用过// 幂等性Token服务 Service public class IdempotentTokenService { Autowired private RedisTemplateString, String redisTemplate; private static final String IDEMPOTENT_PREFIX idempotent:token:; private static final long TOKEN_EXPIRE_SECONDS 300; // Token有效期5分钟 /** * 生成幂等性Token * / public String generateToken(String userId) { String token UUID.randomUUID().toString(); String redisKey IDEMPOTENT_PREFIX userId : token; // 存储Token设置过期时间 redisTemplate.opsForValue().set( redisKey, 1, TOKEN_EXPIRE_SECONDS, TimeUnit.SECONDS ); return token; } /** * 检查并消费Token * return true: Token有效且消费成功; false: Token无效或已消费 */ public boolean checkAndConsumeToken(String userId, String token) { String redisKey IDEMPOTENT_PREFIX userId : token; // 使用Lua脚本保证原子性 String luaScript if redis.call(get, KEYS[1]) 1 then redis.call(del, KEYS[1]) return 1 else return 0 end ; DefaultRedisScriptLong redisScript new DefaultRedisScript(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); Long result redisTemplate.execute( redisScript, Collections.singletonList(redisKey) ); return result ! null result 1L; } } // 使用AOP实现幂等性校验 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface Idempotent { String key() default ; // 幂等键支持SpEL表达式 long expireTime() default 300; // 过期时间秒 } Aspect Component public class IdempotentAspect { Autowired private RedisTemplateString, String redisTemplate; Around(annotation(idempotent)) public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable { // 1. 获取方法参数 Object[] args joinPoint.getArgs(); MethodSignature signature (MethodSignature) joinPoint.getSignature(); Method method signature.getMethod(); // 2. 解析幂等键支持SpEL String keyExpression idempotent.key(); String redisKey parseKey(keyExpression, method, args); // 3. 尝试获取分布式锁防止并发请求同时通过检查 String lockKey redisKey :lock; boolean lockAcquired false; try { // 尝试加锁 lockAcquired redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (!lockAcquired) { thrownew BusinessException(系统繁忙请稍后重试); } // 4. 检查Token是否已使用 Boolean exists redisTemplate.hasKey(redisKey); if (Boolean.TRUE.equals(exists)) { // Token已使用直接返回之前的处理结果这里需要根据实际业务调整 throw new BusinessException(请勿重复提交订单); } // 5. 执行业务逻辑 Object result joinPoint.proceed(); // 6. 标记Token已使用 redisTemplate.opsForValue().set( redisKey, processed, idempotent.expireTime(), TimeUnit.SECONDS ); return result; } finally { // 释放锁 if (lockAcquired) { redisTemplate.delete(lockKey); } } } private String parseKey(String expression, Method method, Object[] args) { // 这里实现SpEL表达式解析获取实际的幂等键 // 例如可以从参数中提取userIdorderToken return parsed:key:from:expression; } } // 在订单提交接口上使用 RestController RequestMapping(/order) public class OrderController { PostMapping(/submit) Idempotent(key #request.userId : #request.submitToken, expireTime 300) public ApiResponseOrderSubmitResult submitOrder(RequestBody OrderSubmitRequest request) { // 这里是真正的订单创建逻辑 OrderSubmitResult result orderService.createOrder(request); return ApiResponse.success(result); } }3.3 基于唯一业务标识的幂等除了Token方案还可以利用业务的自然唯一性实现幂等Service public class OrderService { Autowired private OrderMapper orderMapper; Transactional public OrderSubmitResult createOrder(OrderSubmitRequest request) { // 方法1先查询是否存在 Order existingOrder orderMapper.selectByUniqueKey( request.getUserId(), request.getProductId(), request.getSubmitTime() ); if (existingOrder ! null) { // 订单已存在直接返回 return convertToResult(existingOrder); } // 方法2利用数据库唯一约束 try { Order newOrder buildOrder(request); orderMapper.insert(newOrder); return convertToResult(newOrder); } catch (DuplicateKeyException e) { // 捕获唯一键冲突异常 log.warn(订单重复提交uniqueKey{}, request.getUniqueKey()); // 查询已创建的订单并返回 Order createdOrder orderMapper.selectByUniqueKey( request.getUserId(), request.getProductId(), request.getSubmitTime() ); if (createdOrder null) { throw new BusinessException(订单处理异常请稍后重试); } return convertToResult(createdOrder); } } // 订单表可添加唯一索引 // ALTER TABLE t_order ADD UNIQUE KEY uk_user_product_time (user_id, product_id, submit_time); }幂等性设计小结Token方案通用性强适合大多数场景业务标识方案更自然但依赖业务的天然唯一性关键点所有幂等性检查必须在事务开始前完成否则可能失效04 第三道防线数据库层防护数据库是数据持久化的最后一道关卡在这里设置防护至关重要。4.1 唯一约束与乐观锁-- 订单表设计示例 CREATE TABLEt_order ( idbigint(20) NOTNULL AUTO_INCREMENT COMMENT主键, order_novarchar(32) NOTNULLCOMMENT订单号业务唯一, user_idbigint(20) NOTNULLCOMMENT用户ID, product_idbigint(20) NOTNULLCOMMENT商品ID, quantityint(11) NOTNULLCOMMENT购买数量, amountdecimal(10,2) NOTNULLCOMMENT订单金额, statustinyint(4) NOTNULLDEFAULT1COMMENT订单状态1-待支付2-已支付, submit_tokenvarchar(64) DEFAULTNULLCOMMENT提交Token用于幂等, versionint(11) NOTNULLDEFAULT1COMMENT版本号用于乐观锁, create_time datetime NOTNULLDEFAULTCURRENT_TIMESTAMP, update_time datetime NOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEYuk_order_no (order_no), -- 订单号唯一 UNIQUE KEYuk_user_submit_token (user_id, submit_token), -- 提交Token唯一 UNIQUE KEYuk_user_product_time (user_id, product_id, create_time), -- 业务维度唯一 KEYidx_user_id (user_id), KEYidx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;4.2 数据库层面的幂等实现// 使用数据库事务唯一约束保证最终一致性 Service public class OrderServiceV2 { Autowired private OrderMapper orderMapper; Autowired private IdempotentTokenService tokenService; Transactional(rollbackFor Exception.class) public OrderSubmitResult submitOrderWithDBProtection(OrderSubmitRequest request) { String userId request.getUserId(); String submitToken request.getSubmitToken(); // 1. 检查幂等Token在事务外先检查一次 if (!tokenService.checkAndConsumeToken(userId, submitToken)) { throw new BusinessException(请勿重复提交订单); } try { // 2. 生成订单号雪花算法等分布式ID生成器 String orderNo generateOrderNo(); // 3. 创建订单对象 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setProductId(request.getProductId()); order.setQuantity(request.getQuantity()); order.setAmount(calculateAmount(request)); order.setSubmitToken(submitToken); // 4. 插入订单这里依赖数据库唯一约束 orderMapper.insert(order); // 5. 更新库存等后续操作... updateProductStock(request.getProductId(), request.getQuantity()); returnnew OrderSubmitResult(orderNo, 订单创建成功); } catch (DuplicateKeyException e) { // 6. 处理唯一约束冲突 log.warn(订单重复提交userId{}, token{}, userId, submitToken); // 查询已创建的订单 Order existingOrder orderMapper.selectBySubmitToken(userId, submitToken); if (existingOrder ! null) { return new OrderSubmitResult( existingOrder.getOrderNo(), 订单已创建成功请勿重复提交 ); } // 理论上不会走到这里除非有极端情况 throw new BusinessException(订单处理异常请稍后重试); } } }05 第四道防线分布式锁在分布式环境下多个实例可能同时处理同一个请求需要分布式锁来保证只有一个实例执行核心逻辑。5.1 基于Redis的分布式锁Component public class DistributedLockService { Autowired private RedissonClient redissonClient; /** * 尝试获取分布式锁 * param lockKey 锁的key * param waitTime 等待时间毫秒 * param leaseTime 持有时间毫秒 * return 锁对象获取失败返回null */ public RLock tryLock(String lockKey, long waitTime, long leaseTime) { RLock lock redissonClient.getLock(lockKey); try { boolean acquired lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS); return acquired ? lock : null; } catch (InterruptedException e) { Thread.currentThread().interrupt(); returnnull; } } /** * 订单提交分布式锁 */ public RLock lockForOrderSubmit(String userId, String submitToken) { String lockKey String.format(order:submit:lock:%s:%s, userId, submitToken); return tryLock(lockKey, 100, 5000); // 等待100ms锁持有5秒 } } // 在订单服务中使用分布式锁 Service public class OrderServiceV3 { Autowired private DistributedLockService lockService; Autowired private OrderMapper orderMapper; public OrderSubmitResult submitOrderWithDistributedLock(OrderSubmitRequest request) { String userId request.getUserId(); String submitToken request.getSubmitToken(); // 1. 获取分布式锁 RLock lock lockService.lockForOrderSubmit(userId, submitToken); if (lock null) { throw new BusinessException(系统繁忙请稍后重试); } try { // 2. 检查是否已处理 Order existingOrder orderMapper.selectBySubmitToken(userId, submitToken); if (existingOrder ! null) { return new OrderSubmitResult( existingOrder.getOrderNo(), 订单已创建成功请勿重复提交 ); } // 3. 执行业务逻辑 return doCreateOrder(request); } finally { // 4. 释放锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } private OrderSubmitResult doCreateOrder(OrderSubmitRequest request) { // 实际的订单创建逻辑 // 这里已经保证了同一时刻只有一个线程在处理同一个提交请求 // ... } }5.2 分布式锁的注意事项使用分布式锁时要注意锁粒度不要太粗影响性能也不要太细增加复杂度锁超时必须设置合理的超时时间防止死锁锁续期对于长时间操作需要实现锁续期机制可重入性同一个线程可以重复获取锁容错性Redis集群故障时要有降级方案06 第五道防线异步处理与消息队列对于高并发场景可以采用异步处理模式将同步请求转为异步任务。实现代码示例// 异步订单处理实现 Component public class AsyncOrderService { Autowired private RocketMQTemplate rocketMQTemplate; Autowired private RedisTemplateString, String redisTemplate; /** * 异步提交订单 */ public AsyncSubmitResult asyncSubmitOrder(OrderSubmitRequest request) { // 1. 生成唯一请求ID String requestId generateRequestId(request.getUserId()); // 2. 快速验证库存、用户状态等 quickValidate(request); // 3. 将请求ID与用户关联用于查询结果 String pendingKey order:pending: request.getUserId() : requestId; redisTemplate.opsForValue().set(pendingKey, processing, 10, TimeUnit.MINUTES); // 4. 发送到消息队列 OrderMessage message new OrderMessage(); message.setRequestId(requestId); message.setRequest(request); message.setTimestamp(System.currentTimeMillis()); rocketMQTemplate.asyncSend( ORDER_SUBMIT_TOPIC, message, new SendCallback() { Override public void onSuccess(SendResult sendResult) { log.info(订单消息发送成功: {}, requestId); } Override public void onException(Throwable throwable) { log.error(订单消息发送失败: {}, requestId, throwable); // 发送失败更新状态 redisTemplate.opsForValue().set( pendingKey, failed, 5, TimeUnit.MINUTES ); } } ); // 5. 立即返回告知用户处理中 return new AsyncSubmitResult(requestId, 订单提交成功正在处理中); } } // 消息消费者 Component RocketMQMessageListener( topic ORDER_SUBMIT_TOPIC, consumerGroup order-submit-consumer-group ) public class OrderSubmitConsumer implements RocketMQListenerOrderMessage { Autowired private OrderMapper orderMapper; Override public void onMessage(OrderMessage message) { String requestId message.getRequestId(); OrderSubmitRequest request message.getRequest(); // 1. 幂等检查基于requestId Order existing orderMapper.selectByRequestId(requestId); if (existing ! null) { log.info(订单已处理跳过: {}, requestId); return; } // 2. 创建订单 Order order createOrder(request, requestId); try { orderMapper.insert(order); log.info(订单创建成功: {}, order.getOrderNo()); // 3. 更新处理状态 updateProcessingStatus(request.getUserId(), requestId, success, order.getOrderNo()); } catch (DuplicateKeyException e) { log.warn(订单重复requestId{}, requestId); // 查询已创建的订单 Order created orderMapper.selectByRequestId(requestId); if (created ! null) { updateProcessingStatus(request.getUserId(), requestId, success, created.getOrderNo()); } } } }07 综合方案多层次联合防护在实际生产环境中我们通常会采用多层次、立体化的防护策略。以下是一个完整的综合方案流程图这个多层次方案中每一层都有其特定作用前端层用户体验优化拦截大部分无意识重复网关层安全防护防刷、限流业务层核心幂等逻辑分布式锁保证并发安全数据层最终保障唯一约束防止数据不一致异步层削峰填谷提升系统吞吐量08 实战不同场景下的方案选择不同的业务场景需要不同的防护策略这里给出一些实践建议8.1 普通电商订单// 普通电商订单推荐方案 Service public class StandardOrderService { // 综合使用前端防抖 Token幂等 数据库唯一约束 public OrderSubmitResult submitStandardOrder(OrderSubmitRequest request) { // 1. 参数校验 validateRequest(request); // 2. 幂等Token检查Redis if (!idempotentCheck(request.getUserId(), request.getSubmitToken())) { return getExistingOrderResult(request.getUserId(), request.getSubmitToken()); } // 3. 分布式锁防并发 RLock lock acquireOrderLock(request.getUserId(), request.getProductId()); try { // 4. 库存检查等业务校验 checkInventory(request.getProductId(), request.getQuantity()); // 5. 创建订单依赖数据库唯一约束 return createOrderInTransaction(request); } finally { lock.unlock(); } } }8.2 秒杀订单// 秒杀订单需要更极致的优化 Service public class FlashSaleOrderService { // 秒杀方案异步处理 库存预扣 最终一致性 public FlashSaleSubmitResult submitFlashSaleOrder(FlashSaleRequest request) { // 1. 验证用户资格和活动状态缓存中检查 if (!checkUserQualification(request.getUserId(), request.getActivityId())) { throw new BusinessException(您不具备参与资格); } // 2. 预扣库存Redis原子操作 boolean stockDeducted preDeductStock( request.getActivityId(), request.getProductId(), request.getUserId() ); if (!stockDeducted) { throw new BusinessException(库存不足); } // 3. 生成唯一请求ID String requestId generateRequestId(request.getUserId(), request.getActivityId()); // 4. 发送到消息队列快速返回 sendToMQ(request, requestId); // 5. 立即返回 return new FlashSaleSubmitResult(requestId, 秒杀请求已接受处理中); } // 消费者异步创建订单 Transactional public void processFlashSaleOrder(FlashSaleRequest request, String requestId) { // 这里只需要处理真正的订单创建 // 因为库存已在Redis中预扣只需保证最终一致性 try { createOrder(request, requestId); // 同步库存到数据库 syncStockToDB(request.getProductId(), request.getActivityId()); } catch (Exception e) { // 失败时回滚Redis库存 rollbackStockInRedis(request.getActivityId(), request.getProductId(), request.getUserId()); throw e; } } }10 总结防止重复提交订单是一个系统工程需要从前到后、多层次的防护。让我们回顾一下关键点前端防护是体验不是保障按钮防抖、请求拦截能改善用户体验但不能作为唯一防线。幂等性是核心理念无论是Token方案还是业务唯一标识都要保证同一操作执行多次的结果一致。分布式锁解决并发问题在分布式环境下防止多个实例同时处理同一请求。数据库是最后防线唯一约束、乐观锁等机制能在应用层防护失效时保证数据一致性。异步处理提升吞吐对于高并发场景将同步请求转为异步处理提高系统整体吞吐量。监控告警必不可少没有监控的系统就像没有仪表的飞机无法发现问题和优化性能。在实际架构设计中我通常建议采用前端防抖 网关限流 Token幂等 分布式锁 数据库唯一约束的综合方案对于秒杀等极致场景再加入异步处理。有些小伙伴可能会觉得这些防护措施太复杂影响开发效率。但请记住预防的成本远低于修复的成本。一次重复提交导致的资损事故可能就需要整个团队加班数周来修复数据和安抚用户。技术方案没有银弹只有最适合业务场景的平衡。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询