(8)SprintBoot 2.X 秒杀功能的实现(秒杀业务逻辑处理)
1.秒杀业务逻辑
1.1 前端点击秒杀按钮进入秒杀业务逻辑
- 前端goods_detail.html里面的秒杀按钮点击之后提交/miaosha/do_miaosha,以POST类型提交,带有数据是秒杀商品的goodsId
1.2 秒杀业务逻辑
- 判断登录,如果用户为空,则返回至登录页面
- 根据商品id从数据库拿到商品库存,判断库存,库存足够,进行秒杀,不足则结束
- 判断同一用户是否重复秒杀
- 以上都通过,那么该用户可以秒杀商品
- 秒杀业务逻辑中的减库存和下订单是一个原子操作,是一个事务,所以需要@Transactional注解
注意:执行秒杀事务的时候,先生成详细订单,然后生成秒杀订单,为了进一步确保秒杀过程中一个用户只能秒杀一件商品,我们给秒杀订单表miaosha_order表添加一个唯一索引,如果再次插入相同的userid与goodsId相同的字段,那么将不会被允许,从而在事务中插入失败而回退
2. 代码实现
2.1 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.下订单是一个事务
OrderInfo orderinfo=miaoshaService.miaosha(user,goodsvo);
model.addAttribute("orderinfo", orderinfo);
model.addAttribute("goods", goodsvo);
return "order_detail";//跳转订单详情页
}
}
2.2 MiaoshaService的实现
- 只有减库存成功才能进入下订单环节,所以需要对reduceStock()进行判断
@Service
public class MiaoshaService {
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
//保证这三个操作,减库存 下订单 写入秒杀订单是一个事物
@Transactional
public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
boolean success = goodsService.reduceStock(goods);
if (success){
//下订单 写入秒杀订单
return orderService.createOrder(user, goods);
}else {
return null;
}
}
}
2.3 减库存的实现
2.3.1 GoodsService
@Service
public class GoodsService {
@Autowired
GoodsDao goodsDao;
public List<GoodsVo> listGoodsVo() {
return goodsDao.listGoodsVo();
}
public GoodsVo getGoodsVoByGoodsId(long goodsId) {
return goodsDao.getGoodsVoByGoodsId(goodsId);
}
public boolean reduceStock(GoodsVo goods) {
MiaoshaGoods g = new MiaoshaGoods();
g.setGoodsId(goods.getId());
return goodsDao.reduceStock(g) > 0 ;
}
}
2.3.2 GoodsDao
@Mapper
public interface GoodsDao {
@Select("select g.*, mg.miaosha_price, mg.stock_count, mg.start_date, mg.end_date from miaosha_goods mg left join goods g on mg.goods_id = g.id")
public List<GoodsVo> listGoodsVo();
@Select("select g.*, mg.miaosha_price, mg.stock_count, mg.start_date, mg.end_date from miaosha_goods mg left join goods g on mg.goods_id = g.id where g.id = #{goodsId}")
public GoodsVo getGoodsVoByGoodsId(@Param("goodsId") long goodsId);
@Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0 ")
public int reduceStock(MiaoshaGoods g);
}
2.4 下订单的实现
2.4.1 OrderService
@Service
public class OrderService {
@Autowired
OrderDao orderDao;
/**
* 根据用户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);
}
}
2.4.2 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);
}