java实现秒杀业务之接口优化【使用redis预减库存,rabbitmq做消息队列实现异步请求】(重难点)

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>

 

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值