搭建:从零到一搭建数据库

打开MongoDB的下载地址,选择以下配置再下载。(右键复制下载地址)打开浏览器下载列表,查看当前文件的下载的地址为https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-5.0.6.tgz,再取消当前的下载。

安装

打开CMD工具,登录服务器。因为yum不存在Mongodb源,所以无法执行yum install mongodb安装MongoDB。通过以下方式手动安装MongoDB。
进入工具目录

1
2
bash
复制代码cd /tool

下载MongoDB

1
2
bash
复制代码wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-5.0.6.tgz

解压MongoDB

1
2
bash
复制代码tar -zxvf mongodb-linux-x86_64-rhel80-5.0.6.tgz -C /tool

重命名目录

1
2
bash
复制代码mv mongodb-linux-x86_64-rhel80-5.0.6 mongodb
配置

安装完毕还需手动增加一些配置,为了方便管理MongoDB相关文件,将这些配置文件都存放到mongodb文件夹中。
进入目录并创建文件夹与日志文件

1
2
bash
复制代码cd mongodb && mkdir data && mkdir log && touch log/mongodb.log

生效环境变量
设置软链接,可通过mongod快速调用命令。

1
2
3
bash
复制代码echo "export PATH=$PATH:/tool/mongodb/bin" >> ~/.bash_profile
source ~/.bash_profile

执行vim /tool/mongodb/mongodb.conf,加入以下内容。该文件作为MongoDB的配置文件,在执行mongod时必须指定配置文件的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bash
复制代码# 数据库
dbpath=/tool/mongodb/data
# 日志文件
logpath=/tool/mongodb/log/mongodb.log
# 使用追加的方式更新日志
logappend=true
# 端口
port=27017
# 以守护进程的方式运行MongoDB(创建服务器进程)
fork=true
# 启用用户验证
# auth=true
# 绑定服务IP(绑定127.0.0.1只能本机访问,若不指定则默认本地所有IP)
bind_ip=0.0.0.0

执行mongod -f /tool/mongodb/mongodb.conf启动MongoDB,输出以下信息表示开启成功。

1
2
3
4
bash
复制代码about to fork child process, waiting until server is ready for connections.
forked process: 2028
child process started successfully, parent exiting

执行ps -ef | grep mongod查看MongoDB状态,输出以下信息表示MongoDB正常运行。

1
2
3
bash
复制代码root 2028 1 0 22:44 ? 00:00:02 mongod -f /tool/mongodb/mongodb.conf
root 2281 2072 0 22:48 pts/3 00:00:00 grep --color=auto mongod

执行mongod –shutdown -f /tool/mongodb/mongodb.conf关闭MongoDB,输出以下信息表示关闭成功。

1
2
bash
复制代码killing process with pid: 2028

若无使用MongoDB的应用在运行,请关闭MongoDB,这样才能让服务器在CPU低占用的情况下保持稳定的性能。当用到MongoDB时再开启也不迟。

连接

在启动MongoDB的情况下,执行mongo连接MongoDB,输出以下信息表示连接成功。

1
2
3
4
5
bash
复制代码MongoDB shell version v5.0.6
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("a1cb8b2e-3ac2-4be9-8547-eb161c4dea74") }
MongoDB server version: 5.0.6

连接MongoDB后,终端会进入MongoDB模式,该模式可执行MongoDB相关命令,可查看MongoDB教程,在此不深入讲述了。
执行use admin切换到admin数据库,该数据库是MongoDB的默认数据库,用于管理用户权限。在上述配置文件中并未开启auth=true,因此创建root用户后需开启auth=true。
执行以下命令为admin数据库创建root用户。

1
2
bash
复制代码db.createUser({ user: "root", pwd: "123456", roles: [{ role: "root", db: "admin" }] })

输出以下信息表示创建成功。

1
2
3
4
5
6
7
8
bash
复制代码Successfully added user: {
"user": "root",
"roles": [{
"role": "root",
"db": "admin"
}]
}

