尚品汇-秒杀下单实现-页面轮询查询订单状态(五十三)

news2024/9/19 23:15:57

目录:

(1)整合秒杀业务

(2)秒杀下单

(3)秒杀下单监听

(4)页面轮询接口

(1)整合秒杀业务

秒杀的主要目的就是获取一个下单资格,拥有下单资格就可以去下单支付,获取下单资格后的流程就与正常下单流程一样,只是没有购物车这一 步,总结起来就是,秒杀根据库存获取下单资格,拥有下单资格进入下单页面(选择地址,支付方式,提交订单,然后支付订单)

步骤:

  1. 校验下单码,只有正确获得下单码的请求才是合法请求
  2. 校验状态位state

State为null,说明非法请求;

State0说明已经售罄;

State为1,说明可以抢购

状态位是在内存中判断,效率极高,如果售罄,直接就返回了,不会给服务器造成太大压力

  1. 前面条件都成立,将秒杀用户加入队列,然后直接返回
  2. 前端轮询秒杀状态,查询秒杀结果

(2)秒杀下单

添加mq常量MqConst

/**
 * 秒杀
 */
public static final String EXCHANGE_DIRECT_SECKILL_USER = "exchange.direct.seckill.user";
public static final String ROUTING_SECKILL_USER = "seckill.user";
//队列
public static final String QUEUE_SECKILL_USER  = "queue.seckill.user";

定义实体UserRecode

记录哪个用户要购买哪个商品!

@Data
public class UserRecode implements Serializable {

   private static final long serialVersionUID = 1L;

   private Long skuId;
   
   private String userId;
}

编写控制器SeckillGoodsApiController

@Autowired
private RabbitService rabbitService;
/**
 * 根据用户和商品ID实现秒杀下单
 * @param skuId
 * @return
 */
@PostMapping("auth/seckillOrder/{skuId}")
public Result seckillOrder(@PathVariable("skuId") Long skuId, HttpServletRequest request) throws Exception {
    //校验下单码(抢购码规则可以自定义)
      String userId = AuthContextHolder.getUserId(request);
    String skuIdStr = request.getParameter("skuIdStr");
    if (!skuIdStr.equals(MD5.encrypt(userId))) {
        //请求不合法
        return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);
    }

    
    //校验状态位
    //产品标识, 1:可以秒杀 0:秒杀结束
      String state = (String) CacheHelper.get(skuId.toString());
    if (StringUtils.isEmpty(state)) {
        //请求不合法
        return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);
    }
    if ("1".equals(state)) {
        //用户记录
        UserRecode userRecode = new UserRecode();
        userRecode.setUserId(userId);
        userRecode.setSkuId(skuId);
        //发送消息
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_USER, MqConst.ROUTING_SECKILL_USER, userRecode);

    } else {
        //已售罄
        return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
    }
    return Result.ok();
}

全局统一返回结果类: 

package com.atguigu.gmall.common.result;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * 全局统一返回结果类
 *
 */
@Data
@ApiModel(value = "全局统一返回结果")
public class Result<T> {

    @ApiModelProperty(value = "返回码")
    private Integer code;

    @ApiModelProperty(value = "返回消息")
    private String message;

    @ApiModelProperty(value = "返回数据")
    private T data;

    public Result(){}

    // 返回数据
    protected static <T> Result<T> build(T data) {
        Result<T> result = new Result<T>();
        if (data != null)
            result.setData(data);
        return result;
    }

    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }

    public static<T> Result<T> ok(){
        return Result.ok(null);
    }

    /**
     * 操作成功
     * @param data
     * @param <T>
     * @return
     */
    public static<T> Result<T> ok(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }

    public static<T> Result<T> fail(){
        return Result.fail(null);
    }

    /**
     * 操作失败
     * @param data
     * @param <T>
     * @return
     */
    public static<T> Result<T> fail(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.FAIL);
    }

    public Result<T> message(String msg){
        this.setMessage(msg);
        return this;
    }

    public Result<T> code(Integer code){
        this.setCode(code);
        return this;
    }

    public boolean isOk() {
        if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
            return true;
        }
        return false;
    }
}

统一返回结果状态信息类:

package com.atguigu.gmall.common.result;

import lombok.Getter;

/**
 * 统一返回结果状态信息类
 *
 */
@Getter
public enum ResultCodeEnum {

