1. 程式人生 > 其它 >Springboot-06:Panic Buying Project 秒殺設計

Springboot-06:Panic Buying Project 秒殺設計

技術標籤:Springboot

先去設定資料庫裡面的秒殺時間
假設當前時間是2019-05-28 19:30:12
1.已經開始
2.秒殺結束
3.秒殺倒計時
在這裡插入圖片描述
所以我們去秒殺第一個商品:
在這裡插入圖片描述
在之前的goods_detail.html裡面的秒殺按鈕點選之後提交/miaosha/do_miaosha,以POST型別提交,帶有資料是秒殺商品的goodsId

  1. 新建一個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);    
	
}