执行vim /tool/mongodb/mongodb.conf将# auth=true的#去掉,重启MongoDB。重启动作包括mongod -f /tool/mongodb/mongodb.conf与mongod –shutdown -f /tool/mongodb/mongodb.conf。
执行以下命令,输出1表示用户登录成功。接着可基于MongoDB操作数据了。

1
2
3
bash
复制代码use admin
db.auth("root", "123456")
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
## 这步开始刚刚安装好mongodb,没有进行什么操作
use admin
#创建管理员账户(不能创建数据库)
db.createUser({ user: "admin", pwd: "123456", roles: [{ role: "userAdminAnyDatabase", db: "admin" }] })
# 创建超级管理员(可以创建数据库)
db.createUser({user:"root",pwd:"123456",roles:[{role: 'root', db: 'admin'}]})
# 退出
exit
# 登录admin 数据库
mongo admin
# root管理员登录
db.auth('root','123456');
# 创建test数据库
use test;
# 查看是否有test数据库,结果是没有的
show dbs
# 需要往里面添加数据,test才会生成
db.createCollection("list")
# 查看是否有test数据库,结果是有的
show dbs
#退出
exit
# 重新mongo服务
# 进行mongo 初始化认证,这步是Authentication failed 和connect failed问题解决步骤(这步开始是已经创建好root账号和创建test数据库)
mongod --auth --dbpath=/data/db --logpath=/data/log
# 登录admin 数据库
mongo admin -u root -p 123456
# 进入 test
use test;
# 创建test数据库的管理员
db.createUser({user: "user1", pwd: "123456", roles:["dbOwner"]})
# 退出
exit
# 可以登录,问题解决
mongo test -u user1 -p 123456


在不熟悉MongoDB命令的情况下,盲目操作数据库会引发很大风险,因此不建议在CMD工具中操作MongoDB。好在MongoDB官方提供一个界面漂亮操作流畅的GUI工具MongoDB Compass,不习惯命令操作MongoDB的同学可用Compass。打开Compass的下载地址,选择以下配置再下载。

打开Compass,发现需填写一条MongoDB标准URL才能连接数据库。

MongoDB标准URL是什么?它是MongoDB基于shell命令连接数据库的标准化URL,由以下参数组成。

1
2
txt
复制代码mongodb://username:password@host:port/database[?options]
参数 说明 描述
mongodb:// 协议 可理解成HTTP
username 账号 上述创建的root
password 密码 上述创建的root的密码123456
host 实例公有IP 云服务器IP
port 端口 默认27017
database 数据库 上述切换的admin数据库
options 配置 用得很少

那拼接起来的URL就是mongodb://root:123456@aaa.bbb.ccc.ddd:27017/admin,输入到Compass中就可连接数据库了。首次连接肯定失败,那是因为还未配置服务器的安全组。
每次新开端口都需在服务器中配置安全组,可回看第7章的个人官网-安全组配置那部分内容。这次增加27017端口,那在安全组中配置一个27017端口。再次连接MongoDB就可正常访问了。

注意配置文件bind_ip不要写127.0.0.1,不然只能服务器本机访问数据库了。
image.png

开发:初试小型接口系统

自行部署数据库相比选购第三方平台数据可省下好几千元。因为MongoDB文档的概念与JSON很相似,所以很多开发者都会把MongoDB作为首选数据库。
为了让MongoDB能派上用场,以下会在Node环境中结合mongoose开发一个小型接口系统,通过Nginx代理接口,输出一个可基于HTTPS访问的接口系统。设计该场景的目的是为了让你掌握以下技能,通过数据库、数据操作和接口调试三个突破点学习接口系统的搭建思路。当然代码内容不是主要,主要的是搭建思路的大体框架。

在本地环境创建项目data-base,使用koa创建Node服务,该服务启动后初始全部接口的路由实例,每个路由实例根据需求处理数据并使用mongoose读写数据库。(用nestjs体验更好)
该服务提供一个产品的增删改查功能,整体围绕着产品数据做读写操作。

初始项目

