第二十四篇:订单系统的实现

前言:
我们已经实现了加入购物车的功能,加入购物车以后,我们下一步一般都是形成订单准备付款啦;那么如何形成订单呢?下面就实现订单系统

1.订单系统的搭建

先来分析一波,我们可以看到订单系统和订单服务是分开来的,即我们要建一个服务工程和一个前台工程;

image.png

建立的过程可以参考taotao-manager和taotao-manager-web
image.png

具体的一些web.xml,pom.xml,还有mybatis等配置可以参考工程里面的~这里就不贴出来了
git传送门
https://github.com/AslanYJ/shopping.git

2.实现登录拦截器

搭建完工程后,我们知道京东的话虽然可以加入购物车但是在结算的时候是一定要登录,也就是说购物车列表页面直接跳转到登录,这里要用到拦截器
拦截器我们采取Springmvc提供的拦截器

image.png

下面贴出逻辑代码

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 {
        //执行Handler之前执行此方法,拦截请求让用户登录就在这个方法拦截
        //1.从cookie中取token。
        String token = CookieUtils.getCookieValue(httpServletRequest,TOKEN_KEY);
        //2.没有token,需要跳转到登录页面。
        if(StringUtils.isBlank(token)) {
            //取当前请求的url
            String url = httpServletRequest.getRequestURL().toString();
            //跳转到登录页面,用redirect比较合适,登录之后还要回到当前页面,因此要在请求url中添加一个回调地址
            httpServletResponse.sendRedirect(SSO_URL + "/page/login?url=" + url);
            //由于没有登录,拦截
            return false;
        }
        //3.有token。调用sso系统的服务,根据token查询用户信息。
        TaotaoResult taotaoResult = userService.getUserMessageByToken(token);
        //4.如果查不到用户信息。用户登录已经过期。需要跳转到登录页面。
        if(taotaoResult.getStatus() != 200) {
            //取当前请求的url
            String url = httpServletRequest.getRequestURL().toString();
            //跳转到登录页面,用redirect比较合适,登录之后还要回到当前页面,因此要在请求url中添加一个回调地址
            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执行之后,modelAndView返回之前,可以对返回值进行处理
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        // 在ModelAndView返回之后,这时只能做些异常处理了
    }
}

用到的常量在配置文件里面配置

image.png

记得配置在springmvc.xml里面加载拦截器和拦截器的请求
image.png

3.订单展示确认界面

  • 功能分析
    我们在购物的界面里面cart.jsp中可以看到请求的url,根据这个请求的地址我们可以写个Controller


    image.png
  • Controller
    请求的url:/order/order-cart
    参数:无
    业务逻辑:
    从cookie中取商品列表展示到页面。
    返回值:逻辑视图。

@Controller
public class OrderController {
    
    @Value("${TT_CART}")
    private String TT_CART;

    /**
     * 展示订单确认页面。
     * <p>Title: showOrderCart</p>
     * <p>Description: </p>
     * @return
     */
    @RequestMapping("/order/order-cart")
    public String showOrderCart(HttpServletRequest request) {
        //取用户id
        //从cookie中取token,然后根据token查询用户信息。需要调用sso系统的服务。
        //根据用户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转换成商品列表返回
            List<TbItem> list = JsonUtils.jsonToList(json, TbItem.class);
            return list;
        }
        return new ArrayList<>();
    }
}

但是测试的时候会跳回主页,这不是我们想要的,不信你试试~
如果是的话,请往下看!
在taotao-sso-web中可以看到这一段代码;js首先会去尝试获取从Controller端传过来的回调地址,如果取到了回调地址,那么登录成功后会跳转到回调地址,如果没有取到回调地址,那么登录成功后直接访问的便是淘淘商城首页,在上图中之所以我登录成功后访问到的是淘淘商城首页就是因为我们没有在PageController 当中添加回调地址。


image.png
image.png

那么我们改造一下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.跳转就完成了!

