一.知识回顾
【0.三高商城系统的专题专栏都帮你整理好了,请点击这里!】
【1-系统架构演进过程】
【2-微服务系统架构需求】
【3-高性能、高并发、高可用的三高商城系统项目介绍】
【4-Linux云服务器上安装Docker】
【5-Docker安装部署MySQL和Redis服务】
【6-Git安装与配置过程、Gitee码云上创建项目、IDEA关联克隆的项目】
【7-创建商城系统的子模块并将修改后的信息使用Git提交到Gitee上】
【8-数据库表结构的创建&后台管理系统的搭建】
【9-前端项目的搭建部署、Node安装、VSCode安装】
【10-Node的安装以及全局环境变量的相关配置&解决启动报错的问题(1.Error: Cannot find module ‘fs/promises)(2.npm安装node-sass报错)】
【11-导入人人generator项目并自动生成相关的文件&商品子模块的调试&公共模块common子模块的抽离与实现&Lombok插件的安装】
【12-商品子模块整合MyBatisPlus技术&其它模块通过generator的自动生成与补充完善】
【13-项目中微服务组件的学习-SpringCloudAlibaba微服务生态体系的学习&SpringCloudAlibaba的依赖管理&项目中SpringBoot和SpringCloud版本的统一】
【14-微服务的注册中心与配置中心Nacos&Windows操作系统上安装Nacos和Linux操作系统上用Docker中安装Nacos&每个子项目模块使用Nacos进行服务注册与发现】
【15-项目中服务的远程调用之OpenFeign&订单模块与商品模块集成使用OpenFeign的案例】
【16-配置中心之Nacos的基本使用&Nacos服务之命令空间、Nacos服务之配置组、Nacos服务之配置拆分】
【17-微服务网关之Spring Cloud Gateway&Spring Cloud Gateway网关服务搭建】
【18-业务开发-基础业务-商品模块-分类管理-前后端管理系统的启动-为分类管理表增加数据-Json插件的下载-返回具有层级目录、父子关系结构的数据】
【19-业务开发-基础业务-商品模块-分类管理-管理系统新建菜单-后端项目renren注册到Nacos注册中心和配置中心去-项目gateway网关模块的搭建-浏览器的同源策略与解决跨域问题实操案例】
【20-业务开发-基础业务-商品模块-分类管理-前端展示后端具有层级关系的目录数据-商品系统三级分类的逻辑删除前后端代码实现】
【21-业务开发-基础业务-商品模块-分类管理-商品系统三级分类的新增类别前后端代码实现-商品系统三级分类的更新类别前后端代码实现-之前错误的Bug修正】
【22-业务开发-基础业务-商品模块-分类管理-商品系统三级分类拖拽页面的功能-前后端代码的逻辑实现-访问测试-拖拽开关的开启和关系-批量更新拖拽数据-批量删除选定数据】
【23-业务开发-基础业务-品牌管理-品牌管理项目搭建-品牌管理实现的增删改查操作测试-后端数据显示状态使用前端组件开关按钮展示-以及数据处理以及测试】
【24-业务开发-基础业务-品牌管理-图片管理-阿里云OSS服务开通和使用-阿里云OSS服务API使用-SpringCloudAlibaba OSS服务的使用】
【25-业务开发-基础业务-品牌管理-图片管理-图片上传方式的三种实现方式-第三方公共服务模块集成到项目中-服务端生成签名实战】
【26-业务开发-基础业务-品牌管理-图片管理-上传图片功能实现-基于阿里云OSS服务-解决跨域问题-设置跨域规则-修改ACL权限为公共读】
【27-业务开发-基础业务-品牌管理-图片管理-添加修改品牌信息并显示图片-前端数据校验-后端数据JSR303校验实现-统一异常处理-自定义响应编码规则-分组校验-自定义校验注解-项目Bug解决】
【28-业务开发-基础业务-属性管理-SKU和SPU基本概念-SKU和SPU关联关系-属性实体之间的关联关系-批量菜单创建】
【29-业务开发-基础业务-属性管理-属性组业务逻辑开发-页面布局-三级分类组件功能-属性组表单-父子组件传值-属性组数据展示-属性组数据添加-属性组数据修改-前后端项目整合交互测试】
【30-业务开发-基础业务-品牌管理-分类维护-解决分类维护业务开发中的一个Bug-品牌管理-分页插件-分页功能的逻辑实现-品牌管理-检索条件模糊查询品牌管理-增加更新操作中排序字段检验还是存在问题】
【31-业务开发-基础业务-品牌管理-级联类别信息业务功能实现-品牌管理和商品分类管理俩者业务关联出现数据冗余,导致数据不同步的问题-开启事务-项目测试】
【32-业务开发-基础业务-规格参数-保存数据-查询数据-更新操作之数据回显展示-更新操作-前后端项目交互整合与测试-总结收获】
【33-业务开发-基础业务-规格参数-销售属性-多表之间的关联增删改查操作-前后端项目交互整合与测试-Cannot read property ‘publish‘ of undefined】
【34-业务开发-基础业务-属性组和基本属性-属性组和基本属性建立关联-属性组和基本属性解除关联-未关联属性查询-确认新增】
【35-业务开发-基础业务-商品服务-新增商品-会员模块服务-mall-member-会员模块数据维护-规格参数维护-前端项目Bug解决-PubSub依赖缺失】
【36-业务开发-基础业务-商品服务SPU-前后端处理商品数据Json-发布商品前后端业务逻辑-feign服务远程调用-DTO数据传输对象-商品服务的检索-商品管理的检索项目中修改更正完善逻辑操作】
【37-业务开发-基础业务-库存管理- 仓库模块Nacos注册中心的配置-Gateway网关配置-仓库维护的增删改查-商品库存管理-采购流程-采购需求维护-采购需求合并-领取采购单完成采购操作】
【插入------>ElasticSearch专栏相关的知识内容都整理好了,在这里哟!】
【38-商品上架功能结合ElasticSearch全文检索的流程-商品ES关系映射模型&Docker安装ik分词器-实现上架功能复杂的逻辑实现-Postman+Kibana访问测试】
【39-商品整合thymeleaf模板引擎-商城用户端的实现逻辑-部署devtools工具依赖-商品后台-三级分类逻辑分析实现-Docker 安装部署Nginx-Nginx对网关实现反向代理负载均衡】
【40-系统性能压力测试基本概念-相关性能指标HPS&TPS&QPS&RT-安装Jmeter教程-JMeter测试流程-线程组-取样器-监视器-测试商城首页-JMeter Address 占用的问题】
【41-系统性能压力测试优化-JVM知识回顾-jconsole和jvisualvm-jvisualvm安装Visual GC插件-Nginx压力测试- 网关gateway压测-Nginx实现动静分离】
【42-缓存的基本概念-是否使用缓存的场景-本地缓存-分布式缓存-项目中整合Redis-修改三级分类逻辑代码+加入缓存-三级分类加入缓存后压力测试-缓存穿透-缓存雪崩-缓存击穿】
【43-本地锁-分布式锁概念原理-分布式锁解决方案-Redis实现分布式锁-Redisson分布式锁-项目整合Redisson-缓存数据一致性问题-解决缓存一致性的方案-SpringCache缓存】
【44-商城检索服务的搭建-页面跳转调整-elasticsearch检索服务前后端响应的VO对象-检索服务前后端逻辑实现-构建SearchRequest、SearchResponse对象】
【45-线程的实现方式-线程池的创建方式-线程池的执行顺序-CompletableFutrue异步处理】
【46-商品详情页服务搭建架构图-前后端商品响应详情VO对象-商品详情后端逻辑处理-商品详情页前端渲染逻辑-CompletableFuture的异步编排处理】
【47-商城认证服务-Nacos注册中心配置中心-Nginx动静分离&反向代理-Gateway网关路由配置-视图解析器-验证码逻辑-阿里云短信服务-短信功能实现-Feign调用模块-Redis验证码】
【48-注册功能JSR303数据校验-mall-auth-server服务远程调用mall-member服务保存注册用户信息&登陆功能-阿里云SMS服务-Redis验证码存储-Feign远程调用】
【49.Auth2.0认证与授权过程-微博开放平台认证授权过程-百度开放平台认证授权过程-社交登录实现(微博授权)-分布式Session问题与解决方案-SpringSession整合-Redis】
【50-购物车模块事项逻辑操作-购物车数据存储方式-添加商品到购物车实现的逻辑操作-Nginx负载均衡&动静分离-Gateway网关路由配置-SpringSession-Redis会话信息存储】
【51-订单模块-资源整合-整合SpringSession-订单中心核心逻辑-认证拦截-订单提交-订单生成逻辑-接口幂等性处理-接口幂等性解决方案】
【52-分布式事务-本地事务-事务特性-事务隔离级别-事务传播行为-分布式事务CAP定理-Base理论-分布式事务解决方案-微服务分布式Seata解决方案-Docker安装RocketMQ详细教程】
二.秒杀服务-商品上架
秒杀活动的结构图
通过定时任务触发:
/**
* 定时上架秒杀商品信息
*/
@Slf4j
@Component
public class SeckillSkuSchedule {
@Autowired
SeckillService seckillService;
@Autowired
RedissonClient redissonClient;
/**
* 异步设置定时任务
*/
@Async
@Scheduled(cron = "*/5 * * * * *")
public void uploadSeckillSku3Days(){
log.info("定时上架秒杀商品执行了...." + new Date());
// 分布式锁
RLock lock = redissonClient.getLock("seckill:upload:lock");
lock.lock(10, TimeUnit.SECONDS);
try {
// 调用上架商品的方法
seckillService.uploadSeckillSku3Days();
}catch (Exception e){
lock.unlock();
}
}
}
进入到Service中处理
@Override
public void uploadSeckillSku3Days() {
// 1. 通过OpenFegin 远程调用Coupon服务中接口来获取未来三天的秒杀活动的商品
R r = couponFeignService.getLates3DaysSession();
if(r.getCode() == 0){
// 表示查询操作成功
String json = (String) r.get("data");
List<SeckillSessionEntity> seckillSessionEntities = JSON.parseArray(json,SeckillSessionEntity.class);
// 2. 上架商品 Redis数据保存
// 缓存商品
// 2.1 缓存每日秒杀的SKU基本信息
saveSessionInfos(seckillSessionEntities);
// 2.2 缓存每日秒杀的商品信息
saveSessionSkuInfos(seckillSessionEntities);
}
}
/**
* 保存每日活动的信息到Redis中
* @param seckillSessionEntities
*/
private void saveSessionInfos(List<SeckillSessionEntity> seckillSessionEntities) {
for (SeckillSessionEntity seckillSessionEntity : seckillSessionEntities) {
// 循环缓存每一个活动 key: start_endTime
long start = seckillSessionEntity.getStartTime().getTime();
long end = seckillSessionEntity.getEndTime().getTime();
// 生成Key
String key = SeckillConstant.SESSION_CHACE_PREFIX+start+"_"+end;
Boolean flag = redisTemplate.hasKey(key);
if(!flag){// 表示这个秒杀活动在Redis中不存在,也就是还没有上架,那么需要保存
// 需要存储到Redis中的这个秒杀活动涉及到的相关的商品信息的SKUID
List<String> collect = seckillSessionEntity.getRelationEntities().stream().map(item -> {
// 秒杀活动存储的 VALUE是 sessionId_SkuId
return item.getPromotionSessionId()+"_"+item.getSkuId().toString();
}).collect(Collectors.toList());
redisTemplate.opsForList().leftPushAll(key,collect);
}
}
}
/**
* 存储活动对应的 SKU信息
* @param seckillSessionEntities
*/
private void saveSessionSkuInfos(List<SeckillSessionEntity> seckillSessionEntities) {
seckillSessionEntities.stream().forEach(session -> {
// 循环取出每个Session,然后取出对应SkuID 封装相关的信息
BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
session.getRelationEntities().stream().forEach(item->{
String skuKey = item.getPromotionSessionId()+"_"+item.getSkuId();
Boolean flag = redisTemplate.hasKey(skuKey);
if(!flag){
SeckillSkuRedisDto dto = new SeckillSkuRedisDto();
// 1.获取SKU的基本信息
R info = productFeignService.info(item.getSkuId());
if(info.getCode() == 0){
// 表示查询成功
String json = (String) info.get("skuInfoJSON");
dto.setSkuInfoVo(JSON.parseObject(json,SkuInfoVo.class));
}
// 2.获取SKU的秒杀信息
/*dto.setSkuId(item.getSkuId());
dto.setSeckillPrice(item.getSeckillPrice());
dto.setSeckillCount(item.getSeckillCount());
dto.setSeckillLimit(item.getSeckillLimit());
dto.setSeckillSort(item.getSeckillSort());*/
BeanUtils.copyProperties(item,dto);
// 3.设置当前商品的秒杀时间
dto.setStartTime(session.getStartTime().getTime());
dto.setEndTime(session.getEndTime().getTime());
// 4. 随机码
String token = UUID.randomUUID().toString().replace("-","");
dto.setRandCode(token);
// 分布式信号量的处理 限流的目的
RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + token);
// 把秒杀活动的商品数量作为分布式信号量的信号量
semaphore.trySetPermits(item.getSeckillCount().intValue());
hashOps.put(skuKey,JSON.toJSONString(dto));
}
});
});
}
启动服务,数据会被保存到Redis中
三.秒杀服务-商品上架
通过当前时间获取对应的秒杀活动及对应的SKU信息。
/**
* 查询出当前时间内的秒杀活动及对应的商品SKU信息
* @return
*/
@Override
public List<SeckillSkuRedisDto> getCurrentSeckillSkus() {
// 1.确定当前时间是属于哪个秒杀活动的
long time = new Date().getTime();
// 从Redis中查询所有的秒杀活动
Set<String> keys = redisTemplate.keys(SeckillConstant.SESSION_CHACE_PREFIX + "*");
for (String key : keys) {
//seckill:sessions1656468000000_1656469800000
String replace = key.replace(SeckillConstant.SESSION_CHACE_PREFIX, "");
// 1656468000000_1656469800000
String[] s = replace.split("_");
Long start = Long.parseLong(s[0]); // 活动开始的时间
Long end = Long.parseLong(s[1]); // 活动结束的时间
if(time > start && time < end){
// 说明的秒杀活动就是当前时间需要参与的活动
// 取出来的是SKU的ID 2_9
List<String> range = redisTemplate.opsForList().range(key, -100, 100);
BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
List<String> list = ops.multiGet(range);
if(list != null && list.size() > 0){
List<SeckillSkuRedisDto> collect = list.stream().map(item -> {
SeckillSkuRedisDto seckillSkuRedisDto = JSON.parseObject(item, SeckillSkuRedisDto.class);
return seckillSkuRedisDto;
}).collect(Collectors.toList());
return collect;
}
}
}
return null;
}
然后定义相关的Controller接口就可以访问了
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
SeckillService seckillService;
@GetMapping("/currentSeckillSessionSkus")
public R getCurrentSeckillSessionSkus(){
List<SeckillSkuRedisDto> currentSeckillSkus = seckillService.getCurrentSeckillSkus();
return R.ok().put("data", JSON.toJSONString(currentSeckillSkus));
}
}
四.秒杀服务-模块搭建
4.1 网关配置
首先在host中配置域名
然后在网关中配置路由信息
4.2 秒杀首页配置
通过Ajax来访问获取秒杀的相关信息
$.get("http://seckill.msb.com/seckill/currentSeckillSessionSkus",function(resp){
if(resp.data.length > 0){
// 说明有秒杀的数据
console.log($.parseJSON(resp.data))
$.parseJSON(resp.data).forEach(function(item){
$("<li></li>").append("<img width='130px' height='130px' src='"+item.skuInfoVo.skuDefaultImg+"'/>")
.append("<p>"+item.skuInfoVo.skuSubtitle+"</p>")
.append("<span>"+item.seckillPrice+"</span>")
.append("<s>"+item.skuInfoVo.price+"</s>")
.appendTo("#seckillSessionContent");
})
/*<li>
<img src="/static/index/img/section_second_list_img1.jpg" alt="">
<p>花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千克) (日本官方直采) 花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千</p>
<span>¥83.9</span><s>¥99.9</s>
</li>*/
}
})
4.3 秒杀商品详情
在购买商品的时候,进入到商品详情页,如果该商品也参与了秒杀活动,那么对应的需要展示相关的信息
首先我们需要在秒杀服务中提供一个根据SKUID查询相关的秒杀活动的接口
/**
* 根据SKUID查询秒杀活动对应的信息
* @param skuId
* @return
*/
@Override
public SeckillSkuRedisDto getSeckillSessionBySkuId(Long skuId) {
// 1.找到所有需要参与秒杀的商品的sku信息
BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
Set<String> keys = ops.keys();
if(keys != null && keys.size() > 0){
String regx = "\\d_"+ skuId;
for (String key : keys) {
boolean matches = Pattern.matches(regx, key);
if(matches){
// 说明找到了对应的SKU的信息
String json = ops.get(key);
SeckillSkuRedisDto dto = JSON.parseObject(json, SeckillSkuRedisDto.class);
return dto;
}
}
}
return null;
}
然后在查询商品详情的时候异步查询出对应的秒杀活动信息
然后在模板页面中展示相关的信息
<div class="box-summary clear">
<ul>
<li>京东价</li>
<li>
<span>¥</span>
<span th:text="${#numbers.formatDecimal(item.info.price,3,2)}">4499.00</span>
</li>
<li style="color: red">
<span th:if="${#dates.createNow().getTime() < item.seckillVO.startTime}">
商品将在:[[${#dates.format(new java.util.Date(item.seckillVO.startTime),'yyyy-MM-dd HH:mm:ss')}]] 开始秒杀
</span>
<span th:if="${#dates.createNow().getTime() > item.seckillVO.startTime
&& #dates.createNow().getTime() < item.seckillVO.endTime }">
秒杀价: [[${#numbers.formatDecimal(item.seckillVO.seckillPrice,1,2)}]]
</span>
</li>
<li>
<a href="/static/item/">
预约说明
</a>
</li>
</ul>
</div>
首页调整到商品详情页
function goItem(skuId){
location.href="http://item.ljw.com/"+skuId+".html"
}
$.get("http://seckill.ljw.com/seckill/currentSeckillSessionSkus",function(resp){
if(resp.data.length > 0){
// 说明有秒杀的数据
console.log($.parseJSON(resp.data))
$.parseJSON(resp.data).forEach(function(item){
$("<li οnclick='goItem("+item.skuId+")'></li>")
.append("<img width='130px' height='130px' src='"+item.skuInfoVo.skuDefaultImg+"'/>")
.append("<p>"+item.skuInfoVo.skuSubtitle+"</p>")
.append("<span>"+item.seckillPrice+"</span>")
.append("<s>"+item.skuInfoVo.price+"</s>")
.appendTo("#seckillSessionContent");
})
五.秒杀服务-秒杀活动
5.1 秒杀活动关注点
秒杀活动的最大特点就是高并发而且是短时间内的高并发,那么对我们的服务要求就非常高,针对这种情况所产生的共性问题,对应的解决方案:
5.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.ljw.com/seckill/kill?killId="+killId + "&code="+code+"&num="+num;
}else{
alert("请先登录才能参加秒杀活动!!!");
}
return false;
});
5.3 秒杀活动后端逻辑处理
前端提交的秒杀请求,在后端具体的处理
5.3.1 登录校验
秒杀活动必须是在登录状态下进行的,如果没有认证就不让秒杀。这时我们需要整合进来SpringSession。
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
添加对应的配置信息
添加拦截器
/**
* 秒杀活动的拦截器 确认是杂登录的状态下操作的
*/
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.ljw.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("ljw.com"); // 设置session对应的一级域名
cookieSerializer.setCookieName("ljwsession");
return cookieSerializer;
}
/**
* 对存储在Redis中的数据指定序列化的方式
* @return
*/
@Bean
public RedisSerializer<Object> redisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
最后在启动类中开启redis回话存储信息注解
5.3.2 秒杀活动流程
秒杀活动流程
登录校验
通过拦截器处理:在秒杀活动中并不是所有的请求都是需要在登录状态下的,所有这个拦截器应该只需要拦截部分的请求。
/**
* 秒杀活动的拦截器 确认是杂登录的状态下操作的
*/
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.ljw.com/login.html");
return false;
}
}
配置拦截部分请求
@Configuration
public class MyWebInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/seckill/kill");
}
}
合法性校验
校验的内容有四块:时效性,随机码是否合法,是否满足限购条件,还有幂等性
信号量处理
通过信号量来控制秒杀的商品数量。降低了对库存商品操作,提升了处理能力
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发送消息,订单服务订阅消息,实现异步下单,从而降低了对秒杀系统的影响。
然后在订单服务中订阅对应的信息
@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);
}
}
秒杀成功跳转到成功页面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title></title>
<script type="text/javascript" src="/static/cart/js/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="/static/cart/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript" src="/static/cart/js/swiper.min.js"></script>
<script src="js/swiper.min.js"></script>
<link rel="stylesheet" type="text/css" href="/static/cart/css/swiper.min.css"/>
<link rel="stylesheet" type="text/css" href="/static/cart/bootstrap/css/bootstrap.css"/>
<link rel="stylesheet" type="text/css" href="/static/cart/css/success.css"/>
</head>
<body>
<!--头部-->
<div class="alert-info">
<div class="hd_wrap_top">
<ul class="hd_wrap_left">
<li class="hd_home"><i class="glyphicon glyphicon-home"></i>
<a href="http://mall.msb.com/home">马士兵商城首页</a>
</li>
</ul>
<ul class="hd_wrap_right">
<li th:if="${session.loginUser == null}"><a href="http://auth.msb.com/login.html" style="color: red;">你好,请登录</a></li>
<li th:if="${session.loginUser != null}"><span style="color: red;">[[${session.loginUser.nickname}]]</span></li>
<li class="spacer"></li>
<li>
<a href="/javascript:;">我的订单</a>
</li>
</ul>
</div>
</div>
<div class="nav-tabs-justified">
<div class="nav_wrap">
<div class="nav_top">
<div class="nav_top_one">
<a href="http://mall.msb.com/home"><img src="/static/cart/img/logo1.jpg"
style="height: 60px;width:180px;"/></a>
</div>
<div class="nav_top_two"><input type="text"/>
<button>搜索</button>
</div>
</div>
</div>
</div>
<div class="main">
<div class="success-wrap">
<div class="w" id="result">
<div class="m succeed-box">
<div class="mc success-cont" th:if="${orderSn!=null}">
<h1>恭喜您!秒杀成功,订单号[[${orderSn}]]</h1>
<h2>正在准备订单数据...10秒后跳转到支付页面</h2>
<a href="#">去支付</a>
</div>
<div class="mc success-cont" th:if="${orderSn==null}">
<h1>很遗憾~没有抢购到!欢迎下次再来参与....</h1>
</div>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="/static/cart/js/success.js"></script>
</html>
好了,关于【53-秒杀服务-秒杀活动关注的问题-秒杀活动实现的流程思路-微服务项目(gateway+sentinel+skywalking+nacos+feign)+redis+nginx+mysql】就先学习到这里,更多的内容持续创作学习中。