day80_淘淘商城项目_13_订单系统搭建 + 展示订单确认页面 + 用户身份认证 + 实现提交订单功能_匠心笔记

课程计划

  • 1、订单系统搭建
  • 2、订单确认页面展示
  • 3、用户认证
  • 4、创建订单
  • 5、创建订单成功后显示订单号

1、订单系统搭建

1.1、功能分析

1、在购物车页面点击【去结算】按钮跳转到订单确认页面。
  a) 展示商品列表
  b) 配送地址列表
  c) 选择支付方式
2、展示订单确认页面之前,应该确认用户身份。
  a) 使用拦截器实现。
  b) Cookie中取token。
  c) 取不到token跳转到登录页面。
  d) 取到token,根据token查询用户信息。
  e) 如果没有用户信息,登录过期跳转到登录页面。
  f) 取到用户信息,放行。
3、提交订单
  a) 生成订单。
  b) 展示订单提交成功页面。。

1.2、工程搭建

1.2.1、创建订单服务层工程

  taotao-order
    |–taotao-order-interface
    |–taotao-order-service

taotao-order



pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd"
>

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.taotao</groupId>
        <artifactId>taotao-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>taotao-order</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>taotao-order-interface</module>
        <module>taotao-order-service</module>
    </modules>
    <dependencies>
        <!-- 配置对common的依赖 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- 配置Tomcat插件  -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <port>8091</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

taotao-order-interface



pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd"
>

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.taotao</groupId>
        <artifactId>taotao-order</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>taotao-order-interface</artifactId>
    <dependencies>
        <!-- 配置对pojo的依赖 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-manager-pojo</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

taotao-order-service



pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd"
>

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.taotao</groupId>
        <artifactId>taotao-order</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>taotao-order-service</artifactId>
    <packaging>war</packaging>
    <dependencies>
        <!-- 配置对dao的依赖 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-manager-dao</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- 配置对taotao-order-interface的依赖:服务层发布服务要通过该接口 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-order-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- 配置对spring的依赖 -->
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- 配置对dubbo的依赖 -->
        <!-- dubbo相关 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <!-- 排除对低版本jar包的依赖 -->
            <exclusions>
                <exclusion>
                    <artifactId>spring</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>netty</artifactId>
                    <groupId>org.jboss.netty</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- 配置打包时跳过测试 ,首次配置使用的时候会自动联网进行下载 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

可以参考taotao-manager整合。



web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    id="WebApp_ID" version="2.5">

    <display-name>taotao-order-service</display-name>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <!-- 初始化spring容器:也即加载spring容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/applicationContext*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

1.2.2、创建订单表现层工程

表现层工程处理订单的确认页面和订单提交后产生的订单号
taotao-order-web打包方式war。



pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd"
>

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.taotao</groupId>
        <artifactId>taotao-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>taotao-order-web</artifactId>
    <packaging>war</packaging>
    <dependencies>
        <!-- 配置对common的依赖 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- 配置对taotao-order-interface的依赖:表现层调用服务要通过该接口 -->
        <dependency>
            <groupId>com.taotao</groupId>
            <artifactId>taotao-order-interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!-- JSP相关 -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- 配置对dubbo的依赖 -->
        <!-- dubbo相关 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <!-- 排除对低版本jar包的依赖 -->
            <exclusions>
                <exclusion>
                    <artifactId>spring</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>netty</artifactId>
                    <groupId>org.jboss.netty</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- 配置Tomcat插件  -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <port>8092</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

可以参考taotao-portal-web整合。

2、展示订单确认页面

2.1、导入静态页面



修改taotao-order-web工程中的src/main/resources/spring/下的springmvc.xml文件,
添加配置资源映射标签:

2.2、功能分析

在购物车表现层工程中的,有我们订单确认页面的URL:
/taotao-cart-web/src/main/webapp/WEB-INF/jsp/cart.jsp



由于订单确认页面展示在taotao-order-web系统,所以修改跳转地址为:http://localhost:8092/order/order-cart.html

在购物车页面点击“去结算”按钮跳转到订单确认页面。

我们打开订单确认页面order-cart.jsp,看看需要准备什么数据。我们发现只需要准备购物车列表
List<TbItem>,就可以了,但是还需要将
${cart.images[0]}改为
${cart.image}



