背景
本文结合课程所学知识对工程实践项目——抽奖系统,进行系统分析和设计,总结其中的软件结构特点。
技术选型和运行环境
技术选型 Node.js + Express + MongoDB
- Why Node.js?
- Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
- Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
从 web 服务器开发的角度来看,Node 有很多好处:
- 卓越的性能表现!
- 代码还是熟悉的老伙伴JavaScript。
- Node 包管理工具(NPM)提供了数十万个可重用的工具包。
- Node.js 是可移植的,可运行于Windows、macOS、Linux等。
- 有活跃的第三方生态系统和开发者社区。
- Why Express?
Express 是最流行的 Node 框架,是许多其它流行 Node 框架 的底层库。它提供了以下机制:
- 为不同 URL 路径中使用不同 HTTP 动词的请求(路由)编写处理程序。
- 集成了“视图”渲染引擎,以便通过将数据插入模板来生成响应。
- 设置常见 web 应用设置,比如用于连接的端口,以及渲染响应模板的位置。
- 在请求处理管道的任何位置添加额外的请求处理“中间件”。
- 极简风格,通过各类兼容的中间件包解决了几乎所有的 web 开发问题。
- Why MongoDB?
- MongoDB 是一个通用的、基于文档的、分布式的数据库,为云计算时代的现代应用程序开发者而生,没有数据库比 MongoDB 在应用开发效率上更加高效。
- MongoDB 是一种文档数据库,也就是说 MongoDB 用类似 JSON 格式的文档来存储数据。目前普遍认为 JSON 格式是理解和存储数据最自然的方式,JSON 格式比传统的关系数据模型有更强大的数据表达能力。
运行环境
Node 可以在 Windows、macOS、Linux 的诸多发行版本或 Docker 等环境运行。Express 运行在 Node 环境中,因此可运行 Node 的平台均可运行 Express。
设计方案
MVC 架构/中介者模式
-
MVC 架构:
- Model(模型)用来封装核心数据和功能,它独立于特定的输出表示和输入行为,是执行某些任务的代码。
- View(视图)用来向用户显示信息,它获得来自模型的数据,决定模型以什么样的方式展示给用户。
- Controller(控制器)作用于模型和视图上,控制数据流向模型对象,并在数据变化时更新视图。
-
中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。采用“中介者模式”可以大大降低对象之间的耦合性,提高系统的灵活性。
Tips: 从面向对象的设计来看,MVC 是对象组合的综合应用;从设计模式的角度看,Controller(控制器)是 Model(模型)和 View(视图)之间的中介者(Mediator),是典型的中介者模式。
本项目也是基于 MVC 架构进行设计和开发,具体的设计参见下文的关键视图,在本项目中,模型、视图和控制器三者的具体作用如下:
- 模型:可以用来查找、创建、更新和删除特定类型的对象。
- 控制器:从模型中获取请求的数据,创建一个 HTML 页面显示出数据,并将页面返回给用户,以便在浏览器中查看。
- 视图(模板):供控制器用来渲染数据。
Tips: MongoDB 数据库中,每个模型都映射至一组文档。
客户-服务的架构风格
本项目采用客户-服务的架构风格,也可描述为 Client/Server(C/S)和 Browser/Server(B/S)架构。客户-服务模式的架构风格是指客户代码通过请求和应答的方式访问或者调用服务代码。在本项目中,请求和应答指的是 HTTP 协议中的 Request 和 Response,具体表现为:
- 对于管理员用户,通过浏览器访问该系统,浏览器发出 HTTP 请求,该系统返回 HTTP 响应;
- 对于普通用户,通过微信 APP 访问该系统,微信 APP 发出 HTTP 请求,该系统返回 HTTP 响应。
客户-服务模式的架构风格具有典型的模块化特征,降低了系统中客户和服务构件之间耦合度,提高了服务构件的可重用性。
接口 API
本项目前后端接口调用是通过路由层来实现的,路由把需要支持的请求(以及请求 URL 中包含的任何信息)转发到适当的控制器函数,可以分为三大部分:主页、奖品和抽奖项,具体的URL和对应的控制器回调函数见下述代码:
//主页
router.get('/', home_controller.index);
router.get('/login', home_controller.login_get);
router.post('/login', home_controller.login_post);
router.get('/logout', home_controller.logout);
//奖品
router.get('/prizes', prize_controller.prize_list);
router.get('/prize/:id', prize_controller.prize_detail);
router.get('/prize/create', prize_controller.prize_create_get);
router.post('/prize/create', prize_controller.prize_create_post);
router.get('/prize/:id/delete', prize_controller.prize_delete_get);
router.post('/prize/:id/delete', prize_controller.prize_delete_post);
//抽奖项
router.get('/projects', project_controller.project_list);
router.get('/project/create', project_controller.project_create_get);
router.post('/project/create', project_controller.project_create_post);
router.get('/project/:id', project_controller.project_detail_get);
router.post('/project/:id', project_controller.project_detail_post);
router.get('/project/:id/update', project_controller.project_update_get);
router.post('/project/:id/update', project_controller.project_update_post);
router.get('/project/:id/delete', project_controller.project_delete_get);
router.post('/project/:id/delete', project_controller.project_delete_post);
router.get('/project/:id/qrcode', project_controller.project_qrcode);
关键视图
分解视图
依赖视图
执行视图
这里以创建抽奖项目为例给出执行视图:
实现视图
LotterySystem
├── app.js //应用入口
├── routes //路由
│ └── router.js
├── controllers //控制器
│ ├── homeController.js
│ ├── prizeController.js
│ └── projectController.js
├── models //模型
│ ├── prize.js
│ ├── project.js
│ └── user.js
├── views //视图
│ ├── error.pug
│ ├── index.pug
│ ├── layout.pug
│ ├── login.pug
│ ├── lottery.pug
│ ├── prize_delete.pug
│ ├── prize_detail.pug
│ ├── prize_form.pug
│ ├── prize_list.pug
│ ├── project_delete.pug
│ ├── project_form.pug
│ ├── project_list.pug
│ └── qrcode.pug
├── public //静态文件
│ ├── images
│ │ ├── Apple Watch Series 6.jpg
│ │ ├── Earphone Pro.jpg
│ │ ├── H445e0280b88f4f34a848238a848a687fw.png
│ │ ├── HUAWEI Mate 40 Pro+.jpg
│ │ ├── iPad Pro.jpg
│ │ ├── iPhone 12 Pro.jpg
│ │ ├── 小米 10 Ultra.jpg
│ │ └── 谢谢参与.jpg
│ ├── javascripts
│ │ ├── bootstrap.min.js
│ │ ├── bootstrap.min.js.map
│ │ ├── jquery-3.5.1.min.js
│ │ └── main.js
│ ├── qrcode
│ │ ├── 5fd738629f88ca228c49aa7e.jpg
│ │ ├── 5fd738c99f88ca228c49aa7f.jpg
│ │ └── 5fd9cbdbccc01d2180508f31.jpg
│ └── stylesheets
│ ├── bootstrap.min.css
│ ├── bootstrap.min.css.map
│ ├── login.css
│ ├── main.css
│ └── style.css
├── package.json //配置文件
└── README.md //简介
部署视图
数据库设计
- 用户模型(User)
const UserSchema = new Schema({
openid: {type: String, required: true},
projects: [{type: Schema.Types.ObjectId, ref: 'Project'}],
times: {type: [{type: Number}], required: true},
prizes: {type: [{type: [String]}], required: true}
});
- 奖品模型(Prize)
const PrizeSchema = new Schema({
name: {type: String, required: true, max: 100},
pictureUrl: {type: String, required: true}
});
- 抽奖项模型(Project)
const ProjectSchema = new Schema({
name: {type: String, required: true, max: 100},
times: {type: Number, min: 1, max: 10, required: true},
startTime: {type: Date, required: true},
finishTime: {type: Date, required: true},
prizeList: {type: [{type: Schema.Types.ObjectId, ref: 'Prize'}], required: true},
numList: {type: [{type: Number}], required: true},]
});
概念原型的核心工作机制
- 管理员用户:通过该抽奖系统快速便捷地创建和发布抽奖项目;
- 普通用户:通过微信扫码参与抽奖项目。