秒殺靜態化
改造商品詳情頁面的點擊秒殺的業務邏輯,我們調用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>