SpringBoot构建电商秒杀项目(下篇)
四、商品模块开发
步骤1:商品模型构建之dao层
创建一个ItemModel
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
创建数据库表格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中加上校验相关的注释:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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;
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包下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
最后编写与前端交互的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,编写前端代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
运行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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
运行project,打开该页面,我们可以看到:
踩雷:price相关的所有数据类型都要改成Double,尤其是ItemController
步骤7:
itemlist中点击每个商品的任意一部分,都将跳转到他们对应的商品详情页面getitem.html。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
五、交易模型管理
步骤1:交易dao层模型创建
首先我们在model包中添加一个OrderModel,添加itemprice,userId等字段
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
创建数据库表格如下:
在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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
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文件,建立秒杀模型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
在数据库中建立对应的表格,并手动改添加一条活动数据
在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后,刷新页面开启下单通道。且在活动期间,隐藏原价。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
踩雷:注意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元买下。
于是到这里我们的项目就全部完成啦~
七、项目总结