当前位置: 首页 > news >正文

黑马点评 秒杀业务「一人一单」并发安全

在秒杀优惠券的业务中,我们需要保证同一个用户只能下单一次,避免出现一人多单、库存超卖的情况。在高并发与集群环境下,这个问题会被无限放大,因此需要从单机锁逐步优化到分布式锁。

订单服务接口(IVoucherOrderService.java)

该接口定义了秒杀优惠券的核心方法,是业务的入口。

public interface IVoucherOrderService extends IService<VoucherOrder> { /** * 秒杀优惠券 * @param voucherId 优惠券ID * @return 订单编号 / 错误提示 */ Result seckillVoucher(Long voucherId); }

订单服务实现类(单机锁版本)

最开始我们使用synchronized保证单机环境下的并发安全,只允许同一个用户串行执行。

@Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { // 优惠券服务,用于扣减库存 @Resource private IVoucherService voucherService; // Redis全局唯一ID生成器,用于生成订单号 @Resource private RedisIdWorker redisIdWorker; /** * 秒杀入口方法(单机锁版本) */ @Override public Result seckillVoucher(Long voucherId) { // 获取当前登录的用户ID Long userId = UserHolder.getUser().getId(); // 对用户ID字符串加锁,保证同一用户同时只有一个请求执行 // intern() 确保相同userId使用常量池中的同一个字符串对象,锁才能真正生效 synchronized (userId.toString().intern()) { // 获取当前类的AOP代理对象,事务必须通过代理调用才生效 IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy(); return proxy.createVoucherOrder(voucherId); } } /** * 真正创建订单的方法(带事务) * 必须被代理对象调用 */ @Transactional public Result createVoucherOrder(Long voucherId) { // 获取当前登录用户 Long userId = UserHolder.getUser().getId(); // 1. 判断用户是否已经下单(一人一单核心) Integer count = query() .eq("user_id", userId) // 根据用户ID查询 .eq("voucher_id", voucherId) // 根据优惠券ID查询 .count(); // 统计订单数量 // 若数量大于0,说明已经买过了,直接返回失败 if (count > 0) { return Result.fail("您已经购买过此优惠券,无法重复购买"); } // 2. 扣减库存(乐观锁机制,只有库存大于0时才扣减) boolean success = voucherService.update() .setSql("stock = stock - 1") // 库存减1 .eq("voucher_id", voucherId) // 匹配对应优惠券 .gt("stock", 0) // 库存必须大于0 .update(); // 扣减失败,说明库存不足 if (!success) { return Result.fail("库存不足,秒杀失败"); } // 3. 创建订单 VoucherOrder order = new VoucherOrder();
http://www.cnnetsun.cn/news/1933673.html

相关文章:

  • YOLOv8 模型训练轨道交通障碍物检测数据集 通过训练出的轨道交通安全道路障碍物数据集的权重 建立深度学习铁路轨道障碍物检测系统
  • B站会员购自动化抢票系统架构设计与性能优化指南
  • 如何一键实现多平台直播推流:obs-multi-rtmp插件完整指南
  • Windows 64位环境:Tesseract 4.1与Leptonica 1.74编译实战与第三方库依赖全解
  • 告别数据焦虑:用MedAugment给你的医学影像数据集‘打鸡血’(附Python实战代码)
  • 2026-04-17:将数字变成二进制回文数的最少操作。用go语言,给你一个整数数组 nums。对数组里的每个位置 i,你都可以对 nums[i] 进行任意次“+1”或“-1”的调整(可以一次不做)。
  • 如何统计SQL分组汇总数据_详解GROUP BY与HAVING用法
  • 彻底搞懂 C 语言 `fread` 函数:从入门到精通
  • 【A40i-Android7.1】--- 从源码到镜像:A40i Android系统构建全链路解析
  • PHP HTML转PDF终极指南:快速生成专业PDF文档的完整方法
  • 大模型赛道香吗?过来人掏心窝子告诉你4个真相!月薪30K+的AI岗,你真的能抓住吗?
  • S 型、T 型、多项式、三次样条多段轨迹规划及圆弧过渡算法研究(Matlab代码实现)
  • AI绘画商用翻车实录:为什么你买的“商用授权”根本无效?——深度拆解Stable Diffusion生态中7层隐性版权链
  • AutoGod:安卓-全兼容!一站式自动化框架,开发效率直接拉满
  • CVE-2026-5281全解析:Chrome WebGPU零日漏洞暴露的图形安全新战场
  • 从零理解归一化:BatchNorm和LayerNorm在Transformer与CNN中的不同表现(含PyTorch示例)
  • 房东网络/合租上网必看:如何用一台新路由器安全搭建自己的“子网”(华硕/腾达路由器设置详解)
  • DataX:从原理到实战,构建企业级数据同步平台的完整指南
  • 多主题领域EI会议推荐:好中、快审、稳检索
  • 【Nordic实战】nRF54L15引脚规划:从电源域到GPIO的功耗优化指南
  • SAP要关闭 SAP 科目相关的凭证拆分,优先在公司代码层面撤销激活(适用于 ECC 新总账 / S/4HANA),辅以科目分类、规则与强制字段的清理,避免过账报错
  • 告别配置烦恼:CLion + Qt + CMake 一站式开发环境实战指南
  • 移动端自动化测试:Appium 入门
  • 《AI元人文·延伸卷》拟合提纲
  • Python3 正则表达式详解
  • jEasyUI 创建学校课程表
  • 从试点到推广:政务 AI 规模化应用的实施策略
  • vi编辑器模式切换与高效操作指南
  • STM32F407 HAL库实战:用定时器中断实现步进电机精准脉冲计数(附完整代码)
  • RabbitMQ 消息持久化:如何实现消息不丢失?三重持久化完整实战