【IDEA +SpringBoot+Java商城秒杀实战19】秒杀静态化+订单详情静态化

秒杀静态化

改造商品详情页面的点击秒杀的业务逻辑,我们调用js方法实现ajax异步发送消息,如果秒杀成功,那么直接由客户端去跳转详情页面

window.location.href="order_detail.htm?orderId="+data.data.id;
function doMiaosha(){
		//alert("秒杀!");
		$.ajax({
			url:"/miaosha/do_miaosha",
			type:"POST",
			data:{
				goodsId:$("#goodsId").val()			
			},
			success:function(data){
				if(data.code==0){
					//秒杀成功,跳转详情页面
					window.location.href="order_detail.htm?orderId="+data.data.id;		
				}else{
					layer.msg(data.msg);
				}
			},
			error:function(){
				layer.msg("请求有误!");
			}
			//token如果cookie里面有,会自己带过去
			
		});
	}

改造后台接收秒杀请求的doMiaosha方法接口,让其不在去跳转页面了,而是直接返回包装好的数据
原来我们是这样写的:

@RequestMapping("/do_miaosha")//传入user对象啊,不然怎么取user的值,${user.nickname}
public String toList(Model model,MiaoshaUser user,@RequestParam("goodsId") Long goodsId) {
	model.addAttribute("user", user);
	//如果用户为空,则返回至登录页面
	if(user==null){
		return "login";
	}
	GoodsVo goodsvo=goodsService.getGoodsVoByGoodsId(goodsId);
	//判断商品库存,库存大于0,才进行操作,多线程下会出错
	int  stockcount=goodsvo.getStockCount();		
	if(stockcount<=0) {//失败			库存至临界值1的时候,此时刚好来了加入10个线程,那么库存就会-10
		model.addAttribute("errorMessage", CodeMsg.MIAOSHA_OVER_ERROR);
		return "miaosha_fail";
	}
	//判断这个秒杀订单形成没有,判断是否已经秒杀到了,避免一个账户秒杀多个商品 
	MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdAndCoodsId(user.getId(),goodsId);
	if(order!=null) {//重复下单
		model.addAttribute("errorMessage", CodeMsg.REPEATE_MIAOSHA);
		return "miaosha_fail";
	}
	//可以秒杀,原子操作:1.库存减1,2.下订单,3.写入秒杀订单--->是一个事务
	OrderInfo orderinfo=miaoshaService.miaosha(user,goodsvo);
	//如果秒杀成功,直接跳转到订单详情页上去。
	model.addAttribute("orderinfo", orderinfo);
	model.addAttribute("goods", goodsvo);
	return "order_detail";//返回页面login
}

上面返回的是订单详情页面,但是现在我们通过json返回给我们的前台,秒杀成功则返回订单信息,不成功返回相应的数据信息,现在我们的代码如下:

	/**
	 * 
	 * 做了页面静态化的,直接返回订单的信息
	 */
	//POST请求 
	@RequestMapping(value="/do_miaosha",method=RequestMethod.POST)
	@ResponseBody
	public Result<OrderInfo> doMiaosha(Model model,MiaoshaUser user,@RequestParam(value="goodsId",defaultValue="0") long goodsId) {
		model.addAttribute("user", user);
		//如果用户为空,则返回至登录页面
		if(user==null){
			return Result.error(CodeMsg.SESSION_ERROR);
		}
		GoodsVo goodsvo=goodsService.getGoodsVoByGoodsId(goodsId);
		//判断商品库存,库存大于0,才进行操作,多线程下会出错
		int  stockcount=goodsvo.getStockCount();		
		if(stockcount<=0) {//失败			库存至临界值1的时候,此时刚好来了加入10个线程,那么库存就会-10
			//model.addAttribute("errorMessage", CodeMsg.MIAOSHA_OVER_ERROR);
			return Result.error(CodeMsg.MIAOSHA_OVER_ERROR);
		}
		//判断这个秒杀订单形成没有,判断是否已经秒杀到了,避免一个账户秒杀多个商品 
		MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdAndGoodsId(user.getId(),goodsId);
		if(order!=null) {//重复下单
			//model.addAttribute("errorMessage", CodeMsg.REPEATE_MIAOSHA);
			return Result.error(CodeMsg.REPEATE_MIAOSHA);
		}
		//可以秒杀,原子操作:1.库存减1,2.下订单,3.写入秒杀订单--->是一个事务
		OrderInfo orderinfo=miaoshaService.miaosha(user,goodsvo);
		//如果秒杀成功,直接跳转到订单详情页上去。
		model.addAttribute("orderinfo", orderinfo);
		model.addAttribute("goods", goodsvo);
		return Result.success(orderinfo);
	}

