软件设计文档 (SD)

注:Tiny Hippo 小河马点餐系统同时也是该小组成员在 2018 年春季学期中山大学系统分析与设计课程的大作业。按照本课程作业要求在 SD 文档开头点明。

1. Client

1.1 点餐系统

1.1.1 技术选型及理由

微信小程序框架

我们选择了微信小程序开发框架作为用户点餐系统的前端框架。

基于以上理由,选择微信小程序作为前端框架对用户和开发者都十分友好。

1.1.2 架构设计

MINA框架

微信团队为小程序提供的框架命名为MINA应用框架,MINA框架让开发者能够非常方便地使用微信客户端提供的各种基础功能与能力,快速构建一个应用。

在页面视图层,MINA的wxml是一套类似html标签的语言以及一系列基础组件。开发者使用wxml文件来搭建页面的基础视图结构,使用wxss文件来控制页面的展现样式。AppService应用逻辑层是MINA的服务中心,由微信客户端页面视图层外启用异步线程单独加载运行,MINA 中所有使用 javascript 编写的交互逻辑、网络请求、数据处理都必须在 AppService 中实现,但AppService中不能使用DOM操作相关的脚本代码。小程序中的各个页面可以通过AppService实现数据管理、网络通信、应用生命周期管理和页面路由管理。

MINA框架还为页面组件提供了bindtap、bindtouchstart等事件监听相关的属性,来与AppService中的事件处理函数绑定在一起,实现面向AppService层同步用户交互数据。MINA框架的核心是一个响应的数据绑定系统,提供了很多方法将AppService中的数据与页面进行单向绑定,让数据与视图非常简单地保持同步,当AppService中的数据变更时,会主动触发对应页面组件的重新渲染。MINA使用virtualdom技术,加快了页面的渲染效率。

MINA程序包含一个描述整体程序的app和多个描述各自页面的page,一个MINA程序主体部分由三个文件组成,必须放在项目的根目录,如下:

文件 作用  
app.js 小程序逻辑  
app.json 小程序公共设置  
app.wxss 小程序公共样式表  

一个MINA页面由四个文件组成,分别是:

文件 作用  
js 页面逻辑  
json 页面配置  
wxss 页面样式表  
wxml 页面结构  

MINA框架图如下:

小程序框架图

1.1.3 模块划分

根据业务逻辑和UI设计图,将用户点餐系统前端分为5个页面,按照小程序开发要求的文件结构,每个页面内包含js文件、json文件、wxml文件和wxss文件,分别处理逻辑、配置、结构和样式;根据页面的划分,得到页面模块的细分:

五个页面模块包括:

此外还定义了三个复杂的组件,构成组件模块。 三个组件包括:

加上小程序的全局文件,包括逻辑文件,配置文件,样式文件,构成app模块; 还有资源文件,包括图片,构成资源模块; 最后还有小程序的工具配置模块,和公共代码模块,各个模块关系如下:

模块

最后前端点餐系统的结构如下:

├── project.config.json //开发工具配置
├── app.js //小程序的全局逻辑文件
├── app.json //小程序的全局配置
├── app.wxss //小程序的全局样式
├── images //小程序利用到的图片
|
├── pages //小程序的页面文件存放文件夹
|   ├── authorize // 授权登录页面
│   │   ├── authorize.js //授权登录页面的逻辑文件
|   |   ├── authorize.json //授权登录页面配置文件
│   │   ├── authorize.wxml // 授权登录页面的结构文件
│   │   └── authorize.wxss  // 授权登录页面的样式文件
|   |
│   ├── index //主菜单页面
│   │   ├── index.js //主菜单页面的逻辑文件
|   |   ├── index.json //主菜单页面配置文件
│   │   ├── index.wxml // 主菜单页面的结构文件
│   │   └── index.wxss  // 主菜单页面的样式文件
|   |
│   ├── recommendation //今日推荐页面
│   |   ├── recommendation.js //今日推荐页面的逻辑文件
│   |   ├── recommendation.json //今日推荐页面配置文件
│   |   ├── recommendation.wxml //今日推荐页面的结构文件
│   |   └── recommendation.wxss //今日推荐页面的样式文件
|   |
│   ├── recommendation-details //今日推荐卡片细节页面
│   |   ├── recommendation-details.js //今日推荐卡片细节页面的逻辑文件
│   |   ├── recommendation-details.json //今日推荐卡片细节页面配置文件
│   |   ├── recommendation-details.wxml //今日推荐卡片细节页面的结构文件
│   |   └── recommendation-details.wxss //今日推荐卡片细节页面的样式文件
|   |
│   ├── cart //购物车页面
│   |   ├── cart.js //购物车页面的逻辑文件
│   |   ├── cart.json //购物车页面配置文件
│   |   ├── cart.wxml //购物车页面的结构文件
│   └── └── cart.wxss //购物车页面的样式文件
|
├── component //自定义组件
│    ├── card //今日推荐卡片组件
│    |   ├── card.js //今日推荐卡片组件的逻辑文件
│    |   ├── card.json //今日推荐卡片组件的配置文件
│    |   ├── card.wxml //今日推荐卡片组件的结构文件
│    |   └── card.wxss //今日推荐卡片组件的样式文件
|    |
|    ├── hSwiper //滑动框组件
│    |   ├── hSwiper.js //滑动框组件的逻辑文件
│    |   ├── hSwiper.json //滑动框组件的配置文件
│    |   ├── hSwiper.wxml //滑动框组件的结构文件
│    |   └── hSwiper.wxss //今日推荐卡片组件的样式文件
|    |
|    └── orderItem //购物车or订单item组件
│    |   ├── orderItem.js //订单item的逻辑文件
│    |   ├── orderItem.json //订单item的配置文件
│    |   ├── orderItem.wxml //订单item组件的结构文件
│    └── └── orderItem.wxss //订单item组件的样式文件
|
└── utils //公共的js代码
     └── util.js

1.1.4 软件设计技术

MVVM设计模式

MVVM的全称为Model-View-ViewModel,M表示Model,V表示视图View,VM表示数据与模型,当前端View变化时,由于View与VM进行了绑定,VM又与M进行交互,从而使M得到了改变;当M发生变化时,小程序检测到变化并通知VM,由于VM和V进行了绑定,因此V得到改变。

小程序的框架MINA内的设计思想就包含了MVVM,因此小程序内页面和组件模块都自动采用了MVVM设计模式。

1.2 商家管理系统

1.2.1 技术选型及理由

webpack+vue(vue/vue-router/vue-material)+axios

Vue

vue-router

vue-material

webpack

axios

1.2.2 架构设计

商家管理系统主要包括应用界面、业务逻辑和产品构建等开发内容,还应提供配置项用于构建项目打包时的目标路径及服务器地址等信息,同时还要进行单元测试与集成测试。根据需要,项目确定必须有以下四个包:

build包根据config中的配置调整webpack打包规则,将src中的源码进行打包。另外,项目构建后,还会生成dist包,为构建后的产品文件。

1.2.3 模块划分

根据业务逻辑和UI设计图,将餐厅管理系统前端分为6个页面:

商家管理系统各界面渲染、路由控制等操作都在前端完成,采取组件化开发的思想,将每个页面抽象为一个组件,页面中也可以包含子组件。

综合考虑使用的技术框架,并根据应用需要的组件,可以得到详细的项目结构如下所示。如果过需要新增界面、功能,引入新组件,也可以很方便地将新内容添加到项目中去。

├── index.html // 管理系统主界面框架
├── build      // 提供开发环境和构建项目的代码
├── config     // 项目的webpack打包配置
├── server     // 处理web端和服务器数据传递涉及的跨域等问题
├── src        // 商家管理系统开发代码
|   ├── assets // 存放前端的静态资源,例如图片、图标和字体等
│   ├── components // 页面组件
│   │   ├── AddItemPopup.vue // 添加、修改和删除菜品的弹窗组件
|   |   ├── Button.vue       // 按钮组件
│   │   ├── ImageUpload.vue  // 图片上传组件
│   │   └── Nav.vue          // 导航组件
|   |
│   ├── sections // 页面
│   |   ├── Login.vue       // 餐厅管理者登录
│   |   ├── About.vue       // 团队简介
│   |   ├── Dashboard.vue   // 餐厅环境展示及展示图片管理
│   |   ├── Menu.vue        // 菜单管理
│   |   ├── Recommendation.vue // 推荐菜品管理
│   |   └── Order.vue       // 订单管理
|   |
│   ├── sections // 页面
│   |   ├── Login.vue       // 餐厅管理者登录
│   |   ├── About.vue       // 团队简介
│   |   ├── Dashboard.vue   // 餐厅环境展示及展示图片管理
│   |   ├── Menu.vue        // 菜单管理
│   |   ├── Recommendation.vue // 推荐菜品管理
│   |   └── Order.vue       // 订单管理
|   |
│   ├── service   // 公共服务
│   |   ├── api.js    // 与服务器进行数据传递的地址与方法处理
│   |   └── auth.js   // 授权操作
|   |
│   ├── store    // 管理系统数据管理
│   |    └── index.js 
|   |
│   ├── utils   // 公共函数
|   |
│   ├── main.js     // 全局初始化
|   └── router.js   // 页面跳转路由  
|   |
├── test       // 测试
|   ├── unit       // 单元测试
|   └── e2e        // 端到端测试
|   |
├── .babelrc       // babel编译参数,vue开发需要babel编译 
└── package.json   // 项目文件,记载着一些命令和依赖还有简要的项目描述信息