执行npm init创建package.json,执行以下命令安装项目所需依赖。

1
2
npm i cross-env dayjs koa koa-body koa-json koa-logger koa-onerror koa-router mongoose
npm i nodemon -D
启动服务

在根目录中创建src文件夹,在该文件夹中创建入口文件index.js。该文件使用koa创建Node服务,接入一些常见中间件升级koa的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Koa from "koa";
import { koaBody } from "koa-body";
import KoaJson from "koa-json";
import KoaLogger from "koa-logger";
import KoaOnerror from "koa-onerror";

// 创建实例
const app = new Koa();
KoaOnerror(app); // 美化错误参数
app.on("error", (err, ctx) => console.error("server error", err, ctx)); // 捕获错误

// 配置中间件
app.use(KoaLogger()); // 日志解析
app.use(koaBody({ multipart: true })); // Body解析
app.use(KoaJson()); // JSON解析

// 监听服务
app.listen(3000);
console.log("===Node服务已启动,监听端口3000 ===");

(需要在package.json 添加: “type”: “module”)

这样一个基础的Node服务就搭建好了。在package.json中指定scripts,加入开发环境与生产环境的启动命令。在开发环境中为了修改代码后能重启Node服务,使用nodemon代理node的启动功能,可回看第2章的监听脚本自动重启命令那部分内容。

1
2
3
4
5
6
7
{
"scripts": {
"clean": "rimraf coverage dist node_modules package-lock.json yarn.lock",
"deploy": "cross-env NODE_ENV=prod node --es-module-specifier-resolution=node src/index.js",
"start": "cross-env NODE_ENV=dev nodemon --es-module-specifier-resolution=node src/index.js"
}
}

执行npm start,输出以下信息表示启动成功。

1
Node服务已启动,监听端口3000
连接数据库

有了上述搭建好的MongoDB作为载体,整个接口系统在执行时产生的数据都经过MongoDB处理,处理MongoDB数据当然离不开mongoose。
mongoose是一个在Node环境中操作MongoDB对象模型的Npm模块,其封装了MongoDB对文档增删改查的方法。其文档已很完善,可查看mongoose文档,在此不深入讲述了。
还记得MongoDB标准URL的组成吗?连接数据库需用到它。

1
mongodb://username:password@host:port/database[?options]

为了方便管理这些参数,在src文件夹中创建app.config.js文件。在本地环境使用实例公有IP,在服务器环境使用127.0.0.1。

1
2
3
4
5
6
7
8
9
10
import { env } from "process";

export default {
mongodb: {
host: env.NODE_ENV === "dev" ? "your server host" : "127.0.0.1",
password: "123456",
port: 27017,
username: "root"
}
};

安装mongoose依赖;
在src文件夹中创建database/index.js文件。使用connect()连接数据库,使用connection()监听数据库状态事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Mongoose from "mongoose";

import AppConfig from "../app.config";

const { connect, connection } = Mongoose;
const { mongodb: { host, password, port, username } } = AppConfig;

connect(`mongodb://${username}:${password}@${host}:${port}/mall`, {
authSource: "admin",
useNewUrlParser: true,
useUnifiedTopology: true
});
connection.on("connected", () => console.log("数据库连接成功"));
connection.on("disconnected", () => console.log("数据库连接断开"));
connection.on("error", () => console.log("数据库连接异常"));

保存文件后会自动重启Node服务(需要引入database/index.js),输出以下信息表示启动成功。

1
2
Node服务已启动,监听端口3000
数据库连接成功
定义数据

mongoose中有三个很重要的概念,分别是Schema、Model和Entity。(Schema 用来定义数据结构和规则,Model 用来执行数据库操作,Entity 则是 Model 的实例,代表了数据库中的具体数据。)

  • Schema: 表示模式,一种以文档形式存储的数据库模型骨架,不具备数据库操作能力
  • Model: 表示模型,由Schema生成的模型,具备抽象属性与数据库操作能力
  • Entity: 表示实例,由Model创建的实例,具备操作数据库操作能力