order-cart.jsp的第191行还需要修改:



不然会报错!

请求分析:
请求的url:/order/order-cart
参数:没有参数。
返回值:逻辑视图String(order-cart.jsp),展示订单确认页面。
业务逻辑:
  1、首先用户先登录,从cookie中获取token,根据token调用SSO服务获取登录的用户信息。
  2、从cookie中获取购物车的商品的列表数据。
  3、从redis中获取该用户的购物车的商品列表数据。
  4、将这两者的数据进行合并,展示商品数据。并清除cookie中的数据。
  5、展示配送地址列表,需要根据用户id从数据库中查询收货地址列表,这里暂时使用静态数据。
  6、展示支付方式,也需要从数据库中查询支付的方式列表,这里暂时使用静态数据。

2.3、Dao层、Service层(无)

  需要根据用户id查询收货地址列表。
  需要查询支付方式。
  但是由于这两个都使用的静态数据,所以服务层不需要编写。
  静态数据,做静态页面处理。
  业务逻辑:
    直接调用其他的服务层的服务即可。(需要添加对应的依赖,并引用服务。)

2.4、发布服务

  由于表现层需要调服务层的SSO服务和购物车服务,所以需要在taotao-sso-service和taotao-cart-service工程中发布服务。

2.5、引用服务

在taotao-order-web工程中的springmvc.xml文件中引用服务:

2.6、Controller

参考功能分析的业务逻辑。
请求的url:/order/order-cart
参数:无

    @Autowired
    private UserLoginService userLoginService;

    @Autowired
    private CartService cartService;

    @Value("COOKIE_TOKEN_KEY")
    private String COOKIE_TOKEN_KEY;

    @Value("COOKIE_CART_KEY")
    private String COOKIE_CART_KEY;

    @RequestMapping("/order/order-cart")
    public String showOrderCart(HttpServletRequest request, HttpServletResponse response) {
        // 1、从cookie中获取token
        String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);
        // 2、根据token调用SSO服务获取登录的用户信息,查看用户是否已经过期
        TbUser tbUser = null;
        if (StringUtils.isNotBlank(token)) {
            TaotaoResult result = userLoginService.getUserByToken(token);
            if (result.getStatus() == 200) {
                tbUser = (TbUser) result.getData();
            }
        }
        // 3、用户必须登录才展示订单页面
        // 4、展示用户的配送地址列表,根据用户id从数据库中查询收货地址列表,这里暂时使用静态数据
        // 5、展示支付方式列表,从数据库中查询支付的方式列表,这里暂时使用静态数据
        // 6、从cookie中获取购物车的商品列表
        List<TbItem> cartList2 = getCartListFromCookie(request);
        // 7、调用购物车服务从redis数据库中获取购物车的商品列表
        List<TbItem> cartList1 = cartService.queryCartListByUserId(tbUser == null ? -1 : tbUser.getId());
        // 8、合并数据到redis中
        for (TbItem tbItem2 : cartList2) { // 遍历cookie的购物车数据
            boolean flag = false;
            if (cartList1 != null) { // redis数据库中有购物车数据
                for (TbItem tbItem1 : cartList1) { // 遍历redis数据库中购物车的数据
                    if (tbItem1.getId() == tbItem2.getId().longValue()) {
                        // 商品数量相加
                        tbItem1.setNum(tbItem1.getNum() + tbItem2.getNum());
                        // 更新redis数据库
                        cartService.updateTbItemCartByUserIdAndItemId(tbUser.getId(), tbItem1.getId(), tbItem1.getNum());
                        flag = true// 表示找到
                    }
                }
            }
            if (flag == false) { // 如果找了redis数据库还没有找到,说明cookie中的商品是新的,需要添加至redis数据库
                cartService.addItemCart(tbUser.getId(), tbItem2, tbItem2.getNum());
            }
        }
        // 9、合并数据后删除cookie
        if (!cartList2.isEmpty()) {
            CookieUtils.deleteCookie(request, response, COOKIE_CART_KEY);
        }
        // 10、再次调用购物车服务从redis数据库中获取新的购物车的商品列表
        List<TbItem> cartList = cartService.queryCartListByUserId(tbUser.getId());
        request.setAttribute("cartList", cartList);
        return "order-cart";
    }

    /**
     * 从cookie中获取购物车的商品列表的方法
     * @param request
     * @return
     */

    private List<TbItem> getCartListFromCookie(HttpServletRequest request) {
        // 1、从cookie中获取商品列表字符串
        String cartJson = CookieUtils.getCookieValue(request, COOKIE_CART_KEY, true);
        // 2、将字符串转换成java对象
        List<TbItem> list = new ArrayList<>();
        if (StringUtils.isNotBlank(cartJson)) {
            list = JsonUtils.jsonToList(cartJson, TbItem.class);
        }
        return list;
    }

