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

四、秒杀活动

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_10var 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();@Overridepublic 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 {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/seckill/kill");}}

设置Cookie的配置

@Configuration
public class MySessionConfig {/*** 自定义Cookie的配置* @return*/@Beanpublic CookieSerializer cookieSerializer(){DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();cookieSerializer.setDomainName("msb.com"); // 设置session对应的一级域名cookieSerializer.setCookieName("msbsession");return cookieSerializer;}/*** 对存储在Redis中的数据指定序列化的方式* @return*/@Beanpublic RedisSerializer<Object> redisSerializer(){return new GenericJackson2JsonRedisSerializer();}
}

最后在启动类中开启

image.png

3.2 秒杀活动流程

image.png

登录校验

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

/*** 秒杀活动的拦截器 确认是杂登录的状态下操作的*/
public class AuthInterceptor implements HandlerInterceptor {public static ThreadLocal threadLocal = new ThreadLocal();@Overridepublic 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 {@Overridepublic 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层

    @AutowiredStringRedisTemplate redisTemplate;@AutowiredRedissonClient redissonClient;@AutowiredRocketMQTemplate rocketMQTemplate;/*** 实现秒杀逻辑* @param killId* @param code* @param num* @return*/@Overridepublic 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 + skuIdMemberVO 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("-", "");// 继续完成快速下订单操作  --> RocketMQSeckillOrderDto 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("-", "");// 继续完成快速下订单操作  --> RocketMQSeckillOrderDto 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> {@AutowiredOrderService orderService;@Overridepublic void onMessage(String s) {// 订单关单的逻辑实现SeckillOrderDto orderDto = JSON.parseObject(s,SeckillOrderDto.class);orderService.quickCreateOrder(orderDto);}
}
    /*** 快速完成订单的处理  秒杀活动* @param orderDto*/@Transactional@Overridepublic 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.rhkb.cn/news/129481.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单片机C语言实例:6、定时器的应用

定时器原理 1、什么是定时器&#xff1f;什么是计数器&#xff1f; 定时器&#xff0c;字面意思就是定时的。手机的闹钟就是个定时器&#xff0c;定个每天早上7点的闹钟。 计数器&#xff0c;字面意思计时统计个数的&#xff0c;来一个就加一个&#xff0c;一直累加着。 其实…

什么是Docker和Docker-Compose?

Docker的构成 Docker仓库&#xff1a;https://hub.docker.com Docker自身组件 Docker Client&#xff1a;Docker的客户端 Docker Server&#xff1a;Docker daemon的主要组成部分&#xff0c;接受用户通过Docker Client发出的请求&#xff0c;并按照相应的路由规则实现路由分发…

@ApiImplicitParams这个注解的作用

ApiImplicitParams这个注解的作用&#xff1f; ApiImplicitParams是一个用于描述方法参数的注解&#xff0c;它可以用在方法上&#xff0c;作用是为方法中的参数定义多个注解&#xff0c;并将这些注解集中到一个注解集中进行统一管理。通过ApiImplicitParams注解&#xff0c;我…

使用k8s helm离线部署spark-operator(私有仓库)

制作镜像 docker pull ghcr.io/googlecloudplatform/spark-operator:v1beta2-1.3.8-3.1.1 docker images docker save ImageID > ./spark.tar将制作的镜像上传到目的机器中&#xff0c;加载镜像 docker load < ./spark.tar打标签其中xxxx.xxx/xx/为私有仓库的地址 doc…

JavaScript个人笔记

1.常用数据布尔值判断 const data [,0,-1,null,undefined,[],{},()>{}]data.forEach(item>{if(item){console.log(item,结果)} 打印结果&#xff1a;、0、null、undefined这四个值的布尔值都是false&#xff0c;其余都是true

17.Xaml DockPanel控件 ---> 停靠面板

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

【Java基础篇 | 面向对象】—— 继承

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 本专栏旨在分享学习JavaSE的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 继承允许一个类继承另一个…

微信小程序中识别html标签的方法

rich-text组件 在微信小程序中有一个组件rich-text可以识别文本节点或是元素节点 具体入下: //需要识别的数据放在data中,然后放在nodes属性中即可 <rich-text nodes"{{data}}"></rich-text>详情可以参考官方文档:https://developers.weixin.qq.com/mi…

openGauss学习笔记-65 openGauss 数据库管理-创建和管理数据库

文章目录 openGauss学习笔记-65 openGauss 数据库管理-创建和管理数据库65.1 前提条件65.2 背景信息65.3 注意事项65.4 操作步骤65.4.1 创建数据库65.4.2 查看数据库65.4.3 修改数据库65.4.4 删除数据库 openGauss学习笔记-65 openGauss 数据库管理-创建和管理数据库 65.1 前提…

IT运维:使用数据分析平台监控奇安信

监控目标 本文基于鸿鹄2.10.0版本。 ●监控奇安信日志类型分布 ●监控奇安信攻击行为、分析攻击类型 ●监控奇安信攻击来源情况 操作步骤 数据导入 1、创建数据集&#xff0c;如使用已经存在的数据集&#xff0c;可跳过此步骤 数据集名称&#xff1a;qax_syslog&#xff08;仪表…

Spring Batch:处理大数据和批量任务的解决方案

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

接口自动化测试系列-excel管理测试用例

代码源码&#xff1a; 框架结构 核心代码 excel用例demo excel数据处理 from configureUtil.LogUtil import getlog logger getlog(targetNameHandleData) import xlrd from openpyxl import load_workbook,workbook from openpyxl.styles import Font, colors import o…

【HCIE】03.BGP高级特性

每一条BGP路由都可以携带多个路径属性&#xff0c;针对其属性也有特有的路由匹配工具&#xff0c;包括&#xff1a;AS Path Filter和Community Filter。 import方向的属性&#xff0c;出现在如策略里面&#xff0c;加入到BGP路由表中&#xff0c;再传给路由表里&#xff0c;出去…

如何使用聊天GPT自定义说明

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 OpenAI ChatGPT正在席卷全球。一周又一周&#xff0c;更新不断提高您可以使用这种最先进的语言模型做什么的标准。 在这里&#xff0c;我们深入研究了OpenAI最近在ChatGPT自定义指令上发布的公告。此功能最初以测试版…

蓝桥杯打卡Day4

文章目录 首字母大写字符串转换整数 一、首字母大写IO链接 本题思路:本题就是语法题 #include <bits/stdc.h>int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);std::string str;std::getline(std::cin,str);for(int i0;i&…

# Spring MVC与RESTful API:如何设计高效的Web接口

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Spring02

Spring02 1.Spring简介 Spring 是一个生态圈Spring是一个开源的Java企业级应用框架&#xff0c;提供了广泛的功能和特性&#xff0c;用于开发和管理Java应用程序。它的核心原则是基于IOC&#xff08;Inversion of Control&#xff0c;控制反转&#xff09;和AOP&#xff08;A…

分享一下奶茶店怎么在小程序上做商城功能

随着移动互联网的普及&#xff0c;越来越多的消费者倾向于在手机上完成购物需求。对于奶茶店来说&#xff0c;在小程序上开设商城功能不仅可以扩大销售渠道&#xff0c;还能提高品牌知名度和用户体验。本文将探讨如何在小程序上为奶茶店实现商城功能。 对于奶茶店的商城功能&am…

Java从入门到精通-类和对象(一)

0. 类和对象 1. 面向对象概述 Java面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是一种强大的编程范式&#xff0c;它基于对象、类、封装、继承和多态等核心概念。这种编程范式使得代码更加模块化、可维护、可重用和可扩展。 1.1 对象和类…

docker系列(2) - 常用命令篇

文章目录 2. docker常用命令2.1 参数说明(tomcat案例)2.2 基本命令2.3 高级命令2.4 其他 2. docker常用命令 2.1 参数说明(tomcat案例) 注意如果分成多行&#xff0c;\后面不能有空格 # 拉取运行 docker run \ -d \ -p 8080:8080 \ --privilegedtrue \ --restartalways \ -m…