前言:
我們已經(jīng)實現(xiàn)了加入購物車的功能,加入購物車以后悉默,我們下一步一般都是形成訂單準(zhǔn)備付款啦;那么如何形成訂單呢苟穆?下面就實現(xiàn)訂單系統(tǒng)
1.訂單系統(tǒng)的搭建
先來分析一波抄课,我們可以看到訂單系統(tǒng)和訂單服務(wù)是分開來的,即我們要建一個服務(wù)工程和一個前臺工程雳旅;
建立的過程可以參考taotao-manager和taotao-manager-web
具體的一些web.xml跟磨,pom.xml,還有mybatis等配置可以參考工程里面的~這里就不貼出來了
git傳送門
https://github.com/AslanYJ/shopping.git
2.實現(xiàn)登錄攔截器
搭建完工程后攒盈,我們知道京東的話雖然可以加入購物車但是在結(jié)算的時候是一定要登錄抵拘,也就是說購物車列表頁面直接跳轉(zhuǎn)到登錄,這里要用到攔截器
攔截器我們采取Springmvc提供的攔截器
下面貼出邏輯代碼
package com.taotao.order.interceptor;
import com.taotao.common.pojo.TaotaoResult;
import com.taotao.pojo.TbUser;
import com.taotao.sso.service.UserService;
import com.taotao.utils.CookieUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
@Value("${TOKEN_KEY}")
private String TOKEN_KEY;
@Value("${SSO_URL}")
private String SSO_URL;
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//執(zhí)行Handler之前執(zhí)行此方法型豁,攔截請求讓用戶登錄就在這個方法攔截
//1.從cookie中取token僵蛛。
String token = CookieUtils.getCookieValue(httpServletRequest,TOKEN_KEY);
//2.沒有token尚蝌,需要跳轉(zhuǎn)到登錄頁面。
if(StringUtils.isBlank(token)) {
//取當(dāng)前請求的url
String url = httpServletRequest.getRequestURL().toString();
//跳轉(zhuǎn)到登錄頁面充尉,用redirect比較合適飘言,登錄之后還要回到當(dāng)前頁面,因此要在請求url中添加一個回調(diào)地址
httpServletResponse.sendRedirect(SSO_URL + "/page/login?url=" + url);
//由于沒有登錄驼侠,攔截
return false;
}
//3.有token姿鸿。調(diào)用sso系統(tǒng)的服務(wù),根據(jù)token查詢用戶信息泪电。
TaotaoResult taotaoResult = userService.getUserMessageByToken(token);
//4.如果查不到用戶信息般妙。用戶登錄已經(jīng)過期纪铺。需要跳轉(zhuǎn)到登錄頁面相速。
if(taotaoResult.getStatus() != 200) {
//取當(dāng)前請求的url
String url = httpServletRequest.getRequestURL().toString();
//跳轉(zhuǎn)到登錄頁面,用redirect比較合適鲜锚,登錄之后還要回到當(dāng)前頁面突诬,因此要在請求url中添加一個回調(diào)地址
httpServletResponse.sendRedirect(SSO_URL + "/page/login?url=" + url);
//由于沒有登錄,攔截
return false;
}
TbUser user = (TbUser) taotaoResult.getData();
httpServletRequest.setAttribute("user",user);
//5.查詢到用戶信息芜繁。放行
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
// handler執(zhí)行之后旺隙,modelAndView返回之前,可以對返回值進(jìn)行處理
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
// 在ModelAndView返回之后骏令,這時只能做些異常處理了
}
}
用到的常量在配置文件里面配置
記得配置在springmvc.xml里面加載攔截器和攔截器的請求
3.訂單展示確認(rèn)界面
-
功能分析
我們在購物的界面里面cart.jsp中可以看到請求的url蔬捷,根據(jù)這個請求的地址我們可以寫個Controller
image.png Controller
請求的url:/order/order-cart
參數(shù):無
業(yè)務(wù)邏輯:
從cookie中取商品列表展示到頁面。
返回值:邏輯視圖榔袋。
@Controller
public class OrderController {
@Value("${TT_CART}")
private String TT_CART;
/**
* 展示訂單確認(rèn)頁面周拐。
* <p>Title: showOrderCart</p>
* <p>Description: </p>
* @return
*/
@RequestMapping("/order/order-cart")
public String showOrderCart(HttpServletRequest request) {
//取用戶id
//從cookie中取token,然后根據(jù)token查詢用戶信息凰兑。需要調(diào)用sso系統(tǒng)的服務(wù)妥粟。
//根據(jù)用戶id查詢收貨地址列表
//從cookie中取商品列表
List<TbItem> cartList = getCartList(request);
//傳遞給頁面
request.setAttribute("cartList", cartList);
//返回邏輯視圖
return "order-cart";
}
/**
* 從cookie中取購物車列表
* <p>Title: getCartList</p>
* <p>Description: </p>
* @param request
* @return
*/
private List<TbItem> getCartList(HttpServletRequest request) {
//取購物車列表
String json = CookieUtils.getCookieValue(request, TT_CART, true);
//判斷json是否為null
if (StringUtils.isNotBlank(json)) {
//把json轉(zhuǎn)換成商品列表返回
List<TbItem> list = JsonUtils.jsonToList(json, TbItem.class);
return list;
}
return new ArrayList<>();
}
}
但是測試的時候會跳回主頁,這不是我們想要的吏够,不信你試試~
如果是的話勾给,請往下看!
在taotao-sso-web中可以看到這一段代碼锅知;js首先會去嘗試獲取從Controller端傳過來的回調(diào)地址播急,如果取到了回調(diào)地址,那么登錄成功后會跳轉(zhuǎn)到回調(diào)地址售睹,如果沒有取到回調(diào)地址旅择,那么登錄成功后直接訪問的便是淘淘商城首頁,在上圖中之所以我登錄成功后訪問到的是淘淘商城首頁就是因為我們沒有在PageController 當(dāng)中添加回調(diào)地址侣姆。
那么我們改造一下PageController
@Controller
public class PageController {
@RequestMapping("/page/register")
public String showRegister(){
return "register";
}
@RequestMapping("/page/login")
public String showLogin(String url, Model model){
model.addAttribute("redirect",url);
return "login";
}
}
OK.跳轉(zhuǎn)就完成了生真!
4.生產(chǎn)訂單
-
訂單數(shù)據(jù)庫分析
我們可以看到訂單的生產(chǎn)主要涉及三張表沉噩,一張是存訂單的主要信息,一張是存儲訂單的明細(xì)表柱蟀,一張是訂單的收貨人地址的表川蒙;
我們先來看下tb_order表,如下圖所示长已,可以看到畜眨,主鍵order_id是字符串類型,不是自增長的术瓮,因此我們需要自己生成訂單編號康聂,我們平時使用京東、天貓等購物網(wǎng)站胞四,發(fā)現(xiàn)人家的訂單號都是用數(shù)字組成的恬汁,我們也使用數(shù)字作為訂單號,但是怎樣才能使訂單號不重復(fù)呢辜伟?用時間加隨機(jī)數(shù)的方案生成的訂單其實還是可能會重復(fù)的氓侧,當(dāng)同一時刻生成的訂單越多越有可能出現(xiàn)訂單號一樣的情況,因此我們不能使用這種方案导狡。比較好的方案是什么呢约巷?是用redis的incr方法,由于redis是單線程的旱捧,因此無論多少個線程共同訪問也不會出現(xiàn)訂單編號一樣的情況独郎。payment字段是實付金額,需要從前臺傳過來枚赡,保留小數(shù)點后2位氓癌,payment_type是支付類型,分為在線支付和貨到付款标锄,也需要從前臺頁面?zhèn)鬟^來顽铸,post_free字段是郵費,郵費得由前臺傳過來料皇,因為很多電商都搞活動谓松,買夠多少錢的東西就免郵費,因此郵費是動態(tài)變化的践剂。status字段是訂單狀態(tài)鬼譬,訂單狀態(tài)我們暫且定義了6種狀態(tài),未付款逊脯、已付款优质、未發(fā)貨、已發(fā)貨、交易成功巩螃、交易關(guān)閉演怎。create_time字段是訂單創(chuàng)建時間,這沒什么可說的避乏,update_time字段是訂單更新時間爷耀,這個通常是訂單狀態(tài)發(fā)生了變化,payment_time字段是付款時間拍皮,consign_time字段是發(fā)貨時間歹叮,end_time字段是交易完成時間,這個通常是用戶點確認(rèn)收貨的時間铆帽,交易關(guān)閉時間則是該訂單的所有流程都走完后的時間咆耿。shipping_name字段是物流名稱,即用的誰家的快遞爹橱。shipping_code字段是物流單號萨螺,這個不用廢話。user_id字段當(dāng)然是指購買者ID宅荤。buyer_message字段是指買家留言屑迂,buyer_nick字段指買家昵稱浸策。buyer_rate字段記錄買家是否已經(jīng)評價冯键。表中還可以看到create_time、buyer_nick庸汗、status惫确、payment_type四個字段由key修飾,說明為這四個字段建立了索引蚯舱。
image.png
可以看到訂單表中并沒有購買商品詳情信息改化,那么商品詳情信息在哪兒存放呢?它被存放到了tb_order_item表中枉昏,主鍵id字段也是個字符串陈肛,我們也需要為其生成主鍵,不過我倒是覺得兄裂,如果id用Long類型并且主鍵自增長會更好點句旱。如下圖所示
image.png
接著我們看tb_order_shipping,這張表存放的是用戶的收貨信息晰奖,包括收貨人姓名谈撒、固定電話、移動電話匾南、省、市、區(qū)/縣夹厌、街道門牌號裆悄、郵政編碼尊流,而且收貨人信息與訂單是一對一的,因此收貨地址表的主鍵是order_id崖技。
image.png -
訂單生成功能分析
生成訂單是在訂單確認(rèn)頁面進(jìn)行的钟哥,如下圖所示迎献,可以看到"提交訂單"按鈕。
image.png
我們找到這個頁面對應(yīng)的jsp文件腻贰,那就是order-cart.jsp,搜索"提交訂單"冀瓦,可以看到如下圖所示搜索結(jié)果写烤,可以看到這是個button按鈕,該按鈕的onclick事件中使用id選擇器來得到表單感局,并且將該表單提交暂衡。
可以看到表單的內(nèi)容是隱藏的。我們可以根據(jù)表單的內(nèi)容撑毛,來用一個對象來存儲
我們分析下上圖的表單藻雌,這個表單中包含了三張表的信息疹吃,其中<input type="hidden" name="paymentType" value="1"/>便是tb_order表中的付款類型字段,這里默認(rèn)是1了歉摧,<c:forEach>遍歷的是購物車列表,var="cart"表示單個購物車對象再悼,varStatus="status"的用法如下所示
varStatus屬性可以方便我們實現(xiàn)一些與行數(shù)相關(guān)的功能,如:奇數(shù)行膝但、偶數(shù)行差異;最后一行特殊處理等等跟束。先就varStatus屬性常用參數(shù)總結(jié)下:
${status.index} 輸出行號冀宴,從0開始。
${status.count} 輸出行號甚疟,從1開始逃延。
${status.current} 當(dāng)前這次迭代的(集合中的)項
${status.first} 判斷當(dāng)前項是否為集合中的第一項,返回值為true或false
${status.last} 判斷當(dāng)前項是否為集合中的最后一項讽膏,返回值為true或false
begin盔然、end愈案、step分別表示:起始序號鹅搪,結(jié)束序號,跳躍步伐恢准。
我們可以用一個List集合來表示商品列表的集合甫题,一個orderShipping來表示坠非;
我們在taotao-order-interface中存放這個對象,因為如果放common-utils的話盟迟,我們taotao-order已經(jīng)依賴了common-utils工程了,如果再依賴就變成了相互依賴迫皱,這是行不通的辖众!所以放taotao-order-interface中
public class OrderInfo extends TbOrder implements Serializable {
//訂單商品表實體的集合
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;
}
}
- 功能分析(生產(chǎn)訂單)
業(yè)務(wù)邏輯:
1凹炸、接收表單的數(shù)據(jù)
2、生成訂單id
3饲握、向訂單表插入數(shù)據(jù)救欧。
4锣光、向訂單明細(xì)表插入數(shù)據(jù)
5、向訂單物流表插入數(shù)據(jù)蹬刷。
6频丘、返回TaotaoResult。
返回值:TaotaoResult
- Dao層
可以使用逆向工程迂卢。 - Service層
參數(shù):OrderInfo
返回值:TaotaoResult
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private TbOrderMapper orderMapper;
@Autowired
private TbOrderItemMapper orderItemMapper;
@Autowired
private TbOrderShippingMapper orderShippingMapper;
@Autowired
private JedisClient jedisClient;
@Value("${ORDER_GEN_KEY}")
private String ORDER_GEN_KEY;
@Value("${ORDER_ID_BEGIN}")
private String ORDER_ID_BEGIN;
@Value("${ORDER_ITEM_ID_GEN_KEY}")
private String ORDER_ITEM_ID_GEN_KEY;
@Override
public TaotaoResult createOrder(OrderInfo orderInfo) {
// 1而克、接收表單的數(shù)據(jù)
// 2怔毛、生成訂單id
if (!jedisClient.exists(ORDER_GEN_KEY)) {
//設(shè)置初始值
jedisClient.set(ORDER_GEN_KEY, ORDER_ID_BEGIN);
}
String orderId = jedisClient.incr(ORDER_GEN_KEY).toString();
orderInfo.setOrderId(orderId);
orderInfo.setPostFee("0");
//1拣度、未付款蜂莉,2混卵、已付款幕随,3、未發(fā)貨辕录,4梢卸、已發(fā)貨,5蚣旱、交易成功戴陡,6恤批、交易關(guān)閉
orderInfo.setStatus(1);
Date date = new Date();
orderInfo.setCreateTime(date);
orderInfo.setUpdateTime(date);
// 3、向訂單表插入數(shù)據(jù)诀浪。
orderMapper.insert(orderInfo);
// 4延都、向訂單明細(xì)表插入數(shù)據(jù)
List<TbOrderItem> orderItems = orderInfo.getOrderItems();
for (TbOrderItem tbOrderItem : orderItems) {
//生成明細(xì)id
Long orderItemId = jedisClient.incr(ORDER_ITEM_ID_GEN_KEY);
tbOrderItem.setId(orderItemId.toString());
tbOrderItem.setOrderId(orderId);
//插入數(shù)據(jù)
orderItemMapper.insert(tbOrderItem);
}
// 5窄潭、向訂單物流表插入數(shù)據(jù)。
TbOrderShipping orderShipping = orderInfo.getOrderShipping();
orderShipping.setOrderId(orderId);
orderShipping.setCreated(date);
orderShipping.setUpdated(date);
orderShippingMapper.insert(orderShipping);
// 6、返回TaotaoResult躏惋。
return TaotaoResult.ok(orderId);
}
}
-
發(fā)布服務(wù)
image.png -
引用服務(wù)(taotao-order-web)
image.png - Controller
請求的url:/order/create
參數(shù):使用OrderInfo接收
返回值:邏輯視圖距误。
業(yè)務(wù)邏輯:
1、接收表單提交的數(shù)據(jù)OrderInfo趁俊。
2刑然、補全用戶信息泼掠。
3、調(diào)用Service創(chuàng)建訂單挡逼。
4腻豌、返回邏輯視圖展示成功頁面
a) 需要Service返回訂單號
b) 當(dāng)前日期加三天。 -
測試
image.png
添加訂單成功的畫面.png
可以到我們的攔截器,訂單系統(tǒng)都完全跑起來了~