|
|
# 秒杀服务
|
|
|
|
|
|
# 一、商品上架
|
|
|
|
|
|
秒杀活动的结构图
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/3708d1516dcd49f884f48b52e347eea1.png)
|
|
|
|
|
|
通过定时任务触发:
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 定时上架秒杀商品信息
|
|
|
*/
|
|
|
@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中处理
|
|
|
|
|
|
```java
|
|
|
@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中
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/4c70312c248346c5b86ca922e7b9f3ba.png)
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/e00adbc3e5534dbfa085f1ffb50334d3.png)
|
|
|
|
|
|
# 二、秒杀商品查询
|
|
|
|
|
|
  通过当前时间获取对应的秒杀活动及对应的SKU信息。
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 查询出当前时间内的秒杀活动及对应的商品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接口就可以访问了
|
|
|
|
|
|
```java
|
|
|
@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));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
然后对应的访问效果:
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/265a362ef2f64653a729c34ec4288fe6.png)
|
|
|
|
|
|
# 三、页面渲染
|
|
|
|
|
|
## 1.网关配置
|
|
|
|
|
|
首先在host中配置域名
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/d4de905466644f75ade6995a8450d321.png)
|
|
|
|
|
|
然后在网关中配置路由信息
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/f7cda457c1254c3a92675e2ae88db8c2.png)
|
|
|
|
|
|
然后重启服务访问:
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/53f0a08248564ea4a366c84ac8b2a570.png)
|
|
|
|
|
|
能访问到数据就表示域名配置成功
|
|
|
|
|
|
## 2.首页配置
|
|
|
|
|
|
  通过Ajax来访问获取秒杀的相关信息
|
|
|
|
|
|
```javascript
|
|
|
$.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>*/
|
|
|
|
|
|
}
|
|
|
})
|
|
|
```
|
|
|
|
|
|
展示的效果
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/d3e87a6f82b548b59884348ffbdfcdb9.png)
|
|
|
|
|
|
## 3.商品详情
|
|
|
|
|
|
  在购买商品的时候,进入到商品详情页,如果该商品也参与了秒杀活动,那么对应的需要展示相关的信息
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/734265102c104b319c45089e8695c34f.png)
|
|
|
|
|
|
首先我们需要在秒杀服务中提供一个根据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;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
然后在查询商品详情的时候异步查询出对应的秒杀活动信息
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/fd30afb548ed4f6fa47951836de48585.png)
|
|
|
|
|
|
然后在模板页面中展示相关的信息
|
|
|
|
|
|
```html
|
|
|
<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>
|
|
|
```
|
|
|
|
|
|
首页调整到商品详情页
|
|
|
|
|
|
```javascript
|
|
|
function goItem(skuId){
|
|
|
location.href="http://item.msb.com/"+skuId+".html"
|
|
|
}
|
|
|
|
|
|
$.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 onclick='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");
|
|
|
})
|
|
|
/*<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>*/
|
|
|
|
|
|
}
|
|
|
})
|
|
|
```
|
|
|
|
|
|
# 四、秒杀活动
|
|
|
|
|
|
## 1.秒杀活动关注点
|
|
|
|
|
|
  秒杀活动的最大特点就是高并发而且是短时间内的高并发,那么对我们的服务要求就非常高,针对这种情况所产生的共性问题,对应的解决方案:
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/de1e4610e08d42f088c96e6949472385.png)
|
|
|
|
|
|
## 2. 秒杀服务前端
|
|
|
|
|
|
  当我们点击 `秒杀抢购`按钮后,对应我们需要把当前的商品信息提交到后端服务。活动编号+"_"+SkuId,Code随机码,抢购商品的数量。
|
|
|
|
|
|
```html
|
|
|
<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操作
|
|
|
|
|
|
```html
|
|
|
$("#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](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/b395d344527c4eca9a626b412b0da657.png)
|
|
|
|
|
|
## 3.后端逻辑处理
|
|
|
|
|
|
  前端提交的秒杀请求,在后端具体的处理
|
|
|
|
|
|
### 3.1 登录校验
|
|
|
|
|
|
  秒杀活动必须是在登录状态下进行的,如果没有认证就不让秒杀。这时我们需要整合进来SpringSession。
|
|
|
|
|
|
```xml
|
|
|
<dependency>
|
|
|
<groupId>org.springframework.session</groupId>
|
|
|
<artifactId>spring-session-data-redis</artifactId>
|
|
|
</dependency>
|
|
|
```
|
|
|
|
|
|
然后添加对应的配置信息
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/fe67f88cd1ab48fbb40685fa808bed30.png)
|
|
|
|
|
|
然后添加拦截器
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 秒杀活动的拦截器 确认是杂登录的状态下操作的
|
|
|
*/
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
配置拦截器
|
|
|
|
|
|
```java
|
|
|
@Configuration
|
|
|
public class MyWebInterceptorConfig implements WebMvcConfigurer {
|
|
|
@Override
|
|
|
public void addInterceptors(InterceptorRegistry registry) {
|
|
|
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/seckill/kill");
|
|
|
}
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
设置Cookie的配置
|
|
|
|
|
|
```java
|
|
|
@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](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/e6ff13db46a84084b10ed4312f5f741d.png)
|
|
|
|
|
|
### 3.2 秒杀活动流程
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/efcef49fd25042a89c377fa28c595e4e.png)
|
|
|
|
|
|
**登录校验**
|
|
|
|
|
|
通过拦截器处理:在秒杀活动中并不是所有的请求都是需要在登录状态下的,所有这个拦截器应该只需要拦截部分的请求。
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 秒杀活动的拦截器 确认是杂登录的状态下操作的
|
|
|
*/
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
配置拦截部分请求
|
|
|
|
|
|
```java
|
|
|
@Configuration
|
|
|
public class MyWebInterceptorConfig implements WebMvcConfigurer {
|
|
|
@Override
|
|
|
public void addInterceptors(InterceptorRegistry registry) {
|
|
|
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/seckill/kill");
|
|
|
}
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**合法性校验**
|
|
|
|
|
|
  校验的内容有四块:时效性,随机码是否合法,是否满足限购条件,还有幂等性
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/5d9de5879e584da79d5e0c7f06a82e23.png)
|
|
|
|
|
|
**信号量处理**
|
|
|
|
|
|
  通过信号量来控制秒杀的商品数量。降低了对库存商品操作,提升了处理能力
|
|
|
|
|
|
```java
|
|
|
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](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/8c3e0422271e4b5abdd955dbc3ae3a32.png)
|
|
|
|
|
|
然后在订单服务中订阅对应的信息
|
|
|
|
|
|
```java
|
|
|
@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);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
秒杀成功跳转到成功页面:
|
|
|
|
|
|
```html
|
|
|
<!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>
|
|
|
|
|
|
```
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/e7097ca1b1f3496eb239786178d32a2d.png)
|
|
|
|
|
|
![image.png](https://fynotefile.oss-cn-zhangjiakou.aliyuncs.com/fynote/fyfile/1462/1655971234063/282f22eafbf8414c87ca76d4384009ad.png)
|
|
|
|
|
|
好了~到这儿秒杀活动搞定!
|