订单详情静态化

当秒杀成功之后,由客户端直接跳转至静态订单详情页面,与之前一样,初始化执行方法getOrderDetail,发起ajax请求获取数据来渲染我们的静态页面

$(function(){
		getOrderDetail();
	});

getOrderDetail方法:

function getOrderDetail() {
		//取参数orderId
		var orderId=getQueryString("orderId");
		$.ajax({
			url : "/order/detail",
			type : "GET",
			data : {
				orderId :orderId
			},
			success : function(data) {
				if (data.code == 0) {
					render(data.data);
				} else {
					layer.msg(data.msg);
				}
			},
			error : function() {
				layer.msg("请求有误!");
			}
		});
	}

render方法和getQueryString方法:

//渲染页面--------5-17

function render(detail){
		//alert(detail.status);
		var goods=detail.goodsVo;
		var order=detail.order;
		$("#goodsName").text(goods.goodsName);
		$("#goodsImg").attr("src",goods.goodsImg); 
		$("#goodsPrice").text(order.goodsPrice);	
		$("#createDate").text(order.createDate);
		//判断订单的状态orderStatus
		var status="";
		if(order.orderStatus==0){
			$("#orderStatus").text("未支付");
		}else if(order.orderStatus==1){
			$("#orderStatus").text("待发货");
		}else if(order.orderStatus==2){
			$("#orderStatus").text("已发货");
		}else if(order.orderStatus==3){
			$("#orderStatus").text("待收货");
		}
		
	}
//获取请求路径里面的参数
function getQueryString(name){
	var reg=new RegExp("(^|&)"+name+"=([^&]*)(&|$)");
	var r=window.location.search.substr(1).match(reg);
	if(r!=null){
		return unescape(r[2]);
	}
	return null;
}

OrderDetailVo封装来专门给页面传值(json信息):

	public class OrderDetailVo {
	private GoodsVo goodsVo;
	private OrderInfo order;
	public GoodsVo getGoodsVo() {
		return goodsVo;
	}
	public void setGoodsVo(GoodsVo goodsVo) {
		this.goodsVo = goodsVo;
	}
	public OrderInfo getOrder() {
		return order;
	}
	public void setOrder(OrderInfo order) {
		this.order = order;
	}
	
}

后台OrderController里面接收订单详情请求的接口代码:

@RequestMapping("/order")
	@Controller
	public class OrderController {
	@Autowired
	GoodsService goodsService;
	@Autowired
	OrderService orderService;
	
	@RequestMapping("/detail") 
	@ResponseBody
	public Result<OrderDetailVo> info(Model model,MiaoshaUser user,
			@RequestParam("orderId") long orderId) {
		if(user==null) {
			return Result.error(CodeMsg.SESSION_ERROR);
		}
		OrderInfo order=orderService.getOrderByOrderId(orderId);
		if(order==null) {
			return Result.error(CodeMsg.ORDER_NOT_EXIST);
		}
		//订单存在的情况
		long goodsId=order.getGoodsId();
		GoodsVo gVo=goodsService.getGoodsVoByGoodsId(goodsId);
		OrderDetailVo oVo=new OrderDetailVo();
		oVo.setGoodsVo(gVo);
		oVo.setOrder(order);
		return Result.success(oVo);//返回页面login
	}	
}

GoodsService 代码:

	@Service
	public class GoodsService {
	public static final String COOKIE1_NAME_TOKEN="token";	
	@Autowired
	GoodsDao goodsDao;
	@Autowired
	RedisService redisService;	
	public List<GoodsVo>  getGoodsVoList() {
		return goodsDao.getGoodsVoList();
	}	
	public GoodsVo getGoodsVoByGoodsId(long goodsId) {
		return goodsDao.getGoodsVoByGoodsId(goodsId);
	}	
	public void reduceStock(GoodsVo goodsvo) {
		MiaoshaGoods goods=new MiaoshaGoods();
		goods.setGoodsId(goodsvo.getId());
		goodsDao.reduceStock(goods);
	}	
}

解决超卖

