【SpringBoot篇】优惠券秒杀 — 添加优惠劵操作(基本操作 | 一人仅一张券的操作)

文章目录

  • 🍔发放优惠券
    • 🎆基本操作
      • 🎄数据库表
      • 🛸思路
      • 🌹代码实现
    • 🎆完善后的操作
      • 🛸乐观锁
      • 🌹代码实现
  • 🍔一人仅一张优惠券
      • 🛸思路
      • 🌹代码
      • ⭐代码分析

在这里插入图片描述

🍔发放优惠券

🎆基本操作

🎄数据库表

普通券

我们来看这一张表
在这里插入图片描述
里面包含了主键,商铺id,使用规则,时间等内容
可以看到里面没有库存,意味着所有人都可以来购买,所以是普通券

秒杀券

我们看下面这一张表

在这里插入图片描述
这是一张秒杀券,里面包含了普通券的所有信息,还有秒杀券独有的特点,比如库存,生效时间,生效时间等信息

🛸思路

  • 秒杀是否开始或者结束,如果尚未开始或者已经结束就无法下单
  • 库存是否充足,如果不足,就无法下单

请添加图片描述

🌹代码实现

VoucherOrderController

package com.hmdp.controller;import com.hmdp.dto.Result;
import com.hmdp.service.IVoucherOrderService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController
@RequestMapping("/voucher-order")
public class VoucherOrderController {@Resourceprivate IVoucherOrderService voucherOrderService;@PostMapping("seckill/{id}")public Result seckillVoucher(@PathVariable("id") Long voucherId) {return voucherOrderService.seckillVoucher(voucherId);}
}

在这里插入图片描述


在这里插入图片描述

package com.hmdp.service;import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;
import com.baomidou.mybatisplus.extension.service.IService;/*** <p>*  服务类* </p>*/
public interface IVoucherOrderService extends IService<VoucherOrder> {Result seckillVoucher(Long voucherId);
}

在这里插入图片描述

@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Override@Transactional   //由于使用了2张表,这里加上事务比较好,一旦重新了问题,可以及时回滚public Result seckillVoucher(Long voucherId) {//查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//判断秒杀是否开始if(voucher.getBeginTime().isAfter(LocalDateTime.now())){//尚未开始return Result.fail("秒杀尚未开始");}//判断秒杀是否结束if(voucher.getEndTime().isBefore(LocalDateTime.now())){//已结束return Result.fail("秒杀已结束");}//判断库存是否充足if(voucher.getStock() < 1){//库存不足return Result.fail("库存不足");}//扣减库存//mybatisplusboolean success=seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if(!success){return Result.fail("扣减库存失败");}//创建订单VoucherOrder voucherOrder=new VoucherOrder();//订单idlong ordrId=redisIdWorker.nextId("order");voucherOrder.setId(ordrId);//用户idlong userId=UserHolder.getUser().getId();voucherOrder.setUserId(userId);//代金券idvoucherOrder.setVoucherId(voucherId);//把订单写入数据库save(voucherOrder);//返回订单idreturn Result.ok(ordrId);}
}

我们使用上面的操作,线程少的话,没问题,可以执行

请添加图片描述

但是线程多的话,就会发生线程安全问题

请添加图片描述

于是我们可以使用下面的方法来解决问题

🎆完善后的操作

🛸乐观锁

乐观锁(Optimistic Locking)是一种并发控制机制,用于多线程或分布式系统中的数据一致性控制。它假设不会有或尽可能减少冲突,因此不会每次都进行锁冲突的检查。在使用乐观锁时,多个用户可以同时读取数据,但只有在更新数据时才检查版本号等机制来判断数据是否被其他用户修改。 在使用乐观锁时,通常会通过在数据上添加版本号(Version)信息来实现。当用户读取数据时,会将数据的版本号一并读取。当用户提交更新请求时,系统会先检查数据的版本号是否与之前读取的一致。如果一致,则更新成功;如果不一致,则表示数据已被其他用户修改,更新失败。

请添加图片描述

🌹代码实现

整体代码都差不多,其实就修改一部分就行了


我们进入VoucherOrderServiceImpl

在这里插入图片描述
库存大于0,证明有库存,这样子就行了

🍔一人仅一张优惠券

对于有些优惠力度比较大的券,为了防止黄牛,我们需要设置一人一张券

🛸思路

请添加图片描述

🌹代码

VoucherOrderServiceImpl

