【业务功能篇106】 微服务-springcloud-springboot-电商订单模块--秒杀服务-定时任务【下篇】

news2025/1/16 1:58:45

四、秒杀活动

1.秒杀活动关注点

  秒杀活动的最大特点就是高并发而且是短时间内的高并发,那么对我们的服务要求就非常高,针对这种情况所产生的共性问题,对应的解决方案:

image.png

2. 秒杀服务前端

  当我们点击 秒杀抢购按钮后,对应我们需要把当前的商品信息提交到后端服务。活动编号+“_”+SkuId,Code随机码,抢购商品的数量。

<div class="box-btns-two" th:if="${#dates.createNow().getTime() < item.seckillVO.startTime
                            || #dates.createNow().getTime() >  item.seckillVO.endTime }">
                        <a href="#" id="addCart" th:attr="skuId=${item.info.skuId}">
                            加入购物车
                        </a>
                    </div>
                    <div class="box-btns-two" th:if="${#dates.createNow().getTime() > item.seckillVO.startTime
                             && #dates.createNow().getTime() < item.seckillVO.endTime }">
                        <a href="#" id="seckillId" th:attr="skuId=${item.info.skuId},sessionId=${item.seckillVO.promotionSessionId},code=${item.seckillVO.randCode}">
                            抢购商品
                        </a>
                    </div>

对应的js操作

$("#seckillId").click(function(){
        var isLogin = [[${session.loginUser !=null}]]
        if(isLogin){
            // 1. 获取活动编号和SkuId 2_10
            var killId = $(this).attr("sessionId") + "_" + $(this).attr("skuId");
            // 2. 获取对应的随机码
            var code = $(this).attr("code");
            // 3. 获取秒杀的商品数量
            var num = $("#numInput").val();
            location.href="http://seckill.msb.com/seckill/kill?killId="+killId + "&code="+code+"&num="+num;
        }else{
            alert("请先登录才能参加秒杀活动!!!");
        }

        return false;
    });

访问测试:

image.png

3.后端逻辑处理

  前端提交的秒杀请求,在后端具体的处理

3.1 登录校验

  秒杀活动必须是在登录状态下进行的,如果没有认证就不让秒杀。这时我们需要整合进来SpringSession。 当然我们一般是把用户信息存在reids缓存,所以redis依赖也是要引入的

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

然后添加对应的配置信息

image.png

然后添加拦截器

/**
 * 秒杀活动的拦截器 确认是杂登录的状态下操作的
 */
public class AuthInterceptor implements HandlerInterceptor {

    public static ThreadLocal threadLocal = new ThreadLocal();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 通过HttpSession获取当前登录的用户信息
        HttpSession session = request.getSession();
        Object attribute = session.getAttribute(AuthConstant.AUTH_SESSION_REDIS);
        if(attribute != null){
            MemberVO memberVO = (MemberVO) attribute;
            threadLocal.set(memberVO);
            return true;
        }
        // 如果 attribute == null 说明没有登录,那么我们就需要重定向到登录页面
        session.setAttribute(AuthConstant.AUTH_SESSION_MSG,"请先登录");
        response.sendRedirect("http://auth.msb.com/login.html");
        return false;
    }
}

配置拦截器

@Configuration
public class MyWebInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/seckill/kill");
    }

}

设置Cookie的配置

@Configuration
public class MySessionConfig {

    /**
     * 自定义Cookie的配置
     * @return
     */
    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("msb.com"); // 设置session对应的一级域名
        cookieSerializer.setCookieName("msbsession");
        return cookieSerializer;
    }

    /**
     * 对存储在Redis中的数据指定序列化的方式
     * @return
     */
    @Bean
    public RedisSerializer<Object> redisSerializer(){
        return new GenericJackson2JsonRedisSerializer();
    }
}

最后在启动类中开启

image.png

3.2 秒杀活动流程

image.png

登录校验

通过拦截器处理:在秒杀活动中并不是所有的请求都是需要在登录状态下的,所有这个拦截器应该只需要拦截部分的请求。

/**
 * 秒杀活动的拦截器 确认是杂登录的状态下操作的
 */
public class AuthInterceptor implements HandlerInterceptor {

    public static ThreadLocal threadLocal = new ThreadLocal();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 通过HttpSession获取当前登录的用户信息
        HttpSession session = request.getSession();
        Object attribute = session.getAttribute(AuthConstant.AUTH_SESSION_REDIS);
        if(attribute != null){
            MemberVO memberVO = (MemberVO) attribute;
            threadLocal.set(memberVO);
            return true;
        }
        // 如果 attribute == null 说明没有登录,那么我们就需要重定向到登录页面
        session.setAttribute(AuthConstant.AUTH_SESSION_MSG,"请先登录");
        response.sendRedirect("http://auth.msb.com/login.html");
        return false;
    }
}

配置拦截部分请求

@Configuration
public class MyWebInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/seckill/kill");
    }

}
/**
 * 秒杀活动涉及的常量
 */
public class SeckillConstant {

    public static final String SESSION_CHACE_PREFIX = "seckill:sessions";
    public static final String SKU_CHACE_PREFIX = "seckill:skus";
    public static final String SKU_STOCK_SEMAPHORE = "seckill:stock:";
}

秒杀请求接口
controller层

 /**
     * 秒杀抢购   跳转到success页面 显示是否秒杀成功
     * killId=1_9&code=69d55333c9ec422381024d34fdfd3e85&num=1
     * @return
     */
@Controller
@RequestMapping("/seckill")
public class SeckillController {
    @GetMapping("/kill")
    public String seckill(@RequestParam("killId") String killId,
                          @RequestParam("code") String code,
                          @RequestParam("num") Integer num,
                          Model model){
        String orderSN = seckillService.kill(killId,code,num);
        model.addAttribute("orderSn",orderSN);
        return "success";
    }
}

service层

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RocketMQTemplate rocketMQTemplate;
 /**
     * 实现秒杀逻辑
     * @param killId
     * @param code
     * @param num
     * @return
     */
    @Override
    public String kill(String killId, String code, Integer num) {
        // 1.根据killId获取当前秒杀的商品的信息  Redis中
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
        String json = ops.get(killId);
        if(StringUtils.isNotBlank(json)){
            SeckillSkuRedisDto dto = JSON.parseObject(json, SeckillSkuRedisDto.class);
            // 校验合法性  1.校验时效性
            Long startTime = dto.getStartTime();
            Long endTime = dto.getEndTime();
            long now = new Date().getTime();
            if(now > startTime && now < endTime){
                // 说明是在秒杀活动时间范围内容的请求
                // 2.校验 随机和商品 是否合法
                String randCode = dto.getRandCode();
                Long skuId = dto.getSkuId();
                String redisKillId = dto.getPromotionSessionId() + "_" + skuId;
                if(randCode.equals(code) && killId.equals(redisKillId)){
                    // 随机码校验合法
                    // 3.判断抢购商品数量是否合法
                    if(num <= dto.getSeckillLimit().intValue()){
                        // 满足限购的条件
                        // 4.判断是否满足 幂等性
                        // 只要抢购成功我们就在Redis中 存储一条信息 userId + sessionID + skuId
                        MemberVO memberVO = (MemberVO) AuthInterceptor.threadLocal.get();
                        Long id = memberVO.getId();
                        String redisKey = id + "_" + redisKillId;
                        Boolean aBoolean = redisTemplate.opsForValue()
                                .setIfAbsent(redisKey, num.toString(), (endTime - now), TimeUnit.MILLISECONDS);
                        if(aBoolean){
                            // 表示数据插入成功 是第一次操作
                            RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE+randCode);
                            try {
                                boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                if(b){
                                    // 表示秒杀成功
                                    String orderSN = UUID.randomUUID().toString().replace("-", "");
                                    // 继续完成快速下订单操作  --> RocketMQ
                                    SeckillOrderDto orderDto = new SeckillOrderDto() ;
                                    orderDto.setOrderSN(orderSN);
                                    orderDto.setSkuId(skuId);
                                    orderDto.setSeckillPrice(dto.getSeckillPrice());
                                    orderDto.setMemberId(id);
                                    orderDto.setNum(num);
                                    orderDto.setPromotionSessionId(dto.getPromotionSessionId());
                                    // 通过RocketMQ 发送异步消息
                                    rocketMQTemplate.sendOneWay(OrderConstant.ROCKETMQ_SECKILL_ORDER_TOPIC
                                            ,JSON.toJSONString(orderDto));
                                    return orderSN;
                                }
                            } catch (InterruptedException e) {
                                return null;
                            }
                        }
                    }

                }
            }
        }
        return null;
    }

合法性校验

  校验的内容有四块:时效性,随机码是否合法,是否满足限购条件,还有幂等性

image.png

信号量处理

  通过信号量来控制秒杀的商品数量。降低了对库存商品操作,提升了处理能力

if(aBoolean){
                            // 表示数据插入成功 是第一次操作
                            RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE+randCode);
                            try {
                                boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                                if(b){
                                    // 表示秒杀成功
                                    String orderSN = UUID.randomUUID().toString().replace("-", "");
                                    // 继续完成快速下订单操作  --> RocketMQ
                                    SeckillOrderDto orderDto = new SeckillOrderDto() ;
                                    orderDto.setOrderSN(orderSN);
                                    orderDto.setSkuId(skuId);
                                    orderDto.setSeckillPrice(dto.getSeckillPrice());
                                    orderDto.setMemberId(id);
                                    orderDto.setNum(num);
                                    orderDto.setPromotionSessionId(dto.getPromotionSessionId());
                                    // 通过RocketMQ 发送异步消息
                                    rocketMQTemplate.sendOneWay(OrderConstant.ROCKETMQ_SECKILL_ORDER_TOPIC
                                            ,JSON.toJSONString(orderDto));
                                    return orderSN;
                                }
                            } catch (InterruptedException e) {
                                return null;
                            }
                        }

MQ异步下单

  秒杀成功后给RocketMQ发送消息,订单服务订阅消息,实现异步下单,从而降低了对秒杀系统的影响。

image.png

  • 然后在订单服务中订阅对应的信息:创建一个消费者类注入bean同时作为监听器异步去监听指定的消息生产者topic = OrderConstant.ROCKETMQ_SECKILL_ORDER_TOPIC 由秒杀服务来创建 消息生产者,由订单服务来创建消息消费者,因两个服务用的都是同个RocketMQ,所以都是可以访问到的。
  • 监听到有对应消息后,表示秒杀成功,那么在onMessage中通过形参传递过来的数据,就是消息生产者所传递的数据JSON.toJSONString(orderDto)),订单封装相关数据类,解析之后,再调用自身订单服务中的快速下单接口,去插入对应的订单数据信息
  • 而后续还需要支付,这里不再展示,结合先前文字的调用支付服务去完成即可
@RocketMQMessageListener(topic = OrderConstant.ROCKETMQ_SECKILL_ORDER_TOPIC,consumerGroup = "test")
@Component
public class SeckillOrderConsumer implements RocketMQListener<String> {
    @Autowired
    OrderService orderService;
    @Override
    public void onMessage(String s) {
        // 订单关单的逻辑实现
        SeckillOrderDto orderDto = JSON.parseObject(s,SeckillOrderDto.class);
        orderService.quickCreateOrder(orderDto);
    }
}
    /**
     * 快速完成订单的处理  秒杀活动
     * @param orderDto
     */
    @Transactional
    @Override
    public void quickCreateOrder(SeckillOrderDto orderDto) {
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(orderDto.getOrderSN());
        orderEntity.setStatus(OrderConstant.OrderStateEnum.FOR_THE_PAYMENT.getCode());
        orderEntity.setMemberId(orderDto.getMemberId());
        orderEntity.setTotalAmount(orderDto.getSeckillPrice().multiply(new BigDecimal(orderDto.getNum())));
        this.save(orderEntity);
        OrderItemEntity itemEntity = new OrderItemEntity();
        // TODO 根据SKUID查询对应的SKU信息和SPU信息
        itemEntity.setOrderSn(orderDto.getOrderSN());
        itemEntity.setSkuPrice(orderDto.getSeckillPrice());
        itemEntity.setSkuId(orderDto.getSkuId());
        itemEntity.setRealAmount(orderDto.getSeckillPrice().multiply(new BigDecimal(orderDto.getNum())));
        itemEntity.setSkuQuantity(orderDto.getNum());

        orderItemService.save(itemEntity);
    }

