Redis代金卷(优惠卷)秒杀案例-单应用版

news2025/2/2 12:49:06

优惠卷表:优惠卷基本信息,优惠金额,使用规则  包含普通优惠卷和特价优惠卷(秒杀卷)

优惠卷的库存表:优惠卷的库存,开始抢购时间,结束抢购时间.只有特价优惠卷(秒杀卷)才需要填写这些信息

优惠卷订单表

卷的表里已经有一条普通优惠卷记录

下面首先新增一条秒杀优惠卷记录

{
  "shopId": 1,
  "title": "100元代金券",
  "subTitle": "周一至周五均可使用",
  "rules": "全场通用\\n无需预约\\n可无限叠加\\n不兑现、不找零\\n仅限堂食",
  "payValue": 8000,
  "actualValue": 10000,
  "type": 1,
  "stock": 100,
  "beginTime": "2022-01-25T10:09:17",
  "endTime": "2022-01-26T12:09:04"
}
 

返回一个订单

实现秒杀下单

就是往下面这张表中添加数据  创建订单  

这里暂时只需要添加主键id  购买的代金卷id  

对应的还需要去扣减库存

关于订单的主键

使用Redis生成全局唯一ID示例-CSDN博客

控制器

业务层

传入的优惠卷id

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


    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher byId = seckillVoucherService.getById(voucherId);
        if (byId.getBeginTime().isAfter(LocalDateTime.now())) {
            //2.判断秒杀是否开始
            return Result.fail("秒杀尚未开始");
        }
        if (byId.getEndTime().isBefore(LocalDateTime.now())) {
            //3.判断秒杀是否结束
            return Result.fail("秒杀已经结束");
        }
        if (byId.getStock() < 1) {
            //4.判断库存是否充足
            return Result.fail("库存不足");
        }
        //5.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .update();
        if(!success){
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1.订单id
        long order = redisIdWorker.nextId("order");
        voucherOrder.setId(order);
        voucherOrder.setUserId(1L);//登录用户先写死
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        return Result.ok(order);
    }
}

以上代码,在高并发场景下会出现超卖现象

以下用乐观锁方式解决超卖问题

用乐观锁 CAS法解决超卖问题

就是在更新时候添加个条件

当200个并发线程打进去之后

虽然解决了并发安全问题,但是出现成功率低的问题   200个线程进来  没有卖完 很多都失败了

如何解决??

修改下条件  where id=? and stock>0 即可

这样就可以解决超卖问题

以上是用JMeter并发测试的,从结果看,系统还存在一个问题,例如对方开启并发访问的工具,这样会导致所有订单都是同一个用户购买的情况,或者说一个人购买了好几单

在订单表中出现如图情况

这样的漏洞,就好比黄牛了,那么系统如何实现一人一单,就是说一个用户最多下一个订单的需求

其实很简单  只要满足user_id 和 voucher_id唯一即可

就是做个查询,该用户有没有下单

如果这样写  并发情况下还是会出现问题

因为当你去判断count>0时候可能已经有10个线程都完成了查询   在判断count的时候  都查出来等于0的情况

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


    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher byId = seckillVoucherService.getById(voucherId);
        if (byId.getBeginTime().isAfter(LocalDateTime.now())) {
            //2.判断秒杀是否开始
            return Result.fail("秒杀尚未开始");
        }
        if (byId.getEndTime().isBefore(LocalDateTime.now())) {
            //3.判断秒杀是否结束
            return Result.fail("秒杀已经结束");
        }
        if (byId.getStock() < 1) {
            //4.判断库存是否充足
            return Result.fail("库存不足");
        }
        Long id = UserHolder.getUser().getId();//表示获取用户id
        //id.toString()实际是new了一个String对象,然后调用intern()方法,这个方法会去常量池中查找有没有这个对象,如果有就返回这个对象,如果没有就添加到常量池中,然后返回这个对象
        synchronized (id.toString().intern()){
            //如果这样调用,前面有个this 事务使用代理对象去执行的
            //return createVoucherOrder(voucherId);
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }
    }
    @Transactional(rollbackFor = Exception.class)
    public Result createVoucherOrder(Long voucherId) {
        Long id = UserHolder.getUser().getId();//
        //一人一单的查询
        Integer count = query().eq("user_id", id)
                .eq("voucher_id", voucherId)
                .count();
        if(count > 0){
            return Result.fail("您已经购买过一次了");
        }
        //5.扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();
        if(!success){
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1.订单id
        long order = redisIdWorker.nextId("order");
        voucherOrder.setId(order);
        voucherOrder.setUserId(id);//登录用户先写死
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        Result.ok(order);
    }
}