    SUCCESS(200,"成功"),
    FAIL(201, "失败"),
    SERVICE_ERROR(2012, "服务异常"),
    ILLEGAL_REQUEST( 204, "非法请求"),
    PAY_RUN(205, "支付中"),

    LOGIN_AUTH(208, "未登陆"),
    PERMISSION(209, "没有权限"),
    SECKILL_NO_START(210, "秒杀还没开始"),
    SECKILL_RUN(211, "正在排队中"),
    SECKILL_NO_PAY_ORDER(212, "您有未支付的订单"),
    SECKILL_FINISH(213, "已售罄"),
    SECKILL_END(214, "秒杀已结束"),
    SECKILL_SUCCESS(215, "抢单成功"),
    SECKILL_FAIL(216, "抢单失败"),
    SECKILL_ILLEGAL(217, "请求不合法"),
    SECKILL_ORDER_SUCCESS(218, "下单成功"),
    COUPON_GET(220, "优惠券已经领取"),
    COUPON_LIMIT_GET(221, "优惠券已发放完毕"),
    ;

    private Integer code;

    private String message;

    private ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

(3)秒杀下单监听

思路:

  1. 首先判断产品状态位,我们前面不是已经判断过了吗?因为产品可能随时售罄,mq队列里面可能堆积了十万数据,但是已经售罄了,那么后续流程就没有必要再走了;
  2. 判断用户是否已经下过订单,这个地方就是控制用户重复下单,同一个用户只能抢购一个下单资格,怎么控制呢?很简单,我们可以利用setnx控制用户,当用户第一次进来时,返回true,可以抢购,以后进入返回false,直接返回,过期时间可以根据业务自定义,这样用户这一段咋们就控制注了
  3. 获取队列中的商品,如果能够获取,则商品有库存,可以下单。如果获取的商品id为空,则商品售罄,商品售罄我们要第一时间通知兄弟节点,更新状态位,所以在这里发送redis广播
  4. 将订单记录放入redis缓存,说明用户已经获得下单资格,秒杀成功
  5. 秒杀成功要更新库存

SeckillReceiver添加监听方法

@Autowired
private SeckillGoodsService seckillGoodsService;

//  监听用户与商品的消息!
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = MqConst.QUEUE_SECKILL_USER,durable = "true",autoDelete = "false"),
        exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_SECKILL_USER),
        key = {MqConst.ROUTING_SECKILL_USER}
))
public void seckillUser(UserRecode userRecode,Message message,Channel channel){
    try {
        //  判断接收过来的数据
        if (userRecode!=null){
            //  预下单处理!
            seckillGoodsService.seckillOrder(userRecode.getSkuId(),userRecode.getUserId());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    //  手动确认
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}

预下单接口SeckillGoodsService接口


/**
 * 根据用户和商品ID实现秒杀下单
 * @param skuId
 * @param userId
 */
void seckillOrder(Long skuId, String userId);

秒杀订单实体类

package com.atguigu.gmall.model.activity;


@Data
public class OrderRecode implements Serializable {

   private static final long serialVersionUID = 1L;

   private String userId;

   private SeckillGoods seckillGoods;

   private Integer num;

   private String orderStr;
}

 实现类

/***
 * 创建订单
 * @param skuId
 * @param userId
 */
@Override
public void seckillOrder(Long skuId, String userId) {
    //产品状态位, 1:可以秒杀 0:秒杀结束
    String state = (String) CacheHelper.get(skuId.toString());
    if("0".equals(state)) {
        //已售罄
        return;
    }

    //判断用户是否下单
    boolean isExist = redisTemplate.opsForValue().setIfAbsent(RedisConst.SECKILL_USER + userId, skuId.toString(), RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
    if (!isExist) {
        return;
    }

    //获取队列中的商品,取List中的一个,从右边出来一个,如果能够获取,则商品存在,可以下单
    String goodsId = (String) redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).rightPop();
    if (StringUtils.isEmpty(goodsId)) {
        //商品售罄,更新状态位  0位售罄状态  发布订阅消息,商品已经销售完了
        redisTemplate.convertAndSend("seckillpush", skuId+":0");
        //已售罄
        return;
    }

    //订单记录
    OrderRecode orderRecode = new OrderRecode();
    orderRecode.setUserId(userId);
    orderRecode.setSeckillGoods(this.getSeckillGoods(skuId));
    orderRecode.setNum(1);
    //生成订单单码
    orderRecode.setOrderStr(MD5.encrypt(userId+skuId));

    //订单数据存入Reids
    redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).put(orderRecode.getUserId(), orderRecode);
   //更新库存
   this.updateStockCount(orderRecode.getSeckillGoods().getSkuId());
}

更新库存

//  表示更新mysql -- redis 的库存数据!
public void updateStockCount(Long skuId) {
    //  加锁!
    Lock lock = new ReentrantLock();
    //  上锁
    lock.lock();
    try {
        //  获取到存储库存剩余数!
        //  key = seckill:stock:46
        String stockKey = RedisConst.SECKILL_STOCK_PREFIX + skuId;
        //  redisTemplate.opsForList().leftPush(key,seckillGoods.getSkuId());

        //获取库存
        //Long count = this.redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).size();
        Long count = redisTemplate.boundListOps(stockKey).size();
        //  减少库存数!方式一减少压力!
        //if (count%2==0){

            //  开始更新数据!
            SeckillGoods seckillGoods = this.getSeckillGoods(skuId);
            //  赋值剩余库存数!
            seckillGoods.setStockCount(count.intValue());
            //  更新的数据库!
            seckillGoodsMapper.updateById(seckillGoods);
            //  更新缓存!
            redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).put(seckillGoods.getSkuId().toString(),seckillGoods);
        //}
    } finally {
        //  解锁!
        lock.unlock();
    }
}

 /**
     * 获取商品详情
     * @param skuId
     * @return
     */
    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        return (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());
    }

 

