第三章秒殺功能
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>