以上方案仅适用于单机版本

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

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

相关文章

51单片机 01 LED

一、点亮一个LED 在STC-ISP中单片机型号选择 STC89C52RC/LE52RC&#xff1b;如果没有找到hex文件&#xff08;在objects文件夹下&#xff09;&#xff0c;在keil中options for target-output- 勾选 create hex file。 如果要修改编程 &#xff1a;重新编译-下载/编程-单片机重…

MusicFree-开源的第三方音乐在线播放和下载工具, 支持歌单导入[对标落雪音乐]

MusicFree 链接&#xff1a;https://pan.xunlei.com/s/VOI0RrVLTTWE9kkpt0U7ofGBA1?pwd4ei6#

消息队列篇--原理篇--常见消息队列总结(RabbitMQ,Kafka,ActiveMQ,RocketMQ,Pulsar)

1、RabbitMQ 特点&#xff1a; AMQP协议&#xff1a;RabbitMQ是基于AMQP&#xff08;高级消息队列协议&#xff09;构建的&#xff0c;支持多种消息传递模式&#xff0c;如发布/订阅、路由、RPC等。多语言支持&#xff1a;支持多种编程语言的客户端库&#xff0c;包括Java、P…

nacos 配置管理、 配置热更新、 动态路由

文章目录 配置管理引入jar包添加 bootstrap.yaml 文件配置在application.yaml 中添加自定义信息nacos 配置信息 配置热更新采用第一种配置根据服务名确定配置文件根据后缀确定配置文件 动态路由DynamicRouteLoaderNacosConfigManagerRouteDefinitionWriter 路由配置 配置管理 …

(笔记+作业)书生大模型实战营春节卷王班---L0G2000 Python 基础知识

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/QtJnweAW1iFl8LkoMKGcsUS9nld 课程视频&#xff1a;https://www.bilibili.com/video/BV13U1VYmEUr/ 课程文档&#xff1a;https://github.com/InternLM/Tutorial/tree/camp4/docs/L0/Python 关卡作业&#xff1a;htt…

SpringBoot中Excel表的导入、导出功能的实现

文章目录 一、easyExcel简介二、Excel表的导出2.1 添加 Maven 依赖2.2 创建导出数据的实体类4. 编写导出接口5. 前端代码6. 实现效果 三、excel表的导出1. Excel表导入的整体流程1.1 配置文件存储路径 2. 前端实现2.1 文件上传组件 2.2 文件上传逻辑3. 后端实现3.1 文件上传接口…

动态规划DP 背包问题 完全背包问题(题目分析+C++完整代码)

概览检索 动态规划DP 概览&#xff08;点击链接跳转&#xff09; 动态规划DP 背包问题 概览&#xff08;点击链接跳转&#xff09; 完全背包问题 原题链接 AcWiing 3. 完全背包问题 题目描述 有 N种物品和一个容量是 V的背包&#xff0c;每种物品都有无限件可用。 第 i种物…

【cocos creator】【模拟经营】餐厅经营demo

下载&#xff1a;【cocos creator】模拟经营餐厅经营

【深度学习】softmax回归的从零开始实现