mongoose中任何事物都要从Schema开始。每个Schema对应MongoDB中的一个集合Collection。Schema中定义了集合中文档Document的格式。
通过Schema为产品数据定义一个模式,再通过Model为产品模式定义一个模型。

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
js
复制代码import Day from "dayjs";
import Mongoose from "mongoose";

const { model, Schema } = Mongoose;

// 定义模式
const Product = {
brand: {
match: /^.{2,200}$/,
msg: "品牌只能由2到200位任意字符组成",
required: true,
trim: true,
type: String
},
code: {
match: /^[A-Za-z0-9]{4,30}$/,
msg: "条形码只能由4到30位英文或数字组成",
required: true,
trim: true,
type: String
},
createtime: { ... },
description: { ... },
name: { ... }
origin: { ... }
};
// 定义模型
const ProductModel = model("product", new Schema(Product, { versionKey: false }));

export {
Product,
ProductModel
};

其中每个产品数据都使用type定义数据类型,其类型值只能使用以下有效类型。String、Number、Boolean、Array和Date为原生数据类型,无需引用可直接使用。Buffer为Node的特有数据类型,可查看Node Buffer。ObjectId与Mixed为mongoose定义的数据类型。ObjectId表示主键,每个Schema都会默认配置该属性,键值为_id,在数据入库时会自动创建。Mixed表示混合类型,可认为是引用类型的对象Object。

通过export导出Product(产品模式)与ProductModel(产品类型),就能在路由实例中自由操作MongoDB了。

定义接口

示例是一个小型接口系统,仅仅是为了配合本课程讲述数据库的搭建与操作,所以在代码形式上更多是为了确保搭建好的数据库的可用性与稳定性,因此只编写了产品的增删改查四个接口供你学习,对于更多其他需求的接口,可根据本章学习的内容自行编写。
新增产品
使用koa-router创建路由实例,该实例提供get()与post()定义接口类型。两个方法需传入两个参数,第一个是接口路径,第二个参数是上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js
复制代码import KoaRouter from "koa-router";
import { AsyncTo } from "@yangzw/bruce-us/dist/node";

import { Product, ProductModel } from "../../models";
import { CheckData } from "../../utils/setting";

const Router = KoaRouter();

Router.post("/mall/product/create", async ctx => {
// 接口逻辑
});

export default Router;

接口路径的前缀在开发环境与生产环境可能不尽相同。在开发环境中为了方便可能需省略前缀/mall,在生产环境中为了区分不同系统就必须增加前缀。
在上线前会确认开发环境与生产环境的接口URL。

基于上述确定情况,可用一个变量控制该前缀。在app.config.js加入以下内容。

1
2
3
4
5
js
复制代码export default {
// ...
publicPath: env.NODE_ENV === "dev" ? "" : "/mall"
};

接着开发第一个接口:新增产品。新增产品需用到几个参数,分别是brand、logo、description、name和origin,无需createtime是因为可在数据入库时自动创建时间。
在开发接口时,需做到以下细节才能保障数据的准确性、安全性、可靠性和稳定性。

  • 校验全部字段是否为空
  • 校验全部字段是否符合正则
  • 根据某个字段判断文档是否存在
  • 读写数据库
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
js
复制代码Router.post(`${AppConfig.publicPath}/product/create`, async ctx => {
const params = ctx.request.body;
// 校验全部字段是否为空
if (!CheckData(params, 5)) {
ctx.body = { code: 300, msg: "产品信息都不能为空" };
return false;
}
// 校验全部字段是否符合正则
const checkMsg = Object.entries(params).reduce((t, v) => {
const { match, msg } = Product[v[0]];
return !t && !match.test(v[1]) ? { code: 400, msg } : t;
}, "");
if (checkMsg) {
ctx.body = checkMsg;
return false;
}
// 判断产品是否存在
const [err1, res1] = await AsyncTo(ProductModel.findOne({ code: params.code }));
if (err1) {
ctx.body = { code: 400, msg: "新增产品失败" };
return false;
}
if (res1) {
ctx.body = { code: 400, msg: "当前产品已存在" };
return false;
}
// 新增产品
const [err2, res2] = await AsyncTo(ProductModel.create(params));
if (!err2 && res2) {
ctx.body = { code: 200, data: res2, msg: "新增产品成功" };
} else {
ctx.body = { code: 400, msg: "新增产品失败" };
}
});