@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Overridepublic Result seckillVoucher(Long voucherId) {//查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//判断秒杀是否开始if(voucher.getBeginTime().isAfter(LocalDateTime.now())){//尚未开始return Result.fail("秒杀尚未开始");}//判断秒杀是否结束if(voucher.getEndTime().isBefore(LocalDateTime.now())){//已结束return Result.fail("秒杀已结束");}//判断库存是否充足if(voucher.getStock() < 1){//库存不足return Result.fail("库存不足");}Long userId=UserHolder.getUser().getId();synchronized(userId.toString().intern()) {//获取代理对象IVoucherOrderService proxy= (IVoucherOrderService) AopContext.currentProxy();//代理对象进行调用return proxy.createVoucherOrder(voucherId);}}//上面操作都是查询,不需要添加@Transactional了@Transactionalpublic Result createVoucherOrder(Long voucherId) {//一人一单Long userId=UserHolder.getUser().getId();//查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();//判断是否存在if (count > 0) {return Result.fail("用户已购买过该优惠券");}//扣减库存//mybatisplusboolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0) //增加对stock值的判断.update();if (!success) {return Result.fail("扣减库存失败");}//count< = 0的时候//创建订单VoucherOrder voucherOrder = new VoucherOrder();//订单idlong ordrId = redisIdWorker.nextId("order");voucherOrder.setId(ordrId);//用户id
//        long userId=UserHolder.getUser().getId();voucherOrder.setUserId(userId);//代金券idvoucherOrder.setVoucherId(voucherId);//把订单写入数据库save(voucherOrder);//返回订单idreturn Result.ok(ordrId);}
}

IVoucherOrderService

在这里插入图片描述

要使用代理,需要在pom文件中加入下面的代码

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>

并且需要在启动类上加上注解@EnableAspectJAutoProxy(exposeProxy = true) 来暴露这个代理对象

⭐代码分析

为什么要加上锁

在该函数中,使用了synchronized关键字加上锁,这是为了确保在多线程环境下,同一时间只有一个线程能够执行该代码块。这样可以避免多个线程同时修改共享资源导致的数据不一致问题。具体来说,在该代码块中,使用了线程的id作为锁,可以确保每个线程都有自己的锁,互不干扰。通过加锁,能够保证在多线程环境下,该代码块的执行是线程安全的。

synchronized(userId.toString().intern()) {
//获取代理对象
IVoucherOrderService proxy= (IVoucherOrderService) AopContext.currentProxy();
//代理对象进行调用
return proxy.createVoucherOrder(voucherId);
}
这段代码里面的获取代理对象有什么用

如果我们不写成 return proxy.createVoucherOrder(voucherId); ,写成 return createVoucherOrder(voucherId); 那么默认的是 this 进行调用 createVoucherOrder(voucherId)
使用 默认的 this 进行调用的话,我们拿到的是当前的 createVoucherOrder(voucherId) ,而不是代理对象
( 这里我们知道,@Transactional要想生效,其实是因为spring对当前类(VoucherOrderServiceImpl) 进行了动态代理 ,拿到了代理对象createVoucherOrder , 然后使用createVoucherOrder进行代理 )

我们使用上面的方法进行代理后,事务就可以生效了

上面那段代码的userId.toString().intern()有什么用

因为我们希望id值一样的 用的是同一把锁,每次请求的都是不同的对象,对象变了,为了保证值一样,我们使用了tostring()方法
但是实际上我们每调用一次tostring()方法,都传入了一个全新的字符串对象,这样子值还是会发生变化
为了保证值不变,我们需要加上intern()方法
intern()方法是去字符串常量池里面,找到和之前id的值一样的字符串地址,然后进行返回
这样子我们就保证了,只要值一样,不论new了多少个新字符串对象,返回的结果都是一样的

在技术的道路上,我们不断探索、不断前行,不断面对挑战、不断突破自我。科技的发展改变着世界,而我们作为技术人员,也在这个过程中书写着自己的篇章。让我们携手并进,共同努力,开创美好的未来!愿我们在科技的征途上不断奋进,创造出更加美好、更加智能的明天!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/303457.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于“Python”的核心知识点整理大全44

目录 ​编辑 15.3.4 模拟多次随机漫步 rw_visual.py 注意 15.3.5 设置随机漫步图的样式 15.3.6 给点着色 rw_visual.py 15.3.7 重新绘制起点和终点 rw_visual.py 15.3.8 隐藏坐标轴 rw_visual.py 15.3.9 增加点数 rw_visual.py 15.3.10 调整尺寸以适合屏幕 rw_vi…