softmax回归的从零开始实现 (就像我们从零开始实现线性回归一样&#xff0c;)我们认为softmax回归也是重要的基础&#xff0c;因此(应该知道实现softmax回归的细节)。 本节我们将使用Fashion-MNIST数据集&#xff0c;并设置数据迭代器的批量大小为256。 import torch from IP…

【Redis】set 和 zset 类型的介绍和常用命令

1. set 1.1 介绍 set 类型和 list 不同的是&#xff0c;存储的元素是无序的&#xff0c;并且元素不允许重复&#xff0c;Redis 除了支持集合内的增删查改操作&#xff0c;还支持多个集合取交集&#xff0c;并集&#xff0c;差集 1.2 常用命令 命令 介绍 时间复杂度 sadd …

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<3>

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 今天我们来对上一节做一些小补充&#xff0c;了解学习一下assert断言&#xff0c;指针的使用和传址调用…

神经网络的数据流动过程(张量的转换和输出)

文章目录 1、文本从输入到输出&#xff0c;经历了什么&#xff1f;2、数据流动过程是张量&#xff0c;如何知道张量表达的文本内容&#xff1f;3、词转为张量、张量转为词是唯一的吗&#xff1f;为什么&#xff1f;4、如何保证词张量的质量和合理性5、总结 &#x1f343;作者介…

爬取鲜花网站数据

待爬取网页&#xff1a; 代码&#xff1a; import requestsfrom lxml import etree import pandas as pdfrom lxml import html import xlwturl "https://www.haohua.com/xianhua/"header {"accept":"image/avif,image/webp,image/apng,image/sv…

vue框架技术相关概述以及前端框架整合

vue框架技术概述及前端框架整合 1 node.js 介绍&#xff1a;什么是node.js Node.js就是运行在服务端的JavaScript。 Node.js是一个事件驱动I/O服务端JavaScript环境&#xff0c;基于Google的V8引擎。 作用 1 运行java需要安装JDK&#xff0c;而Node.js是JavaScript的运行环…

数据结构 树2

文章目录 前言 一&#xff0c;二叉搜索树的高度 二&#xff0c;广度优先VS深度优先 三&#xff0c;广度优先的代码实现 四&#xff0c;深度优先代码实现 五&#xff0c;判断是否为二叉搜索树 六&#xff0c;删除一个节点 七&#xff0c;二叉收索树的中序后续节点 总结 …

NeetCode刷题第19天(2025.1.31)

文章目录 099 Maximum Product Subarray 最大乘积子数组100 Word Break 断字101 Longest Increasing Subsequence 最长递增的子序列102 Maximum Product Subarray 最大乘积子数组103 Partition Equal Subset Sum 分区等于子集和104 Unique Paths 唯一路径105 Longest Common Su…

Google Chrome-便携增强版[解压即用]

Google Chrome-便携增强版 链接&#xff1a;https://pan.xunlei.com/s/VOI0OyrhUx3biEbFgJyLl-Z8A1?pwdf5qa# a 特点描述 √ 无升级、便携式、绿色免安装&#xff0c;即可以覆盖更新又能解压使用&#xff01; √ 此增强版&#xff0c;支持右键解压使用 √ 加入Chrome增强…

[EAI-027] RDT-1B,目前最大的用于机器人双臂操作的机器人基础模型

Paper Card 论文标题&#xff1a;RDT-1B: a Diffusion Foundation Model for Bimanual Manipulation 论文作者&#xff1a;Songming Liu, Lingxuan Wu, Bangguo Li, Hengkai Tan, Huayu Chen, Zhengyi Wang, Ke Xu, Hang Su, Jun Zhu 论文链接&#xff1a;https://arxiv.org/ab…

[EAI-028] Diffusion-VLA,能够进行多模态推理和机器人动作预测的VLA模型

Paper Card 论文标题&#xff1a;Diffusion-VLA: Scaling Robot Foundation Models via Unified Diffusion and Autoregression 论文作者&#xff1a;Junjie Wen, Minjie Zhu, Yichen Zhu, Zhibin Tang, Jinming Li, Zhongyi Zhou, Chengmeng Li, Xiaoyu Liu, Yaxin Peng, Chao…

DIFY源码解析

偶然发现Github上某位大佬开源的DIFY源码注释和解析&#xff0c;目前还处于陆续不断更新地更新过程中&#xff0c;为大佬的专业和开源贡献精神点赞。先收藏链接&#xff0c;后续慢慢学习。 相关链接如下&#xff1a; DIFY源码解析