减少一个:之前9个 

生成一个订单

用户买过之后生成一个用户的信息(防止多次下单)

(4)页面轮询接口

 

思路:

1.  判断用户是否在缓存中存在

 

2.  判断用户是否抢单成功

3.  判断用户是否下过订单

4.  判断状态位

接口

SeckillGoodsService接口
/***
 * 根据商品id与用户ID查看订单信息
  * @param skuId
 * @param userId
 * @return
 */
Result checkOrder(Long skuId, String userId);

实现类

@Override
public Result checkOrder(Long skuId, String userId) {
    // 用户在缓存中存在,有机会秒杀到商品
    boolean isExist =redisTemplate.hasKey(RedisConst.SECKILL_USER + userId);
    if (isExist) {
        //判断用户是否正在排队
        //判断用户是否抢单成功
        boolean isHasKey = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).hasKey(userId);
        if (isHasKey) {
            //抢单成功  获取用户临时订单:说明还没有支付
            OrderRecode orderRecode = (OrderRecode) redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);
            // 秒杀成功! 
            return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
        }
    }

    //判断是否下单,查询总订单 seckill:orders:users userId OrderId
    boolean isExistOrder = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).hasKey(userId);
    if(isExistOrder) {
        String orderId = (String)redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).get(userId);
        return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
    }

    String state = (String) CacheHelper.get(skuId.toString());
    if("0".equals(state)) {
        //已售罄 抢单失败
        return Result.build(null, ResultCodeEnum.SECKILL_FAIL);
    }

    //正在排队中
    return Result.build(null, ResultCodeEnum.SECKILL_RUN);
}

控制器

SeckillGoodsApiController
@GetMapping(value = "auth/checkOrder/{skuId}")
public Result checkOrder(@PathVariable("skuId") Long skuId, HttpServletRequest request) {
    //当前登录用户
    String userId = AuthContextHolder.getUserId(request);
    return seckillGoodsService.checkOrder(skuId, userId);
}

 

 

 

轮询排队页面

该页面有四种状态:

  1. 排队中
  2. 各种提示(非法、已售罄等)
  3. 抢购成功,去下单
  4. 抢购成功,已下单,显示我的订单

抢购成功,页面显示去下单,跳转下单确认页面

<div class="seckill_dev" v-if="show == 3">
    抢购成功&nbsp;&nbsp;

    <a href="/seckill/trade.html" target="_blank">去下单</a>
</div>

总结:

商品秒杀流程:

1.用户抢单的时候先会生成一个下单码,后面会先校验用户的下单码,只有正确获得下单码的请求才是合法请求,然后再校验状态位state,状态位是在内存中判断,效率极高,如果售罄,直接就返回了,不会给服务器造成太大压力,前面条件都成立,将秒杀用户加入队列,然后直接返回

2.监听队列,进行清单,首先判断产品状态位,我们前面不是已经判断过了吗?因为产品可能随时售罄,mq队列里面可能堆积了十万数据,但是已经售罄了,那么后续流程就没有必要再走了

然后判断用户是否已经下过订单,这个地方就是控制用户重复下单,同一个用户只能抢购一个下单资格,我们可以利用setnx控制用户,当用户第一次进来时,返回true,可以抢购,以后进入返回false

要控制库存数量,不能超卖,提供一种解决方案,那就我们在导入商品缓存数据时,同时将商品库存信息导入队列{list},利用redis队列的原子性,保证库存不超卖

然后将订单记录放入redis缓存,说明用户已经获得下单资格,秒杀成功,秒杀成功要更新库存(更新Mysql的库存和Redis中的库存)

3.前端页面轮训查询订单状态,判断用户是否抢单成功,下单了Redis生成临时订单数据(支付了会删除临时订单)抢单成功,去下单页面,判断用户是否下单查询总订单数据是否支付,下单了去我们订单页面,判断状态位,是否售罄

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

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

相关文章

《深度学习》—— 神经网络模型对手写数字的识别

神经网络模型对手写数字的识别 import torch from torch import nn # 导入神经网络模块 from torch.utils.data import DataLoader # 数据包管理工具&#xff0c;打包数据, from torchvision import datasets # 封装了很多与图像相关的模型&#xff0c;数据集 from torchvi…

codetop字符串刷题,刷穿地心!!不再畏惧!!暴打面试官!!

主要供自己回顾与复习&#xff0c;题源codetop标签字符串近半年&#xff0c;会不断更新 1.有效的括号字符串2.括号生成3.最长单词4.字符串转换整数(atoi)5.整数转罗马数字6.罗马数字转整数7.比较版本号8.最长公共前缀9.面试题17.15.最长单词10.验证IP地址11.面试题01.06.字符串…

介绍一下常用的激活函数?

常用的激活函数 Sigmoid函数Tanh函数ReLU函数Leaky ReLU函数Softmax函数 Sigmoid函数 特点&#xff1a; 将任意实数映射到(0,1)区间内&#xff0c;输出值可以作为概率来解释。 函数平滑且易于求导&#xff0c;但其导数在两端趋近于0&#xff0c;即存在梯度消失问题。 输出值不…

CWFED:自然灾害检测数据集(猫脸码客 第192期)

Cyclone Wildfire Flood Earthquake Database 在自然灾害频发的今天&#xff0c;准确、及时地获取并分析相关数据对于灾害预防、预警及响应至关重要。为此&#xff0c;Cyclone Wildfire Flood Earthquake Database&#xff08;以下简称CWFE Database&#xff09;应运而生&…

计算机毕业设计 农场投入品运营管理系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

gcc升级(含命令行升级、手动升级两种方式)

gcc升级 1.yum源替换1.1 备份原始repo配置文件1.2 重新配置CentOS-Base.reporepo文件1.3 清除缓存并重新创建 2. gcc安装3.命令行升级gcc4.手动升级4.1 安装包下载4.2 解压4.3 gcc升级4.3.1 依赖拉取4.3.2 gmp安装4.3.3 mpfr安装4.3.4 mpc安装4.3.5 gcc编译、安装 4.4 gcc命令配…

Linux环境变量进程地址空间

目录 一、初步认识环境变量 1.1常见的环境变量 1.2环境变量的基本概念 二、命令行参数 2.1通过命令行参数获取环境变量 2.2本地变量和内建命令 2.3环境变量的获取 三、进程地址空间 3.1进程&#xff08;虚拟&#xff09;地址空间的引入 3.2进程地址空间的布局和理解 …

简易CPU设计入门:本CPU项目的指令格式

在这一节里面&#xff0c;主要是理论知识&#xff0c;基本上不讲代码。不过&#xff0c;本项目的代码包&#xff0c;大家还是需要下载的。 本项目的代码包的下载方法&#xff0c;参考下面的链接所指示的文章。 下载本项目代码 本节&#xff0c;其实是要讲本项目CPU的指令集。…

大模型蒸馏技术

一篇题为《The Mamba in the Llama: Distilling and Accelerating Hybrid Models》的论文证明&#xff1a;通过重用注意力层的权重&#xff0c;大型 transformer 可以被蒸馏成大型混合线性 RNN&#xff0c;只需最少的额外计算&#xff0c;同时可保留其大部分生成质量。 先来说…

Python学习——【2.1】if语句相关语法

文章目录 【2.1】if语句相关一、布尔类型和比较运算符&#xff08;一&#xff09;布尔类型&#xff08;二&#xff09;比较运算符 二、if语句的基本格式※、练习 三、if-else组合判断语句※、练习 四、if-elif-else多条件判断语句※、练习 五、判断语句的嵌套※、实战案例 【2.…

AlexNet项目图片分类通用模型代码

目录 一&#xff1a;建立AlexNet模型&#xff08;在model文件中写&#xff09; 1.构造5层卷积层 2.构造3层神经网络层 3.forward函数 4.模型最终代码 二&#xff1a;训练数据&#xff08;在train中写&#xff09; 1.读出数据 2.训练 3. 测试模型更新参数 4.完整的训练…

Datawhile 组队学习Tiny-universe Task01

Task01&#xff1a;LLama3模型讲解 仓库链接&#xff1a;GitHub - datawhalechina/tiny-universe: 《大模型白盒子构建指南》&#xff1a;一个全手搓的Tiny-Universe 参考博客&#xff1a;LLaMA的解读与其微调(含LLaMA 2)&#xff1a;Alpaca-LoRA/Vicuna/BELLE/中文LLaMA/姜子…

新的突破,如何让AI与人类对话变得“顺滑”:Moshi背后的黑科技

你有没有想过,当我们跟智能音箱、客服机器人或者语音助手对话时,它们是怎么“听懂”我们说的话,又是怎么迅速给出回应的?就好像你对着Siri、Alexa说一句:“给我订个披萨”,它立刻明白你想要干嘛,然后帮你下单。背后的技术其实比我们想象的要复杂得多,但现在,有了Moshi…

Qt_布局管理器

目录 1、QVBoxLayout垂直布局 1.1 QVBoxLayout的使用 1.2 多个布局管理器 2、QHBoxLayout水平布局 2.1 QHBoxLayout的使用 2.2 嵌套的Layout 3、QGridLayout网格布局 3.1 QGridLayout的使用 3.2 设置控件大小比例 4、QFormLayout 4.1 QFormLayout的使用 5、…

【2024】前端学习笔记8-内外边距-边框-背景

学习笔记 外边距&#xff1a;Margin内边距&#xff1a;Padding边框&#xff1a;Border背景&#xff1a;Background 外边距&#xff1a;Margin 用于控制元素周围的空间&#xff0c;它在元素边框之外创建空白区域&#xff0c;可用于调整元素与相邻元素&#xff08;包括父元素和兄…

AI预测福彩3D采取888=3策略+和值012路或胆码测试9月19日新模型预测第92弹

经过90多期的测试&#xff0c;当然有很多彩友也一直在观察我每天发的预测结果&#xff0c;得到了一个非常有价值的信息&#xff0c;那就是9码定位的命中率非常高&#xff0c;90多期一共只错了10次&#xff0c;这给喜欢打私房菜的朋友提供了极高价值的预测结果~当然了&#xff0…

教育政策与智能技术:构建新时代教师队伍

据最新统计&#xff0c;我国目前拥有各级各类教师共计1891.8万人&#xff0c;这一庞大的教师群体不仅支撑起了全球规模最大的教育体系&#xff0c;更成为了推动教育创新与变革的主力军。面对教育数字化的不断发展&#xff0c;育人内容、目标要求、方式方法的全面升级&#xff0…

【测向定位】差频MUSIC算法DOA估计【附MATLAB代码】

​微信公众号&#xff1a;EW Frontier QQ交流群&#xff1a;554073254 摘要 利用多频处理方法&#xff0c;在不产生空间混叠的情况下&#xff0c;估计出高频区域平面波的波达方向。该方法利用了差频&#xff08;DF&#xff09;&#xff0c;即两个高频之间的差。这使得能够在可…

鹏鼎控股社招校招入职SHL综合能力测评:高分攻略及真题题库解析答疑

鹏鼎控股&#xff08;深圳&#xff09;股份有限公司&#xff0c;成立于1999年4月29日&#xff0c;是一家专注于印制电路板&#xff08;PCB&#xff09;的设计、研发、制造与销售的高新技术企业。公司总部位于中国广东省深圳市&#xff0c;并在全球多个地区设有生产基地和服务中…

【软考】数据字典(DD)

目录 1. 说明2. 数据字典的内容2.1 说明2.2 数据流条目2.3 数据存储条目2.4 数据项条目2.5 基本加工条目 3. 数据词典管理4. 加工逻辑的描述4.1 说明4.2 结构化语言4.3 判定表4.3 判定树 5. 例题5.1 例题1 1. 说明 1.数据流图描述了系统的分解&#xff0c;但没有对图中各成分进…