秒杀成功跳转到成功页面:

image.png

image.png

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

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

相关文章

【C#-1】C#调用matlab生成的dll库

matlab打包dll 1、matlab示例程序&#xff1a; function untitled4(x)z peaks(x);figuresurf(z) end 2、输入deploytool打包matlab程序&#xff0c;具体如下&#xff1a; 3、拷贝 打包成功后&#xff0c;将生成for_redistribution_files_only文件夹中的dll文件拷贝到C#程序…

【设计模式】三、概述分类+单例模式

文章目录 概述设计模式类型 单例模式饿汉式&#xff08;静态常量&#xff09;饿汉式&#xff08;静态代码块&#xff09;懒汉式(线程不安全)懒汉式(线程安全&#xff0c;同步方法)懒汉式(线程安全&#xff0c;同步代码块)双重检查静态内部类枚举单例模式在 JDK 应用的源码分析 …

Llama2-Chinese项目:1-项目介绍和模型推理

Atom-7B与Llama2间的关系&#xff1a;Atom-7B是基于Llama2进行中文预训练的开源大模型。为什么叫原子呢&#xff1f;因为原子生万物&#xff0c;Llama中文社区希望原子大模型未来可以成为构建AI世界的基础单位。目前社区发布了6个模型&#xff0c;如下所示&#xff1a; FlagAl…

无线振动传感器革新:设备维护的新范式

随着工业界的不断发展和技术的进步&#xff0c;设备维护的方法也在不断演变。过去&#xff0c;维护通常是基于固定的时间表和例行的检查进行的&#xff0c;这种方法往往会导致资源的浪费和不必要的停机时间。然而&#xff0c;现在&#xff0c;随着无线振动传感器的革新&#xf…

【C++】内联函数 ⑤ ( 内联函数总结 | 内联函数代码示例 )

文章目录 一、内联函数总结二、内联函数代码示例1、代码示例 - 普通函数执行分析2、代码示例 - 内联函数执行分析3、代码示例 - 宏代码片段执行分析 一、内联函数总结 回顾下 之前的博客中 介绍的 内联函数 : 内联函数编译 : C 编译器 编译 内联函数 时 , 会直接 将 内联函数 …

前端开发纷繁复杂,是否有更高效的开发方式?

一、前言 此前&#xff0c;我曾跟大家聊到&#xff0c;低代码编程&#xff0c;在现阶段互联网业务疯狂增长的带动之下&#xff0c;被赋予了全新的使命和义务&#xff0c;即帮助开发者在前期以较低成本的方式&#xff0c;快速构建一个可投入市场的应用。 那么&#xff0c;有没有…

腾讯云轻量2核4G5M服务器_CPU内存_流量_带宽_系统盘

腾讯云轻量2核4G5M服务器&#xff1a;CPU内存流量带宽系统盘性能测评&#xff1a;轻量应用服务器2核4G5M带宽&#xff0c;免费500GB月流量&#xff0c;60GB系统盘SSD盘&#xff0c;5M带宽下载速度可达640KB/秒&#xff0c;流量超额按照0.8元每GB支付流量费&#xff0c;轻量2核4…

都2023年了,别再用ifconfig啦! 试试这个吧!

下午好&#xff0c;我的网工朋友。 ifconfig这玩意儿&#xff0c;还有多少人在用&#xff1f;举个手看看 总在说ifconfig即将被淘汰&#xff0c;但其实很多网工还在用吧。 比如说组合使用诸如ifconfig、route、arp和netstat等命令行工具&#xff08;来源于安装包net-tools&a…

调查称全球多所顶尖高校网站存在网络攻击风险

Cyber News的一项调查研究显示&#xff0c;全球多所顶尖高校的网站未能及时更新安全补丁&#xff0c;存在敏感信息泄露&#xff0c;甚至被攻击者全面接管的风险。 Cyber​​ News 研究团队详细调查了 20 个每月有数百万访问量的高校网站&#xff0c;其中至少有6个是位于全球Top…

