做秒杀,先把功能做出来再进行优化,因此本节主要实现下单的一个基本流程。
1. 表设计
商品表:
1 2 3 4 5 6 7 8 9 10 CREATE TABLE `goods` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT COMMENT '商品ID' , `goods_name` VARCHAR (16 ) DEFAULT NULL COMMENT '商品名称' , `goods_title` VARCHAR (64 ) DEFAULT NULL COMMENT '商品标题' , `goods_img` VARCHAR (64 ) DEFAULT NULL COMMENT '商品图片' , `goods_detail` LONGTEXT COMMENT '商品的详情介绍' , `goods_price` DECIMAL (10 ,2 ) DEFAULT '0.00' COMMENT '商品单价' , `goods_stock` INT (11 ) DEFAULT '0' COMMENT '商品库存,-1表示没有限制' , PRIMARY KEY (`id` ) );
秒杀商品表:
1 2 3 4 5 6 7 8 9 CREATE TABLE `miaosha_goods` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT COMMENT '秒杀商品ID' , `goods_id` BIGINT (16 ) DEFAULT NULL COMMENT '商品id' , `miaosha_price` DECIMAL (10 ,2 ) DEFAULT '0.00' COMMENT '秒杀价' , `stock_count` INT (11 ) DEFAULT '0' COMMENT '库存数量' , `start_date` datetime DEFAULT NULL COMMENT '秒杀开始时间' , `end_date` datetime DEFAULT NULL COMMENT '秒杀结束时间' , PRIMARY KEY (`id` ) );
订单信息表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 CREATE TABLE `order_info` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT COMMENT 'order ID' , `user_id` BIGINT (20 ) DEFAULT NULL COMMENT '用户id' , `goods_id` BIGINT (20 ) DEFAULT NULL COMMENT '商品id' , `delivery_addr_id` BIGINT (20 ) DEFAULT NULL COMMENT '收货地址' , `goods_name` VARCHAR (16 ) DEFAULT NULL COMMENT '商品名称' , `goods_count` INT (11 ) DEFAULT '0' COMMENT '商品数量' , `goods_price` DECIMAL (10 ,2 ) DEFAULT '0.00' COMMENT '商品单价' , `order_channel` TINYINT(4 ) DEFAULT '0' COMMENT '1pc,2android,3ios' , `status` TINYINT(4 ) DEFAULT '0' COMMENT '0新建未支付,2已支付,3已发货4,已收货,5已完成' , `create_date` datetime DEFAULT NULL COMMENT '订单创建时间' , `pay_date` datetime DEFAULT NULL COMMENT '支付时间' , PRIMARY KEY (`id` ) );
秒杀订单表:
1 2 3 4 5 6 7 CREATE TABLE `miaosha_order` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT COMMENT '秒杀 order ID' , `user_id` BIGINT (20 ) DEFAULT NULL COMMENT '用户id' , `order_id` BIGINT (20 ) DEFAULT NULL COMMENT '订单id' , `goods_id` BIGINT (20 ) DEFAULT NULL COMMENT '商品id' , PRIMARY KEY (`id` ) );
2. 商品列表页展示
1 2 3 4 5 6 @Mapper public interface GoodsDao { @Select ("select g.*,mg.stock_count,mg.start_date,mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id" ) List<GoodsVo> getGoodsVoList () ; }
注意要创建一个vo对象来承载goods和miaosha_goods两个对象。
另外注意,之前的yml中对于mybatis的配置,忘记了配置驼峰写法:
1 2 configuration: map-underscore-to-camel-case : true
jsp就不贴在这了。
3. 商品详情页面
接收前端传来的goods_id
,因为要处理显示秒杀活动还剩多少秒,所以进行了相应的判断,以及秒杀的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @RequestMapping ("/to_detail/{goodsId}" )public String toList (@PathVariable("goodsId" ) long goodsId,Model model, MiaoshaUser user) { if (user == null ) return "login" ; model.addAttribute("user" ,user); GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); model.addAttribute("goods" ,goodsVo); long startAt = goodsVo.getStartDate().getTime(); long endAt = goodsVo.getEndDate().getTime(); long now = System.currentTimeMillis(); int miaoshaStatus = 0 ; int remainSeconds = 0 ; if (now < startAt){ miaoshaStatus = Constants.MiaoshaStatus.BEFORE_START; remainSeconds = (int )(startAt-now)/1000 ; }else if (now > endAt){ miaoshaStatus = Constants.MiaoshaStatus.AFTER_MIAOSHA; remainSeconds = -1 ; }else { miaoshaStatus = Constants.MiaoshaStatus.ON_MIAOSHA; remainSeconds = 0 ; } model.addAttribute("miaoshaStatus" ,miaoshaStatus); model.addAttribute("remainSeconds" ,remainSeconds); return "goods_detail" ; }
对于前端,只需要拿到这个remainSeconds
就可以了,可以对应显示秒杀还剩多久、秒杀是否结束等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function countDown () { var remainSeconds = $("#remainSeconds" ).val(); var timeout; if (remainSeconds > 0 ){ $("#buyButton" ).attr("disabled" , true ); timeout = setTimeout(function(){ $("#countDown" ).text(remainSeconds - 1 ); $("#remainSeconds" ).val(remainSeconds - 1 ); countDown(); },1000 ); }else if (remainSeconds == 0 ){ $("#buyButton" ).attr("disabled" , false ); if (timeout){ clearTimeout(timeout); } $("#miaoshaTip" ).html("秒杀进行中" ); }else { $("#buyButton" ).attr("disabled" , true ); $("#miaoshaTip" ).html("秒杀已经结束" ); } }
4. 秒杀功能实现
判断库存
|
根据userId和goodsId判断是否已经抢过了
|
减库存,下订单,并且写入秒杀订单(同一事务中完成)
判断库存:
1 2 3 4 5 6 GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId); if (goodsVo.getStockCount() <= 0 ){ model.addAttribute("errmsg" , CodeMsg.MIAO_SHA_OVER.getMsg()); return "miaosha_fail" ; }
判断是否已经抢过了:
1 2 3 4 5 6 MiaoshaOrder miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(),goodsId); if (miaoshaOrder != null ){ model.addAttribute("errmsg" , CodeMsg.REPEATE_MIAOSHA.getMsg()); return "miaosha_fail" ; }
减库存,下订单,并且写入秒杀订单(同一事务中完成):
1 2 OrderInfo orderInfo = miaoshaService.miaosha(user,goodsVo);
在MiaoshaService中写:
1 2 3 4 5 6 7 8 9 10 @Transactional public OrderInfo miaosha (MiaoshaUser user, GoodsVo goods) { boolean success =goodsService.reduceStock(goods); if (success){ return orderService.createOrder(user,goods); }else { return null ; } }
对于减库存:
这里只需要减miaosha_goods
表里的库存即可。因为秒杀的数据是先从goods
表里得到的,所以goods
表里的库存此段已经减掉了。
1 2 @Update ("update miaosha_goods set stock_count = stock_count-1 where goods_id=#{goodsId}" )int reduceStock (MiaoshaGoods g) ;
1 2 3 4 5 6 public boolean reduceStock (GoodsVo goods) { MiaoshaGoods g = new MiaoshaGoods(); g.setGoodsId(goods.getId()); int ret = goodsDao.reduceStock(g); return ret > 0 ; }
对于创建订单:先是普通的order_info
表插入,还有一个是miaosha_order
表插入,那么就要在一个事务中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Transactional public OrderInfo createOrder (MiaoshaUser user, GoodsVo goods) { OrderInfo orderInfo = new OrderInfo(); orderInfo.setCreateDate(new Date()); orderInfo.setDeliveryAddrId(0L ); orderInfo.setGoodsCount(1 ); orderInfo.setGoodsId(goods.getId()); orderInfo.setGoodsName(goods.getGoodsName()); orderInfo.setGoodsPrice(goods.getMiaoshaPrice()); orderInfo.setOrderChannel(1 ); orderInfo.setStatus(Constants.OrderStatus.NOT_PAID.getStatus()); orderInfo.setUserId(user.getId()); orderDao.insert(orderInfo); MiaoshaOrder miaoshaOrder = new MiaoshaOrder(); miaoshaOrder.setGoodsId(goods.getId()); miaoshaOrder.setOrderId(orderInfo.getId()); miaoshaOrder.setUserId(user.getId()); orderDao.insertMiaoshaOrder(miaoshaOrder); return orderInfo; }
对于,orderDao.insert(orderInfo)
的具体实现:
1 2 3 4 5 @Insert ("insert into order_info(user_id,goods_id,goods_name,goods_price,goods_count,order_channel,status,create_date) " + "values(#{userId},#{goodsId},#{goodsName},#{goodsPrice},#{goodsCount},#{orderChannel},#{status},#{" + "createDate})" ) @SelectKey (keyColumn = "id" ,keyProperty = "id" ,resultType = long .class,before = false ,statement = "select last_insert_id()" )long insert (OrderInfo orderInfo) ;
对于orderDao.insertMiaoshaOrder(miaoshaOrder)
的具体实现:
1 2 @Insert ("insert into miaosha_order(user_id,goods_id,order_id)values(#{userId},#{goodsId},#{orderId})" )int insertMiaoshaOrder (MiaoshaOrder miaoshaOrder) ;
下单成功的话,就跳到订单详情页面:
否则跳到错误提示页面: