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

    @Resource
    private 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 {

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    
    @Resource
    private 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("库存不足");
        }
        //扣减库存
            //mybatisplus
        boolean success=seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();
        if(!success){
            return Result.fail("扣减库存失败");
        }
        //创建订单
        VoucherOrder voucherOrder=new VoucherOrder();
            //订单id
        long ordrId=redisIdWorker.nextId("order");
        voucherOrder.setId(ordrId);
            //用户id
        long userId=UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
            //代金券id
        voucherOrder.setVoucherId(voucherId);
        //把订单写入数据库
        save(voucherOrder);
        //返回订单id
        return Result.ok(ordrId);
        
    }
}

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

请添加图片描述

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

请添加图片描述

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

🎆完善后的操作

🛸乐观锁

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

请添加图片描述

🌹代码实现

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


我们进入VoucherOrderServiceImpl

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

🍔一人仅一张优惠券

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

🛸思路

请添加图片描述

🌹代码

VoucherOrderServiceImpl

@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    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("库存不足");
        }

        Long userId=UserHolder.getUser().getId();

        synchronized(userId.toString().intern()) {
            //获取代理对象
            IVoucherOrderService proxy= (IVoucherOrderService) AopContext.currentProxy();
			//代理对象进行调用
            return proxy.createVoucherOrder(voucherId);
        }

    }
    
    
    //上面操作都是查询,不需要添加@Transactional了

    @Transactional
    public 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("用户已购买过该优惠券");
            }

            //扣减库存
            //mybatisplus
            boolean 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();
            //订单id
            long ordrId = redisIdWorker.nextId("order");
            voucherOrder.setId(ordrId);
            //用户id
//        long userId=UserHolder.getUser().getId();
            voucherOrder.setUserId(userId);
            //代金券id
            voucherOrder.setVoucherId(voucherId);
            //把订单写入数据库
            save(voucherOrder);
            //返回订单id
            return 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了多少个新字符串对象,返回的结果都是一样的

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序开发系列-05登录小程序

本文继续学习下微信小程序登录相关的内容。 微信平台小程序用户体系 普通用户视角&#xff1a;对于每个小程序&#xff0c;微信都会将用户的微信ID映射出一个小程序OpenID&#xff0c;作为这个用户在这个小程序的唯一标识。&#xff08;注意&#xff1a;同一微信用户在不同小…

unityc用vs2017介绍

21版unity能用17vs&#xff0c;只要在unity的Edit/Preferences/ExternalTools里面改既可。

继承-继承方式

继承方式 继承的语法: class 子类 &#xff1a;继承方式 父类 继承方式一共有三种&#xff1a; 1.公共继承 2.保护继承 3.私有继承 //继承方式 #include<iostream> using namespace std; class Base1 { public:int m_A; protected:int m_B; private:int m_C; }; cla…

卷积神经网络基础

全连接层 BP&#xff08;back propagation&#xff09;算法包括信号的前向传播和误差的反向传播两个过程。即计算误差输出时按从输入到输出的方向进行&#xff0c;而调整权值和阈值则从输出到输入的方向进行。 误差值&#xff1a;将输出值和所期望的值进行对比&#xff0c;可以…

设备智能运维利器:无线振温一体式传感器

在现代工业领域中&#xff0c;设备的状态监测和维护是确保生产正常运行的关键环节。随着技术的不断进步&#xff0c;传感器在设备监测中发挥着越来越重要的作用。其中&#xff0c;无线振温一体式传感器作为设备智能运维的利器&#xff0c;具有独特的优势和潜力。本文将介绍无线…

C++ Qt开发:TableView与TreeView组件联动

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍TableView与TreeView组件联动的常用方法及灵活…

Transfer Learning(迁移学习)

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

表单(HTML)

<!DOCTYPE html> <html><head><meta charset"utf-8"><title>个人信息</title></head><body><h1>个人信息</h1><form><fieldset><legend>基本信息</legend><label for"…

强光led电筒控制芯片方案:OC5338

强光LED电筒控制芯片OC5338是一款内置100V/3A功率MOS的高性能芯片方案。它具有多种功能&#xff0c;包括100%输出、25%输出和爆闪。此外&#xff0c;它还具有宽输入电压范围&#xff0c;从3.6V到100V&#xff0c;并且具有高达90%的高效率。 强光led电筒控制芯片方案:OC5338 L…

