改造商品详情页面的点击秒杀的业务逻辑,我们调用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完整代码:
<html>
<head>
<meta charset="UTF-8"/>
<title>订单详情title>
<script type="text/javascript" src="/jquery-validation/lib/jquery-1.11.1.js">script>
<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 15008484456td>
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>