SpringBoot构建电商秒杀项目(下篇)

  四、商品模块开发

步骤1:商品模型构建之dao层

创建一个ItemModel

 1 public class ItemModel {
 2     private Integer id;
 3 
 4     //商品名称
 5     private String title;
 6 
 7     //商品价格
 8     private BigDecimal price;
 9 
10     //商品的库存
11     private Integer stock;
12 
13     //商品的描述
14     private String description;
15 
16     //商品的销量
17     private Integer sales;
18 
19     //商品描述图片的url
20     private String imgUrl;
21 
22     public Integer getId() {
23         return id;
24     }
25 
26     public void setId(Integer id) {
27         this.id = id;
28     }
29 
30     public String getTitle() {
31         return title;
32     }
33 
34     public void setTitle(String title) {
35         this.title = title;
36     }
37 
38     public BigDecimal getPrice() {
39         return price;
40     }
41 
42     public void setPrice(BigDecimal price) {
43         this.price = price;
44     }
45 
46     public Integer getStock() {
47         return stock;
48     }
49 
50     public void setStock(Integer stock) {
51         this.stock = stock;
52     }
53 
54     public String getDescription() {
55         return description;
56     }
57 
58     public void setDescription(String description) {
59         this.description = description;
60     }
61 
62     public Integer getSales() {
63         return sales;
64     }
65 
66     public void setSales(Integer sales) {
67         this.sales = sales;
68     }
69 
70     public String getImgUrl() {
71         return imgUrl;
72     }
73 
74     public void setImgUrl(String imgUrl) {
75         this.imgUrl = imgUrl;
76     }
77 }
ItemModel

创建数据库表格item与item_stock

修改pom配置,不允许自动覆盖

<overwrite>false</overwrite>

在mybatis-generator中添加如下代码用来生成商品相关的dao文件,注意最好把之前两张表的代码注释一下,避免反复生成:

1        <table tableName="item" domainObjectName="ItemDO" enableCountByExample="false"
2                enableUpdateByExample="false" enableDeleteByExample="false"
3                enableSelectByExample="false" selectByExampleQueryId="false"
4         ></table>
5         <table tableName="item_stock" domainObjectName="ItemStockDO" enableCountByExample="false"
6                enableUpdateByExample="false" enableDeleteByExample="false"
7                enableSelectByExample="false" selectByExampleQueryId="false"
8         ></table>

在两张表对应的mapper文件中,在insert与insertSelective操作下都添加自增(与之前的两张表一样)

执行generator文件,可以看到dao包与daoobject包中都生成了对应的文件。

步骤2:商品模型构建之service层&Controller层

写一个service/Itemservice接口:

1 public interface ItemService {
2     //创建商品
3     ItemModel createItem(ItemModel itemModel) throws BusinessException;
4     //商品列表浏览
5     List<ItemModel> listItem();
6     //商品详情浏览
7     ItemModel getItemById(Integer id);
8 }

ItemModel中加上校验相关的注释:

 1 private Integer id;
 2 
 3     //商品名称
 4     @NotBlank(message = "商品名称不能为空")
 5     private String title;
 6 
 7     //商品价格
 8     @NotNull(message = "商品价格不能为空")
 9     @Min(value = 0,message = "商品价格必须大于0")
10     private BigDecimal price;
11 
12     //商品的库存
13     @NotNull(message = "库存不能不填")
14     private Integer stock;
15 
16     //商品的描述
17     @NotBlank(message = "商品描述信息不能为空")
18     private String description;
19 
20     //商品的销量
21     private Integer sales;
22 
23     //商品描述图片的url
24     @NotBlank(message = "商品图片不能为空")
25     private String imgUrl;
ItemModel校验注释

ItemStockDoMapper.xml中添加一个select标签,写一个selectByItemId操作,与数据库对接

1   <select id="selectByItemId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
2     select
3     <include refid="Base_Column_List" />
4     from item_stock
5     where item_id = #{item_id,jdbcType=INTEGER}
6   </select>

同时修改ItemStockDoMapper接口文件,添加对应的方法

ItemStockDO selectByItemId(Integer itemId);

然后我们进行ItemServiceImpl的编写:

 1 @Service
 2 public class ItemServiceImpl implements ItemService {
 3     @Autowired
 4     private ValidatorImpl validator;
 5 
 6     @Autowired
 7     private ItemDOMapper itemDOMapper;
 8 
 9     @Autowired
10     private ItemStockDOMapper itemStockDOMapper;
11 
12     private ItemDO convertItemDOFromItemModel(ItemModel itemModel){
13         if (itemModel == null) {
14             return null;
15         }
16         ItemDO itemDO = new ItemDO();
17         BeanUtils.copyProperties(itemModel, itemDO);
18         itemDO.setPrice(itemModel.getPrice().doubleValue());
19         return itemDO;
20     }
21 
22     private ItemStockDO convertItemStockDOFromItemModel(ItemModel itemModel) {
23         if (itemModel == null) {
24             return null;
25         }
26         ItemStockDO itemStockDO = new ItemStockDO();
27         itemStockDO.setItemId(itemModel.getId());
28         itemStockDO.setStock(itemModel.getStock());
29 
30         return itemStockDO;
31     }
32 
33     @Override
34     @Transactional
35     public ItemModel createItem(ItemModel itemModel) throws BusinessException {
36         //校验入参
37         ValidationResult result = validator.validate(itemModel);
38         if (result.isHasErrors()) {
39             throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());
40         }
41         //转化itemmodel->dataobject
42         ItemDO itemDO = this.convertItemDOFromItemModel(itemModel);
43 
44         //写入数据库
45         itemDOMapper.insertSelective(itemDO);
46         itemModel.setId(itemDO.getId());
47 
48         ItemStockDO itemStockDO = this.convertItemStockDOFromItemModel(itemModel);
49         itemStockDOMapper.insertSelective(itemStockDO);
50 
51         //返回创建完成的对象
52         return this.getItemById(itemModel.getId());
53 
54     }
55 
56     @Override
57     public List<ItemModel> listItem() {
58         return null;
59     }
60 
61     @Override
62     public ItemModel getItemById(Integer id) {
63         ItemDO itemDO = itemDOMapper.selectByPrimaryKey(id);
64         if (itemDO == null) {
65             return null;
66         }
67         //操作获得库存数量
68         ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
69 
70         //将dataobject-> Model
71         ItemModel itemModel = convertModelFromDataObject(itemDO, itemStockDO);
72         return itemModel;
73     }
74 
75     private ItemModel convertModelFromDataObject(ItemDO itemDO, ItemStockDO itemStockDO) {
76         ItemModel itemModel = new ItemModel();
77         BeanUtils.copyProperties(itemDO, itemModel);
78         itemModel.setStock(itemStockDO.getStock());
79         return itemModel;
80     }
81 }

 为了使前端与service层分离,我们模仿之前的UserVO,写一个ItemVO文件,放在viewobject包下:

 1 public class ItemVO {
 2     private Integer id;
 3 
 4     //商品名称
 5     private String title;
 6 
 7     //商品价格
 8     private BigDecimal price;
 9 
10     //商品的库存
11     private Integer stock;
12 
13     //商品的描述
14     private String description;
15 
16     //商品的销量
17     private Integer sales;
18 
19     //商品描述图片的url
20     private String imgUrl;
21 }
ItemVO