C#与VisionPro联合编程

C#与VisionPro联合 1. 参照康耐视提供的样例2. 参照样例写一个1. 创建工程2. 添加引用3. 声明变量4. 初始化5. 刷新队列6. 用户数据获取7. 跨线程访问Windows控件--委托8. 显示图像9. 释放资源 3. 代码4. 资源下载 1. 参照康耐视提供的样例 C:\Program Files\Cognex\VisionPro…

【Linux操作系统】命令补全

补全命令 快捷键&#xff1a;Tab 示例&#xff1a; 在终端中输入“ifc”&#xff0c;按Tab键&#xff0c;自动补全为“ifconfig”命令——查询IP地址。

k8s的二进制部署

k8s的二进制部署&#xff1a;源码包部署 k8smaster01: 20.0.0.101 kube-apiserver kube-controller-manager kube-scheduler etcd k8smaster02: 20.0.0.102 kube-apiserver kube-controller-manager kube-scheduler node节点01: 20.0.0.103 kubelet kube-proxy etcd node节点02…

最优化考试之牛顿法

最优化考试之牛顿法 一、牛顿法1.问题条件2.求解过程3.例子 PS 一、牛顿法 1.问题条件 目标函数 f ( x ) f(x) f(x)&#xff0c;求极小值初始点 x 0 x_0 x0​精度要求e&#xff08;没有提就是近似0&#xff09; 2.求解过程 求解一阶雅克比矩阵 ∇ f ( x ) ∇f(x) ∇f(x)和二…

Jmeter接口工具大全使用—响应断言

断言的作用&#xff1a;一个HTTP请求发出去&#xff0c;怎么判断执行的任务是否成功呢&#xff1f;通过检查服务器响应数据&#xff0c;是否返回预期想要的数据&#xff0c;如果是&#xff0c;判断任务成功&#xff0c;反之任务失败。 1.添加断言 选中一个取样器&#xff0c;…

少走弯路:单片机使用点阵字体通过像素化的正确获取

要在单片机内自由显示文字&#xff0c;必须准备相应的字库。之前也发文介绍过&#xff1a; 在esp32(esp8266) 提供软字库显示中文的解决方案_esp32中文字库-CSDN博客 包括已经开源的项目&#xff1a; https://github.com/StarCompute/tftziku 这种字体获取思路是&#xff1a…

HackTheBox - Medium - Linux - Agile

Agile Agile 是一个中等难度的 Linux 机器&#xff0c;在端口 80 上有一个密码管理网站。创建帐户并添加几个密码后&#xff0c;发现网站的导出到 CSV 功能容易受到任意文件读取的攻击。其他终结点的枚举显示“/download”在访问时引发错误&#xff0c;并显示“Werkzeug”调试…

Duboo-入门到学废【上篇】

目录 1&#x1f95e;.什么是duboo 2&#x1f32d;.架构图 3.&#x1f37f;快速入门 4.&#x1f9c7;浅浅理解 1.什么是duboo&#x1f936;&#x1f936;&#x1f936; Dubbo是一个由阿里巴巴开发的基于Java的开源RPC框架。它提供了高性能、透明化的远程方法调用&#xff0…

通讯录管理系统简单实现

1.功能介绍 今天我们要实现的通讯录管理系统主要有7项功能&#xff1a;添加联系人&#xff0c;显示联系人&#xff0c;删除联系人&#xff0c;查找联系人&#xff0c;修改联系人&#xff0c;清空联系人&#xff0c;退出通讯录 2.功能实现 2.1创建联系人结构体 通讯录结构体 一…

linux 中 ext2文件系统实现

ext2文件系统结构 图片的svg下载链接&#xff08;图中关于buffer的部分&#xff0c;上下两部分是重复的&#xff0c;是从不同维度下看的buffer结构&#xff09; linux内核本身不提供ext2文件系统的格式化功能&#xff0c;可以参考busybox中对mkfs.ext2的实现&#xff08;mkfs.…

【数据结构】顺序表与单链表的增删查改

文章目录 前言顺序表增删查改顺序表的定义与初始化增删查改操作测试代码完整代码 单链表的增删查改数据结构定义动态申请节点单链表的尾插和头插单链表的尾删和头删单链表的查找单链表的插入和删除销毁链表测试代码完整代码 总结 前言 在计算机编程领域&#xff0c;数据结构是…