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

【黑马点评二刷日记】优惠券秒杀下单

实现全局唯一ID

为什么我们需要全局唯一ID?
因为:
如果我们使用数据库自增ID会导致问题的产生

id规律太明显,
受单表数据量的限制

用Redis来实现全局唯一ID

@ComponentpublicclassRedisIdWorker{/** * 开始时间戳 */privatestaticfinallongBEGIN_TIMESTAMP=1640995200L;/** * 序列号的位数 */privatestaticfinalintCOUNT_BITS=32;privateStringRedisTemplatestringRedisTemplate;publicRedisIdWorker(StringRedisTemplatestringRedisTemplate){this.stringRedisTemplate=stringRedisTemplate;}//生成分布式IDpubliclongnextId(StringkeyPrefix){// 1.生成时间戳LocalDateTimenow=LocalDateTime.now();longnowSecond=now.toEpochSecond(ZoneOffset.UTC);longtimestamp=nowSecond-BEGIN_TIMESTAMP;// 2.生成序列号// 2.1.获取当前日期,精确到天。以当天的时间戳为key,防止一直自增下去导致超时Stringdate=now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 2.2.自增长longcount=stringRedisTemplate.opsForValue().increment("icr:"+keyPrefix+":"+date);// 3.拼接并返回returntimestamp<<COUNT_BITS|count;}}

实现优惠券发放

业务理解:发放优惠券实现业务增长
需要实现的:添加优惠券,实现秒杀下单,库存超卖,实现一人一单
在实现优惠券发放过程,我们通过调用Voucher接口来实现

@RestController@RequestMapping("/voucher")publicclassVoucherController{@ResourceprivateIVoucherServicevoucherService;/** * 新增普通券 * @param voucher 优惠券信息 * @return 优惠券id */@PostMappingpublicResultaddVoucher(@RequestBodyVouchervoucher){voucherService.save(voucher);returnResult.ok(voucher.getId());}/** * 新增秒杀券 * @param voucher 优惠券信息,包含秒杀信息 * @return 优惠券id */@PostMapping("seckill")publicResultaddSeckillVoucher(@RequestBodyVouchervoucher){voucherService.addSeckillVoucher(voucher);returnResult.ok(voucher.getId());}/** * 查询店铺的优惠券列表 * @param shopId 店铺id * @return 优惠券列表 */@GetMapping("/list/{shopId}")publicResultqueryVoucherOfShop(@PathVariable("shopId")LongshopId){returnvoucherService.queryVoucherOfShop(shopId);}}

实现优惠券秒杀的下单功能

业务实现:秒杀是否开始或结束,如果未开始或结束则无法下单;库存是否充足,不足无法下单。

@ServicepublicclassVoucherOrderServiceImplextendsServiceImpl<VoucherOrderMapper,VoucherOrder>implementsIVoucherOrderService{@ResourceprivateISeckillVoucherServiceseckillVoucherService;@AutowiredprivateRedisIdWorkerredisIdWorker;@OverridepublicResultseckillVoucher(LongvoucherId){//1.查询秒杀券SeckillVouchervoucher=seckillVoucherService.getById(voucherId);//2.判断秒杀券是否合法if(voucher.getBeginTime().isAfter(LocalDateTime.now())){//秒杀券的开始时间在当前时间后returnResult.fail("秒杀尚未开始");}if(voucher.getEndTime().isBefore(LocalDateTime.now())){// 秒杀券的结束时间在当前时间之前returnResult.fail("秒杀已结束");}if(voucher.getStock()<1){returnResult.fail("秒杀券已抢空");}//5.秒杀券合法,则秒杀券抢购成功,秒杀券库存数量减一booleanflag=seckillVoucherService.update(newLambdaUpdateWrapper<SeckillVoucher>().eq(SeckillVoucher::getVoucherId,voucherId).setSql("stock=stock-1"));if(!flag){thrownewRuntimeException("秒杀券扣减失败");}//6.秒杀成功,创建对应的订单,并保存到数据库VoucherOrdervoucherOrder=newVoucherOrder();longorderId=redisIdWorker.nextId(null);voucherOrder.setId(orderId);voucherOrder.setUserId(ThreadLocalUtls.getUser().getId());voucherOrder.setVoucherId(voucherOrder.getId());flag=this.save(voucherOrder);if(!flag){thrownewRuntimeException("创建秒杀券订单失败");}//返回订单idreturnResult.ok(orderId);}}

乐观锁解决超卖问题(版本号,CAS)

这里我们有两种解决方案:

  1. 版本号解决

  2. CAS乐观锁解决

    CAS不需要再数据库表中添加version字段,节省了内存开销的问题,矮子里面挑将军,我们用CAS来实现。

使用CAS的优点:

避免了内存过大的开销

弊端:

成功率很低

// 5、秒杀券合法,则秒杀券抢购成功,秒杀券库存数量减一booleanflag=seckillVoucherService.update(newLambdaUpdateWrapper<SeckillVoucher>().eq(SeckillVoucher::getVoucherId,voucherId).eq(SeckillVoucher::getStock,voucher.getStock()).setSql("stock = stock -1"));

一人一单

需求:修改秒杀业务,要求同一个优惠券,每人只能用一次。

/** * 抢购秒杀券 * * @param voucherId * @return */@Transactional@OverridepublicResultseckillVoucher(LongvoucherId){// 1、查询秒杀券SeckillVouchervoucher=seckillVoucherService.getById(voucherId);// 2、判断秒杀券是否合法if(voucher.getBeginTime().isAfter(LocalDateTime.now())){// 秒杀券的开始时间在当前时间之后returnResult.fail("秒杀尚未开始");}if(voucher.getEndTime().isBefore(LocalDateTime.now())){// 秒杀券的结束时间在当前时间之前returnResult.fail("秒杀已结束");}if(voucher.getStock()<1){returnResult.fail("秒杀券已抢空");}// 3、创建订单LonguserId=ThreadLocalUtls.getUser().getId();synchronized(userId.toString().intern()){// 创建代理对象,使用代理对象调用第三方事务方法, 防止事务失效IVoucherOrderServiceproxy=(IVoucherOrderService)AopContext.currentProxy();returnproxy.createVoucherOrder(userId,voucherId);}}/** * 创建订单 */@TransactionalpublicResultcreateVoucherOrder(LonguserId,LongvoucherId){// synchronized (userId.toString().intern()) {// 1、判断当前用户是否是第一单intcount=this.count(newLambdaQueryWrapper<VoucherOrder>().eq(VoucherOrder::getUserId,userId));if(count>=1){// 当前用户不是第一单returnResult.fail("用户已购买");}// 2、用户是第一单,可以下单,秒杀券库存数量减一booleanflag=seckillVoucherService.update(newLambdaUpdateWrapper<SeckillVoucher>().eq(SeckillVoucher::getVoucherId,voucherId).gt(SeckillVoucher::getStock,0).setSql("stock = stock -1"));if(!flag){thrownewRuntimeException("秒杀券扣减失败");}// 3、创建对应的订单,并保存到数据库VoucherOrdervoucherOrder=newVoucherOrder();longorderId=redisIdWorker.nextId(SECKILL_VOUCHER_ORDER);voucherOrder.setId(orderId);voucherOrder.setUserId(ThreadLocalUtls.getUser().getId());voucherOrder.setVoucherId(voucherOrder.getId());flag=this.save(voucherOrder);if(!flag){thrownewRuntimeException("创建秒杀券订单失败");}// 4、返回订单idreturnResult.ok(orderId);// }}

关于锁,我们需要注意的是:

  • 锁的范围要尽量小;锁范围越大,性能越低。synchronized尽量锁代码块。
  • 锁的对象是一个不变的值,在面对long类型的userId时,每次请求都会创建一个新的userId对象,使用我们使用toString()方法变为String类型。在toString源码中我们发现,底层调用的是long的静态函数,还是会产生新的对象,所以调用intern() 方法从常量池中寻找与当前 字符串值一致的字符串对象。
  • 我们哟啊锁的是一整个事务,不能锁定一个代码块,否则还是会出现超卖现象。
http://www.jsqmd.com/news/483534/

相关文章:

  • 第八章 第一性原理:人生决策与自我管理的底层公理
  • MATLAB代码:拉丁超立方采样技术在风光场景生成与削减中的应用
  • 2026年Q1武汉租车连锁店综合实力深度评测 - 2026年企业推荐榜
  • 复试第十四天
  • 2026年专业寻猫服务价格解析与品牌推荐 - 2026年企业推荐榜
  • 为什么DataGrip进软件的时候连接超时:connect timed out X Check region settings......如何解决?
  • 光伏PCS:储能双向功率换流器-包含双向DC/DC、3 Level逆变器、仿真与源码、原理图P...
  • 2026.3.13+14 CAD学习六--【第二张图】椭圆EL、等轴测圆EL-i、多边形POL
  • 感应电机MPTC实战手记:从代码里看磁场舞蹈
  • Linux 的 base32 命令
  • 2026年3月14日GESP五级现场直击
  • Flutter 三方库 translations_code_gen 鸿蒙适配指南 - 实现强类型国际化资产自动化编译、在 OpenHarmony 上打造编译时安全的多语言工作流实战
  • Simulink中的Boost电路模块搭建与多种闭环控制策略:传递函数验证、参数整定与伯德图分析
  • 探索 M00292:多算法雷达一维恒虚警检测 CFAR 可视化系统
  • 57c1-2四轮轮毂电机驱动汽车的DYC直接横摆力矩稳定性控制,上层控制器DYC产生横摆力矩Mz
  • Qwen2.5-72B-GPTQ-Int4效果惊艳:128K长文档关键信息定位与问答
  • 亚像素以及实现原理、方法
  • PMSM传统滑模观测器+PLL仿真模型,加上了相位补偿观测波形与实际波形基本重合。 以下图一为...
  • Flutter 三方库 aws_sqs_api 鸿蒙适配指南 - 实现分布式消息异步解耦、在 OpenHarmony 上打造高可用云端队列控制中枢实战
  • 【AOP】Spring Framework核心:AOP:静态代理 vs 动态代理(JDK动态代理 vs CGLIB)、AOP核心概念、应用场景
  • 探索 FPGA 电机控制源码:Verilog + Nios II 架构的奇妙之旅
  • REX-UniNLU网络安全应用:恶意文本智能检测系统
  • 从对象头到内存屏障,搞懂Java锁的底层原理
  • 直流调速系统Simulink仿真:包含参数设置代码、Simulink仿真模型及撰写文档
  • RP2040+CircuitPython打造可交互惊喜盒子硬件平台
  • Factory Io超大型仿真场景:全方位模拟工业生产流程
  • AIGlasses OS Pro辅助C语言学习:通过视觉分析理解指针与内存操作
  • springboot在自定义RPC框架中的使用
  • ROS系统中基于强化学习算法的移动机器人路径规划策略研究:应用DQN、DDPG、SAC及TD3算法
  • DS_store文件泄露漏洞全流程演示(在kali系统中报错时搭建虚拟环境演示以及其他方法)