这几天,由于后端开发任务实在是太多,因此我打算自己用node写一个服务器,将所有的接口都调好之后写一个swagger文件给后端,以此来提高开发效率。
此次实践使用到的技术栈如下所示:
- 后端:
nodeJs express sequelize mysql2 swagger
- 前端:
antd react mobx6
通过本文,您可以获得以下经验:
- 如何在node中链接mysql数据库
- 如何在node中操作mysql数据库
- 如何配置swagger
- 如何生成swagger文件
- express的一些基本使用技巧和常见中间件
- 如何前后端配合实现一些常见操作
- ant design pro组件库中的proTable组件的使用
需求介绍
作为App后台管理人员,我希望能够添加用户账户以方便在合适的时间给所添加的账户发送邮件。
- 账户包括用户的邮箱地址和用户名称。
- 账户可以被管理人员修改或删除。
- 账户具有激活/关闭状态(根据状态的不同可以选择性的发送或者不发送邮件给此账户)
- 所有的账户都应该展示在一个可以分页查询的列表中,并且默认按照修改/新增时间倒序排列。
后端
根据需求将数据流设计如下所示:
1. 搭建后端项目结构
mkdir news
cd news
npm init -y
# npm i express@4.18.2 lodash@4.17.21 mysql2@3.9.1 sequelize@6.35.2 swagger-jsdoc@6.2.8 swagger-ui-express@5.0.0 yamljs@0.3.0
mkdir app config
mkdir app/controllers app/models app/routes app/services
touch app.js swagger.js api-spec.yaml
touch config/database.js
touch app/controllers/mailController.js
touch app/models/index.js app/models/suitesBooking.js
touch app/routes/mail.js
touch app/services/mailService.js
2. 构建主入口文件app.js
在主入口文件中搭建express的基本结构,以中间件的方式使用router和swagger;并设置端口号。
整体的代码如下所示:
const express = require('express');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./swagger');
// const YAML = require('yamljs');
// const swaggerDocument = YAML.load('./api-spec.yaml');
const mailRouter = require('./app/routes/mail');
const app = express();
const port = 7777;
// 使用swaggerUi提供可视化界面
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// 解析JSON有效载荷
app.use(express.json());
// 启用CORS
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use('/', mailRouter);
// 所有路由之后的错误处理中间件
app.use((err, req, res, next) => {
// 检查错误对象是否是一个常规错误或HTTP错误
if (err instanceof Error) {
// 处理常规错误
console.error('Error:', err.message);
res.status(500).json({
code: 500,
success: false,
msg: 'Internal Server Error',
});
} else if (typeof err === 'number') {
// 处理HTTP错误码
res.status(err).json({
code: err,
success: false,
msg: http.STATUS_CODES[err] || 'Unknown Error',
});
} else {
// 处理其他类型的错误
console.error('Unknown Error:', err);
res.status(500).json({
code: 500,
success: false,
msg: 'Internal Server Error',
});
}
});
// 启动服务器
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
将上述代码拆分理解
导入模块:
const express = require('express');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./swagger');
const mailRouter = require('./app/routes/mail');
- 导入必要的模块:
express
用于创建服务器,swaggerUi
用于提供 Swagger UI,swaggerSpec
用于 Swagger API 规范,mailRouter
用于处理邮件相关的路由。
创建 Express 应用:
const app = express();
const port = 7777;
- 创建 Express 实例并设置端口号。
提供 Swagger UI:
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
- 使用 Swagger UI 中间件,在 '/api-docs' 路径上提供 API 文档。
解析 JSON 载荷:
app.use(express.json());
- 使用
express.json()
中间件解析传入请求中的 JSON 载荷。
启用 CORS:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
- 启用跨域资源共享 (CORS) 以允许来自任何来源的请求。
使用 Router 处理邮件路由:
app.use('/', mailRouter);
- 使用
mailRouter
处理以 '/' 开头的路由。
错误处理中间件:
app.use((err, req, res, next) => {
// 用于所有路由的错误处理中间件
// 检查错误类型并发送适当的响应
// ...
});
- 为所有路由实现错误处理中间件。区分常规错误、HTTP 错误码和其他类型的错误。
启动服务器:
app.listen(port, () => {
console.log(`服务器正在运行,访问地址:http://localhost:${port}`);
});
- 启动服务器并在控制台记录监听的端口。
上面的代码对应的环节如下所示:
3. 通过yaml写swagger
从上面的代码中可以看出来,这里使用了两种注释swagger的方式。一种是通过js配置一个对象;另外一种是通过读取yaml文件中的内容。两种方式都是可以的,但是在这个样例中我主要使用的是后者。因此在此只放出api-spec.yaml
文件的内容而不进一步加以解释。
openapi: 3.0.0
info:
title: Mail API
version: '1.0'
paths:
/healthcode/pc/email/preSutBooking/save:
post:
tags:
- Mail
summary: Update stored data
description: Update the subjectDailyReport and sendTime in the stored data
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
subjectDailyReport:
type: string
sendTime:
type: string
format: date-time
responses:
200:
description: Successfully updated the data
content:
application/json:
schema:
type: object
properties:
code:
type: integer
success:
type: boolean
data:
type: object
msg:
type: string
4. 通过swagger.js向外暴露配置对象
如果想要通过注释路由的方式生成swagger文档,那么就需要首先构建一个swagger模型,这通常可以通过在js文件中向外暴露出一个配置对象来完成,而这个对象在入口文件(app.js)中被使用。
swagger.js中的代码
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerDefinition = {
openapi: '3.0.0', // 使用OpenAPI Specification 3.0版本
info: {
title: 'Mail模块新增功能相关API文档',
version: '1.0.0',
description: 'Mail模块新增Preference - Suites booking表单和Recipient - Suites booking列表相关的API',
},
servers: [
{
url: 'http://localhost:7777',
description: '前端Mock数据格式',
},
{
url: 'http://localhost:7777',
description: '前端Mock数据格式2',
},
],
tags: [ // 定义标签
{
name: 'Mail', // 标签名称
description: 'Mail模块新增操作', // 标签描述
},
// 其他标签...
]
};
const options = {
swaggerDefinition,
apis: ['./app/routes/*.js'], // 指向API文档的路径
};
const swaggerSpec = swaggerJSDoc(options);
module.exports = swaggerSpec;
代码解释
导入模块:
const swaggerJSDoc = require('swagger-jsdoc');
- 导入用于生成 Swagger 文档的
swagger-jsdoc
模块。
定义 Swagger 文档基本信息:
const swaggerDefinition = {
openapi: '3.0.0',
info: {
title: 'Mail 模块新增功能相关 API 文档',
version: '1.0.0',
description: 'Mail 模块新增 Preference - Suites booking 表单和 Recipient - Suites booking 列表相关的 API',
},
servers: [
{
url: 'http://localhost:7777',
description: '前端 Mock 数据格式',
},
{
url: 'http://localhost:7777',
description: '前端 Mock 数据格式2',
},
],
tags: [
{
name: 'Mail',
description: 'Mail 模块新增操作',
},
// 其他标签...
]
};
- 定义了 Swagger 文档的基本信息,包括标题、版本、描述、服务器地址等。
配置 Swagger-jsdoc 的选项:
const options = {
swaggerDefinition,
apis: ['./app/routes/*.js'],
};
- 配置
swagger-jsdoc
的选项,指定 Swagger 文档定义和 API 路径。
生成 Swagger 规范对象:
const swaggerSpec = swaggerJSDoc(options);
- 使用配置选项生成 Swagger 规范对象。
导出 Swagger 规范对象:
module.exports = swaggerSpec;
- 将生成的 Swagger 规范对象导出供其他模块使用。
4. 链接数据库
首先需要保证机器上已经装好了mysql server(也就是保证下图所示的环节能够正常运行),在此基础之上通过一个database.js
文件向外暴露出一个连接函数,此函数被调用之后返回sequelize
对象(如果成功表示已经链接到数据库),而sequelize
对象可以用来定义model
。
MySql server对应的环节如下所示:
config/database.js文件内容如下:
const Sequelize = require('sequelize');
// 请根据你的数据库配置填写以下信息
const dbName = 'db3'; // 数据库名
const dbUser = 'root'; // 数据库用户
const dbPassword = 'Laotie666@'; // 数据库密码
const dbHost = 'localhost'; // 数据库主机地址
const dbDialect = 'mysql'; // 数据库类型,可以是mysql、postgres等
async function initializeDatabase() {
let sequelize = {};
try {
const url = `mysql://${dbUser}:${dbPassword}@${dbHost}`;
sequelize = new Sequelize(url, {
host: dbHost,
dialect: dbDialect,
logging: false,
});
// 创建数据库(如果不存在)
await sequelize.query(`CREATE DATABASE IF NOT EXISTS ${dbName};`);
console.log('Database checked/created successfully');
// 关闭初始连接
await sequelize.close();
// 重新连接到新创建的数据库
sequelize = new Sequelize(dbName, dbUser, dbPassword, {
host: dbHost,
dialect: dbDialect,
logging: false,
});
} catch(e){
throw new Error(e.message);
} finally {
return sequelize;
}
}
module.exports = initializeDatabase;
上述代码通过预制的账户和密码链接对应的数据库,如果密码错误则无法连接;如果数据库不存在就新建一个。
通过这个函数调用,就将mysql这个数据库的处理映射到了sequelize对象上去,因此操作sequelize就相当于操作数据库。
上面的代码对应的环节如下所示:
5. 生成表 -- 获得数据库中表对应的js中的映射对象
我们不能将数据直接存储在数据库中。因此除了代表数据库的sequelize对象,还需要代表“表”的对象。在本示例的技术选型中,“表”这个概念对应的是model
,或者称为model层。
app/models/index.js中的内容
作为model的入口文件,这个文件主要是将所有的model收集起来向外暴露出去,因此,文件的内容为:
const SuitesBooking = require('./suitesBooking');
module.exports = {
SuitesBooking,
};
app/models/suitesBooking.js中的内容
如果说model表示的是“表”的概念,那我们定义表示“表”的对象的时候,显而易见需要定义每一列的信息,因此,这个文件中的内容为:
const Sequelize = require('sequelize');
const initializeDatabase = require('../../config/database');
const SuitesBooking = async () => {
const sequelize = await initializeDatabase();
let temp = '';
const SuitesBooking = await sequelize.define('suitesbooking', {
recipientName: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
mailAddress: {
type: Sequelize.STRING,
allowNull: true,
unique: false,
},
status: {
type: Sequelize.INTEGER,
allowNull: true,
unique: false,
},
id: {
type: Sequelize.STRING,
allowNull: true,
unique: false,
primaryKey: true,
defaultValue: (+new Date())+'',
},
},{
hooks: {
// beforeUpdate: (instance, options) => {
// console.log('options.individualHooks:', instance.getDataValue('updatedAt'))
// console.log('options.individualHooks:', options.individualHooks)
// // 在更新之前执行的操作
// if (options.individualHooks) {
// temp = instance.getDataValue('updatedAt')
// console.log('temp:', temp)
// }
// },
beforeBulkUpdate: ( options) => {
// status改变的时候是不需要更行updateTime的
if (options.individualHooks) {
options.fields = ['status'];
options.attributes = {status: options.attributes.status}
}
},
// afterUpdate: (instance, options) => {
// console.log('options.individualHooks:', instance.getDataValue('updatedAt'))
// console.log('options.individualHooks:', temp)
// console.log('options.individualHooks:', options.individualHooks)
// // 在更新之前执行的操作
// if (options.individualHooks) {
// instance.setDataValue('temp', instance.getDataValue('updatedAt'));
// }
// },
}
});
await sequelize.sync(); // important
return sequelize.models.suitesbooking;
}
module.exports = SuitesBooking;
上述代码其实就是使用之前生成的sequelize
定义了一个model对象,其实就是名为suitesbooking
的一张表。然后暴露了出去。这张表有recipientName、mailAddress、status、id四列,并对每一列的数据类型,是否可为空,是否唯一,是否为主键,默认值等做了详细的设置。
在此基础之上通过内置hook实现了,status值更新的时候内置列updateAt的值不发生更新的定制要求。
不过需要注意的是一定要在此函数返回之前调用await sequelize.sync(); // important
以保证与数据库信息的同步。
上面的代码对应的环节如下所示:
6. 操作表
使用model可以对数据库中的一张表。对表的操作一般就是增、删、改、查,我们可以针对每一项特殊的需求都封装一个函数并暴露出去。这层对model的操作被称为servies层。
因此,文件app/services/mailService.js中的全部代码为:
// 导入模块
const { SuitesBooking } = require('../models');
/**
* 获取 Suites Booking 列表
* @param {object} body - 请求体
* @param {object} query - 查询参数
* @returns {object} - 响应数据
*/
const getpreSutBookingList = async (body, query) => {
try {
// 解构查询参数,设置默认值
const { current = 1, size = 10 } = query;
// 获取 SuitesBooking 模型
const SuitesBookingModel = await SuitesBooking();
// 使用 Sequelize 的 findAndCountAll 方法进行分页查询
const { count, rows } = await SuitesBookingModel.findAndCountAll({
limit: size, // 每页数量
offset: (current - 1) * size, // 起始位置
order: [['updatedAt', 'DESC']], // 根据 updatedAt 列降序排序
});
// 将查询结果进行格式化
const formattedRecords = rows.map((record) => ({
id: record.id,
recipientName: record.recipientName,
mailAddress: record.mailAddress,
status: record.status,
createTime: +new Date(record.createdAt),
updateTime: +new Date(record.updatedAt),
}));
// 构造响应数据
const responseData = {
code: 200,
success: true,
data: {
records: formattedRecords, // 使用格式化后的结果作为 records
total: count,
size: size,
current: current,
searchCount: true,
pages: Math.ceil(count / size), // 总页数
},
msg: "SUCCESS",
};
return responseData;
} catch (error) {
// 错误处理逻辑
throw error;
}
};
/**
* 添加新的 Suites Booking 记录
* @param {string} mailAddress - 邮箱地址
* @param {string} recipientName - 收件人姓名
* @returns {object} - 响应数据
*/
const addNew2preSutBookingList = async (mailAddress, recipientName) => {
try {
const _ = await SuitesBooking();
const item = await _.create({ mailAddress, recipientName });
const rst = {
code: 200,
success: true,
data: item,
msg: "Success",
};
return rst;
} catch (error) {
// 错误处理逻辑
throw error;
}
};
/**
* 更新 Suites Booking 记录
* @param {string} id - 记录ID
* @param {string} mailAddress - 邮箱地址
* @param {string} recipientName - 收件人姓名
* @returns {object} - 响应数据
*/
const updatepreSutBookingList = async (id, mailAddress, recipientName) => {
try {
const _ = await SuitesBooking();
const item = await _.findOne({ where: {id} });
const updatedRecord = await item.update({
mailAddress,
recipientName,
});
const rst = {
code: 200,
success: true,
data: updatedRecord,
msg: "Success",
};
return rst;
} catch (error) {
// 错误处理逻辑
throw error;
}
};
/**
* 更改 Suites Booking 记录状态
* @param {string} id - 记录ID
* @param {string} status - 状态
* @returns {object} - 响应数据
*/
const chgStatuspreSutBookingList = async (id, status) => {
try {
const _ = await SuitesBooking();
const updatedRecord = await _.update({status}, { where: {id}, individualHooks: true });
const rst = {
code: 200,
success: true,
data: updatedRecord,
msg: "Success",
};
return rst;
} catch (error) {
// 错误处理逻辑
throw error;
}
};
/**
* 删除 Suites Booking 记录
* @param {string} id - 记录ID
* @returns {object} - 响应数据
*/
const deletepreSutBookingList = async (id) => {
try {
const _ = await SuitesBooking();
const item = await _.findOne({ where: {id} });
let updatedRecord = null;
if (item) {
// 删除实例
updatedRecord = item.destroy();
}
const rst = {
code: 200,
success: true,
data: updatedRecord,
msg: "Success",
};
return rst;
} catch (error) {
// 错误处理逻辑
throw error;
}
};
// 导出模块
module.exports = {
getpreSutBookingList,
addNew2preSutBookingList,
updatepreSutBookingList,
chgStatuspreSutBookingList,
deletepreSutBookingList,
};
这个文件向外暴露出五种操作这张表的方法:以分页的方式查询表、向表中添加一条新的记录、通过id修改某条记录、通过id修改某条记录的状态值、通过id删除某条记录。
上面的代码对应的环节如下所示:
显然这五个函数或者方法会被五个不同的路由的回调处理函数所使用,而路由的回调函数所在的层被称为:controller层。
7. 路由的回调函数
将每条路由的回调函数放在一起统一管理,而与mail功能相关的路由的回调函数放在app/controllers/mailController.js
文件中,这是这个文件的所有内容:
// 导入模块
const mailService = require('../services/mailService');
/**
* 获取 Suites Booking 列表
* @param {object} req - 请求对象
* @param {object} res - 响应对象
*/
const preSutBookinglist = async (req, res) => {
try {
// 获取请求体中的数据
const { body, query } = req.body;
// 调用服务层函数获取响应数据
const responseData = await mailService.getpreSutBookingList(body, query);
// 发送响应
res.json(responseData);
} catch (error) {
// 错误处理逻辑
console.error('Error:', error);
res.status(500).json({
code: 500,
success: false,
msg: 'Internal Server Error',
});
}
};
/**
* 添加新的 Suites Booking 记录
* @param {object} req - 请求对象
* @param {object} res - 响应对象
*/
const addNew2preSutBookingList = async (req, res) => {
try {
// 获取请求体中的数据
const { mailAddress, recipientName } = req.body;
// 调用服务层函数获取响应数据
const responseData = await mailService.addNew2preSutBookingList(mailAddress, recipientName);
// 发送响应
res.json(responseData);
} catch (error) {
// 错误处理逻辑
console.error('Error:', error);
res.status(500).json({
code: 500,
success: false,
msg: 'Internal Server Error',
});
}
};
/**
* 更新 Suites Booking 记录
* @param {object} req - 请求对象
* @param {object} res - 响应对象
*/
const updatepreSutBookingList = async (req, res) => {
try {
// 获取请求体中的数据
const { mailAddress, recipientName, id } = req.body;
// 调用服务层函数获取响应数据
const responseData = await mailService.updatepreSutBookingList(id, mailAddress, recipientName);
// 发送响应
res.json(responseData);
} catch (error) {
// 错误处理逻辑
console.error('Error:', error);
res.status(500).json({
code: 500,
success: false,
msg: 'Internal Server Error',
});
}
};
/**
* 更改 Suites Booking 记录状态
* @param {object} req - 请求对象
* @param {object} res - 响应对象
*/
const chgStatuspreSutBookingList = async (req, res) => {
try {
// 获取请求体中的数据
const { status, id } = req.body;
// 调用服务层函数获取响应数据
const responseData = await mailService.chgStatuspreSutBookingList(id, status);
// 发送响应
res.json(responseData);
} catch (error) {
// 错误处理逻辑
console.error('Error:', error);
res.status(500).json({
code: 500,
success: false,
msg: 'Internal Server Error',
});
}
};
/**
* 删除 Suites Booking 记录
* @param {object} req - 请求对象
* @param {object} res - 响应对象
*/
const deletepreSutBookingList = async (req, res) => {
try {
// 获取查询字符串中的数据
const { id } = req.query;
// 调用服务层函数获取响应数据
const responseData = await mailService.deletepreSutBookingList(id);
// 发送响应
res.json(responseData);
} catch (error) {
// 错误处理逻辑
console.error('Error:', error);
res.status(500).json({
code: 500,
success: false,
msg: 'Internal Server Error',
});
}
};
// 导出模块
module.exports = {
preSutBookinglist,
addNew2preSutBookingList,
updatepreSutBookingList,
chgStatuspreSutBookingList,
deletepreSutBookingList,
};
用一句话总结就是,解析请求携带的载荷或者查询字符串然后将数据库中的操作结果封装之后返回给客户端。
上面的代码对应的环节如下所示:
8. 完成路由
现在,就可以闭合整个过程的最后一环了,那就是设置路由表,这样浏览器就和数据库链接到了一起,所有的路由放在route层,处于controller层之上。
app/routes/mail.js
中的内容如下:
const mailController = require('../controllers/mailController.js');
const express = require('express');
const router = express.Router();
/**
* @swagger
* /save:
* post:
* tags: [Mail]
* summary: Update stored data
* description: Update the subjectDailyReport and sendTime in the stored data
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* subjectDailyReport:
* type: string
* sendTime:
* type: string
* format: date-time
* responses:
* 200:
* description: Successfully updated the data
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* success:
* type: boolean
* data:
* type: object
* msg:
* type: string
*/
router.post('/save', mailController.addNew2preSutBookingList);
/**
* @swagger
* /update:
* post:
* tags: [Mail]
* summary: update Mail Record data
* description: update the data of a mail record based on the provided data
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* recipientName:
* type: string
* mailAddress:
* type: string
* status:
* type: integer
* createTime:
* type: integer
* updateTime:
* type: integer
* responses:
* 200:
* description: Successfully deleted the mail record
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* description: HTTP status code
* success:
* type: boolean
* description: Request success indicator
* data:
* type: object
* description: Empty object
* msg:
* type: string
* description: Response message
*/
router.post('/update', mailController.updatepreSutBookingList);
/**
* @swagger
* /chgStatus:
* post:
* tags: [Mail]
* summary: Change Mail Record Status
* description: Change the status of a mail record based on the provided data
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* recipientName:
* type: string
* mailAddress:
* type: string
* status:
* type: integer
* createTime:
* type: integer
* updateTime:
* type: integer
* responses:
* 200:
* description: Successfully deleted the mail record
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* description: HTTP status code
* success:
* type: boolean
* description: Request success indicator
* data:
* type: object
* description: Empty object
* msg:
* type: string
* description: Response message
*/
router.post('/chgStatus', mailController.chgStatuspreSutBookingList);
/**
* @swagger
* /delete:
* get:
* tags: [Mail]
* summary: Delete Mail Record
* description: Delete a mail record by ID
* parameters:
* - in: query
* name: id
* schema:
* type: string
* required: true
* description: ID of the mail record to delete
* responses:
* 200:
* description: Successfully deleted the mail record
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* description: HTTP status code
* success:
* type: boolean
* description: Request success indicator
* data:
* type: object
* description: Empty object
* msg:
* type: string
* description: Response message
*/
router.get('/delete', mailController.deletepreSutBookingList);
/**
* @swagger
* /pageList:
* post:
* tags: [Mail]
* summary: Retrieve Mail Records Pagelist
* description: Retrieve a pagelist related to suites booking in the stored data
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* body:
* type: object
* query:
* type: object
* properties:
* current:
* type: integer
* size:
* type: integer
* responses:
* 200:
* description: Successfully retrieved the pagelist
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* description: HTTP status code
* success:
* type: boolean
* description: Request success indicator
* data:
* type: object
* properties:
* records:
* type: array
* description: List of mail records
* items:
* type: object
* properties:
* id:
* type: string
* description: Record ID
* recipientName:
* type: string
* description: Recipient's name
* mailAddress:
* type: string
* description: Email address
* status:
* type: integer
* description: Status of the mail record
* createTime:
* type: integer
* description: Timestamp of record creation
* updateTime:
* type: integer
* description: Timestamp of record update
* total:
* type: integer
* description: Total number of records
* size:
* type: integer
* description: Number of records per page
* current:
* type: integer
* description: Current page number
* searchCount:
* type: boolean
* description: Search count indicator
* pages:
* type: integer
* description: Total number of pages
* msg:
* type: string
* description: Response message
*/
router.post('/pageList', mailController.preSutBookinglist);
module.exports = router;
由于层层封装,这一层就比较简洁了。需要额外说明的是:swagger注释在这一层,因此在route文件中出现了大量的需要格式化的注释内容。
上面的代码对应的环节如下所示:
9. 试运行
在终端使用node app.js
或者nodemon app.js
开启后端服务,然后在浏览器中访问api-docs路径就可以看到swagger生成的api文档了(如下图所示)。等前端调试完成之后,将此文档直接分享给后端同学,只要后端按照swagger的说明进行开发,这样就实现了前后端分离。