软件设计文档 (SD)
注:Tiny Hippo 小河马点餐系统同时也是该小组成员在 2018 年春季学期中山大学系统分析与设计课程的大作业。按照本课程作业要求在 SD 文档开头点明。
1. Client
1.1 点餐系统
1.1.1 技术选型及理由
微信小程序框架
我们选择了微信小程序开发框架作为用户点餐系统的前端框架。
- 小程序框架提供了自己的视图层描述语言 WXML 和 WXSS,以及基于 JavaScript 的逻辑层框架,容易上手;
- 在视图层与逻辑层间提供了数据传输和事件系统,支持响应式、双向数据绑定等特性,可以让数据与视图非常简单地保持同步:当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新,因而能够很好地支持丰富的用户交互体验支持,可以让开发者方便的聚焦于数据与逻辑上;
- 框架还管理了整个小程序的页面路由,可以做到页面间的无缝切换,并给以页面完整的生命周期,开发者需要做的只是将页面的数据,方法,生命周期函数注册进框架中,其他的一切复杂的操作都交由框架处理;
- 此外,框架还提供了微信风格的组件和丰富的api,实现了尽量以简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。
- 4g网络和移动支付的普及推动了微信小程序作为点餐系统的应用发展,使用微信小程序作为点餐系统对用户更加快捷和便利。
基于以上理由,选择微信小程序作为前端框架对用户和开发者都十分友好。
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文件,分别处理逻辑、配置、结构和样式;根据页面的划分,得到页面模块的细分:
五个页面模块包括:
- 授权登录,获取用户的信息
- 主菜单,用户浏览上架菜品以及点餐
- 今日推荐, 商家推出的推荐菜单,每个菜单内有不同菜式
- 今日推荐卡片细节,推荐菜单内的菜式介绍
- 购物车,修改购物车内容以及下单和支付
此外还定义了三个复杂的组件,构成组件模块。 三个组件包括:
- 今日推荐的卡片,封装了卡片的样式结构以及交互事件
- 滑动框,自动滑动图片,使页面更美观
- 订单item,封装订单和购物车条目的样式结构以及交互事件
加上小程序的全局文件,包括逻辑文件,配置文件,样式文件,构成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:一个类MVVM的渐进式JavaScript框架
- vue-router:单页应用前端路由
- vue-material:UI框架
- webpack:项目构建打包
- axios:ajax异步请求工具库
Vue
- 当下前端开发中的前沿技术框架,Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合;
- 另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动;
- Vue 支持组件化开发,其独特的 .vue 文件可以将组件的HTML/CSS/JS代码整合在一个文件中,有利于进行代码管理;
- Vue 也支持响应式、双向数据绑定等 MVVM 特性,能够很好地支持丰富的用户交互体验。
vue-router
- vue-router 和 vuex 是官方提供的前端路由和数据状态管理的库,能够完美地结合 Vue 框架,实现前端路由和应用数据状态管理,代码结构清晰,程序复杂度降低。
vue-material
- Google material design 风格组件在 vue 框架下的实现,是一套优雅的 UI 框架;
- 该框架封装了多种 UI 组件,使用方便,支持按需加载,编程时可以只引用需要的组件,无需引入整个库,减少了前端代码体积。
webpack
- 当下前端最流行的构建打包工具,利用 webpack 搭配相应的 loader,可以在前端项目中使用 es6 进行开发,效率更高,开发完成后,webpack 还可以将代码编译到 es5 以兼容大部分浏览器;
- webpack 2优化了构建打包的算法,优化前端的模块加载,整合各个文件的代码、图片、字体、样式表等等,使得构建打包后的产品代码体积更小,也减少了这些资源在传输给用户时的时间损耗。
axios
- 基于 promise 的工具库,使用这个库可以给服务端发送 ajax 请求;
- 由于这个库基于 promise,使得在处理异步请求的时候不需要嵌套回调函数,代码架构上有了极大优化,可维护性更高;
- 简单、轻量,不会影响前端的加载速度。
1.2.2 架构设计
商家管理系统主要包括应用界面、业务逻辑和产品构建等开发内容,还应提供配置项用于构建项目打包时的目标路径及服务器地址等信息,同时还要进行单元测试与集成测试。根据需要,项目确定必须有以下四个包:
- build:提供开发环境和构建项目的代码
- config:项目的webpack打包配置
- src:项目的源代码,主要都在src中进行开发
- test:单元测试等系列测试代码
- server:处理web端和服务器数据传递涉及的跨域等问题
build
包根据config
中的配置调整webpack打包规则,将src
中的源码进行打包。另外,项目构建后,还会生成dist
包,为构建后的产品文件。
1.2.3 模块划分
根据业务逻辑和UI设计图,将餐厅管理系统前端分为6个页面:
- Login:餐厅管理者登录页面
- About:餐厅简介及团队简介页面
- Dashboard:餐厅环境展示页面
- Menu:菜单展示及菜品的增删改查管理页面
- Order:餐厅订单管理页面
商家管理系统各界面渲染、路由控制等操作都在前端完成,采取组件化开发的思想,将每个页面抽象为一个组件,页面中也可以包含子组件。
- 系统的各界面全部整合在 section 中
- 系统的界面跳转由前端路由 router 进行控制;
- 系统的数据由位于全局位置的 store 进行管理,实现视图和数据的分离;
- 系统的每个界面可能还会使用到公共的资源,例如公共组件、公共服务、工具函数等等,将这些公共部分分别抽离成 components、service、utils等子包;
- assets 存放前端的静态资源,例如图片、图标和字体等。
综合考虑使用的技术框架,并根据应用需要的组件,可以得到详细的项目结构如下所示。如果过需要新增界面、功能,引入新组件,也可以很方便地将新内容添加到项目中去。
├── 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
面向对象的基本思想是使用类, 对象, 继承, 封装, 消息等基本概念进行程序设计。 面向对象方法的三个基本特征:
- 封装性:将对象的实现细节隐藏起来, 通过一些公共的接口方法来供外部调用对象的功能
- 继承性:是面向对象实现的的重要手段,子类继承父类, 子类直接获得父类的非private属性和方法
- 多态性:子类对象可以赋值给父类对象引用, 但运行的时候仍然表现出子类的行为特征,同一个类型的对象在执行同一个方法时, 可能表现出不同的特征
在项目中,对各对象、组件进行了封装,只留给外部接口,调用它的其他组件不用知道其内部的具体实现方式,每个组件拥有自己的方法与行为,在解决整个事务的某问题中行使具体职责。
2. Server
2.1 技术选型理由
后台主要技术栈是: Nginx + Python Flask + Redis + Mysql 完全 Docker 化, 理由如下:
Nginx:
- 后台需要同时处理微信小程序前端和 Vue 框架写出来的商家管理 Web 前端两种 Client 的请求。
- 使用 Nginx 相对于 Apache 更容易配置端口转发,也容易实现前后端分离开发。
- Nginx 是如今业界较为受欢迎的服务器程序之一。
Python Flask:
- 和 Python 内部的服务器框架对比:相对比于 Django, Flask 框架更加轻量;相比于 tornado,Flask 处理异步消息更加友好;相比于 webpy, Flask 的社区维护更加持续。
- 和 Java, C++ 语言相比较,动态编程语言对于开发这种小型的服务器的迭代速度更快。
- 和 nodejs 相比较,队伍中负责后台的同学熟悉 python 的同学更多,学习成本更低。
Redis :
- 作为缓冲 Cache, 因为点餐菜单信息需要频繁修改,同时为了实现多人点餐,数据交换速率需要有保证。所以引入一个 Cache 来进行临时数据交互。
- 为了增加后台的服务性能,需要扩充 python flask 容器的个数,此时每个容器都对一个 Redis 中的数据进行操作,可以保证数据的一致性。
Mysql:
- 免费
- 后台成员对于 Mysql 有一定操作经验,降低学习成本
Docker:
- 和宿主机环境高度分离
- 简化部署流程
- 可扩展性高
- 业界主流
2.2 架构设计
整体是经典的 **Client-Sever ** 架构,以下为部署架构图,其中后台的每个部分均进行了 docker 化
使用 docker-compose 可以极大地简化我们的部署流程,加上使用 Makefile 辅助可以实现后台代码的一键部署。
当我们想要提高服务器的服务性能时,可以通过 docker 多开几个 Web Server 的容器示例,很方便地达到扩充的目的。
代码链接:
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 配置如下:
2.4.4 Service Oriented Architecture through Dockerize
Service Oriented Architecture 需要达到可重复使用, 粒度, 模块性, 可组合型, 对象化原件, 构件化以及具交互操作性,这些通过 docker 化来实现。
通过容器化部署方案,可以使得各容器内的环境分离开来,减少了代码环境强耦合的影响。同时可以使得每个容器各自安装的依赖最小化,极大地节约了宿主机的存储资源。
通过一个 docker-compose.yml
的配置文件即把所有的容器都组织起来,以后可以非常方便地改变部署的结构。
每个容器维护各自的 Dockerfile
,实现各自的构建过程,将后台每个部分都组件化,以后可以非常方便地对后台的摸个部分进行技术栈的迁移。如:可以将 Nginx 服务器换成 Apache 服务器,将 Python Flask Web 框架换成 Nodejs 框架,将关系型数据库 Mysqldb 换成其他非关系型数据库。而这些操作只需要简单修改一下对应部分的 Docker 配置文件就可以做到。