最后编写与前端交互的ItemController类:

 1 @Controller("/item")
 2 @RequestMapping("/item")
 3 @CrossOrigin(origins = {"*"},allowCredentials = "true")
 4 public class ItemController extends BaseController{
 5     @Autowired
 6     private ItemService itemService;
 7 
 8     //创建商品的controller
 9     @RequestMapping(value = "/create", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
10     @ResponseBody
11     public CommonReturnType createItem(@RequestParam(name = "title") String title,
12                                        @RequestParam(name = "description") String description,
13                                        @RequestParam(name = "price") BigDecimal price,
14                                        @RequestParam(name = "stock") Integer stock,
15                                        @RequestParam(name = "imgUrl") String imgUrl) throws BusinessException {
16         //封装service请求用来创建商品
17         ItemModel itemModel = new ItemModel();
18         itemModel.setTitle(title);
19         itemModel.setDescription(description);
20         itemModel.setPrice(price);
21         itemModel.setStock(stock);
22         itemModel.setImgUrl(imgUrl);
23 
24         ItemModel itemModelForReturn = itemService.createItem(itemModel);
25         //将创建完的商品的信息返回给前端
26         ItemVO itemVO = convertVOFromModel(itemModelForReturn);
27         return CommonReturnType.create(itemVO);
28     }
29 
30     private ItemVO convertVOFromModel(ItemModel itemModel) {
31         if (itemModel == null) {
32             return null;
33         }
34         ItemVO itemVO = new ItemVO();
35         BeanUtils.copyProperties(itemModel, itemVO);
36         return itemVO;
37     }
38 }

 步骤3:制作前端页面

 创建一个createitem.html,编写前端代码如下:

  1 <html>
  2 <head>
  3     <meta charset="UTF-8">
  4     <script src = "static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
  5     <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
  6     <link href="static/assets/global/plugins/css/component.css" rel="stylesheet" type="text/css"/>
  7     <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css"/>
  8 </head>
  9 <body class="login">
 10     <div class="content">
 11         <h3 class="form-title">创建商品</h3>
 12         <div class="form-group">
 13             <label class="control-label">商品名</label>
 14             <div>
 15                 <input class="form-control" type="text"  name="title" id="title"/>
 16             </div>
 17         </div>
 18         <div class="form-group">
 19             <label class="control-label">商品描述</label>
 20             <div>
 21                 <input class="form-control" type="text"  name="description" id="description"/>
 22             </div>
 23         </div>
 24         <div class="form-group">
 25             <label class="control-label">价格</label>
 26             <div>
 27                 <input class="form-control" type="text"  name="price" id="price"/>
 28             </div>
 29         </div>
 30         <div class="form-group">
 31             <label class="control-label">图片</label>
 32             <div>
 33                 <input class="form-control" type="text"  name="imgUrl" id="imgUrl"/>
 34             </div>
 35         </div>
 36         <div class="form-group">
 37             <label class="control-label">库存</label>
 38             <div>
 39                 <input class="form-control" type="text"  name="stock" id="stock"/>
 40             </div>
 41         </div>
 42         <div class="form-actions">
 43             <button class="btn blue" id="create" type="submit">
 44                 提交创建
 45             </button>
 46         </div>
 47     </div>
 48 
 49 </body>
 50 
 51 <script>
 52     jQuery(document).ready(function () {
 53 
 54         //绑定otp的click事件用于向后端发送获取手机验证码的请求
 55         $("#create").on("click",function () {
 56 
 57             var title=$("#title").val();
 58             var description=$("#description").val();
 59             var imgUrl=$("#imgUrl").val();
 60             var price=$("#price").val();
 61             var stock=$("#stock").val();
 62            
 63             if (title==null || title=="") {
 64                 alert("标题不能为空");
 65                 return false;
 66             }
 67             if (description==null || description=="") {
 68                 alert("描述不能为空");
 69                 return false;
 70             }
 71             if (imgUrl==null || imgUrl=="") {
 72                 alert("图片不能为空");
 73                 return false;
 74             }
 75             if (price==null || price=="") {
 76                 alert("价格不能为空");
 77                 return false;
 78             }
 79             if (stock==null || stock=="") {
 80                 alert("库存不能为空");
 81                 return false;
 82             }
 83         
 84 
 85             //映射到后端@RequestMapping(value = "/register", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
 86             $.ajax({
 87                 type:"POST",
 88                 contentType:"application/x-www-form-urlencoded",
 89                 url:"http://localhost:8090/item/create",
 90                 data:{
 91                     "title":title,
 92                     "description":description,
 93                     "imgUrl":imgUrl,
 94                     "price":price,
 95                     "stock":stock,
 96                     "name":name
 97                 },
 98                 //允许跨域请求
 99                 xhrFields:{withCredentials:true},
100                 success:function (data) {
101                     if (data.status=="success") {
102                         alert("创建成功");
103                     }else {
104                         alert("创建失败,原因为" + data.data.errMsg);
105                     }
106                 },
107                 error:function (data) {
108                     alert("创建失败,原因为"+data.responseText);
109                 }
110             });
111             return false;
112         });
113     });
114 </script>
115 
116 </html>
createitem.html

运行project进行测试:

踩雷:忘记添加service注解,itemModel校验注解写错,遇到问题可以把error百度一下!

最后在前端页面输入各项信息后,页面提示创建成功,并且数据库中增加了刚刚录入的商品数据:

步骤4:测试获取商品详情

在itemController中添加:

1     //商品详情页浏览
2     @RequestMapping(value = "/get", method = {RequestMethod.GET})
3     @ResponseBody
4     public CommonReturnType getItem(@RequestParam(name = "id")Integer id){
5         ItemModel itemModel = itemService.getItemById(id);
6         ItemVO itemVO = convertVOFromModel(itemModel);
7         return CommonReturnType.create(itemVO);
8     }

进入http://localhost:8090/item/get?id=1,可以成功查看到id为1的商品的信息。

步骤5:获取商品列表

修改ItemDoMapper,使商品按照销量倒序排序

1   <select id="listItem" parameterType="java.lang.Integer" resultMap="BaseResultMap">
2     select
3     <include refid="Base_Column_List" />
4     from item order by sales DESC;
5   </select>

在ItemDoMapper接口中添加该方法:

List<ItemDO> listItem();

在ItemServiceImpl中补全该方法额具体实现:

 1     @Override
 2     public List<ItemModel> listItem() {
 3         List<ItemDO> itemDOList = itemDOMapper.listItem();
 4         //使用Java8的stream API
 5         List<ItemModel> itemModelList = itemDOList.stream().map(itemDO -> {
 6             ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
 7             ItemModel itemModel = this.convertModelFromDataObject(itemDO, itemStockDO);
 8             return itemModel;
 9         }).collect(Collectors.toList());
10         return itemModelList;
11     }

 在ItemController中编写对应的与前端交互的代码:

 1     //商品列表页面浏览
 2     @RequestMapping(value = "/list", method = {RequestMethod.GET})
 3     @ResponseBody
 4     public CommonReturnType listItem() {
 5         List<ItemModel> itemModelList = itemService.listItem();
 6         List<ItemVO> itemVOList = itemModelList.stream().map(itemModel -> {
 7             ItemVO itemVO = this.convertVOFromModel(itemModel);
 8             return itemVO;
 9         }).collect(Collectors.toList());
10         return CommonReturnType.create(itemVOList);
11     }

运行project,进入http://localhost:8090/item/list,(提前添加多个商品,并在数据库中手动添加销量数据),可以在页面中看到按照销量降序排列的所有商品的列表。

步骤6:制作商品列表的前端页面

创建一个itemlist.html:

 1 <html>
 2     <head>
 3         <meta charset="utf-8">
 4         <!-- <meta http-equiv="X-UA-Compatible" content="IE=edge">
 5         <title></title>
 6         <meta name="viewport" content="width=device-width, initial-scale=1"> -->
 7         <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
 8         <link href="static/assets/global/css/components.css" rel="stylesheet" type="text/css"/>
 9         <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css"/>
10         <script src="static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
11     </head>
12 
13 <body>
14     <div class="content">
15         <h3 class="form-title">商品列表浏览</h3>
16         <div class="table-responsive">
17             <table class="table">
18                 <thead>
19                     <tr>
20                         <th>商品名</th>
21                         <th>商品图片</th>
22                         <th>商品描述</th>
23                         <th>商品价格</th>
24                         <th>商品库存</th>
25                         <th>商品销量</th>
26                     </tr>
27                 </thead>
28                 
29                 <tbody id="container">
30                     
31                 </tbody>
32             </table>
33         </div>
34     </div>
35 </body>
36 
37 <script>
38     // 定义全局商品数组信息
39     var g_itemList = [];
40     $(document).ready(function() {
41         $.ajax({
42             type: "GET",
43             url: "http://localhost:8090/item/list",
44             xhrFields:{
45                 withCredentials:true,
46             },
47             success: function(data) {
48                 if (data.status == "success") {
49                     g_itemList = data.data;
50                     reloadDom();
51                 } else {
52                     alert("获取商品信息失败,原因为" + data.data.errMsg);
53                 }
54             },
55             error: function(data) {
56                 alert("获取商品信息失败,原因为" + data.responseText);
57             }
58         });
59     });
60 
61     function reloadDom() {
62         for (var i = 0; i < g_itemList.length; i++) {
63             var itemVO =g_itemList[i];
64             var dom = 
65             "<tr data-id='"+itemVO.id+"' id='itemDetail"+itemVO.id+"'>\
66             <td>"+itemVO.title+"</td>\
67             <td><img style='width:100px;heigth:auto;' src='"+itemVO.imgUrl+"'/></td>\
68             <td>"+itemVO.description+"</td>\
69             <td>"+itemVO.price+"</td>\
70             <td>"+itemVO.stock+"</td>\
71             <td>"+itemVO.sales+"</td>\
72             </tr>";
73 
74             $("#container").append($(dom));//使用jquery的方法往id为container的元素中填充dom
75 
76             //点击一行任意的位置 跳转到商品详情页
77             $("#itemDetail"+itemVO.id).on("click", function(e) {
78                 window.location.href="getitem.html?id="+$(this).data("id");
79             });
80         }
81     }
82 </script>
83 
84 </html>
itemlist.html

运行project,打开该页面,我们可以看到:

 

 踩雷:price相关的所有数据类型都要改成Double,尤其是ItemController

 步骤7:

itemlist中点击每个商品的任意一部分,都将跳转到他们对应的商品详情页面getitem.html。

 1 <html>
 2 <head>
 3     <meta charset="UTF-8">
 4     <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" ref="stylesheet">
 5     <link href="static/assets/global/css/components.css" rel="stylesheet" type="text/css">
 6     <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css">
 7     <script src="static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
 8 </head>
 9 <body class="login">
10  <div class="content">
11      <h3 class="form-title">商品详情</h3>
12      <div class="form-group">
13         <div>
14              <label class="control-label" id="title"/>
15          </div>
16      </div>
17      <div class="form-group">
18          <label class="control-label">商品描述</label>
19          <div>
20              <label class="control-label" id="description"/>
21          </div>
22      </div>
23 
24      <div  class="form-group">
25          <label class="control-label">价格</label>
26          <div>
27              <label class="control-label" id="price"/>
28          </div>
29      </div>
30 
31      <div  class="form-group">
32          <div>
33              <img style="width:200px;height:auto" id="imgUrl"/>
34          </div>
35      </div>
36 
37      <div class="form-group">
38          <label class="control-label">库存</label>
39          <div>
40              <label class="control-label" id="stock"/>
41          </div>
42      </div>
43      <div class="form-group">
44          <label class="control-label">销量</label>
45          <div>
46              <label class="control-label" id="sales"/>
47          </div>
48      </div>
49      
50  </div>
51 </body>
52 <script>
53 
54     function getParam(paramName) {
55         paramValue = "", isFound = !1;
56         if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
57             arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
58             while (i < arrSource.length && !isFound) arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
59         }
60         return paramValue == "" && (paramValue = null), paramValue
61     }
62 
63    var g_itemVO = {};
64 
65 
66     jQuery(document).ready(function (){
67             $.ajax({
68                 type: "GET",
69                 url: "http://localhost:8090/item/get",
70                 data: {
71                     "id":getParam("id"),
72                 },
73                 xhrFields:{withCredentials:true},   //前端解决跨域共享session
74                 success: function (data) {
75                     if (data.status == "success") {
76                         g_itemVO = data.data;
77                         reloadDom();
78                     } else {
79                         alert("获取信息失败,原因为:" + data.data.errMsg);
80                     }
81                 },
82                 error: function (data) {
83                     alert("获取信息失败,原因为:" + data.responseText);
84                 }
85             });
86         });
87 
88     function reloadDom() {
89         $("#title").text(g_itemVO.title);
90         $("#description").text(g_itemVO.description);
91         $("#stock").text(g_itemVO.stock);
92         $("#price").text(g_itemVO.price);
93         $("#imgUrl").attr("src",g_itemVO.imgUrl);
94         $("#sales").text(g_itemVO.sales);
95     }
96         
97        
98 </script>
99 </html>
itemlist.html

 五、交易模型管理