1.2.4 软件设计技术

MVVM 在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

Vue.js 可以说是MVVM 架构的最佳实践,专注于 MVVM 中的 ViewModel,在整个项目中采用了MVVM的设计模式。

ObjectOriented Programming

面向对象的基本思想是使用类, 对象, 继承, 封装, 消息等基本概念进行程序设计。 面向对象方法的三个基本特征:

在项目中,对各对象、组件进行了封装,只留给外部接口,调用它的其他组件不用知道其内部的具体实现方式,每个组件拥有自己的方法与行为,在解决整个事务的某问题中行使具体职责。


2. Server

2.1 技术选型理由

后台主要技术栈是: Nginx + Python Flask + Redis + Mysql 完全 Docker 化, 理由如下:

Nginx

Python Flask

Redis

Mysql

Docker:

2.2 架构设计

整体是经典的 **Client-Sever ** 架构,以下为部署架构图,其中后台的每个部分均进行了 docker 化

部署设计图

使用 docker-compose 可以极大地简化我们的部署流程,加上使用 Makefile 辅助可以实现后台代码的一键部署。

当我们想要提高服务器的服务性能时,可以通过 docker 多开几个 Web Server 的容器示例,很方便地达到扩充的目的。

代码链接:

  1. docker-compose 配置文件
  2. Makefile 文件

2.3 模块划分

部署图上关于后台的每个部分都独立为一个文件夹各自进行 docker 化的管理。然后关键的 Web Server 服务器程序上主要分成 app.py 的业务逻辑层和 dbTools 的数据持久化模块。

容器化各容器的文件组织结构

.
├── app
│   ├── app.py
│   ├── data
│   │   ├── menu_database.json
│   │   ├── recommendation.json
│   │   └── restaurant_database.json
│   ├── dbTools
│   │   ├── dbConfig.py
│   │   ├── dbInit.py
│   │   ├── dbOperators.py
│   │   ├── dbPool.py
│   │   ├── __init__.py
│   │   └── tools.py
│   ├── docker-entrypoint.sh
│   ├── Dockerfile
│   └── requirements.txt
├── client
│   └── dist
│       ├── index.html
│       └── static
├── db
│   ├── 01-my-script.sh
│   ├── Dockerfile
│   ├── TinyHippo.sql
│   └── TinyHippoTest.sql
├── docker-compose.yml
├── Makefile
├── nginx
│   ├── conf.d
│   │   └── web.conf
│   └── Dockerfile
├── README.md
|—— tests/

app.py 层级划分:

app.py
├─ Restaurant
    ├─/restaurant/recommendation
    ├─/restaurant/session
    ├─/restaurant/category
    ├─/restaurant/category/
    ├─/restaurant/category/<int:categoryId>
    ├─/restaurant/dish/<int:dishId>
    ├─/restaurant/getdish/<int:dish_id>
    ├─/restaurant/order
├─ Customer
    ├─/restaurant/customer/record
    ├─/restaurant/customer/edit
    ├─/restaurant/customer/read
    ├─/restaurant/table/read
    ├─/restaurant/table/payment
    ├─/restaurant/customer/history

dbTools 数据库模块代码文件结构

├── dbTools
│   ├── dbConfig.py
│   ├── dbInit.py
│   ├── dbOperators.py
│   ├── dbPool.py
│   ├── __init__.py
│   └── tools.py

2.4 软件设计技术

2.4.1 Objected-Oriented Programming

