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

作者: __y | 来源:发表于2018-07-24 11:18 被阅读68次

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

    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

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

    相关文章

      网友评论

      本文标题:第二十四篇:订单系统的实现

      本文链接:https://www.haomeiwen.com/subject/lqqjmftx.html