步骤1:交易dao层模型创建

首先我们在model包中添加一个OrderModel,添加itemprice,userId等字段

 1 //用户下单的交易模型
 2 public class OrderModel {
 3     private String id;
 4     //购买商品的单价
 5     private BigDecimal itemPrice;
 6     private Integer userId;
 7     private Integer itemId;
 8     private Integer amount;
 9     //购买金额
10     private BigDecimal orderPrice;
11     
12     public String getId() {
13         return id;
14     }
15 
16     public void setId(String id) {
17         this.id = id;
18     }
19 
20     public BigDecimal getItemPrice() {
21         return itemPrice;
22     }
23 
24     public void setItemPrice(BigDecimal itemPrice) {
25         this.itemPrice = itemPrice;
26     }
27 
28     public Integer getUserId() {
29         return userId;
30     }
31 
32     public void setUserId(Integer userId) {
33         this.userId = userId;
34     }
35 
36     public Integer getItemId() {
37         return itemId;
38     }
39 
40     public void setItemId(Integer itemId) {
41         this.itemId = itemId;
42     }
43 
44     public Integer getAmount() {
45         return amount;
46     }
47 
48     public void setAmount(Integer amount) {
49         this.amount = amount;
50     }
51 
52     public BigDecimal getOrderPrice() {
53         return orderPrice;
54     }
55 
56     public void setOrderPrice(BigDecimal orderPrice) {
57         this.orderPrice = orderPrice;
58     }
59 }
OrderModel