超卖场景:
不同用户在读请求的时候,发现商品库存足够,然后同时发起请求,进行秒杀操作,减库存,导致库存减为负数。
最简单的方法,更新数据库减库存的时候,进行库存限制条件,在reduceStock(GoodsVo goodsvo)这个方法里,sql要多加一个stock_count > 0即:

//stock_count>0的时候才去更新,数据库本身会有锁,那么就不会在数据库中同时多个线程更新一条记录,使用数据库特性来保证超卖的问题
	@Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
	public void reduceStock(MiaoshaGoods goods);  

也可以对读操作加上显式锁(select … for update)这样一来用户1在进行读操作时用户2就需要排队等待了,但是如果商品很热门并发量很高那么效率就会大大的下降。

订单详情页面order_detail.htm完整代码:

<!DOCTYPE html>
<html>  
<head>
<meta charset="UTF-8"/><!--<meta charset="UTF-8" />  thymeleaf模板引擎默认是Template modes:HTML5解析的,所以解析比较严格。  -->
<title>订单详情</title>
	<!-- thymeleaf引入静态资源的方式,@加大括弧    "/" 代表static路径-->
	<!-- jquery -->
	<!-- <script type="text/javascript" src="/js/jequery.min.js}"></script> -->
	<script type="text/javascript" src="/jquery-validation/lib/jquery-1.11.1.js"></script>
	<!-- bootstrap -->
	<!-- <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous"/>
	 -->
	<link type="text/css" rel="stylesheet" href="/bootstrap/css/bootstrap.css"/>
	<script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>

</head>
<body>
	<div class="panel panel-default">
		<div class="panel-heading">秒杀订单详情</div>
		<table class="table" id="goodslist">
		<tr>
			<td>商品名称</td>
			<td colspan="3" id="goodsName"></td>
		</tr>
		<tr>
			<td>商品图片</td>
			<td colspan="2"><img id="goodsImg" width="80" height="60"></img></td>
		</tr>
		<tr>
			<td>订单原价</td>
			<td colspan="3" id="goodsPrice"></td>
		</tr>
		<tr>
			<td>下单时间</td>
			<td id="createDate" colspan="2"></td>
		</tr>
		<tr>
			<td>订单状态</td>
			<td id="orderStatus"><!-- 根据数值,做显示判断 -->
			</td>
			<td>						<!-- 如果上面是未支付,那么下面会生成一个立即支付的按钮 -->
				<button class="btn btn-primary btn-block" type="submit" id="payButton">立即支付</button>
			</td>
		</tr>
		<tr>
			<td>收货人</td>
			<td colspan="2">tom  15008484456</td>
		</tr>
		<tr>
			<td>收货地址</td>
			<td colspan="2">四川崇州市</td>
		</tr>
		</table>
	</div>
</body>
<script type="text/javascript">
	
	$(function(){
		getOrderDetail();
	});

	function getOrderDetail() {
		//取参数orderId
		var orderId=getQueryString("orderId");
		$.ajax({
			url : "/order/detail",
			type : "GET",
			data : {
				orderId :orderId

			},
			success : function(data) {
				if (data.code == 0) {
					render(data.data);
				} else {
					layer.msg(data.msg);
				}
			},
			error : function() {
				layer.msg("请求有误!");
			}
		//token如果cookie里面有,会自己带过去

		});
	}
	//渲染页面--------5-17
	function render(detail){
		//alert(detail.status);
		var goods=detail.goodsVo;
		var order=detail.order;
		$("#goodsName").text(goods.goodsName);
		$("#goodsImg").attr("src",goods.goodsImg); 
		$("#goodsPrice").text(order.goodsPrice);	
		$("#createDate").text(order.createDate);
		//判断订单的状态orderStatus
		var status="";
		if(order.orderStatus==0){
			$("#orderStatus").text("未支付");
		}else if(order.orderStatus==1){
			$("#orderStatus").text("待发货");
		}else if(order.orderStatus==2){
			$("#orderStatus").text("已发货");
		}else if(order.orderStatus==3){
			$("#orderStatus").text("待收货");
		}
		
	}
		
	//获取请求路径里面的参数
	function getQueryString(name){
		var reg=new RegExp("(^|&)"+name+"=([^&]*)(&|$)");
		var r=window.location.search.substr(1).match(reg);
		if(r!=null){
			return unescape(r[2]);
		}
		return null;
	}
</script>
</html>
原创文章 87 获赞 213 访问量 49万+

猜你喜欢

转载自blog.csdn.net/qq_41620020/article/details/105942464