在商城项目中,之前我们介绍了购物车功能模块的实现,商品加入到购物车之后,就是到购物车结算,然后显示购物车的商品列表,点击去结算,然后到了未提交前的订单列表

点击提交订单后,生成此订单,返回订单的订单号,付款金额,订单预计到达时间。订单系统是一个非常重要的系统,我们的移动端、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层的编码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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文件中配置上:

共同电商学习交流群:168096884

Java订单功能模块设计与实现的更多相关文章

  1. Java生鲜电商平台-订单配送模块的架构与设计

    Java生鲜电商平台-订单配送模块的架构与设计 生鲜电商系统最终的目的还是用户下单支付购买, 所以订单管理系统是电商系统中最为复杂的系统,其作为中枢决定着整个商城的运转, 本文将对于生鲜类电商平台的订 ...

  2. Java开源生鲜电商平台-通知模块设计与架构(源码可下载)

    Java开源生鲜电商平台-通知模块设计与架构(源码可下载) 说明:对于一个生鲜的B2B平台而言,通知对于我们实际的运营而言来讲分为三种方式:           1. 消息推送:(采用极光推送)   ...

  3. Java开源生鲜电商平台-团购模块设计与架构(源码可下载)

    Java开源生鲜电商平台-团购模块设计与架构(源码可下载) 说明:任何一个电商系统中,对于促销这块是必不可少的,毕竟这块是最吸引用户的,用户也是最爱的模块之一,理由很简单,便宜. 我的经验是无论是大的 ...

  4. lin-cms-dotnetcore功能模块的设计

    lin-cms-dotnetcore功能模块的设计 先来回答以下问题. 1.什么是cms? Content Management System,内容管理系统. 2.dotnetcore是什么? .NE ...

  5. webgame设计之功能模块的代理模式

    原文地址:http://chengduyi.com/blog/?post=27 在游戏设计中,通常会将一些实现了具体功能的模块进行封装,达到重用的目的.这些功能模块包括:1.网络通信模块(实现连接,断 ...

  6. Java动态加载类在功能模块开发中的作用

    Java中我们一般会使用new关键字实例化对象然后调用该对象所属类提供的方法来实现相应的功能,比如我们现在有个主类叫Web类这个类中能实现各种方法,比如用户注册.发送邮件等功能,代码如下: /* * ...

  7. Java开源生鲜电商平台-订单表的设计(源码可下载)

    Java开源生鲜电商平台-订单表的设计(源码可下载) 场景分析说明: 买家(餐馆)用户,通过APP进行选菜,放入购物车,然后下单,最终支付的流程,我们称为下单过程. 买家可以在张三家买茄子,李四家买萝 ...

  8. java实现12306的45分钟内支付,45分钟后取消订单功能?

    java实现12306的45分钟内支付,45分钟后取消订单功能? - 回答作者: 匿名用户 https://zhihu.com/question/27254071/answer/35948645

  9. Atitit.收银机小票打印功能的设计  java php c#.net版本

    Atitit.收银机小票打印功能的设计  java php c#.net版本 1. 1. 打印方式有4种:1 1.1. 1.1. 一是不经过任何修改,直接调用javascript中的window.pr ...

随机推荐

  1. Numpy数组对象的操作-索引机制、切片和迭代方法

    前几篇博文我写了数组创建和数据运算,现在我们就来看一下数组对象的操作方法.使用索引和切片的方法选择元素,还有如何数组的迭代方法. 一.索引机制 1.一维数组 In [1]: a = np.arange ...

  2. python爬虫(四)_urllib2库的基本使用

    本篇我们将开始学习如何进行网页抓取,更多内容请参考:python学习指南 urllib2库的基本使用 所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地.在Python中有很 ...

  3. C#将dataGridView中显示的数据导出到Excel(大数据量超有用版)

    开发中非常多情况下须要将dataGridView控件中显示的数据结果以Excel或者Word的形式导出来,本例就来实现这个功能. 因为从数据库中查找出某些数据列可能不是必需显示出来,在dataGrid ...

  4. 【LeetCode-面试算法经典-Java实现】【053-Maximum Subarray(最大子数组和)】

    [053-Maximum Subarray(最大子数组和)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Find the contiguous subarray w ...

  5. C++学习笔记24,方法重写与方法隐藏

    该博文仅用于交流学习.请慎用于不论什么商业用途.本博主保留对该博文的一切权利. 博主博客:http://blog.csdn.net/qq844352155 转载请注明出处: 方法重写.是指在子类中又一 ...

  6. logstash 向elasticsearch写入数据,怎样指定多个数据template

    之前在配置从logstash写数据到elasticsearch时,指定单个数据模板没有问题.可是在配置多个数据模板时候,总是不成功,后来找了非常多资料,最终找到解决的方法,就是要多加一个配置项: te ...

  7. Ubuntu下关闭防火墙

    默认情况下ubuntu无firewall,除非你自己安装了,怎么装的就怎么删呗. . 假设是已启用的自备的iptables 删了即可了 sudo apt-get remove iptables.

  8. 【quickhybrid】架构一个Hybrid框架

    前言 虽然说本系列中架构篇是第一章,但实际过程中是在慢慢演化的第二版中才有这个概念, 经过不断的迭代,演化才逐步稳定 明确目标 首先明确需要做成一个什么样的框架? 大致就是: 一套API规范(统一An ...

  9. python集合增删改查,深拷贝浅拷贝

    集合 集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的.以下是集合最重要的两点: 去重,把一个列表变成集合,就自动去重了. 关系 ...

  10. log4go的输出优化

    又看了一些golang的日志包和相关的文章,仔细阅读了go 1.9.2系统提供的log和go-log,产生了对log4go的日志输出进行优化的想法. 结构化与multiwriter log使用mult ...