创建数据库表格如下:

 在mybatis-generator中添加如下代码(把其他表格的生成代码先注释掉),根据数据库中的表格自动生成对应的OrderDOMapper接口和OrderDO的java文件。

1 <table tableName="order_info" domainObjectName="OrderDO" 
2     enableCountByExample="false"
3     enableUpdateByExample="false" enableDeleteByExample="false"
4     enableSelectByExample="false" selectByExampleQueryId="false">
5 </table>

 步骤2:交易模型之service层

首先在service包中写一个OrderService接口:

1 public interface OrderService {
2     OrderModel createOrder(Integer userId,Integer itemId,Integer amount);
3 }

itemservice接口中添加一个库存扣减的方法

boolean decreaseStock(Integer itemId,Integer amount) throws BusinessException;

itemStockDOMapper.xml中添加关于decreaseStock的sql语句配置:

1   <update id="decreaseStock">
2     update item_stock
3     set stock = stock - #{amount}
4     where item_id = #{itemId} and stock >= #{amount}
5   </update>

itemStockDOMapper接口中添加一个方法

int decreaseStock(@Param("itemId") Integer itemId,@Param("amount") Integer amount);

在itemServiceImpl中实现decreseStock方法:

 1     @Override
 2     @Transactional
 3     public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
 4         int affectedRow = itemStockDOMapper.decreaseStock(itemId, amount);
 5         if (affectedRow > 0) {
 6             //更新库存成功
 7             return true;
 8         } else {
 9             //更新库存失败
10             return false;
11         }
12     }

 EmBusinessError中添加一个错误

//30000开头相关为交易信息错误
STOCK_NOT_ENOUGH(30001,"库存不足"),

 接下来进行OrderServiceImpl的编写:

 1 @Service
 2 public class OrderServiceImpl implements OrderService {
 3 
 4     @Autowired
 5     private SequenceDOMapper sequenceDOMapper;
 6     @Autowired
 7     private ItemService itemService;
 8 
 9     @Autowired
10     private UserService userService;
11 
12     @Autowired
13     private OrderDOMapper orderDOMapper;
14     //创建订单
15     @Override
16     @Transactional
17     public OrderModel createOrder(Integer userId, Integer itemId, Integer amount) throws BusinessException {
18 
19         //1.校验下单状态,下单的商品是否存在,用户是否合法,购买数量是否正确
20         ItemModel itemModel = itemService.getItemById(itemId);
21         if (itemModel == null) {
22             throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "商品信息不存在");
23         }
24 
25         UserModel userModel = userService.getUserById(userId);
26         if (userModel == null) {
27             throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "用户信息不存在");
28         }
29 
30         if (amount <= 0 || amount > 99) {
31             throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "数量信息不存在");
32         }
33 
34         //2.落单减库存
35         boolean result = itemService.decreaseStock(itemId, amount);
36         if (!result) {
37             throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);
38         }
39 
40         //3.订单入库
41         OrderModel orderModel = new OrderModel();
42         orderModel.setUserId(userId);
43         orderModel.setItemId(itemId);
44         orderModel.setAmount(amount);
45         orderModel.setItemPrice(itemModel.getPrice());
46         orderModel.setOrderPrice(itemModel.getPrice().multiply(new BigDecimal(amount)));
47         //生成交易流水号
48         orderModel.setId(generateOrderNO());
49 
50         OrderDO orderDO = convertFromOrderModel(orderModel);
51         orderDOMapper.insertSelective(orderDO);
52 
53         //4.返回前端
54         return orderModel;
55     }
56     @Transactional(propagation = Propagation.REQUIRES_NEW)
57     String generateOrderNO(){
58         //订单号有16位
59         StringBuilder stringBuilder = new StringBuilder();
60         //前8位为时间信息,年月日
61         LocalDateTime now = LocalDateTime.now();
62         String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-","");
63         stringBuilder.append(nowDate);
64         //中间6位为自增序列
65         //获取当前sequence
66         int sequence = 0;
67         SequenceDO sequenceDO = sequenceDOMapper.getSequenceByName("order_info");
68         sequence = sequenceDO.getCurrentValue();
69         sequenceDO.setCurrentValue(sequenceDO.getCurrentValue()+sequenceDO.getStep());
70         sequenceDOMapper.updateByPrimaryKeySelective(sequenceDO);
71         String sequenceStr = String.valueOf(sequence);
72         for(int i=0;i<6-sequenceStr.length();i++){
73             stringBuilder.append(0);
74         }
75         stringBuilder.append(sequenceStr);
76 
77         //最后2位为分库分表位,暂时写死
78         stringBuilder.append("00");
79         return stringBuilder.toString();
80     }
81     private OrderDO convertFromOrderModel(OrderModel orderModel){
82         if(orderModel==null){
83             return null;
84         }
85         OrderDO orderDO = new OrderDO();
86         BeanUtils.copyProperties(orderModel,orderDO);
87         orderDO.setItemPrice(orderModel.getItemPrice().doubleValue());
88         orderDO.setOrderPrice(orderModel.getOrderPrice().doubleValue());
89         return orderDO;
90     }
91 }

为了分库分表,创建一张sequence_info表格并添加如下字段:

在mybtis-generator中添加如下代码,根据数据库表格生成对应的文件:

1         <table tableName="sequence_info" domainObjectName="SequenceDO" enableCountByExample="false"
2                enableUpdateByExample="false" enableDeleteByExample="false"
3                enableSelectByExample="false" selectByExampleQueryId="false">
4         </table>

在SequenceDOMapper.xml中添加与数据库对接的getSequenceByName操作:

1   <select id="getSequenceByName" parameterType="java.lang.String" resultMap="BaseResultMap">
2     select
3     <include refid="Base_Column_List" />
4     from sequence_info
5     where name = #{name,jdbcType=VARCHAR} for update 
6   </select>

在SequenceDOMapper的java文件中添加对应的方法:

SequenceDO getSequenceByName(String name);

步骤3:前端页面制作

