在商城項目中,之前我們介紹了購物車功能模塊的實現,商品加入到購物車之后,就是到購物車結算,然后顯示購物車的商品列表,點擊去結算,然后到了未提交前的訂單列表,
點擊提交訂單后,生成此訂單,返回訂單的訂單號,付款金額,訂單預計到達時間。訂單系統是一個非常重要的系統,我們的移動端、PC端都需要訂單系統,所以這里我們將訂單系統單獨作為一個服務來,留出接口供客戶單來調用
今天我們來看下這個訂單系統到底是如何實現的:
一、訂單系統功能
訂單系統主要包含哪些功能模塊呢?
創建訂單功能、查看訂單列表、根據訂單id查詢訂單的詳細信息、訂單修改、訂單取消、訂單狀態、訂單評價等功能的實現。
今天我們來看下創建訂單的流程:
二、訂單系統的數據庫表的設計
創建訂單說到底就是向訂單表中添加數據,即insert這些信息。
下單功能一定要使用關系型數據庫表,保證數據的一致性,因為創建訂單要保證在一個事務(一個事務就是指向數據庫中進行的一種操作:比如插入,刪除等等)里面,nosql數據庫不支持事務,可能會丟失數據。
我們在網上購物的時候通常這個訂單包含的信息比較多,所以對於訂單系統如何創建它的數據庫也是非常重要的。創建數據庫遵循數據庫設計的三大范式原則來設計。
我們創建了三個表:tb_order(訂單信息表),tb_order_item(訂單詳情表),tb_order_shipping(訂單配送表).
tb_order:這里包含了訂單的基本信息
tb_order_item:訂單詳情表:訂單的詳情主要就是購買商品的信息,通過訂單的id來實現關聯
tb_order_shipping:訂單配送表:
這是三個基本的表,其實還可以有物流信息表,訂單交易信息表。這里我們采用這三張表足夠。
三、訂單系統接口文檔,一般我們開發的時候會收到已經寫好的接口文檔,比如創建訂單的接口文檔。
從上面這個表中,我們可以看到該接口的url,接口的傳入參數和返回值。
接下來我們針對這三個來進行代碼的編寫:
url屬於controller層,
傳入參數這里我們可以看到是數據庫建立的三張表信息:第一個是tb_order,第二個是一個集合式的訂單明細List,第三個是訂單的配送信息表。
所以傳入參數就是這三個對象。這里我們是編寫接口,供客戶端調用,至於客戶端怎么將這些參數傳遞過來,那是客戶端團隊考慮的事情。
返回值這里使用了taotaoresult來包裝了下,因為我們提交訂單成功后,返回的是訂單號,即訂單的id所以,我們需要向客戶端傳遞訂單id過去,並顯示在訂單創建成功的頁面。
下面看下訂單服務接口的service層的實現:
service層的主要實現是將訂單信息添加到數據庫中,即接收controller傳遞過來的對象,然后補全頁面沒有的字段,insert數據庫,這里可以使用逆向工程生成的dao。
另外還有個問題:
訂單編號:訂單編號用什么形式比較好呢?
解決方案一(不能使用):
使用mysql的自增長。
優點:不需要我們自己生成訂單號,mysql會自動生成。
缺點:如果訂單表數量太大時需要分庫分表,此時訂單號會重復。如果數據備份后再恢復,訂單號會變。
方案二:日期+隨機數
采用毫秒+隨機數。
缺點:仍然有重復的可能。不建議采用此方案。在沒有更好的解決方案之前可以使用。
方案三:使用UUID
優點:不會重復。
缺點:長。可讀性查。不建議使用。
方案四:可讀性好,不能太長。一般訂單都是全數字的。可以使用redis的incr命令生成訂單號。
優點:可讀性好,不會重復
缺點:需要搭建redis服務器。
所以我們選取方案四作為生成訂單號的方案。
那么service層的編碼如下:
package com.taotao.order.service.impl; import java.util.Date; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.taotao.common.utils.TaotaoResult; import com.taotao.mapper.TbOrderItemMapper; import com.taotao.mapper.TbOrderMapper; import com.taotao.mapper.TbOrderShippingMapper; import com.taotao.order.dao.JedisClient; import com.taotao.order.service.OrderService; import com.taotao.pojo.TbOrder; import com.taotao.pojo.TbOrderItem; import com.taotao.pojo.TbOrderShipping; //訂單模塊實現 @Service public class OrderServiceImpl implements OrderService { @Autowired private TbOrderMapper orderMapper; @Autowired private TbOrderItemMapper orderItemMapper; @Autowired private TbOrderShippingMapper shippingMapper; @Autowired private JedisClient jedisClient; @Value("${ORDER_GEN_KEY}") private String ORDER_GEN_KEY; @Value("${ORDER_DEFAULT_KEY}") private String ORDER_DEFAULT_KEY; @Value("${ORDER_ITEM_KEY}") private String ORDER_ITEM_KEY; //創建訂單功能實現 @Override public TaotaoResult createOrder(TbOrder order,List<TbOrderItem> orderItem, TbOrderShipping orderShipping) { //邏輯步驟:創建訂單即向數據庫中三個表中插入數據,所以講數據庫中的字段補全即可。頁面傳遞過來的就不需要補全了,直接可以寫入數據庫 /*第一步:訂單號的生成方式:因為訂單號的特殊性,訂單后最好采用數字組成,且不能重復,可以采用redis中自增長的方式來實現, * 在redis中,如果無key,則redis自動創建你寫的key的名字。采用incr的命令來實現訂單號的自增。默認自增是從1開始的,因為考慮到用戶的原因,在redis中設置一個默認的key值 * 這樣用戶體驗會好些,不會因為看到簡單的1,2,3,所以設置一個默認的key值 */ String string = jedisClient.get(ORDER_GEN_KEY); if (StringUtils.isBlank(string)) { //如果redis中的key為空,則利用默認值 String defaultKey = jedisClient.set(ORDER_GEN_KEY,ORDER_DEFAULT_KEY); } //如果存在此key則,采用incr命令自增 long orderId= jedisClient.incr(ORDER_GEN_KEY); //然后向訂單表中插入數據 order.setOrderId(orderId+""); //訂單狀態:狀態:1、未付款,2、已付款,3、未發貨,4、已發貨 order.setStatus(1); order.setCreateTime(new Date()); order.setUpdateTime(new Date()); order.setBuyerRate(0); //補全完信息后,插入數據庫表 orderMapper.insert(order); //補全完訂單表后,將訂單明細表補全,因為之前訂單寫入redis,訂單明細的id也寫入redis吧,自動創建這個id的key。這個不需要默認編號了。對比頁面傳遞的參數,將剩下的補全 String string2 = jedisClient.get(ORDER_ITEM_KEY); long orderItemId = jedisClient.incr(string2); //因為傳遞過來的是一個集合列表,所以遍歷列表 for (TbOrderItem tbOrderItem : orderItem) { tbOrderItem.setId(orderItemId+""); tbOrderItem.setOrderId(orderId+""); orderItemMapper.insert( tbOrderItem); } //接下來補全訂單配送表 orderShipping.setOrderId(orderId+""); orderShipping.setCreated(new Date()); orderShipping.setUpdated(new Date()); shippingMapper.insert(orderShipping); return TaotaoResult.ok(orderId); } }
controller:層實現
controller需要將對象傳遞給service層:(客戶端向服務器端傳入的參數格式,詳見后面博文)
我們看到接口文檔中,controller接收的參數是一個json格式的字符串,也就是說客戶端傳遞過來的是json格式的字符串。
這就涉及到springMVC是如何接收json字符串的,需要用到@RequestBody注解。這里多說幾句:
@ResponseBody注解的原理是response只能響應一個字符串,當我們的返回值是java對象的時候,它有一個默認行為,即利用jackson包將java對象轉為字符串響應。這是一個默認自動的行為,不需要我們設置,只要這個注解即可。
@RequestBody注解同理:利用這個注解告訴springMVC我們現在接收的是一個json字符串,需要采取默認行為利用jackson包將json字符串轉換為java對象,所以controller層我們需要一個java對象的pojo。
package com.taotao.order.pojo; import java.util.List; import com.taotao.pojo.TbOrder; import com.taotao.pojo.TbOrderItem; import com.taotao.pojo.TbOrderShipping; public class Order extends TbOrder { private List<TbOrderItem> orderItems; private TbOrderShipping orderShipping; public List<TbOrderItem> getOrderItems() { return orderItems; } public void setOrderItems(List<TbOrderItem> orderItems) { this.orderItems = orderItems; } public TbOrderShipping getOrderShipping() { return orderShipping; } public void setOrderShipping(TbOrderShipping orderShipping) { this.orderShipping = orderShipping; } }
controller層實現:
package com.taotao.order.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.taotao.common.utils.ExceptionUtil; import com.taotao.common.utils.TaotaoResult; import com.taotao.order.pojo.Order; import com.taotao.order.service.OrderService; //訂單管理模塊 @Controller @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @RequestMapping("/create") @ResponseBody public TaotaoResult createOrder(@RequestBody Order order ){//創建訂單模塊實現 try { TaotaoResult taotaoResult = orderService.createOrder(order ,order.getOrderItems(), order.getOrderShipping()); return taotaoResult; } catch (Exception e) { e.printStackTrace(); return TaotaoResult.build(500,ExceptionUtil.getStackTrace(e)); } } }
以上代碼是訂單服務接口的創建訂單接口代碼實現。
接下來我們看下客戶端如何調用訂單服務層的:
客戶端當我們點擊去購物車結算的時候,顯示購物車列表。
在購物車列表頁面,當點擊去結算的時候,跳轉到未提交訂單的頁面。
點擊提交訂單,跳轉到顯示成功頁面。
controller層實現:
package com.taotao.portal.controller; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.taotao.portal.pojo.CartItem; import com.taotao.portal.pojo.Order; import com.taotao.portal.service.CartService; import com.taotao.portal.service.OrderService; @Controller @RequestMapping("/order") public class OrderController { @Autowired private CartService cartService; @Autowired private OrderService orderService; @RequestMapping("/order-cart") //點擊去結算,顯示訂單的頁面 public String showOrderPage(HttpServletRequest request,HttpServletResponse response,Model model){ List<CartItem> list= cartService.showCartList(request); model.addAttribute("cartList", list); return "order-cart"; } //點擊提交訂單,顯示訂單號,訂單金額,預計送達時間 @RequestMapping("/create") public String showSuccessOrder(Order order,Model model){ try { String orderId= orderService.createOrder(order); model.addAttribute("orderId ",orderId); model.addAttribute("payment",order.getPayment()); model.addAttribute("date", new DateTime().plusDays(3).toString("yyyy-mm-dd")); return "success"; } catch (Exception e) { e.printStackTrace(); model.addAttribute("message", "創建訂單出錯"); return "error/exception"; } } }
service層實現:
package com.taotao.portal.service.impl; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.taotao.common.utils.HttpClientUtil; import com.taotao.common.utils.JsonUtils; import com.taotao.common.utils.TaotaoResult; import com.taotao.portal.pojo.Order; import com.taotao.portal.service.OrderService; @Service public class OrderServiceImpl implements OrderService { @Value("${ORDER_BASE_URL}") private String ORDER_BASE_URL; @Value("${ORDER_CREATE_URL}") private String ORDER_CREATE_URL; //創建訂單的客戶端實現 @Override public String createOrder(Order order) { //因為httpclientUtil中dopost傳遞的是json格式的數據,所以需要將order這個java對象轉換成Json格式 String json = HttpClientUtil.doPost(ORDER_BASE_URL+ORDER_CREATE_URL+JsonUtils.objectToJson(order)); //為了獲取里面的訂單id,我們需要將獲得的字符串轉換為taotaoresult,然后獲取數據,因為服務層傳遞過來的就是訂單的id,所以獲得數據也是獲得的id TaotaoResult result= TaotaoResult.format(json); if (result.getStatus()==200) { Object orderId = result.getData(); return orderId.toString(); } return ""; } }
這里同樣用了pojo類Order類。
攔截器的問題:因為提交訂單我們需要用戶登錄,所以在springMVC.xml文件中配置上: