【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…

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

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

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

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

面试官: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;重启后就会发现在…

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

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

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

前言 进度条的实现学习这个的,这里只是记录下自己笔记 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 在线演示…

opencv和gdal的读写图片波段顺序问题

最近处理遥感影像总是不时听到 图片的波段错了&#xff0c;一开始不明就里&#xff0c;都是图片怎么就判断错了。 1、图像RGB波段顺序判断 后面和大家交流&#xff0c;基本上知道了一个判断标准。 一般来说&#xff0c;进入人眼的自然画面在计算机视觉中一般是rgb波段顺序表示…

Linux账号和权限管理

目录 前言 一、管理用户账号 1、Linux系统中用户账号类型 2、用户标识UID的分类 3、用户账号文件 4、用户账号的初始配置文件 5、用户账号的管理命令 5.1 useradd 5.2 usermod 5.3 passwd 5.4 userdel 二、管理组账号 1、Linux系统中组账号类型 2、组标识号GID的…

drf知识--07

回顾之视图层 # 两个视图基类&#xff1a; from rest_framework.views import APIView&#xff1a; 包装新的request、去除csrf认证、执行三大认证和处理全局异常 -as_view -dispatch -parser_class -render_class from rest_framewo…

5.8 Linux 服务实战

一、项目概述 项目名称&#xff1a;web 网站 项目时间&#xff1a;2022.7.18-2022.7.24 项目需求&#xff1a; ① 客户端使用kickstart部署4台虚拟机(centos7.9)&#xff0c;所有服务器IP都为静态IP。② 客户端使用XShell的密钥登陆跳板机③ 所有后端服务器全部通过跳板机来…

Shell命令与Linux操作系统:深入理解其原理和功能(2/2)

在当今数字化时代&#xff0c;操作系统的安全性和稳定性对于个人用户和企业都至关重要。Linux&#xff0c;作为一个广泛使用的操作系统&#xff0c;其强大的文件权限系统是保护系统安全的核心机制之一。无论是在服务器管理、软件开发还是日常使用中&#xff0c;有效地管理和理解…

MongoDB文档操作

3.3 文档操作 3.1 文档介绍 文档的数据结构和 JSON 基本一样。 所有存储在集合中的数据都是 BSON 格式。 BSON 是一种类似 JSON 的二进制形式的存储格式&#xff0c;是 Binary JSON 的简称。 文档是一组键值(key-value)对(即 BSON)&#xff0c;一个简单的文档例子如下&…

输入日期,计算当前日期是这一年中的第几天(涉及闰年问题)

一、应用到的知识&#xff1a;闰年问题&#xff0c;数组&#xff0c;for循环&#xff0c;命令行参数&#xff0c;atoi函数 1. 闰年问题&#xff1a; 闰年 是指该年有366日&#xff0c;即较平常年份多出一日。每400年就会有一次闰年&#xff1b;或者年份是4的倍数&#xff0c;但…

Flowable-升级为7.0.0.M2-第二节

目录 替换变化的类和配置把javax.servlet 替换为 jakarta.servlet修改redis的配置配置logging.level.org.springframework.boot.autoconfigureerror避免影响视听 替换变化的类和配置 把javax.servlet 替换为 jakarta.servlet import javax.servlet.ServletContext; import ja…