若使用post()可通过ctx.request.body获取入参,若使用get()可通过ctx.request.query获取入参。
剩余的删除产品、更新产品和读取产品也可用同样套路处理,在此不深入讲述了。

调试:测试接口可行性

接口系统运行起来,那就要测试每个接口的可行性,确保接口能正常使用。
Postman是一款功能强大用于测试HTTP请求的接口调试工具。虽然不能像QA那样用得很精,但必须知道如何创建与调试接口请求。

打开Postman的下载地址,根据系统选择适合的安装包再下载。

创建请求

安装完毕打开Postman,点击Get按钮创建请求。

配置请求

在以下圈住的位置输入准确的参数信息,从左到右从上往下依次讲述。

  • 请求名称:简要说明接口功能,可用文件夹分类
  • 请求类型:包括15种请求类型,很多情况选择GET或POST
  • 请求地址:完整的接口请求路径
  • Params:请求参数,用于GET入参,对应以下表格的数据
  • Body:请求体,用于POST入参,对应以下表格的数据

Body可选none/form-data/x-www-from-urlencoded等,无参数选none,普通入参选x-www-from-urlencoded,表单入参选form-data。

输入完毕点击Send,输出以下信息表示请求成功。

请求成功会在数据库中记录该文档。打开Compass,点击刷新按钮,就能看到mongoose自动为MongoDB创建了一个数据库mall,其中又创建了一个集合products。MongoDB有一个很好的功能就是存在某个数据库则使用它,不存在则自动创建。


回顾上述代码,mall数据库是通过以下代码自动创建的。

1
connect(`mongodb://${username}:${password}@${host}:${port}/mall`, { ... });

products集合是通过以下代码自动创建的。为何定义Model的名称是product却生成products?因为在创建时mongoose会动态分析名称的词法,对不是复数的单词会追加s或es的后缀,所以在使用model()定义一个模型时,建议采用单词的单数形式。

1
model("product", new Schema(Product, { versionKey: false }));

使用相同方法在Postman中测试其余三个接口,若全部通过就表示本地环境运行的接口可行性测试通过。

部署:发布到服务器

上述都在本地环境部署,因此也只能在本地环境运行接口系统。若要在外网通过HTTPS访问接口系统,还需将Node服务发布到服务器环境中。

打开FTP工具,在/www/server目录中创建data-base文件夹,把本地的data-base文件夹中除了node_modules文件夹的其他内容上传上去。执行npm i安装依赖。
打开CMD工具,登录服务器。在/etc/nginx/conf.d目录中创建api.yangzw.vip.conf文件,执行vim /etc/nginx/conf.d/api.yangzw.vip.conf,加入以下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
server_name api.yangzw.vip;
location /mall {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
location /blog {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
location /resume {
proxy_pass http://127.0.0.1:3002;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}

最后执行nginx -t验证Nginx配置,再执行nginx -s reload重启Nginx进程。执行npm run deploy启动服务。

调试接口

回到Postman的新增产品的接口,将http://127.0.0.1:3000改成https://api.yangzw.vip/mall。
输入完毕点击Send,输出以下信息表示请求成功。若不成功请检查上述步骤,看看是哪里遗漏了。

在默认情况下,开发环境与生产环境的接口域名都不会一样,但接口路径却一样。在Postman中遇到不同环境调试接口,那得把改接口域名改来改去了。
在Postman右上角的环境变量中选择No Environment,点击右边的查看图标,再点击Add按钮。

输入全局环境的名称,设置当前环境中的变量内容。分别创建开发环境与生产环境的变量。


创建完毕就能愉快地调试不同环境中的接口实例了。