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

2024-03-08 1297阅读

温馨提示:这篇文章已超过393天没有更新,请注意相关的内容是否还可用!

文章目录

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

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

              🍔发放优惠券

              🎆基本操作

              🎄数据库表

              普通券

              我们来看这一张表

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

              里面包含了主键,商铺id,使用规则,时间等内容

              可以看到里面没有库存,意味着所有人都可以来购买,所以是普通券

              秒杀券

              我们看下面这一张表

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

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

              🛸思路

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

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

                🌹代码实现

                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);
                    }
                }
                

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


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

                package com.hmdp.service;
                import com.hmdp.dto.Result;
                import com.hmdp.entity.VoucherOrder;
                import com.baomidou.mybatisplus.extension.service.IService;
                /**
                 * 

                * 服务类 *

                */ public interface IVoucherOrderService extends IService { Result seckillVoucher(Long voucherId); }

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

                @Slf4j
                @Service
                public class VoucherOrderServiceImpl extends ServiceImpl 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()  
                

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

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

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

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

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

                🎆完善后的操作

                🛸乐观锁

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

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

                🌹代码实现

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


                我们进入VoucherOrderServiceImpl

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

                库存大于0,证明有库存,这样子就行了

                🍔一人仅一张优惠券

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

                🛸思路

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

                🌹代码

                VoucherOrderServiceImpl

                @Slf4j
                @Service
                public class VoucherOrderServiceImpl extends ServiceImpl 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()  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 
                 
                 

                IVoucherOrderService

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

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

                            org.aspectj
                            aspectjweaver
                        
                

                并且需要在启动类上加上注解@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了多少个新字符串对象,返回的结果都是一样的

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

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

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]