Springboot-06:Panic Buying Project 秒殺設計
阿新 • • 發佈:2021-02-12
技術標籤:Springboot
先去設定資料庫裡面的秒殺時間
假設當前時間是2019-05-28 19:30:12
1.已經開始
2.秒殺結束
3.秒殺倒計時
所以我們去秒殺第一個商品:
在之前的goods_detail.html裡面的秒殺按鈕點選之後提交/miaosha/do_miaosha,以POST型別提交,帶有資料是秒殺商品的goodsId
- 新建一個MiaoshaController,定義接收該秒殺請求的介面方法doMiaosha
我們秒殺成功之後,那麼會直接進入訂單的詳情頁,所以我們秒殺成功後直接返回訂單資訊,並且返回值訂單頁面。
MiaoshaController程式碼:
@RequestMapping("/miaosha") @Controller public class MiaoshaController{ @Autowired GoodsService goodsService; @Autowired RedisService redisService; @Autowired MiaoshaService miaoshaService; @Autowired OrderService orderService; @RequestMapping("/do_miaosha") 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.getMiaoshaOrderByUserIdAndGoodsId(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 } }
秒殺業務主要邏輯:
- 判斷登入
- 根據商品id從資料庫拿到商品
- 判斷庫存,庫存足夠,進行秒殺,不足則結束
- 判斷是否重複秒殺(我們限制一個使用者只能秒殺一件商品,怎麼判斷?即從資料庫根據商品和使用者id 查詢秒殺訂單表,如果已經存在訂單,說明重複秒殺 ,給出提示,退出)
- 以上都通過,那麼該使用者可以秒殺商品
注意:
執行秒殺邏輯是一個原子操作,是一個事務:
- 庫存減1
- 下訂單(寫入秒殺訂單)
所以使用@Transactional註解標註,其中一步沒有成功,則回滾
MiaoshaService 程式碼:
@Service public class MiaoshaService { @Autowired GoodsService goodsService; @Autowired OrderService orderService; /** * 秒殺,原子操作:1.庫存減1,2.下訂單,3.寫入秒殺訂單--->是一個事務 * 返回生成的訂單 * @param user * @param goodsvo * @return */ @Transactional public OrderInfo miaosha(MiaoshaUser user, GoodsVo goodsvo) { //1.減少庫存,即更新庫存 goodsService.reduceStock(goodsvo);//考慮減少庫存失敗的時候,不進行寫入訂單 //2.下訂單,其中有兩個訂單: order_info miaosha_order OrderInfo orderinfo=orderService.createOrder(user,goodsvo); return orderinfo; } }
注意:執行秒殺事務的時候,先生成詳細訂單,然後生成秒殺訂單,為了進一步確保秒殺過程中一個使用者只能秒殺一件商品,我們給秒殺訂單表miaosha_order表新增一個唯一索引,如果再次插入相同的id與goodsId相同的欄位,那麼將不會被允許,從而在事務中插入失敗而回退。
秒殺訂單表miaosha_order:
為秒殺訂單表新增唯一索引:
OrderService 程式碼:
@Service public class OrderService { @Autowired OrderDao orderDao; /** * 程式碼1.0 * 根據使用者userId和goodsId判斷是否有者條訂單記錄,有則返回此紀錄 * @param id * @param goodsId * @return */ public MiaoshaOrder getMiaoshaOrderByUserIdAndGoodsId(Long userId, Long goodsId) { return orderDao.getMiaoshaOrderByUserIdAndGoodsId(userId,goodsId); } /** * 生成訂單,事務 * @param user * @param goodsvo * @return */ @Transactional public OrderInfo createOrder(MiaoshaUser user, GoodsVo goodsvo) { //1.生成order_info OrderInfo orderInfo=new OrderInfo(); orderInfo.setDeliveryAddrId(0L);//long型別 private Long deliveryAddrId; L orderInfo.setCreateDate(new Date()); orderInfo.setGoodsCount(1); orderInfo.setGoodsId(goodsvo.getId()); //秒殺價格 orderInfo.setGoodsPrice(goodsvo.getMiaoshaPrice()); orderInfo.setOrderChannel(1); //訂單狀態 ---0-新建未支付 1-已支付 2-已發貨 3-已收貨 orderInfo.setOrderStatus(0); //使用者id orderInfo.setUserId(user.getId()); //返回orderId //long orderId= orderDao.insert(orderInfo); //2.生成miaosha_order MiaoshaOrder miaoshaorder =new MiaoshaOrder(); miaoshaorder.setGoodsId(goodsvo.getId()); //將訂單id傳給秒殺訂單裡面的訂單orderid miaoshaorder.setOrderId(orderInfo.getId()); miaoshaorder.setUserId(user.getId()); orderDao.insertMiaoshaOrder(miaoshaorder); return orderInfo; } public OrderInfo getOrderByOrderId(long orderId) { return orderDao.getOrderByOrderId(orderId); } }
OrderDao程式碼:
SelectKey在Mybatis中是為了解決Insert資料時不支援主鍵自動生成的問題,他可以很隨意的設定生成主鍵的方式。
- statement是要執行的SQL語句,它的返回值通過resultType來指定
- before表示查詢語句statement執行的時機
- keyProperty表示查詢結果賦值給程式碼中的哪個物件,keyColumn表示將查詢結果賦值給資料庫表中哪一列
- keyProperty和keyColumn都不是必需的,有沒有都可以
- before=true,插入之前進行查詢,可以將查詢結果賦給keyProperty和keyColumn,賦給keyColumn相當於更改資料庫
- befaore=false,先插入,再查詢,這時只能將結果賦給keyProperty
- 賦值給keyProperty用來“讀”資料庫,賦值給keyColumn用來寫資料庫
- selectKey的兩大作用:1、生成主鍵;2、獲取剛剛插入資料的主鍵。
- 使用selectKey,並且使用MySQL的last_insert_id()函式時,before必為false,也就是說必須先插入然後執行last_insert_id()才能獲得剛剛插入資料的ID
@Mapper
public interface OrderDao {
@Select("select * from miaosha_order where user_id=#{userId} and goods_id=#{goodsId}")
public MiaoshaOrder getMiaoshaOrderByUserIdAndGoodsId(@Param("userId")Long userId, @Param("goodsId")Long goodsId);
@Insert("insert into order_info (user_id,goods_id,goods_name,goods_count,goods_price,order_channel,order_status,create_date) values "
+ "(#{userId},#{goodsId},#{goodsName},#{goodsCount},#{goodsPrice},#{orderChannel},#{orderStatus},#{createDate})")
@SelectKey(keyColumn="id",keyProperty="id",resultType=long.class,before=false,statement="select last_insert_id()")
public long insert(OrderInfo orderInfo);//使用註解獲取返回值,返回上一次插入的id
@Select("select * from order_info where user_id=#{userId} and goods_id=#{goodsId}")
public OrderInfo selectorderInfo(@Param("userId")Long userId, @Param("goodsId")Long goodsId);//使用註解獲取返回值,返回上一次插入的id
@Insert("insert into miaosha_order (user_id,goods_id,order_id) values (#{userId},#{goodsId},#{orderId})")
public void insertMiaoshaOrder(MiaoshaOrder miaoshaorder);
@Select("select * from order_info where id=#{orderId}")
public OrderInfo getOrderByOrderId(@Param("orderId")long orderId);
}