假設當前時間是2020-05-07 16:21:34
- 已經開始
- 秒殺結束
- 秒殺倒計時
所以我們去秒殺第一個商品:
在之前的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代碼:
@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);
}