属性文件内容如下:

2.7、访问测试

  首先安装taotao-order工程,再启动taotao-order-web。需要先登录,再去访问。

3、用户身份认证

  在展示订单确认页面之前,需要对用户身份进行认证,要求用户必须是登录装态。

3.1、功能分析

1、使用springmvc的拦截器实现。需要一个实现类实现HandlerInterceptor接口。
2、业务逻辑
  a) 从cookie中取token。
  b) 没有token,需要跳转到登录页面。
  c) 有token,调用sso系统的服务,根据token查询用户信息。
  d) 如果查不到用户信息、用户登录已经过期。需要跳转到登录页面。
  e) 查询到用户信息,放行。
3、在springmvc.xml中配置拦截器。

3.2、拦截器实现



拦截器代码如下:

/**
 * 判断用户是否登录的拦截器
 * @author chenmingjun
 * @date 2018年12月7日 下午3:41:52
 * @version V1.0
 */

public class LoginInterceptor implements HandlerInterceptor {

    @Value("${COOKIE_TOKEN_KEY}")
    private String COOKIE_TOKEN_KEY;

    @Value("${SSO_URL}")
    private String SSO_URL;

    @Autowired
    private UserLoginService userLoginService;

    /**
     * 在进入目标方法之前执行该方法。
     * 如果返回为false表示拦截,不让访问;如果返回为true,表示放行。
     * 我们在这里进行用户身份的认证。
     */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception 
{
        // 1、从cookie中获取token
        String token = CookieUtils.getCookieValue(request, COOKIE_TOKEN_KEY);
        // 2、判断token是否存在
        if (StringUtils.isEmpty(token)) {
            // 3、如果token不存在,说明用户没登录,重定向到SSO系统的登录页面
            response.sendRedirect(SSO_URL + "/page/login");
            // 拦截
            return false;
        }
        // 4、如果token存在,则调用SSO服务判断用户信息,查看用户登录是否已经过期
        TaotaoResult result = userLoginService.getUserByToken(token);
        if (result.getStatus() != 200) {
            // 5、如果用户登录已过期,重定向到SSO系统的登录页面
            response.sendRedirect(SSO_URL + "/page/login");
            // 拦截
            return false;
        }
        // 6、用户登录且没过期,放行
        return true;
    }

    /**
     * 在进入目标方法之后,在返回ModelAndView之前执行该方法。
     * 我们在这里可以对公用变量进行设置。比如用户名。
     */

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception 
{
    }

    /**
     * 在返回ModelAndView之后执行该方法。
     * 我们在这里可以进行异常的处理、日志的清理等。
     */

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception 
{
    }
}

属性文件中的SSO_URL:



属性文件代码如下:

#cookie中存放token的key
COOKIE_TOKEN_KEY=COOKIE_TOKEN_KEY

#cookie中存放购物车的key
COOKIE_CART_KEY=COOKIE_CART_KEY

#SSO系统的URL
SSO_URL=http://localhost:8088

3.3、配置拦截器

在taotao-order-web工程中的springmvc.xml文件中配置拦截器:



配置文件代码如下:

    <!-- 配置用户身份认证的拦截器,用于拦截订单和订单相关的请求-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- **表示当前路径及其子路径,*表示当前路径 -->
            <mvc:mapping path="/order/**"/><!-- 拦截订单和订单相关的请求 -->
            <bean class="com.taotao.order.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

3.4、访问测试

  访问地址:http://localhost:8092/order/order-cart.html
  发现被拦截后,重定向到登陆界面,登录成功后,跳转到商城首页了。