修改前端页面getitem

  1 <html>
  2 <head>
  3     <meta charset="UTF-8">
  4     <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" ref="stylesheet">
  5     <link href="static/assets/global/css/components.css" rel="stylesheet" type="text/css">
  6     <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css">
  7     <script src="static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
  8 </head>
  9 <body class="login">
 10  <div class="content">
 11      <h3 class="form-title">商品详情</h3>
 12      <div class="form-group">
 13         <div>
 14              <label class="control-label" id="title"/>
 15          </div>
 16      </div>
 17      <div class="form-group">
 18          <label class="control-label">商品描述</label>
 19          <div>
 20              <label class="control-label" id="description"/>
 21          </div>
 22      </div>
 23 
 24      <div  class="form-group">
 25          <label class="control-label">价格</label>
 26          <div>
 27              <label class="control-label" id="price"/>
 28          </div>
 29      </div>
 30 
 31      <div  class="form-group">
 32          <div>
 33              <img style="width:200px;height:auto" id="imgUrl"/>
 34          </div>
 35      </div>
 36 
 37      <div class="form-group">
 38          <label class="control-label">库存</label>
 39          <div>
 40              <label class="control-label" id="stock"/>
 41          </div>
 42      </div>
 43      <div class="form-group">
 44          <label class="control-label">销量</label>
 45          <div>
 46              <label class="control-label" id="sales"/>
 47          </div>
 48      </div>
 49      <div class="form-actions">
 50          <button class="btn blue" id="createorder" type="submit">
 51             下单             
 52          </button> 
 53      </div>
 54      
 55  </div>
 56 </body>
 57 <script>
 58 
 59     function getParam(paramName) {
 60         paramValue = "", isFound = !1;
 61         if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
 62             arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
 63             while (i < arrSource.length && !isFound) arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
 64         }
 65         return paramValue == "" && (paramValue = null), paramValue
 66     }
 67 
 68    var g_itemVO = {};
 69 
 70 
 71     jQuery(document).ready(function (){
 72         $("#createorder").on("click",function(){
 73             $.ajax({
 74                 type: "POST",
 75                 contentType:"application/x-www-form-urlencoded",
 76                 url: "http://localhost:8090/order/createorder",
 77                 data: {
 78                     "itemId":g_itemVO.id,
 79                     "amount":1,
 80                 },
 81                 xhrFields:{withCredentials:true},   //前端解决跨域共享session
 82                 success: function (data) {
 83                     if (data.status == "success") {
 84                         alert("下单成功");
 85                         
 86                     } else {
 87                         alert("下单失败,原因为:"+data.data.errMsg);
 88                         if(data.data.errCode=20003){
 89                             window.location.href="login.html";
 90                         }
 91                     }
 92                 },
 93                 error: function (data) {
 94                     alert("下单失败,原因为:"+data.responseText);
 95                 }
 96             });
 97         })
 98    
 99             $.ajax({
100                 type: "GET",
101                 url: "http://localhost:8090/item/get",
102                 data: {
103                     "id":getParam("id"),
104                 },
105                 xhrFields:{withCredentials:true},   //前端解决跨域共享session
106                 success: function (data) {
107                     if (data.status == "success") {
108                         g_itemVO = data.data;
109                         reloadDom();
110                     } else {
111                         alert("获取信息失败,原因为:" + data.data.errMsg);
112                     }
113                 },
114                 error: function (data) {
115                     alert("获取信息失败,原因为:" + data.responseText);
116                 }
117             });
118             return false;
119     })
120     function reloadDom() {
121         $("#title").text(g_itemVO.title);
122         $("#description").text(g_itemVO.description);
123         $("#stock").text(g_itemVO.stock);
124         $("#price").text(g_itemVO.price);
125         $("#imgUrl").attr("src",g_itemVO.imgUrl);
126         $("#sales").text(g_itemVO.sales);
127     }
128 
129        
130 </script>
131 </html>
getitem

OrderController:

 1 @Controller("order")
 2 @RequestMapping("/order")
 3 @CrossOrigin(origins = {"*"},allowCredentials = "true")
 4 public class OrderController extends BaseController{
 5     @Autowired
 6     private OrderService orderService;
 7 
 8     @Autowired
 9     private HttpServletRequest httpServletRequest;
10     //封装下单请求
11     @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
12     @ResponseBody
13 
14 
15     public CommonReturnType createOrder(@RequestParam(name="itemId")Integer itemId,
16                                         @RequestParam(name="amount")Integer amount) throws BusinessException {
17         Boolean isLogin = (Boolean)httpServletRequest.getSession().getAttribute("IS_LOGIN");
18         if(isLogin == null||!isLogin.booleanValue()){
19             throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登录,不能下单");
20         }
21         //获取用户的登录信息
22         UserModel userModel=(UserModel)httpServletRequest.getSession().getAttribute("LOGIN_USER");
23         OrderModel orderModel = orderService.createOrder(userModel.getId(),itemId,amount);
24     return CommonReturnType.create(null);
25     }
26 }

 在EmBusinessError中加一个USER_NOT_LOGIN错误:

USER_NOT_LOGIN(20003,"用户还未登录"),

在前端页面中修改好跳转逻辑(前端页面略)

商品list——>商品详情页下单——> 若未登录返回20003错误 ——>跳转至登录页面 ——>登录成功后跳转至商品list

踩雷:又一次踩雷price的数据类型问题,这里写一点调试心得。

从itemController开始,发现第一句就是ItemModel itemModel = itemService.getItemById(id);即service层中调用getItemById方法,从数据库拿到数据之后存到itemModel对象中。

然后我查看了model里面的price还是double类型,但是do中就已经是bigdecimal了。于是我检查了service层的convertVOFromModel()方法。果然少了一句itemModel.setPrice(new BigDecimal(itemDO.getPrice()));添加之后问题就解决了。

数据库(double)——>itemDO(double)——>itemModel(bigdecimal)——>itemVO(bigdecimal)

修改itemService接口:

1     //商品销量增加
2     void increaseSales(Integer itemId,Integer amount)throws BusinessException;

itemDOMapper.xml中添加一个sql语句

1 <update id="increaseSales">
2     update item
3     set sales = sales + #{amount}
4     where id = #{id,jdbcType=INTEGER}
5   </update>

itemDOMapper.java文件:

int increaseSales(@Param("id")Integer id,@Param("amount")Integer amount);

itemServiceImpl:

1     @Override
2     @Transactional
3     public void increaseSales(Integer itemId, Integer amount) throws BusinessException {
4         itemDOMapper.increaseSales(itemId,amount);
5     }

OrderServiceImpl中在返回前端之前加上如下代码:

1        //加上商品销量
2         itemService.increaseSales(itemId,amount);

优化前端页面getitem.html:下单成功后,刷新一下 window.location.reload();

运行project,登录后再点击下单,页面提示下单成功,页面中更新库存与销量信息。数据库order_info表格增加订单数据。item_stock表格库存减少。sequence_info表格增加了如下字段。

 

 

   六、秒杀模型管理

步骤1:dao层模型建立

