目录:
(1)下单页面
(2)service-activity-client添加接口
(3)web-all 编写去下单控制器
(4)service-order模块提供秒杀下单接口
(5)service-order-client模块暴露接口
(6)service-activity模块秒杀下单
(7)秒杀结束清空redis缓存
(1)下单页面
我们已经把下单信息记录到redis缓存中,所以接下来我们要组装下单页数据
下单页数据数据接口SeckillGoodsApiController
@Autowired
private RedisTemplate redisTemplate;
/**
* 秒杀确认订单
* @param request
* @return
*/
@GetMapping("auth/trade")
public Result trade(HttpServletRequest request) {
// 获取到用户Id
String userId = AuthContextHolder.getUserId(request);
// 先得到用户想要购买的商品!
OrderRecode orderRecode = (OrderRecode) redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);
if (null == orderRecode) {
return Result.fail().message("非法操作");
}
//获取商品
SeckillGoods seckillGoods = orderRecode.getSeckillGoods();
//获取用户地址
List<UserAddress> userAddressList = userFeignClient.findUserAddressListByUserId(userId);
// 声明一个集合来存储订单明细
ArrayList<OrderDetail> detailArrayList = new ArrayList<>();
OrderDetail orderDetail = new OrderDetail();
orderDetail.setSkuId(seckillGoods.getSkuId());
orderDetail.setSkuName(seckillGoods.getSkuName());
orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
orderDetail.setSkuNum(orderRecode.getNum());
orderDetail.setOrderPrice(seckillGoods.getCostPrice());
// 添加到集合
detailArrayList.add(orderDetail);
// 计算总金额 创建订单对象,计算订单价格
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOrderDetailList(detailArrayList);
orderInfo.sumTotalAmount();
Map<String, Object> result = new HashMap<>();
result.put("userAddressList", userAddressList);
result.put("detailArrayList", detailArrayList);
result.put("totalNum", 1);
// 保存总金额
result.put("totalAmount", orderInfo.getTotalAmount());
return Result.ok(result);
}
(2)service-activity-client添加接口
ActivityFeignClient
/**
* 秒杀确认订单
* @return
*/
@GetMapping("/api/activity/seckill/auth/trade")
Result<Map<String, Object>> trade();
ActivityDegradeFeignClient
@Override
public Result<Map<String, Object>> trade() {
return Result.fail();
}
(3)web-all 编写去下单控制器
SeckillController
/**
* 确认订单
* @param model
* @return
*/
@GetMapping("seckill/trade.html")
public String trade(Model model) {
Result<Map<String, Object>> result = activityFeignClient.trade();
if(result.isOk()) {
model.addAllAttributes(result.getData());
return "seckill/trade";
} else {
model.addAttribute("message",result.getMessage());
return "seckill/fail";
}
}
Result
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;
}
}
下单确认页面
该页面与正常下单页面类似,只是下单提交接口不一样,因为秒杀下单不需要正常下单的各种判断,因此我们要在订单服务提供一个秒杀下单接口,直接下单
(4)service-order模块提供秒杀下单接口
OrderApiController :提供一个重载的下单方法
/**
* 秒杀提交订单,秒杀订单不需要做前置判断,直接下单
* @param orderInfo
* @return
*/
@PostMapping("inner/seckill/submitOrder")
public Long submitOrder(@RequestBody OrderInfo orderInfo) {
Long orderId = orderService.saveOrderInfo(orderInfo);
return orderId;
}
(5)service-order-client模块暴露接口
OrderFeignClient
/**
* 提交秒杀订单
* @param orderInfo
* @return
*/
@PostMapping("/api/order/inner/seckill/submitOrder")
Long submitOrder(@RequestBody OrderInfo orderInfo);
OrderDegradeFeignClient
@Override
public Long submitOrder(OrderInfo orderInfo) {
return null;
}
(6)service-activity模块秒杀下单
SeckillGoodsApiController
@Autowired
private OrderFeignClient orderFeignClient;
@PostMapping("auth/submitOrder")
public Result submitOrder(@RequestBody OrderInfo orderInfo, HttpServletRequest request) {
String userId = AuthContextHolder.getUserId(request);
orderInfo.setUserId(Long.parseLong(userId));
Long orderId = orderFeignClient.submitOrder(orderInfo);
if (null == orderId) {
return Result.fail().message("下单失败,请重新操作");
}
//删除下单信息 临时订单
redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).delete(userId);
//添加一个用户订单记录 下单记录 总订单
redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).put(userId, orderId.toString());
return Result.ok(orderId);
}
页面提交订单代码片段
submitOrder() {
seckill.submitOrder(this.order).then(response => {
if (response.data.code == 200) {
window.location.href = 'http://payment.gmall.com/pay.html?orderId=' + response.data.data
} else {
alert(response.data.message)
}
})
},
说明:下单成功后,后续流程与正常订单一致
(7)秒杀结束清空redis缓存
秒杀过程中我们写入了大量redis缓存,我们可以在秒杀结束或每天固定时间清楚缓存
,释放缓存空间;
实现思路:假如根据业务,我们确定每天18点所有秒杀业务结束,那么我们编写定时任务,每天18点发送mq消息,service-activity模块监听消息清理缓存
Service-task发送消息
添加常量MqConst类
/**
* 定时任务
*/
public static final String ROUTING_TASK_18 = "seckill.task.18";
//队列
public static final String QUEUE_TASK_18 = "queue.task.18";
编写定时任务发送消息
/**
* 每天下午18点执行
*/
//@Scheduled(cron = "0/35 * * * * ?")
@Scheduled(cron = "0 0 18 * * ?")
public void task18() {
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_TASK, MqConst.ROUTING_TASK_18, "");
}
接收消息并处理
Service-activity接收消息
SeckillReceiver
// 监听删除消息!每天18点清空数据
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = MqConst.QUEUE_TASK_18,durable = "true",autoDelete = "false"),
exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_TASK),
key = {MqConst.ROUTING_TASK_18}
))
public void deleteRedisData(Message message, Channel channel){
try {
// 查询哪些商品是秒杀结束的!end_time , status = 1
// select * from seckill_goods where status = 1 and end_time < new Date();
QueryWrapper<SeckillGoods> seckillGoodsQueryWrapper = new QueryWrapper<>();
seckillGoodsQueryWrapper.eq("status",1);
seckillGoodsQueryWrapper.le("end_time",new Date());
List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectList(seckillGoodsQueryWrapper);
// 对应将秒杀结束缓存中的数据删除!
for (SeckillGoods seckillGoods : seckillGoodsList) {
// seckill:stock:46 删除库存对应key 删除List集合
redisTemplate.delete(RedisConst.SECKILL_STOCK_PREFIX+seckillGoods.getSkuId());
// 删除预热
// redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).delete(seckillGoods.getSkuId());
}
// 删除预热等数据! 主要针对于预热数据删除! 我们项目只针对一个商品的秒杀! 如果是多个秒杀商品,则不能这样直接删除预热秒杀商品的key!
// 46 : 10:00 -- 10:30 | 47 : 18:10 -- 18:30
redisTemplate.delete(RedisConst.SECKILL_GOODS);
// 预下单 临时订单
redisTemplate.delete(RedisConst.SECKILL_ORDERS);
// 删除真正下单数据 总订单记录
redisTemplate.delete(RedisConst.SECKILL_ORDERS_USERS);
// 修改数据库秒杀对象的状态!
SeckillGoods seckillGoods = new SeckillGoods();
// 1:表示审核通过 ,2:表示秒杀结束
seckillGoods.setStatus("2");
seckillGoodsMapper.update(seckillGoods,seckillGoodsQueryWrapper);
} catch (Exception e) {
e.printStackTrace();
}
// 手动确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
用户是否抢单信息我们这里在前面存储的时候可以设置超时时间就行了,这里就不用清楚了,因为这里不好获取用户id
说明:情况redis缓存,同时更改秒杀商品活动结束
行秒杀下单,提交成功,页面通过轮询后台方法查询秒杀状态