mycat是阿里巴巴开发出来的分库分表的中间件
第一步:系统初始化,把秒杀商品表库存数量加载到redis
contoller实现InitializingBean类,重写afterPropertiesSet方法
public class SeckillController implements InitializingBean{
private Map<Long,Boolean> localOverMap=new HashMap<Long,Boolean>();
//秒杀接口优化第一步:系统初始化,将秒杀商品表里面库存数量加载到redis里面
public void afterPropertiesSet() throws Exception {
List<GoodsVo> goodsList=goodsService.listGoodsVo();
if(goodsList==null) {
return;
}
for(GoodsVo goods:goodsList) {
redisService.set(GoodsKey.getSeckillGoodsStock,""+goods.getId(),goods.getGoodsStock());
localOverMap.put(goods.getId(), false);
}
}
}
第二步:redis预减库存
第三步:秒杀到请求入rabbitmq队列
/**
* 这边接的是通过form表单带有隐藏域进行传递【重点】
* 参数和前端隐藏域的name对应<input type="hidden" name="goodsId" th:value="${goods.id}" />
* seckillUser这用户的获取是通过config下面的UserArgumentResolver和WebConfig来获取的【从cookie里面获取token对应的用户】
* @param model
* @param seckillUser
* @param goodsId
* @return
*/
@RequestMapping("/do_seckill")
@ResponseBody
public Result<Integer> list(Model model,SeckillUser seckillUser,@RequestParam("goodsId")long goodsId) {
model.addAttribute("user",seckillUser);
//判断用户是否登陆
if(seckillUser ==null) {
return Result.error(CodeMsg.SECKILL_OVER);
// return "login";
}
//内存标记,减少redis访问
boolean over = localOverMap.get(goodsId);
if(over) {
return Result.error(CodeMsg.SECKILL_OVER);
}
//秒杀接口优化第二步:redis预减库存
long stock=redisService.decr(GoodsKey.getSeckillGoodsStock,""+goodsId);
if(stock<0) {
localOverMap.put(goodsId,true);
return Result.error(CodeMsg.SECKILL_OVER);
// return "seckill_fail";
}
//秒杀接口优化第三步:在秒杀订单表里判断是否秒杀到
SeckillOrder order=orderService.getSeckillOrderByUserIdGoodsId(seckillUser.getId(),goodsId);
if(order!=null) {
model.addAttribute("errmsg",CodeMsg.REPEAT_SECKILL.getMsg());
return Result.error(CodeMsg.REPEAT_SECKILL);
// return "seckill_fail";
}
//秒杀接口优化第四步:秒杀到了就进行入队
SeckillMessage seckillMessage=new SeckillMessage();
seckillMessage.setSeckillUser(seckillUser);
seckillMessage.setGoodsId(goodsId);
mqSender.sendSeckillMessage(seckillMessage);
return Result.success(0);//排队中
/*//判断库存
GoodsVo goods=goodService.getGoodsVoByGoodsId(goodsId);
int stockCount=goods.getStockCount();
if(stockCount<=0) {
model.addAttribute("errmsg",CodeMsg.SECKILL_OVER.getMsg());
return "seckill_fail";
}
//判断是否秒杀到了
SeckillOrder order=orderService.getSeckillOrderByUserIdGoodsId(seckillUser.getId(),goodsId);
if(order!=null) {
model.addAttribute("errmsg",CodeMsg.REPEAT_SECKILL.getMsg());
return "seckill_fail";
}
//减库存,生成订单,写入秒杀订单【备注:这3个操作需要在一个事务里面】
OrderInfo orderInfo=seckillService.seckill(seckillUser,goods);
model.addAttribute("orderInfo",orderInfo);//将订单的详情信息写入页面上
model.addAttribute("goods",goods);//将商品信息写入页面上
return "order_detail";
*/
}
第四步:请求出队,减少库存,生成订单
package com.jack.seckill.rabbitmq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.jack.seckill.domain.SeckillOrder;
import com.jack.seckill.domain.SeckillUser;
import com.jack.seckill.redis.RedisService;
import com.jack.seckill.service.GoodsService;
import com.jack.seckill.service.OrderService;
import com.jack.seckill.service.SeckillService;
import com.jack.seckill.vo.GoodsVo;
@Service
public class MQReceiver {
@Autowired
GoodsService goodService;
@Autowired
OrderService orderService;
@Autowired
RedisService redisService;
@Autowired
SeckillService seckillService;
private static Logger log=LoggerFactory.getLogger(MQReceiver.class);
// @RabbitListener(queues=MQconfig.QUEUE)
// public void receive(String message) {
// log.info("receive message:"+message);
// }
//
// @RabbitListener(queues=MQconfig.TOPIC_QUEUE1)
// public void receiveTopic1(String message) {
// log.info("topic queue1 message:"+message);
// }
//
// @RabbitListener(queues=MQconfig.TOPIC_QUEUE2)
// public void receiveTopic2(String message) {
// log.info("topic queue2 message:"+message);
// }
// @RabbitListener(queues=MQconfig.HEADERS_QUEUE)
// public void receiveHeaders(byte[] message) {
// log.info("header queue message:"+new String(message));
// }
@RabbitListener(queues=MQconfig.SECKILL_QUEUE)
public void receive(String message) {
log.info("receive message:"+message);
//传过来的信息在接受者这里进行操作
SeckillMessage seckillMessage=RedisService.stringToBean(message, SeckillMessage.class);
SeckillUser seckillUser=seckillMessage.getSeckillUser();
long goodsId=seckillMessage.getGoodsId();
//秒杀接口优化第五步:判断秒杀商品表的库存
GoodsVo goods=goodService.getGoodsVoByGoodsId(goodsId);
int stockCount=goods.getStockCount();
if(stockCount<=0) {
return;
}
//秒杀接口优化第六步:判断是否秒杀到了
SeckillOrder order=orderService.getSeckillOrderByUserIdGoodsId(seckillUser.getId(),goodsId);
if(order!=null) {
return;
}
//秒杀接口优化第七步:减库存,生成订单,写入秒杀订单【备注:这3个操作需要在一个事务里面】
seckillService.seckill(seckillUser,goods);
}
}
第五步:前端页面轮询展示
<!DOCTYPE HTML>
<html>
<head>
<title>商品详情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" />
<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
<!-- jquery-validator -->
<script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script>
<script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script>
<!-- layer -->
<script type="text/javascript" src="/layer/layer.js"></script>
<!-- md5.js -->
<script type="text/javascript" src="/js/md5.min.js"></script>
<!-- common.js -->
<script type="text/javascript" src="/js/common.js"></script>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading">秒杀商品详情</div>
<div class="panel-body">
<span id="userTip"> 您还没有登录,请登陆后再操作<br/></span>
<span>没有收货地址的提示。。。</span>
</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td colspan="3" id="goodsName"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="3"><img id="goodsImg" width="200" height="200" /></td>
</tr>
<tr>
<td>秒杀开始时间</td>
<td id="startTime"></td>
<td>
<input type="hidden" id="remainSeconds"/>
<span id="seckillTip"></span>
</td>
<td>
<form id="miaoshaForm" method="post" action="/seckill/do_seckill">
<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>
<input type="hidden" name="goodsId" id="goodsId" />
</form>
</td>
</tr>
<tr>
<td>商品原价</td>
<td colspan="3" id="goodsPrice"></td>
</tr>
<tr>
<td>秒杀价</td>
<td colspan="3" id="seckillPrice"></td>
</tr>
<tr>
<td>库存数量</td>
<td colspan="3" id="stockCount"></td>
</tr>
</table>
</div>
</body>
<script>
function getSeckillResult(goodsId){
g_showLoading();
$.ajax({
url:"/seckill/result",
type:"GET",
data:{
goodsId:$("#goodsId").val(),
},
success:function(data){
if(data.code == 0){
var result = data.data;
if(result < 0){
layer.msg("对不起,秒杀失败");
}else if(result == 0){//继续轮询,排队中
setTimeout(function(){
getSeckillResult(goodsId);
}, 50);
}else{
layer.confirm("恭喜你,秒杀成功!查看订单?", {btn:["确定","取消"]},
function(){
window.location.href="/order_detail.htm?orderId="+result;
},
function(){
layer.closeAll();
});
}
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
function doSeckill(){
$.ajax({
url:"/seckill/do_seckill",
type:"POST",
data:{
goodsId:$("#goodsId").val(),
},
success:function(data){
if(data.code == 0){
//window.location.href="/order_detail.htm?orderId="+data.data.id;
getSeckillResult($("#goodsId").val());
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
$(function(){
/* countDown(); */
getDetail();
});
//ajax请求数据
function getDetail(){
var goodsId=g_getQueryString("goodsId");
$.ajax({
url:"/goods/detail/"+goodsId,
type:"GET",
success:function(data){if(data.code==0){render(data.data)}else{layer.msg(data.msg)}},
error:function(){layer.msg("客户端请求错误")}
});
}
//渲染页面
function render(detail){
var seckillStatus=detail.seckillStatus;
var remainSeconds=detail.remainSeconds;
var goods=detail.goods;
var seckillUser=detail.seckillUser;
if(seckillUser){
$("#userTip").hide();
}
$("#goodsName").text(goods.goodsName);
$("#goodsImg").attr("src",goods.goodsImg);
$("#startTime").text(new Date(goods.startDate).format("yyyy-MM-dd hh:mm:ss"));
$("#remainSeconds").val(remainSeconds);
$("#goodsId").val(goods.id);
$("#goodsPrice").text(goods.goodsPrice);
$("#seckillPrice").text(goods.seckillPrice);
$("#stockCount").text(goods.stockCount);
countDown();
}
function countDown(){
var remainSeconds = $("#remainSeconds").val();
var timeout;
if(remainSeconds > 0){//秒杀还没开始,倒计时
$("#buyButton").attr("disabled", true);
$("#seckillTip").html("秒杀倒计时:"+remainSeconds+"秒");
timeout = setTimeout(function(){
$("#countDown").text(remainSeconds - 1);
$("#remainSeconds").val(remainSeconds - 1);
countDown();
},1000);
}else if(remainSeconds == 0){//秒杀进行中
$("#buyButton").attr("disabled", false);
if(timeout){
clearTimeout(timeout);
}
$("#seckillTip").html("秒杀进行中");
}else{//秒杀已经结束
$("#buyButton").attr("disabled", true);
$("#seckillTip").html("秒杀已经结束");
}
}
</script>
</html>