Mac清理垃圾软软件CleanMyMac X的软件功能介绍

在我们使用我们的Mac一定的时间后&#xff0c;总是不可避免的出现Mac内存不足的情况&#xff0c;所以清理垃圾软件也就成为了我们电脑里必不可少的软件。苹果软件商店中有很多各有不同的清理垃圾软件&#xff0c;但我们往往很难从这一大堆软件中找到令自己心满意足的。 但是这…

目录操作函数

mkdir函数 rmdir函数 删除空目录 rename函数 换名 chdir函数 修改当前的工作目录 getcwd函数 获取当前工作的路径

X2-VLM: All-In-One Pre-trained Model For Vision-Language Tasks论文笔记

Title&#xff1a;X2-VLM: All-In-One Pre-trained Model For Vision-Language Tasks Code 1. Motivation CLIP这一类方法只能进行图片级别的视觉和文本对齐&#xff1b;也有一些方法利用预训练的目标检测器进行目标级别的视觉和文本对齐&#xff0c;但是只能编码目标内部的特…

WMS仓储-亿发数字化工厂库存管理系统,提升中小企业仓储管理效率

中小企业竞争日益激烈的市场中&#xff0c;仓库管理的重要性不可低估。为了适应不断变化的业务规模和模式&#xff0c;中小企业对仓库管理提出了更高水平的智能化要求。数字化工厂库存管理系统&#xff0c;正是应运而生&#xff0c;助力企业加强仓库智能作业效率&#xff0c;并…

CRM销售管理系统如何提高销售效率

CRM销售管理系统是帮助企业对销售活动进行管理、执行和优化的软件系统。它可以帮助企业提高销售效率&#xff0c;提高客户转化率&#xff0c;实现企业的业绩增长。那么&#xff0c;CRM销售管理系统好用吗&#xff1f; CRM销售管理系统的功能 线索管理&#xff1a; CRM系统可…

​金蝶云星空管理中心反序列化RCE漏洞复现 附POC

文章目录 ​金蝶云星空管理中心反序列化RCE漏洞复现0x01 前言0x02 漏洞描述0x03 影响范围0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现4.进行ipconfig命令测试 0x06修复建议 ​金蝶云星空管理中心反序列化RCE漏洞复现 0x01 前言 免责声明&#xff1a;请勿利用文…

iframe 实现跨域,两页面之间的通信

一、 背景 一个项目为vue2&#xff0c;一个项目为vue3&#xff0c;两个不同的项目实现iframe嵌入&#xff0c;并实现通信 二、方案 iframe跨域时&#xff0c;iframe组件之间常用的通信&#xff0c;主要是H5的possmessage方法 三、案例代码 父页面-vue2&#xff08;端口号为…

日常开发小汇总(5)元素跟随鼠标移动(在视口下移动)

<div class"mark"><h1>title</h1><div><p>title 鼠标移动 盒子整体移动</p><p>test</p><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Modi, porro.</p></div></div>cons…

微信商城小程序开发

微信小程序商城制作流程详解 微信小程序商城的出现为商家提供了一个全新的线上销售平台&#xff0c;让商家能够在微信生态系统中直接向用户展示和销售自己的产品。本文将详细介绍微信小程序商城的制作流程&#xff0c;帮助商家和开发者更好地理解和掌握这一新兴技术。 一、注…

python+chdb 向量检索,百万数据秒级查询

随机生成向量数据集 import numpy as np import pandas as pddata_vectors [(i, np.random.random(300)) for i in range(1000000)] df pd.DataFrame(data_vectors) df.columns [idx, vector] df转化成parquet格式&#xff0c;分批转化&#xff0c;一次会爆炸 import pyar…

yolov5添加ECA注意力机制

ECA注意力机制简介 论文题目&#xff1a;ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks 论文地址&#xff1a;here 基本原理 &#x1f438; ECANet的核心思想是提出了一种不降维的局部跨通道交互策略&#xff0c;有效避免了降维对于通道注意…