我们项目后台逻辑层部分,将数据库模型中的每一张表的操作分别封装成一个类,实现面向对象,使得逻辑层对表进行操作时,无需考虑 sql 语句,只需调用类中所提供的增删改查操作,即可完成对于数据库的操作。

详见下图:

dbOperators.py
├─ restaurantOperator
├─ dishTypeOperator
├─ tableOperator
├─ QRlinkOperator
├─ dishOperator
├─ dishCommentOperator
├─ orderListOperator
├─ RecommendationOperator
└─ RecommendationDetailsOperators

Code: https://github.com/rookies-sysu/Order-System-Backend/blob/dbpool/app/dbTools/dbOperators.py

2.4.2 Database Connection Pool

在后台与前端对接之后,发现频繁地打开和关闭连接会对性能造成很大的影响,而且之前假设的情况是接受的请求都是同步的,但是前端可能发送异步请求,当两个请求同时到达时,甚至会导致数据库故障。 基于以上原因,就不得不使得我们去考虑使用数据库连接池的方法。

数据库连接池的基本原理:最初,打开一定数量的数据库连接(最小连接数),当收到调用者的调用请求之后,分配给调用者,在调用完毕之后将连接返回到连接池(返回到连接池的连接不会关闭,而是为下一次的调用而分配)。

数据库连接池参数设置

DB_MIN_CACHED=3
DB_MAX_CACHED=3
DB_MAX_SHARED=10
DB_MAX_CONNECYIONS=20
DB_BLOCKING=True
DB_MAX_USAGE=0
DB_SET_SESSION=None

Code: https://github.com/rookies-sysu/Order-System-Backend/blob/dbpool/app/dbTools/dbConfig.py

将数据库连接池封装成类 DBPool:

Code: https://github.com/rookies-sysu/Order-System-Backend/blob/dbpool/app/dbTools/dbPool.py

关于数据库连接池的使用,我们将其作为Tools类的函数:

Code: https://github.com/rookies-sysu/Order-System-Backend/blob/dbpool/app/dbTools/tools.py

2.4.3 前后端分离开发

引入第三个对象—— Mock Sever 打破前端和后台的强耦合关系。

+---------+              +---------+
|         |              |         |
| Object1 |  <-------->  | Object2 |
|         |              |         |
+---------+              +---------+

               Before               


+---------+              +---------+
|         |              |         |
| Object1 |  <----->  | Object2 |
|         |              |         |
+---+-----+              +-----+---+
    |                          |    
    |                          |    
    |                          |    
    |                          |    
    |                          |    
    |       +---------+        |    
    |       |         |        |    
    +-----> | Object3 | <------+    
            |         |             
            +---------+             

               After      

---------------------------------------------------------------------------------------


+-------------------+                     +-------------------+
|                   | +-------- ✕ ------> |                   |
|     Browser       |                     |    Real Server    |
|                   | <---+               |                   |
+--------------+----+     |               +-------------------+
               |          |                                    
               |          |                                    
               |          |                                    
               |          |                                    
           Request      Response                               
               |          |                                    
               |          |                                    
               |          |                                    
               |     +----+--------------+                     
               +---> |                   |                     
                     |    Mock Server    |                     
                     |                   |                     
                     +-------------------+         

这样之后只需要配置好 Nginx 的端口转发,就可以非常容易地在项目后期实现前后端接口对接。Nginx 配置如下:

Nginx 配置

2.4.4 Service Oriented Architecture through Dockerize

Service Oriented Architecture 需要达到可重复使用, 粒度, 模块性, 可组合型, 对象化原件, 构件化以及具交互操作性,这些通过 docker 化来实现。

通过容器化部署方案,可以使得各容器内的环境分离开来,减少了代码环境强耦合的影响。同时可以使得每个容器各自安装的依赖最小化,极大地节约了宿主机的存储资源。

通过一个 docker-compose.yml 的配置文件即把所有的容器都组织起来,以后可以非常方便地改变部署的结构。

docker-compose 配置文件

每个容器维护各自的 Dockerfile ,实现各自的构建过程,将后台每个部分都组件化,以后可以非常方便地对后台的摸个部分进行技术栈的迁移。如:可以将 Nginx 服务器换成 Apache 服务器,将 Python Flask Web 框架换成 Nodejs 框架,将关系型数据库 Mysqldb 换成其他非关系型数据库。而这些操作只需要简单修改一下对应部分的 Docker 配置文件就可以做到。