遥感影像辐射定标

遥感影像原始数据中每个像元代表地面光谱反射率的相对大小&#xff0c;叫做DN值。如果要 用于后续的反演或者生成一些反射率等产品时&#xff0c;必须要做辐射定标。 1.光学影像 对于普通光学影像来说&#xff0c;辐射定标可以输出两种&#xff1a;辐亮度和表观反射率…

【http】HTTP/1.0、HTTP/1.1和HTTP/2.0

✨ 专栏介绍 在当今互联网时代&#xff0c;计算机网络已经成为了人们生活和工作中不可或缺的一部分。而要实现计算机之间的通信和数据传输&#xff0c;就需要依靠各种网络协议来进行规范和约束。无论是浏览网页、发送电子邮件还是进行在线交流&#xff0c;都离不开各种各样的网…

C语言—每日选择题—Day64

前言 两天没更新了&#xff0c;作者在复习期末考试&#xff0c;更新一波&#xff0c;祝大家都能顺利通过期末考试&#xff01;&#xff01;&#xff01; 指针相关博客 打响指针的第一枪&#xff1a;指针家族-CSDN博客 深入理解&#xff1a;指针变量的解引用 与 加法运算-CSDN博…

Redis分布式缓存之主从哨兵分片集群

Redis主从 数据同步原理 Redis哨兵 Redis分片集群 集群伸缩&#xff1a;在集群中插入或删除某个节点 集群故障转移

Linux内核中断

Linux内核中断 ARM里当按下按键的时候&#xff0c;他首先会执行汇编文件start.s里面的异常向量表里面的irq,在irq里面进行一些操作。 再跳转到C的do_irq(); 进行操作&#xff1a;1&#xff09;判断中断的序号&#xff1b;2&#xff09;处理中断&#xff1b;3&#xff09;清除中…

tekton 发布 kubernetes 应用

tekton 发布 kubernetes 应用 基于Kubernetes 服务部署 Tekton Pipeline 实例&#xff0c;部署完成后使用tekton来完成源码拉取、应用打包、镜像推送和应用部署。 本文实现一个 golang-helloworld 项目 CI/CD 的完整流程&#xff0c;具体包括以下步骤&#xff1a; 从 gitee…

基于MATLAB的正态分布与卡方分布(附完整代码与例题)

目录 一. 理论部分 二. MATLAB所使用的函数介绍 2.1 概率密度函数 2.2 概率分布函数 2.3 逆概率分布函数 三. 例题与代码 例题1 例题2 例题3 例题4 一. 理论部分 将连续随机变量的概率密度函数记为&#xff0c;既然跟概率相关&#xff0c;那必然满足两个重要的性质&a…

加速布局!美格智能获国内某自主大厂智能座舱项目模组定点

近日&#xff0c;销售前线又传来重大好消息&#xff0c;美格智能座舱模组正式获得国内某自主大厂前装智能座舱项目定点。此次项目由主机厂直接定点模组&#xff0c;基于美格智能座舱模组SLM925来打造平台化智能座舱解决方案&#xff0c;同时此方案也将会应用于该汽车品牌及旗下…

图灵日记之java奇妙历险记--类和对象

目录 类的定义和使用类的定义格式 类的实例化类和对象的说明 this引用this引用的特性 对象的构造及初始化就地初始化构造方法 封装包导入包中的类自定义包 static成员static修饰成员变量static修饰成员方法 代码块代码块概念及分类构造代码块静态代码块 匿名对象 类的定义和使用…

Transfer Learning(迁移学习)

1. 什么是迁移学习 迁移学习(Transfer Learning)是一种机器学习方法&#xff0c;就是把为任务 A 开发的模型作为初始点&#xff0c;重新使用在为任务 B 开发模型的过程中。迁移学习是通过从已学习的相关任务中转移知识来改进学习的新任务&#xff0c;虽然大多数机器学习算法都…

C语言使用蔡勒公式判断日期的星期

引言 在日常编程中&#xff0c;处理日期和时间是一个常见的任务。而了解一个特定日期是星期几&#xff0c;是许多应用程序中的一个基本需求。本篇博客将深入解析一个用于计算星期的 C 语言函数。 代码概览 这个函数使用了蔡勒公式来实现&#xff0c;蔡勒公式&#xff08;Zel…