【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.mzph.cn/news/580754.shtml

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

相关文章

git远程操作,推送【push】,拉取【pull】,忽略特殊文件,配置别名,标签管理

文章目录 前言&#xff1a;新建远程仓库克隆推送【push】拉取【pull】 配置git忽略特殊文件给命令配置别名 标签管理理解标签创建标签操作标签 前言&#xff1a; 大家如果没有看过前几章git的基础操作的话&#xff0c;推荐先看一下&#xff0c;看完再来看这个远程操作&#xf…

【PHP】取出数组中的第一个元素

目录 1.使用 reset() 函数&#xff1a; 2.使用 array_shift() 函数&#xff1a; 在 PHP 中&#xff0c;可以使用 reset() 函数或者 array_shift() 函数来取出数组中的第一个元素。 1.使用 reset() 函数&#xff1a; $array [1, 2, 3, 4, 5]; $firstElement reset($array);…

2023年总结:反复纠结与成长的一年

前言 这是我第五年写年度总结&#xff1a; 《2022年总结&#xff1a;道阻且长&#xff0c;行则将至》 《2021年总结&#xff1a;前路有光&#xff0c;初心莫忘》 《2020年总结&#xff0c;所有努力只为一份期待》 《2019年总结&#xff0c;平凡的我仍在平凡的生活》 现在…

【超图】SuperMap iClient3D for WebGL/WebGPU —— 数据集合并缓存如何控制对象样式

作者&#xff1a;taco 最近在支持的过程中&#xff0c;遇到了一个新问题&#xff01;之前研究功能的时候竟然没有想到。通常我们控制单个对象的显隐、颜色、偏移的参数都是根据对象所在的图层以及对象单独的id来算的。那么问题来了&#xff0c;合并后的图层。他怎么控制单个对象…

gorm 使用sql方法

var users []User// 查询 执行用Scan 和Find 一样dbdb.Raw("select uid,user_name,age from Users").Scan(&users)//dbdb.Raw("select uid,user_name,age from Users").Find(&users)fmt.Println("Users",users)// 更新和删除.插入用 …

【Recruitment】

Network I)JD I)JD 1、英语听说读写熟练&#xff0c;有较强的英语沟通能力&#xff1b;2、丰富的网络项目管理和运维管理经验&#xff1b;3、有较强的沟通能力&#xff1b;4、有丰富的供应商管理经验&#xff1b;5、熟悉ITIL管理流程&#xff1b;6、有敏锐的发现问题的能力&am…

面试官:SpringBoot项目中,要如何1秒实现异步接口?

今年IT寒冬&#xff0c;大厂都裁员或者准备裁员&#xff0c;作为开猿节流主要目标之一&#xff0c;我们更应该时刻保持竞争力。为了抱团取暖&#xff0c;林老师开通了《知识星球》&#xff0c;并邀请我阿里、快手、腾讯等的朋友加入&#xff0c;分享八股文、项目经验、管理经验…

STM32逆变器方案

输入电压&#xff1a; 额定输入电压&#xff1a;DC110V 输入电压范围&#xff1a;DC77-137.5V 额定输出参数 电压&#xff1a;200V5%&#xff08;200VAC~240VAC 可调&#xff09; 频率&#xff1a; 42Hz0.5Hz&#xff08;35-50 可调&#xff09; 额定输出容量&#xff1a;1…

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

目录 15.4.6 绘制直方图 die_visual.py 注意 15.4.7 同时掷两个骰子 dice_visual.py 15.4.8 同时掷两个面数不同的骰子 different_dice.py 15.5 小结 第 16 章 16.1 CSV 文件格式 16.1.1 分析 CSV 文件头 highs_lows.py 注意 16.1.2 打印文件头及其位置 highs_l…

适合穷人创业项目低成本生意,2024热门创业项目

回收生意&#xff0c;一个月赚20万&#xff1f;别不信&#xff01; 我们两个人靠回收倒闭的酒店和KTV的店内物品&#xff0c;一个月赚了20多万。大件就是家具家电、厨房设备、点唱机&#xff0c;小件就是床品、餐具&#xff0c;只要能卖钱的都收。 卖给谁呢&#xff1f;大部分…

office bookmarks

Word2007Util.java-CSDN博客

webstorm中直接运行ts(TypeScript)

参考&#xff1a;https://www.cnblogs.com/yangfanjie/p/12036118.html 1&#xff1a;安装ts: npm install -g typescript 2&#xff1a;安装直接运行所需依赖包&#xff1a; npm install -g ts-node 3&#xff1a;在设置中安装安装插件后重启 4&#xff1a;重启后就会发现在…

K8s系列 Prometheus+Grafana构建智能化监控系统

集群环境 hd1:192.168.8.11 控制节点 hd2:192.168.8.12 工作节点 hd3:192.168.8.13 工作节点 本文介绍 k8s集群中部署prometheus、grafana、alertmanager&#xff0c;并且配置prometheus的动态、静态服务发现&#xff0c;实现对容器、物理节点、service、pod等资源指标监控&…

vue页面跳转及传参

页面跳转及传参 使用<router-link>跳转,<router-link> 默认会被渲染成一个 <a> 标签 <router-link to"/btn">通过to指定链接</router-link><router-link :to"{ path:/btn} ">根据path跳转</router-link><ro…

Delphi中定义类的几种形式

类定义&#xff1a; type// 基本类定义TMyClass classprivate// 私有成员FPrivateField: Integer;protected// 受保护成员FProtectedField: String;public// 公有成员FPublicField: Double;// 构造函数constructor Create;// 析构函数destructor Destroy; override;// 成员方法…

Solana 生态铭文跨链桥 Sobit 是何神圣?其场外白名单已达到1200U

在短暂的沉寂&#xff0c;在与 Solana 手机 Saga 联合生态 Meme 币 Bonk 掀起一波 meme 浪潮&#xff0c;以及GPU 计算网路Render network 宣布将从公链Polygon迁往Solana 后&#xff0c;Solana 生态再次迎来爆发。随着 SOL 代币在 12 月暴涨&#xff0c;SOL 也在市值上超越了 …

剑指offer题解合集——Week2day1

文章目录 剑指offerWeek2周一&#xff1a;机器人的运动范围AC代码思路&#xff1a; 剑指offerWeek2 周一&#xff1a;机器人的运动范围 题目链接&#xff1a;机器人的运动范围 地上有一个 m行和 n列的方格&#xff0c;横纵坐标范围分别是 0∼m−1和 0∼n−1 。一个机器人从…

前端进度条和进度条流光效果

前言 进度条的实现学习这个的,这里只是记录下自己笔记 https://bytefish.medium.com/css-awesome-trick-how-to-create-a-progress-bar-that-changes-color-according-to-progress-be9652ebdd1c 在线演示地址(原作者) https://codepen.io/bytefishmedium/pen/VwXYKQK 在线演示…

2023.12.27力扣每日一题——保龄球游戏的获胜者

2023.12.27 题目来源我的题解方法一 模拟 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2660 我的题解 方法一 模拟 就纯模拟&#xff0c;在计算玩家分数时&#xff0c;只要不是第一轮都需要判断前两轮中是否有得分为10的&#xff0c;若有则需要将本轮分数*2 时间复杂…