3.5、实现拦截器后存在的问题

存在两个问题:
  1、SSO服务被调用了两次,先在拦截器类中的handler方法中调用了一次,然后进入Controller类中的目标方法获取用户信息时又调用了一次。
  2、登录成功后跳转到了商城首页,应当跳转到订单确认页面才对。

3.5.1、解决调用SSO服务两次的问题

实际上,只需要在拦截器类中的handler方法中调用了一次便可以了,调用之后,将用户信息的数据存放在request域中,等进入目标的方法后,可以直接通过request域获取用户信息。
在拦截器中,将登录的用户信息放到request域中:
拦截器类的代码修改如下:



从request域中获取登录的用户信息。

Controller类的方法修改如下:

3.5.2、解决登录之后跳转到商城首页的问题

这里访问订单确认页面,需要登录,登录之后应当跳转到订单确认页面,不应该回到商城首页。
下面是正常的流程图:



在taotao-sso-web的login.jsp,我们可以看到一个叫做redirectUrl的变量,如果redirectUrl不为空,就会location.href=redirectUrl,

跳转到这个redirectUrl,所以我们可以在拦截器拦截未登录用户跳转http://localhost:8088/page/login时,设置一个参数redirect=http://localhost:8092/order/order-cart.html

比如:

  http://localhost:8088/page/login?redirect=http://localhost:8092/order/order-cart.html

然后在taotao-sso-web的Controller中将redirect接收,并放到Model中。



修改taotao-order-web的未登录用户拦截器,在跳转/page/login时加上参数redirect=url

这个地方是request.getRequestURL()不是request.getRequestURI(),小心看走眼!!!

  request.getRequestURL() 获取当前请求的全名,包括协议、域名、ip、端口等。

  request.getRequestURI() 只能获取端口后的相对路径。



在taotao-sso-web系统中的Controller中接收参数redirect,并放到Model中:

3.5.3、访问测试

  省略。。。

4、实现提交订单功能

用户在购物车点击【去结算】按钮后,点击【提交订单】按钮,会创建一个订单,如下:

4.1、数据库表分析

在数据库中涉及到三张表:tb_order、tb_order_item、tb_order_shipping
在tb_order表中