4.生产订单

  • 订单数据库分析
    我们可以看到订单的生产主要涉及三张表,一张是存订单的主要信息,一张是存储订单的明细表,一张是订单的收货人地址的表;
    我们先来看下tb_order表,如下图所示,可以看到,主键order_id是字符串类型,不是自增长的,因此我们需要自己生成订单编号,我们平时使用京东、天猫等购物网站,发现人家的订单号都是用数字组成的,我们也使用数字作为订单号,但是怎样才能使订单号不重复呢?用时间加随机数的方案生成的订单其实还是可能会重复的,当同一时刻生成的订单越多越有可能出现订单号一样的情况,因此我们不能使用这种方案。比较好的方案是什么呢?是用redis的incr方法,由于redis是单线程的,因此无论多少个线程共同访问也不会出现订单编号一样的情况。payment字段是实付金额,需要从前台传过来,保留小数点后2位,payment_type是支付类型,分为在线支付和货到付款,也需要从前台页面传过来,post_free字段是邮费,邮费得由前台传过来,因为很多电商都搞活动,买够多少钱的东西就免邮费,因此邮费是动态变化的。status字段是订单状态,订单状态我们暂且定义了6种状态,未付款、已付款、未发货、已发货、交易成功、交易关闭。create_time字段是订单创建时间,这没什么可说的,update_time字段是订单更新时间,这个通常是订单状态发生了变化,payment_time字段是付款时间,consign_time字段是发货时间,end_time字段是交易完成时间,这个通常是用户点确认收货的时间,交易关闭时间则是该订单的所有流程都走完后的时间。shipping_name字段是物流名称,即用的谁家的快递。shipping_code字段是物流单号,这个不用废话。user_id字段当然是指购买者ID。buyer_message字段是指买家留言,buyer_nick字段指买家昵称。buyer_rate字段记录买家是否已经评价。表中还可以看到create_time、buyer_nick、status、payment_type四个字段由key修饰,说明为这四个字段建立了索引。


    image.png

    可以看到订单表中并没有购买商品详情信息,那么商品详情信息在哪儿存放呢?它被存放到了tb_order_item表中,主键id字段也是个字符串,我们也需要为其生成主键,不过我倒是觉得,如果id用Long类型并且主键自增长会更好点。如下图所示


    image.png

    接着我们看tb_order_shipping,这张表存放的是用户的收货信息,包括收货人姓名、固定电话、移动电话、省、市、区/县、街道门牌号、邮政编码,而且收货人信息与订单是一对一的,因此收货地址表的主键是order_id。
    image.png
  • 订单生成功能分析
    生成订单是在订单确认页面进行的,如下图所示,可以看到"提交订单"按钮。


    image.png

    我们找到这个页面对应的jsp文件,那就是order-cart.jsp,搜索"提交订单",可以看到如下图所示搜索结果,可以看到这是个button按钮,该按钮的onclick事件中使用id选择器来得到表单,并且将该表单提交。

image.png

可以看到表单的内容是隐藏的。我们可以根据表单的内容,来用一个对象来存储


image.png

我们分析下上图的表单,这个表单中包含了三张表的信息,其中<input type="hidden" name="paymentType" value="1"/>便是tb_order表中的付款类型字段,这里默认是1了,<c:forEach>遍历的是购物车列表,var="cart"表示单个购物车对象,varStatus="status"的用法如下所示

     varStatus属性可以方便我们实现一些与行数相关的功能,如:奇数行、偶数行差异;最后一行特殊处理等等。先就varStatus属性常用参数总结下:
${status.index}      输出行号,从0开始。
${status.count}      输出行号,从1开始。
${status.current}   当前这次迭代的(集合中的)项
${status.first}  判断当前项是否为集合中的第一项,返回值为true或false
${status.last}   判断当前项是否为集合中的最后一项,返回值为true或false
begin、end、step分别表示:起始序号,结束序号,跳跃步伐。

我们可以用一个List集合来表示商品列表的集合,一个orderShipping来表示;
我们在taotao-order-interface中存放这个对象,因为如果放common-utils的话,我们taotao-order已经依赖了common-utils工程了,如果再依赖就变成了相互依赖,这是行不通的!所以放taotao-order-interface中


image.png
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;
    }
}

  • 功能分析(生产订单)

业务逻辑:
1、接收表单的数据
2、生成订单id
3、向订单表插入数据。
4、向订单明细表插入数据
5、向订单物流表插入数据。
6、返回TaotaoResult。
返回值:TaotaoResult

  • Dao层
    可以使用逆向工程。
  • Service层
    参数: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、接收表单的数据
        // 2、生成订单id
        if (!jedisClient.exists(ORDER_GEN_KEY)) {
            //设置初始值
            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、未发货,4、已发货,5、交易成功,6、交易关闭
        orderInfo.setStatus(1);
        Date date = new Date();
        orderInfo.setCreateTime(date);
        orderInfo.setUpdateTime(date);
        // 3、向订单表插入数据。
        orderMapper.insert(orderInfo);
        // 4、向订单明细表插入数据
        List<TbOrderItem> orderItems = orderInfo.getOrderItems();
        for (TbOrderItem tbOrderItem : orderItems) {
            //生成明细id
            Long orderItemId = jedisClient.incr(ORDER_ITEM_ID_GEN_KEY);
            tbOrderItem.setId(orderItemId.toString());
            tbOrderItem.setOrderId(orderId);
            //插入数据
            orderItemMapper.insert(tbOrderItem);
        }
        // 5、向订单物流表插入数据。
        TbOrderShipping orderShipping = orderInfo.getOrderShipping();
        orderShipping.setOrderId(orderId);
        orderShipping.setCreated(date);
        orderShipping.setUpdated(date);
        orderShippingMapper.insert(orderShipping);
        // 6、返回TaotaoResult。
        return TaotaoResult.ok(orderId);
    }

}

image.png
  • 发布服务


    image.png
  • 引用服务(taotao-order-web)


    image.png
  • Controller
    请求的url:/order/create
    参数:使用OrderInfo接收
    返回值:逻辑视图。
    业务逻辑:
    1、接收表单提交的数据OrderInfo。
    2、补全用户信息。
    3、调用Service创建订单。
    4、返回逻辑视图展示成功页面
    a) 需要Service返回订单号
    b) 当前日期加三天。
  • 测试


    image.png

    添加订单成功的画面.png

可以到我们的拦截器,订单系统都完全跑起来了~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容