一、数据库设计
1.商品表
CREATE TABLE `goods` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',`goods_name` varchar(16) CHARACTER SET utf8 DEFAULT NULL COMMENT '商品名称',`goods_title` varchar(64) CHARACTER SET utf8 DEFAULT NULL COMMENT '商品标题',`goods_img` varchar(64) CHARACTER SET utf8 DEFAULT NULL COMMENT '商品图片',`goods_detail` longtext CHARACTER SET utf8 COMMENT '商品详情介绍',`goods_price` decimal(10,2) DEFAULT NULL COMMENT '商品单价',`goods_stock` int(11) DEFAULT '0' COMMENT '商品库存,-1表示无限制',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
2.秒杀商品表
CREATE TABLE `miaosha_goods` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀的商品表',
`goods_id` bigint(20) DEFAULT NULL COMMENT '商品id',
`miaosha_price` decimal(10,2) DEFAULT NULL COMMENT '秒杀价',
`stock_count` int(11) DEFAULT NULL COMMENT '库存数量',
`start_date` datetime DEFAULT NULL COMMENT '秒杀开始时间',
`end_date` datetime DEFAULT NULL COMMENT '秒杀结束时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
3.订单表
CREATE TABLE `order_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`goods_id` bigint(20) DEFAULT NULL COMMENT '商品id',
`delivery_addr_id` bigint(20) DEFAULT NULL COMMENT '收货地址id',
`goods_name` varchar(16) DEFAULT NULL COMMENT '商品名称',
`goods_count` int(11) DEFAULT NULL COMMENT '商品数量',
`goods_price` decimal(10,2) DEFAULT NULL COMMENT '商品单价',
`order_channel` tinyint(4) DEFAULT '0' COMMENT '1pc,2Android,3ios',
`status` tinyint(4) DEFAULT '0' COMMENT '订单状态:0新建未支付,1已支付,2已发货,3已收货,4已退款,5已完成',
`create_date` datetime DEFAULT NULL COMMENT '订单创建时间',
`pay_date` datetime DEFAULT NULL COMMENT '支付时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;
4.秒杀订单表
CREATE TABLE `miaosha_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`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`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
二、商品列表页
1.商品 dao 层
@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")
public List<GoodsVo> lisrGoodsVo();
}
2.商品 Service 层
@Autowired
GoodsDao goodsDao;
public List<GoodsVo> listGoodsVo(){
return goodsDao.lisrGoodsVo();
}
3.商品 Controller 层
@RequestMapping("/to_list")
public String list(Model model , MiaoshaUser user){
model.addAttribute("user",user);
//查询商品列表
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList",goodsList);
return "goods_list";
}
4.前端 goods_list 页面
<h1 th:text="'hello:'+${user.nickname}" ></h1>
<div class="panel panel-default">
<div class="panel-heading">秒杀商品列表</div>
<table class="table" id="goodslist">
<tr><td>商品名称</td><td>商品图片</td><td>商品原价</td><td>秒杀价</td><td>库存数量</td><td>详情</td></tr>
<tr th:each="goods,goodsStat : ${goodsList}">
<td th:text="${goods.goodsName}"></td>
<td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td>
<td th:text="${goods.goodsPrice}"></td>
<td th:text="${goods.miaoshaPrice}"></td>
<td th:text="${goods.stockCount}"></td>
<td><a th:href="'/goods/to_detail/'+${goods.id}">详情</a></td>
</tr>
</table>
</div>
</body>
5.运行效果:
三、商品详情页
1.根据 goodsId 查询商品信息
dao层:
@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 where g.id = #{goodsId}") GoodsVo getGoodsVoByGoodsId(@Param("goodsId") long goodsId);判断秒杀状态 ```java long startAt = goods.getStartDate().getTime(); long endAt = goods.getEndDate().getTime(); long now = System.currentTimeMillis();
int miaoshaStatus = 0; int remainSeconds = 0;
if (now < startAt) {//秒杀还没开始,倒计时 miaoshaStatus = 0; remainSeconds = (int) (startAt - now) / 1000; } else if (now > endAt) {//秒杀已经结束 miaoshaStatus = 2; remainSeconds = -1; } else {//秒杀正在进行 miaoshaStatus = 1; remainSeconds = 0; }
- 返回给前端
```java
model.addAttribute("goods", goods);
model.addAttribute("miaoshaStatus", miaoshaStatus);
model.addAttribute("remainSeconds", remainSeconds);
2.前端 js 实现倒计时
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("秒杀已经结束");
}
}
3.运行效果
四、秒杀功能实现
1.秒杀逻辑
判断用户是否登陆
model.addAttribute("user",user); if (user == null){ return "login"; }查询库存是否满足秒杀
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId); int stock = goods.getStockCount(); if (stock <= 0){ model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMessage()); return "miaosha_fail"; }判断该用户是否已经秒杀过商品,避免重复秒杀
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(),goodsId); if (order != null){ model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMessage()); return "miaosha_fail"; }减库存,下订单
OrderInfo orderInfo = miaoshaService.miaosha(user,goods);2.秒杀业务细节实现
Service 层实现 reduceStock 方法和 createOrder 方法,使用事务
@Transactional public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) { //减库存 goodsService.reduceStock(goods); //创建订单 return orderService.createOrder(user,goods); }reduceStock 根据商品的 id 查询到秒杀商品,将库存减1
public void reduceStock(GoodsVo goods) { MiaoshaGoods g = new MiaoshaGoods(); g.setGoodsId(goods.getId()); goodsDao.reduceStock(g); } //dao层 @Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId}") void reduceStock(MiaoshaGoods g);createOrder 根据 user 和 goods 的信息,创建订单和秒杀订单 ```java @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(0); orderInfo.setUserId(user.getId()); long orderId = orderDao.insert(orderInfo); MiaoshaOrder miaoshaOrder = new MiaoshaOrder(); miaoshaOrder.setGoodsId(goods.getId()); miaoshaOrder.setOrderId(orderId); miaoshaOrder.setUserId(user.getId()); orderDao.insertMiaoshaOrder(miaoshaOrder); return orderInfo; }
//插入订单数据,并返回创建的订单id @Insert(“insert into order_info(user_id, goods_id, goods_name, goods_count, goods_price, order_channel, status, create_date)values(“
+ "#{userId}, #{goodsId}, #{goodsName}, #{goodsCount}, #{goodsPrice}, #{orderChannel},#{status},#{createDate} )")
@SelectKey(keyColumn=”id”, keyProperty=”id”, resultType=long.class, before=false, statement=”select last_insert_id()”) public long insert(OrderInfo orderInfo); //插入秒杀订单数据 @Insert(“insert into miaosha_order (user_id, goods_id, order_id)values(#{userId}, #{goodsId}, #{orderId})”) void insertMiaoshaOrder(MiaoshaOrder miaoshaOrder); ```
3.订单详情页展示秒杀结果


