第三章秒杀功能
1.数据库设计
DROP TABLE IF EXISTS `goods`;
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) NULL DEFAULT 0.00 COMMENT '商品单价',
`goods_stock` int(11) NULL DEFAULT 0 COMMENT '商品库存,-1表示没有限制',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4
CREATE TABLE `miaosha_goods` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀的商品表',
`goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品Id',
`miaosha_price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '秒杀价',
`stock_count` int(11) NULL DEFAULT NULL COMMENT '库存数量',
`start_date` datetime(0) NULL DEFAULT NULL COMMENT '秒杀开始时间',
`end_date` datetime(0) NULL DEFAULT NULL COMMENT '秒杀结束时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4
CREATE TABLE `miaosha_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`order_id` bigint(20) NULL DEFAULT NULL COMMENT '订单ID',
`goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4
CREATE TABLE `order_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
`delivery_addr_id` bigint(20) NULL DEFAULT NULL COMMENT '收获地址ID',
`goods_name` varchar(16) DEFAULT NULL COMMENT '冗余过来的商品名称',
`goods_count` int(11) NULL DEFAULT 0 COMMENT '商品数量',
`goods_price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '商品单价',
`order_channel` tinyint(4) NULL DEFAULT 0 COMMENT '1pc , 2android , 3ios',
`status` tinyint(4) NULL DEFAULT 0 COMMENT '订单状态, 0新建未支付,1已支付, 2已发货, 3已收货, 4已退款, 5已完成',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '订单的创建时间',
`ipay_date` datetime(0) NULL DEFAULT NULL COMMENT '支付时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4
2.商品列表
我们希望得到的商品列表,不仅仅只是商品信息,还希望有秒杀的信息,所以要使用多表连接查询,并且使用一个VO来接受查询出的信息
@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> listGoodsVo();
public class GoodsVo extends Goods{
private Double miaoshaPrice;
private Integer stockCount;
private Date startDate;
private Date endDate;
//get/set方法。。。。。
}
@RequestMapping(value="/to_list")
public String list(Model model, MiaoshaUser user,
HttpServletRequest request,
HttpServletResponse response){
model.addAttribute("user",user);
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList",goodsList);
return "goods_list";
}
3.商品详情
对于商品详情信息,获得商品信息的同时,还要关注秒杀的状态,在这里0表示还没开始,1表示秒杀进行中,2表示秒杀已结束,当秒杀还没有开始的时候,要进行秒杀倒计时,也就是代码中的remainSeconds
@RequestMapping(value="/to_detail2/{goodsId}",produces="text/html")
public String detai2(HttpServletRequest request, HttpServletResponse response,
Model model, MiaoshaUser user,
@PathVariable("goodsId")long goodsId){
model.addAttribute("user",user);
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods",goods);
//
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;
}
//秒杀状态
model.addAttribute("miaoshaStatus", miaoshaStatus);
//秒杀倒计时
model.addAttribute("remainSeconds", remainSeconds);
return "goods_detail";
}
4.秒杀功能
秒杀功能实现之前,我们要明确,该商品是否还有库存,该用户是否已经秒杀了该商品,当出现没有库存或已经秒杀过的情况将不能继续进入的秒杀的逻辑代码。
同时我们要明确,秒杀的逻辑其实是减小库存同时下订单并写入数据库,只要中间一步产生了失误,就要进行事务的回滚。
@Controller
@RequestMapping("/miaosha")
public class MiaoshaController {
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
MiaoshaService miaoshaService;
@RequestMapping(value = "/do_miaosha")
public String hellowrold(Model model, MiaoshaUser user,
@RequestParam("goodsId")long goodsId) {
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.getMsg());
return "miaosha_fail";
}
//判断是否已经秒杀到了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
model.addAttribute("errmsg", CodeMsg.REPEATE_MIAOSHA.getMsg());
return "miaosha_fail";
}
//减库存 下订单 写入秒杀订单
OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
model.addAttribute("orderInfo", orderInfo);
model.addAttribute("goods", goods);
return "order_detail";
}
}
事务处理@Transactional
@Service
public class MiaoshaService {
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
//事务
@Transactional
public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
//减库存 下订单 写入秒杀订单
//减库存
System.out.println("减库存");
goodsService.reduceStock(goods);
//order_info maiosha_order
//下订单 写入秒杀订单
System.out.println("添加订单");
return orderService.createOrder(user, goods);
}
}
添加订单也是同时向order_info和miaosha_order添加记录
@Service
public class OrderService {
@Autowired
OrderDao orderDao;
public OrderInfo getOrderById(long orderId) {
return orderDao.getOrderById(orderId);
}
@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;
}
}
5.订单详情
订单详情的内容,不仅仅只是订单信息,还要有商品的信息,在这里使用一个VO来封装接口返回的订单详情信息
public class OrderDetailVo {
private GoodsVo goods;
private OrderInfo order;
public GoodsVo getGoods() {
return goods;
}
public void setGoods(GoodsVo goods) {
this.goods = goods;
}
public OrderInfo getOrder() {
return order;
}
public void setOrder(OrderInfo order) {
this.order = order;
}
}
@Controller
@RequestMapping("/order")
public class OrderController {
@Autowired
OrderService orderService;
@Autowired
GoodsService goodsService;
@RequestMapping("/detail")
@ResponseBody
public Result<OrderDetailVo> info(Model model, MiaoshaUser user,
@RequestParam("orderId") long orderId) {
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
OrderInfo order = orderService.getOrderById(orderId);
if(order == null) {
return Result.error(CodeMsg.ORDER_NOT_EXIST);
}
long goodsId = order.getGoodsId();
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
OrderDetailVo vo = new OrderDetailVo();
vo.setOrder(order);
vo.setGoods(goods);
return Result.success(vo);
}
}
附录:
页面代码
<!--商品列表页-->
<!--goods_list.html-->
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>商品列表</title>
<meta http-equiv="Content-Type " content="text/html;charset=UTF-8"/>
<!-- jquery -->
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
<!-- jquery-validator -->
<script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
<script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
<!-- layer -->
<script type="text/javascript" th:src="@{/layer/layer.js}"></script>
<!-- md5.js -->
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
<!-- common.js -->
<script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body>
<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_detail.htm?goodsId='+${goods.id}">详情</a></td>
</tr>
</table>
</div>
</body>
</html>
<!--商品详情页-->
<!--goods_detail.html-->
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>商品详情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
<!-- jquery-validator -->
<script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
<script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
<!-- layer -->
<script type="text/javascript" th:src="@{/layer/layer.js}"></script>
<!-- md5.js -->
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
<!-- common.js -->
<script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading">秒杀商品详情</div>
<div class="panel-body">
<span th:if="${user eq null}"> 您还没有登录,请登陆后再操作<br/></span>
<span>没有收货地址的提示。。。</span>
</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td colspan="3" th:text="${goods.goodsName}"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="3"><img th:src="@{${goods.goodsImg}}" width="200" height="200" /></td>
</tr>
<tr>
<td>秒杀开始时间</td>
<td th:text="${#dates.format(goods.startDate, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td id="miaoshaTip">
<!-- 隐藏域-->
<input type="hidden" id="remainSeconds" th:value="${remainSeconds}" />
<span th:if="${miaoshaStatus eq 0}">秒杀倒计时:<span id="countDown" th:text="${remainSeconds}"></span>秒</span>
<span th:if="${miaoshaStatus eq 1}">秒杀进行中</span>
<span th:if="${miaoshaStatus eq 2}">秒杀已结束</span>
</td>
<td>
<form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">
<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒杀</button>
<input type="hidden" name="goodsId" th:value="${goods.id}" />
</form>
</td>
</tr>
<tr>
<td>商品原价</td>
<td colspan="3" th:text="${goods.goodsPrice}"></td>
</tr>
<tr>
<td>秒杀价</td>
<td colspan="3" th:text="${goods.miaoshaPrice}"></td>
</tr>
<tr>
<td>库存数量</td>
<td colspan="3" th:text="${goods.stockCount}"></td>
</tr>
</table>
</div>
</body>
<script>
$(function(){
// 页面初始化
countDown();
});
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("秒杀已经结束");
}
}
</script>
</html>
<!--秒杀失败页-->
<!--miaosha_fail.html-->
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>秒杀失败</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
秒杀失败:<p th:text="${errmsg}"></p>
</body>
</html>
<!--订单详情页-->
<!--order_detail.html-->
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>订单详情</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
<!-- jquery-validator -->
<script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
<script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
<!-- layer -->
<script type="text/javascript" th:src="@{/layer/layer.js}"></script>
<!-- md5.js -->
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
<!-- common.js -->
<script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading">秒杀订单详情</div>
<table class="table" id="goodslist">
<tr>
<td>商品名称</td>
<td th:text="${goods.goodsName}" colspan="3"></td>
</tr>
<tr>
<td>商品图片</td>
<td colspan="2"><img th:src="@{${goods.goodsImg}}" width="200" height="200" /></td>
</tr>
<tr>
<td>订单价格</td>
<td colspan="2" th:text="${orderInfo.goodsPrice}"></td>
</tr>
<tr>
<td>下单时间</td>
<td th:text="${#dates.format(orderInfo.createDate, 'yyyy-MM-dd HH:mm:ss')}" colspan="2"></td>
</tr>
<tr>
<td>订单状态</td>
<td >
<span th:if="${orderInfo.status eq 0}">未支付</span>
<span th:if="${orderInfo.status eq 1}">待发货</span>
<span th:if="${orderInfo.status eq 2}">已发货</span>
<span th:if="${orderInfo.status eq 3}">已收货</span>
<span th:if="${orderInfo.status eq 4}">已退款</span>
<span th:if="${orderInfo.status eq 5}">已完成</span>
</td>
<td>
<button class="btn btn-primary btn-block" type="submit" id="payButton">立即支付</button>
</td>
</tr>
<tr>
<td>收货人</td>
<td colspan="2">XXX 18812341234</td>
</tr>
<tr>
<td>收货地址</td>
<td colspan="2">北京市昌平区回龙观龙博一区</td>
</tr>
</table>
</div>
</body>
</html>