在model包中写一个PromoModel文件,建立秒杀模型

 1 public class PromoModel {
 2     private Integer id;
 3     //秒杀活动状态,1表示还未开始,2表示进行中,3表示已结束
 4     private Integer status;
 5     //秒杀活动名称
 6     private String promoName;
 7     //秒杀活动开始时间
 8     private DateTime startDate;
 9     //秒杀活动结束时间
10     private DateTime endDate;
11     //适用商品
12     private Integer itemId;
13     //商品价格
14     private BigDecimal promoItemPrice;
15 
16     public Integer getId() {
17         return id;
18     }
19 
20     public void setId(Integer id) {
21         this.id = id;
22     }
23 
24     public String getPromoName() {
25         return promoName;
26     }
27 
28     public void setPromoName(String promoName) {
29         this.promoName = promoName;
30     }
31 
32     public DateTime getStartDate() {
33         return startDate;
34     }
35 
36     public void setStartDate(DateTime startDate) {
37         this.startDate = startDate;
38     }
39 
40     public Integer getItemId() {
41         return itemId;
42     }
43 
44     public void setItemId(Integer itemId) {
45         this.itemId = itemId;
46     }
47 
48     public BigDecimal getPromoItemPrice() {
49         return promoItemPrice;
50     }
51 
52     public void setPromoItemPrice(BigDecimal promoItemPrice) {
53         this.promoItemPrice = promoItemPrice;
54     }
55 
56     public DateTime getEndDate() {
57         return endDate;
58     }
59 
60     public void setEndDate(DateTime endDate) {
61         this.endDate = endDate;
62     }
63 
64     public Integer getStatus() {
65         return status;
66     }
67 
68     public void setStatus(Integer status) {
69         this.status = status;
70     }
71 }
PromoModel

在数据库中建立对应的表格,并手动改添加一条活动数据

 在mybatis-generator中使用命令生成数据库对应的文件:

1 <table tableName="promo" domainObjectName="PromoDO" enableCountByExample="false"
2                enableUpdateByExample="false" enableDeleteByExample="false"
3                enableSelectByExample="false" selectByExampleQueryId="false">
4 </table>

步骤2:service层编写

写一个PromoService接口:

1 public interface PromoService {
2     PromoModel getPromoByItemId(Integer itemId);
3 }

PromoDOMapper接口中添加一个数据库操作:

PromoDO selectByItemId(Integer itemId);

PromoDOMapper.xml中进行相应的配置:

1  <select id="selectByItemId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
2     select
3     <include refid="Base_Column_List" />
4     from promo
5     where item_id = #{itemId,jdbcType=INTEGER}
6   </select>

 编写PromoServiceImpl:

 1 @Service
 2 public class PromoServiceImpl implements PromoService {
 3     @Autowired
 4     private PromoDOMapper promoDOMapper;
 5     @Override
 6     public PromoModel getPromoByItemId(Integer itemId) {
 7         //获取对应商品的秒杀活动信息
 8         PromoDO promoDO = promoDOMapper.selectByItemId(itemId);
 9         //dataobject转化成model
10         PromoModel promoModel = convertFromDataObject(promoDO);
11         if(promoModel==null){
12             return null;
13         }
14         //判断当前时间是否有秒杀活动即将或正在进行
15         if(promoModel.getStartDate().isAfterNow()){
16             promoModel.setStatus(1);
17         }else if(promoModel.getEndDate().isBeforeNow()){
18             promoModel.setStatus(3);
19         }else{
20             promoModel.setStatus(2);
21         }
22         return promoModel;
23     }
24 
25     private PromoModel convertFromDataObject(PromoDO promoDO){
26         if(promoDO == null){
27             return null;
28         }
29         PromoModel promoModel= new PromoModel();
30         BeanUtils.copyProperties(promoDO,promoModel);
31         promoModel.setPromoItemPrice(new BigDecimal(promoDO.getPromoItemPrice()));
32         promoModel.setStartDate(new DateTime(promoDO.getStartDate()));
33         promoModel.setEndDate(new DateTime(promoDO.getEndDate()));
34 
35         return promoModel;
36     }
37 }

 步骤3:将秒杀模型融入至商品模型

修改itemModel,加入秒杀的聚合模型,添加如下字段,以及他们的getters&setters:

 1 //使用聚合模型,如果promoModel不为空,则表示其拥有还未结束的秒杀活动
 2     private PromoModel promoModel;
 3 
 4     public PromoModel getPromoModel() {
 5         return promoModel;
 6     }
 7 
 8     public void setPromoModel(PromoModel promoModel) {
 9         this.promoModel = promoModel;
10     }

修改itemServiceImpl,获取活动商品信息,以下为添加与改动的代码:

 1 @Autowired
 2     private PromoService promoService;
 3 
 4    @Override
 5     public ItemModel getItemById(Integer id) {
 6         //从数据库中取出一条id关联的商品信息存放在itemDO中
 7         ItemDO itemDO = itemDOMapper.selectByPrimaryKey(id);
 8         if (itemDO == null) {
 9             return null;
10         }
11         //操作获得库存数量
12         ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
13 
14         //将dataobject-> Model
15         ItemModel itemModel = convertModelFromDataObject(itemDO, itemStockDO);
16 
17         //获取活动商品信息
18         PromoModel promoModel = promoService.getPromoByItemId(itemModel.getId());
19         if(promoModel!=null && promoModel.getStatus().intValue()!=3){
20             itemModel.setPromoModel(promoModel);
21         }
22         return itemModel;
23     }

修改itemVO,让前端展示商品详情时,也展示相应的秒杀活动信息。我们添加如下字段以及他们的getters&setters(略):

注意,这里DateTime为String格式,然后后面在itemController中需要将原itemModel中的Datetime格式数据转化为String格式存放在itemVO中。

 1     //记录商品是否在秒杀活动中,0表示无活动,1表示待开始,2表示进行中
 2     private Integer promoStatus;
 3 
 4     //秒杀活动价格
 5     private BigDecimal promoPrice;
 6 
 7     //秒杀活动ID
 8     private Integer promoId;
 9 
10     //秒杀活动开始时间
11     private String startDate;

修改itemController:

 1     private ItemVO convertVOFromModel(ItemModel itemModel) {
 2         if (itemModel == null) {
 3             return null;
 4         }
 5         ItemVO itemVO = new ItemVO();
 6         BeanUtils.copyProperties(itemModel, itemVO);
 7         if(itemModel.getPromoModel()!=null){
 8             //有正在进行或即将进行的秒杀活动
 9             itemVO.setPromoStatus(itemModel.getPromoModel().getStatus());
10             itemVO.setPromoId(itemModel.getPromoModel().getId());
11             itemVO.setStartDate(itemModel.getPromoModel().getStartDate().toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")));
12             itemVO.setPromoPrice(itemModel.getPromoModel().getPromoItemPrice());
13         }else{
14             itemVO.setPromoStatus(0);
15         }
16         return itemVO;
17     }

步骤4:前端页面制作

添加秒杀价格,并根据秒杀活动的时间状态写不同的页面。最后还要做一个定时器模块,倒计时为0后,刷新页面开启下单通道。且在活动期间,隐藏原价。

  1 <html>
  2 <head>
  3     <meta charset="UTF-8">
  4     <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" ref="stylesheet">
  5     <link href="static/assets/global/css/components.css" rel="stylesheet" type="text/css">
  6     <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css">
  7     <script src="static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
  8 </head>
  9 <body class="login">
 10  <div class="content">
 11      <h3 class="form-title">商品详情</h3>
 12 
 13      <div id="promoStartDateContainer" class="form-group">
 14          <label style="color:blue" id="promoStatus" class="control-label"></label>
 15          <div>
 16              <label style="color:red" class="control-label" id="promoStartDate"/>
 17          </div>
 18      </div>
 19 
 20      <div class="form-group">
 21         <div>
 22              <label class="control-label" id="title"/>
 23          </div>
 24      </div>
 25      <div class="form-group">
 26          <label class="control-label">商品描述</label>
 27          <div>
 28              <label class="control-label" id="description"/>
 29          </div>
 30      </div>
 31 
 32      <div id="normalPriceContainer" class="form-group">
 33          <label class="control-label">价格</label>
 34          <div>
 35              <label class="control-label" id="price"/>
 36          </div>
 37      </div>
 38 
 39      <div id="promoPriceContainer" class="form-group">
 40          <label style="color:red" class="control-label">秒杀价格</label>
 41          <div>
 42              <label style="color:red" class="control-label" id="promoPrice"/>
 43          </div>
 44      </div>
 45 
 46      <div  class="form-group">
 47          <div>
 48              <img style="width:200px;height:auto" id="imgUrl"/>
 49          </div>
 50      </div>
 51 
 52      <div class="form-group">
 53          <label class="control-label">库存</label>
 54          <div>
 55              <label class="control-label" id="stock"/>
 56          </div>
 57      </div>
 58      <div class="form-group">
 59          <label class="control-label">销量</label>
 60          <div>
 61              <label class="control-label" id="sales"/>
 62          </div>
 63      </div>
 64      <div class="form-actions">
 65          <button class="btn blue" id="createorder" type="submit">
 66             下单             
 67          </button> 
 68      </div>
 69      
 70  </div>
 71 </body>
 72 <script>
 73 
 74     function getParam(paramName) {
 75         paramValue = "", isFound = !1;
 76         if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
 77             arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
 78             while (i < arrSource.length && !isFound) arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
 79         }
 80         return paramValue == "" && (paramValue = null), paramValue
 81     }
 82 
 83    var g_itemVO = {};
 84 
 85 
 86     jQuery(document).ready(function (){
 87         $("#createorder").on("click",function(){
 88             $.ajax({
 89                 type: "POST",
 90                 contentType:"application/x-www-form-urlencoded",
 91                 url: "http://localhost:8090/order/createorder",
 92                 data: {
 93                     "itemId":g_itemVO.id,
 94                     "amount":1,
 95                 },
 96                 xhrFields:{withCredentials:true},   //前端解决跨域共享session
 97                 success: function (data) {
 98                     if (data.status == "success") {
 99                         alert("下单成功");
100                         window.location.reload();
101                         
102                     } else {
103                         alert("下单失败,原因为:"+data.data.errMsg);
104                         if(data.data.errCode=20003){
105                             window.location.href="login.html";
106                         }
107                     }
108                 },
109                 error: function (data) {
110                     alert("下单失败,原因为:"+data.responseText);
111                 }
112             });
113         })
114    
115             $.ajax({
116                 type: "GET",
117                 url: "http://localhost:8090/item/get",
118                 data: {
119                     "id":getParam("id"),
120                 },
121                 xhrFields:{withCredentials:true},   //前端解决跨域共享session
122                 success: function (data) {
123                     if (data.status == "success") {
124                         g_itemVO = data.data;
125                         reloadDom();
126                         //定时器
127                         setInterval(reloadDom,1000)
128                     } else {
129                         alert("获取信息失败,原因为:" + data.data.errMsg);
130                     }
131                 },
132                 error: function (data) {
133                     alert("获取信息失败,原因为:" + data.responseText);
134                 }
135             });
136             return false;
137     })
138     function reloadDom() {
139         $("#title").text(g_itemVO.title);
140         $("#description").text(g_itemVO.description);
141         $("#stock").text(g_itemVO.stock);
142         $("#price").text(g_itemVO.price);
143         $("#imgUrl").attr("src",g_itemVO.imgUrl);
144         $("#sales").text(g_itemVO.sales);
145         if(g_itemVO.promoStatus == 1){
146             //秒杀活动还未开始
147             var startTime = g_itemVO.startDate.replace(new RegExp("-","gm"),"/");
148             startTime = (new Date(startTime)).getTime();
149             var nowTime = Date.parse(new Date());
150             var delta =(startTime - nowTime)/1000;
151 
152             if(delta <= 0){
153                 g_itemVO.promoStatus = 2;
154                 reloadDom();
155             }
156 
157             $("#promoStartDate").text("秒杀活动将于: "+g_itemVO.startDate+" 开始售卖 倒计时:"+delta+"");
158             $("#promoPrice").text(g_itemVO.promoPrice);
159             //活动还未开始不能下单
160             $("#createorder").attr("disabled",true);
161         }else if(g_itemVO.promoStatus == 2){
162             //秒杀活动正在进行中
163             $("#promoStartDate").text("秒杀活动正在进行中");
164             $("#promoPrice").text(g_itemVO.promoPrice);
165             //活动开始,可以下单
166             $("#createorder").attr("disabled",false);
167             //秒杀时隐藏原价
168             $("#normalPriceContainer").hide();
169         }
170     }
171        
172 </script>
173 </html>
getitem

踩雷:注意g_itemVO.startDate这里要跟VO中的变量名称统一。

运行一下程序,活动还未开始与开始后的界面如下:

 步骤5:编写秒杀下单链路

修改orderModel,增加一个promoId属性,非空表示秒杀下单,并生成getters&setters:

 1    private String id;
 2     //若非空,则表示是以秒杀价格下单
 3     private Integer promoId;
 4     //购买商品的单价,若promoId非空,则表示秒杀价格
 5     private BigDecimal itemPrice;
 6     private Integer userId;
 7     private Integer itemId;
 8     private Integer amount;
 9     //购买金额,若promoId非空,则表示秒杀价格
10     private BigDecimal orderPrice;

数据库中的order_info表格中添加一个int类型的promo_id字段,默认值为0。(0表示普通下单,非0表示秒杀下单)

添加后,要手动修改一下orderDO,添加该字段,并生成对应的getters&setters。OrderDOMapper.xml中也要修改。

修改OrderService接口:

1 public interface OrderService {
2     //通过前端url上传过来秒杀活动id,然后下单接口内校验对应id是否属于对应商品且活动已开始
3     OrderModel createOrder(Integer userId,Integer itemId,Integer promoId,Integer amount) throws BusinessException;
4 }

修改OrderServiceImpl,校验活动信息,并修改入库时的价格计算(以下仅贴出修改部分代码):

 1        //校验是否在活动期间
 2         if(promoId!=null){
 3             //校验活动是否使用于当前商品
 4             if(promoId.intValue()!=itemModel.getPromoModel().getId()){
 5                 throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "活动信息不正确");
 6                 //校验活动是否正在进行中
 7             }else if(itemModel.getPromoModel().getStatus().intValue()!=2){
 8                 throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "活动信息不正确");
 9             }
10         }
11 
12         //2.落单减库存
13         boolean result = itemService.decreaseStock(itemId, amount);
14         if (!result) {
15             throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);
16         }
17 
18         //3.订单入库
19         OrderModel orderModel = new OrderModel();
20         orderModel.setUserId(userId);
21         orderModel.setItemId(itemId);
22         orderModel.setAmount(amount);
23         if(promoId!=null){
24             orderModel.setItemPrice(itemModel.getPromoModel().getPromoItemPrice());
25         }else{
26             orderModel.setItemPrice(itemModel.getPrice());
27         }

 修改OrderController:

 1    public CommonReturnType createOrder(@RequestParam(name="itemId")Integer itemId,
 2                                         @RequestParam(name="promoId",required = false)Integer promoId,
 3                                         @RequestParam(name="amount")Integer amount) throws BusinessException {
 4         Boolean isLogin = (Boolean)httpServletRequest.getSession().getAttribute("IS_LOGIN");
 5         if(isLogin == null||!isLogin.booleanValue()){
 6             throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登录,不能下单");
 7         }
 8         //获取用户的登录信息
 9         UserModel userModel=(UserModel)httpServletRequest.getSession().getAttribute("LOGIN_USER");
10         OrderModel orderModel = orderService.createOrder(userModel.getId(),itemId,promoId,amount);
11     return CommonReturnType.create(null);
12     }

最后修改前端,在data中加一句"promoId":g_itemVO.promoId

贴一个完整版的getitem.html吧:

  1 <html>
  2 <head>
  3     <meta charset="UTF-8">
  4     <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" ref="stylesheet">
  5     <link href="static/assets/global/css/components.css" rel="stylesheet" type="text/css">
  6     <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css">
  7     <script src="static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
  8 </head>
  9 <body class="login">
 10  <div class="content">
 11      <h3 class="form-title">商品详情</h3>
 12 
 13      <div id="promoStartDateContainer" class="form-group">
 14          <label style="color:blue" id="promoStatus" class="control-label"></label>
 15          <div>
 16              <label style="color:red" class="control-label" id="promoStartDate"/>
 17          </div>
 18      </div>
 19 
 20      <div class="form-group">
 21         <div>
 22              <label class="control-label" id="title"/>
 23          </div>
 24      </div>
 25      <div class="form-group">
 26          <label class="control-label">商品描述</label>
 27          <div>
 28              <label class="control-label" id="description"/>
 29          </div>
 30      </div>
 31 
 32      <div id="normalPriceContainer" class="form-group">
 33          <label class="control-label">价格</label>
 34          <div>
 35              <label class="control-label" id="price"/>
 36          </div>
 37      </div>
 38 
 39      <div id="promoPriceContainer" class="form-group">
 40          <label style="color:red" class="control-label">秒杀价格</label>
 41          <div>
 42              <label style="color:red" class="control-label" id="promoPrice"/>
 43          </div>
 44      </div>
 45 
 46      <div  class="form-group">
 47          <div>
 48              <img style="width:200px;height:auto" id="imgUrl"/>
 49          </div>
 50      </div>
 51 
 52      <div class="form-group">
 53          <label class="control-label">库存</label>
 54          <div>
 55              <label class="control-label" id="stock"/>
 56          </div>
 57      </div>
 58      <div class="form-group">
 59          <label class="control-label">销量</label>
 60          <div>
 61              <label class="control-label" id="sales"/>
 62          </div>
 63      </div>
 64      <div class="form-actions">
 65          <button class="btn blue" id="createorder" type="submit">
 66             下单             
 67          </button> 
 68      </div>
 69      
 70  </div>
 71 </body>
 72 <script>
 73 
 74     function getParam(paramName) {
 75         paramValue = "", isFound = !1;
 76         if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
 77             arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
 78             while (i < arrSource.length && !isFound) arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
 79         }
 80         return paramValue == "" && (paramValue = null), paramValue
 81     }
 82 
 83    var g_itemVO = {};
 84 
 85 
 86     jQuery(document).ready(function (){
 87         $("#createorder").on("click",function(){
 88             $.ajax({
 89                 type: "POST",
 90                 contentType:"application/x-www-form-urlencoded",
 91                 url: "http://localhost:8090/order/createorder",
 92                 data: {
 93                     "itemId":g_itemVO.id,
 94                     "amount":1,
 95                     "promoId":g_itemVO.promoId
 96                 },
 97                 xhrFields:{withCredentials:true},   //前端解决跨域共享session
 98                 success: function (data) {
 99                     if (data.status == "success") {
100                         alert("下单成功");
101                         window.location.reload();
102                         
103                     } else {
104                         alert("下单失败,原因为:"+data.data.errMsg);
105                         if(data.data.errCode=20003){
106                             window.location.href="login.html";
107                         }
108                     }
109                 },
110                 error: function (data) {
111                     alert("下单失败,原因为:"+data.responseText);
112                 }
113             });
114         })
115    
116             $.ajax({
117                 type: "GET",
118                 url: "http://localhost:8090/item/get",
119                 data: {
120                     "id":getParam("id"),
121                 },
122                 xhrFields:{withCredentials:true},   //前端解决跨域共享session
123                 success: function (data) {
124                     if (data.status == "success") {
125                         g_itemVO = data.data;
126                         reloadDom();
127                         //定时器
128                         setInterval(reloadDom,1000)
129                     } else {
130                         alert("获取信息失败,原因为:" + data.data.errMsg);
131                     }
132                 },
133                 error: function (data) {
134                     alert("获取信息失败,原因为:" + data.responseText);
135                 }
136             });
137             return false;
138     })
139     function reloadDom() {
140         $("#title").text(g_itemVO.title);
141         $("#description").text(g_itemVO.description);
142         $("#stock").text(g_itemVO.stock);
143         $("#price").text(g_itemVO.price);
144         $("#imgUrl").attr("src",g_itemVO.imgUrl);
145         $("#sales").text(g_itemVO.sales);
146         if(g_itemVO.promoStatus == 1){
147             //秒杀活动还未开始
148             var startTime = g_itemVO.startDate.replace(new RegExp("-","gm"),"/");
149             startTime = (new Date(startTime)).getTime();
150             var nowTime = Date.parse(new Date());
151             var delta =(startTime - nowTime)/1000;
152 
153             if(delta <= 0){
154                 g_itemVO.promoStatus = 2;
155                 reloadDom();
156             }
157 
158             $("#promoStartDate").text("秒杀活动将于: "+g_itemVO.startDate+" 开始售卖 倒计时:"+delta+"");
159             $("#promoPrice").text(g_itemVO.promoPrice);
160             //活动还未开始不能下单
161             $("#createorder").attr("disabled",true);
162         }else if(g_itemVO.promoStatus == 2){
163             //秒杀活动正在进行中
164             $("#promoStartDate").text("秒杀活动正在进行中");
165             $("#promoPrice").text(g_itemVO.promoPrice);
166             //活动开始,可以下单
167             $("#createorder").attr("disabled",false);
168             //秒杀时隐藏原价
169             $("#normalPriceContainer").hide();
170         }
171     }
172        
173 </script>
174 </html>

 最后运行project,在秒杀活动时间内进行下单操作,可以看到页面提示下单成功,数据库order_info表中多了一条新订单,以秒杀价格2500元买下。

 

 

 于是到这里我们的项目就全部完成啦~

 

七、项目总结

 

 

posted @ 2020-05-30 14:46  菅兮徽音  阅读(302)  评论(0编辑  收藏  举报