CREATE TABLE `tb_order` (
  `order_id` varchar(50COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '订单id',
  `payment` varchar(50COLLATE utf8_bin DEFAULT NULL COMMENT '实付金额。精确到2位小数;单位:元。如:200.07,表示:200元7分',
  `payment_type` int(2DEFAULT NULL COMMENT '支付类型,1、在线支付,2、货到付款',
  `post_fee` varchar(50COLLATE utf8_bin DEFAULT NULL COMMENT '邮费。精确到2位小数;单位:元。如:200.07,表示:200元7分',
  `status` int(10DEFAULT NULL COMMENT '状态:1、未付款,2、已付款,3、未发货,4、已发货,5、交易成功,6、交易关闭',
  `create_time` datetime DEFAULT NULL COMMENT '订单创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '订单更新时间',
  `payment_time` datetime DEFAULT NULL COMMENT '付款时间',
  `consign_time` datetime DEFAULT NULL COMMENT '发货时间',
  `end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
  `close_time` datetime DEFAULT NULL COMMENT '交易关闭时间',
  `shipping_name` varchar(20COLLATE utf8_bin DEFAULT NULL COMMENT '物流名称',
  `shipping_code` varchar(20COLLATE utf8_bin DEFAULT NULL COMMENT '物流单号',
  `user_id` bigint(20DEFAULT NULL COMMENT '用户id',
  `buyer_message` varchar(100COLLATE utf8_bin DEFAULT NULL COMMENT '买家留言',
  `buyer_nick` varchar(50COLLATE utf8_bin DEFAULT NULL COMMENT '买家昵称',
  `buyer_rate` int(2DEFAULT NULL COMMENT '买家是否已经评价',
  PRIMARY KEY (`order_id`),
  KEY `create_time` (`create_time`),
  KEY `buyer_nick` (`buyer_nick`),
  KEY `status` (`status`),
  KEY `payment_type` (`payment_type`)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

详解如下:

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

设计要求:

    1、订单号需要手动生成。
    2、要求订单号不能重复。
    3、订单号可读性好。
    4、订单号不能太长。20位。
    5、可以使用`redis的incr命令生成订单号`。订单号需要一个`初始值`

在tb_order_item表中

CREATE TABLE `tb_order_item` (
  `id` varchar(20COLLATE utf8_bin NOT NULL,
  `item_id` varchar(50COLLATE utf8_bin NOT NULL COMMENT '商品id',
  `order_id` varchar(50COLLATE utf8_bin NOT NULL COMMENT '订单id',
  `num` int(10DEFAULT NULL COMMENT '商品购买数量',
  `title` varchar(200COLLATE utf8_bin DEFAULT NULL COMMENT '商品标题',
  `price` bigint(50DEFAULT NULL COMMENT '商品单价',
  `total_fee` bigint(50DEFAULT NULL COMMENT '商品总金额',
  `pic_path` varchar(200COLLATE utf8_bin DEFAULT NULL COMMENT '商品图片地址',
  PRIMARY KEY (`id`),
  KEY `item_id` (`item_id`),
  KEY `order_id` (`order_id`)
ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

详解如下:

    从订单表中可以看到订单表中并没有购买`商品详情信息`,那么商品详情信息在哪儿存放呢?
    答:它被存放到了tb_order_item表中,主键id字段也是个字符串,我们也需要为其生成主键,同样使用`redis的incr`

设计要求:

    1、展示商品的数据,`增加字段冗余`(目的:`减少关联查询,提升查询性能`)。
    2、逆规范化与反三范式参链接://blog.csdn.net/liuyifeng1920/article/details/54136385

在tb_order_shipping表中

CREATE TABLE `tb_order_shipping` (
  `order_id` varchar(50NOT NULL COMMENT '订单ID',
  `receiver_name` varchar(20DEFAULT NULL COMMENT '收货人全名',
  `receiver_phone` varchar(20DEFAULT NULL COMMENT '固定电话',
  `receiver_mobile` varchar(30DEFAULT NULL COMMENT '移动电话',
  `receiver_state` varchar(10DEFAULT NULL COMMENT '省份',
  `receiver_city` varchar(10DEFAULT NULL COMMENT '城市',
  `receiver_district` varchar(20DEFAULT NULL COMMENT '区/县',
  `receiver_address` varchar(200DEFAULT NULL COMMENT '收货地址,如:xx路xx号',
  `receiver_zip` varchar(6DEFAULT NULL COMMENT '邮政编码,如:310001',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`order_id`)
ENGINE=InnoDB DEFAULT CHARSET=utf8;

详解如下:

    这张表存放的是订单物流信息,包括收货人姓名、固定电话、移动电话、省、市、区/县、街道门牌号、邮政编码,而且收货人信息与订单是一对一的关系,因此收货地址表的主键是order_id。

4.2、前端如何传递三张表的数据

  点击【提交订单】按钮时,会触发$('#orderForm').submit()函数,使用id选择器来得到表单,并且将该表单提交。



  那么,表单在哪儿呢?我们搜索”orderForm”,如下图所示,可以看到这个表单所有的标签都是
隐藏的,是不会被用户看到的,用户看到的只是表单下面展示的信息(这些信息只是做展示用,不会被提交,真正提交的是被隐藏的表单)。表单要提交的话,我们一般
用pojo来接收比较合适,那么这个表单我们应该用什么样的pojo来接收呢?先来看看这个表单都有那些数据。

  在这个表单中包含了
tb_order
tb_order_item
tb_order_shipping三张表的数据,其中
红色线框起来的支付方式paymentType、支付金额payment属于
tb_order
黑色线框起来的商品信息属于
tb_order_item
黄色线框起来的订单物流信息属于
tb_order_shipping表。这里暂时将
tb_order_shipping表的数据写死,支付类型也默认使用“1”。



  综合以上情况,我们来
写个包装的pojo类包含这些表单信息,那么我们这个pojo应该放到哪儿比较合适呢?我们不能把它放到taotao-common当中,因为我们的pojo类要继承TbOrder类,TbOrder类属于taotao-manager-dao工程,即taotao-common工程要依赖taotao-manager-dao了,而taotao-manager-dao工程已经依赖了taotao-common工程,那么便成了
相互依赖了,这是断不可行的。我们还想该pojo类要
尽可能的共用,则把它放到taotao-order-interface工程比较合适,因为taotao-order工程及taotao-order-web工程都依赖taotao-order-interface,因此把pojo写到taotao-order-interface工程比较合适。

  包装pojo类如下图所示,这里用到了一个技巧,那就是
继承了TbOrder类,这样OrderInfo便直接拥有了
TbOrder的属性。即扩展TbOrder,在子类中添加两个属性一个是
商品明细列表,一个是
配送信息表。为了让该pojo在网络中传输,我们需要让它
实现序列化接口

/**
 * 订单信息包装类
 * @author chenmingjun
 * @date 2018年12月7日 下午9:10:56
 * @version V1.0
 */

public class OrderInfo extends TbOrder implements Serializable {

    private static final long serialVersionUID = 1L;

    // 订单明细表
    private List<TbOrderItem> orderItems; // springmvc属性绑定的命名要求:与页面上的一致

    // 订单物流表
    private TbOrderShipping orderShipping; // springmvc属性绑定的命名要求:与页面上的一致

    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;
    }
}

4.3、功能分析

URL: /order/create.html
参数:表单的数据(包含:订单信息、订单明细列表、订单物流信息)
返回值:逻辑视图(包含:订单的ID)
业务逻辑:
  1、接收表单的数据。
  2、生成订单ID。
  3、向订单表插入数据。(注意:不要忘记用户ID设置)
  4、向订单明细表插入数据。
  5、向订单物流表插入数据。
  6、返回TaotaoResult。(包含订单的ID)

返回值:TaotaoResult 包含订单ID

4.4、Dao层

  既然表和接收表单的pojo都有了,代码就好写了。
  直接使用逆向工程生成的代码。

4.5、Service层

在taotao-order-interface创建接口:

/**
 * 订单管理的接口
 * @author chenmingjun
 * @date 2018年12月7日 下午10:57:07
 * @version V1.0
 */

public interface OrderService {

    /**
     * 生成订单
     * @param orderInfo 包含了表单提交的所有数据
     * @return
     */

    TaotaoResult createOrder(OrderInfo orderInfo);
}

在taotao-order-service中创建实现类:
由于要操作redis所以需要jedis的依赖,在pom.xml中添加依赖:

    <!-- 配置对Redis的Java客户端jedis的依赖 -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>

在taotao-order-service工程的/src/main/java中添加jedis的工具包,如下图所示:



在taotao-order-service工程的/src/main/resources/spring/目录下添加applicationContext-redis.xml配置文件。

代码中还用到了常量,我们把常量放到配置文件中,如下所示:

#订单生成的key
ORDER_ID_GEN_KEY=ORDER_ID_GEN

#订单号的初始值
ORDER_ID_INIT_VALUE=100888

#订单明细表主键生成的key
ORDER_ITEM_ID_GEN_KEY=ORDER_ITEM_ID_GEN

还需要在taotao-order-service中applicationContext-dao.xml文件配置对resources.properties文件扫描,如下图所示:



实现类代码如下:

/**
 * 订单管理的Service
 * @author chenmingjun
 * @date 2018年12月7日 下午10:58:44
 * @version V1.0
 */

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private JedisClient jedisClient;

    @Autowired
    private TbOrderMapper tbOrderMapper;

    @Autowired
    private TbOrderItemMapper tbOrderItemMapper;

    @Autowired
    private TbOrderShippingMapper tbOrderShippingMapper;

    @Value("ORDER_ID_GEN_KEY")
    private String ORDER_ID_GEN_KEY;

    @Value("ORDER_ID_INIT_VALUE")
    private String ORDER_ID_INIT_VALUE;

    @Value("ORDER_ITEM_ID_GEN_KEY")
    private String ORDER_ITEM_ID_GEN_KEY;

    @Override
    public TaotaoResult createOrder(OrderInfo orderInfo) {
        // 1、接收表单的数据。
        // 2、向订单表插入数据。
        if (!jedisClient.exists(ORDER_ID_GEN_KEY)) { // 说明订单生成的key不存在
            // 设置订单号的初始值
            jedisClient.set(ORDER_ID_GEN_KEY, ORDER_ID_INIT_VALUE);
        }
        // 2.1、生成订单的ID。通过redis的incr来生成。
        String orderId = jedisClient.incr(ORDER_ID_GEN_KEY).toString();
        // 2.2、补全订单表的其他属性。
        // 设置订单id
        orderInfo.setOrderId(orderId);
        // 设置订单状态:1、未付款,2、已付款,3、未发货,4、已发货,5、交易成功,6、交易关闭
        orderInfo.setStatus(1); 
        // 设置订单邮费
        orderInfo.setPostFee("0");
        // 设置订单创建日期
        orderInfo.setCreateTime(new Date());
        // 设置订单更新日期
        orderInfo.setUpdateTime(orderInfo.getCreateTime());
        // 在Controller中设置
        // 设置买家昵称
        // orderInfo.setBuyerNick(buyerNick);
        // 设置用户id
        // orderInfo.setUserId(userId);
        tbOrderMapper.insert(orderInfo);
        // 3、向订单明细表插入数据。
        List<TbOrderItem> orderItems = orderInfo.getOrderItems();
        for (TbOrderItem tbOrderItem : orderItems) {
            // 3.1、生成订单明细表的ID。通过redis的incr来生成。
            String orderItemId = jedisClient.incr(ORDER_ITEM_ID_GEN_KEY).toString();
            // 3.2、补全订单明细的其他属性。
            // 设置订单明细id
            tbOrderItem.setId(orderItemId);
            // 设置订单明细所属订单id
            tbOrderItem.setOrderId(orderId);
            tbOrderItemMapper.insert(tbOrderItem);
        }
        // 4、向订单物流表插入数据。
        TbOrderShipping orderShipping = orderInfo.getOrderShipping();
        // 4.1、设置订单的ID。
        orderShipping.setOrderId(orderId);
        // 4.2、补全订单物流表的其他属性。
        orderShipping.setCreated(orderInfo.getCreateTime());
        orderShipping.setUpdated(orderInfo.getUpdateTime());
        tbOrderShippingMapper.insert(orderShipping);
        // 5、返回TaotaoResult。(要包含订单的ID)
        return TaotaoResult.ok(orderId);
    }
}

4.6、发布服务

在applicationContext-service.xml发布服务:

4.7、引用服务

在springmvc.xml中引入服务:

4.8、Controller

请求的url:/order/create
参数:使用OrderInfo接收
返回值:逻辑视图。(页面应该显示订单号)
业务逻辑:
  1、接收表单提交的数据OrderInfo。
  2、补全用户信息。
  3、调用Service创建订单。
  4、返回逻辑视图展示订单提交成功页面:
    4.1、需要Service返回订单号。
    4.2、当前日期加三天。
第一步:首先加入时间操作组件的依赖:已经在taotao-common中依赖。

    <!-- 时间操作组件 -->
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>

Controller代码如下:

    @RequestMapping(value="/order/create", method=RequestMethod.POST)
    public String createOrder(OrderInfo orderInfo, HttpServletRequest request) {
        // 0、引入服务,注入服务
        // 1、接收表单提交的数据OrderInfo。
        // 从request域中获取登录的用户信息。
        TbUser tbUser = (TbUser) request.getAttribute("USER_INFO");
        // 2、补全OrderInfo信息。
        orderInfo.setUserId(tbUser.getId());
        orderInfo.setBuyerNick(tbUser.getUsername());
        // 3、调用Service创建订单。
        TaotaoResult result = orderService.createOrder(orderInfo);
        // 4、返回逻辑视图展示订单提交成功页面
        // 4.1、需要Service返回订单号。
        request.setAttribute("orderId", result.getData().toString());
        request.setAttribute("payment", orderInfo.getPayment());
        // 4.2、返回当前日期加三天。
        DateTime dateTime = new DateTime();
        dateTime = dateTime.plusDays(3);
        request.setAttribute("date", dateTime.toString("yyyy-MM-dd"));
        return "success";
    }

4.9、访问测试

  安装taotao-order,启动,用户在购物车点击去结算,点击提交订单,会创建一个订单,查看数据库中已存在数据。

5、参考文章

本文参考: