NodeJS笔记-系列
初识NodeJS
Express框架
NodeJS-MongoDB
NodeJS接口、会话控制
Node-回眸[一]
Node-回眸[二]
Node-回眸[三]
QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
介绍自己
生成预设简介
推荐相关文章
生成AI简介

简介

MongoDB 基于分布式文件存储的数据库

相较于用 db.json 纯文件管理数据,用数据库管理数据速度更快、扩展性高、安全性高

MongoDB的操作语法与JS相似

核心概念:

  1. 数据库(database) 数据库是一个数据仓库,数据库服务下可以创建很多数据库,数据库中可以存
    放很多集合
  2. 集合(collection) 集合类似于 JS 中的数组,在集合中可以存放很多文档,一个集合存放一种文档(字段类型相同的文档)
  3. 文档(document) 文档是数据库中的最小单位,类似于 JS 中的对象,一个文档中有许多字段(对象的属性)

命令行交互

在将bin文件夹添加至环境变量后,在终端中输入 mongod 启动服务端,mongo 启动客户端

1、数据库命令

1
2
3
4
5
6
7
8
9
10
// 显示所有的数据库
show dbs
// 切换到指定数据库,若不存在则创建
use <数据库名>
// 显示当前所在的数据库
db
// 删除当前数据库
use <数据库名>
db.dropDatabase()

2、集合命令

1
2
3
4
5
6
7
8
9
// 创建集合
db.createCollection('集合名')
// 显示当前数据库中的所有集合
show collections
// 删除某个集合
db.集合名.drop()
// 重命名集合
db.集合名.renameCollection('新名')

3、文档命令

1
2
3
4
5
6
7
8
9
// 插入文档
db.集合名.insert(文档对象)
// 查询文档,_id 是 mongodb 自动生成的唯一编号,用来唯一标识文档
db.集合名.find(查询条件)
// 更新文档
db.集合名.update(查询条件,新的文档)
// 删除文档
db.集合名.remove(查询条件)

Mongoose

Mongoose 是一个对象文档模型库,在异步环境中工作,用代码操作 mongodb 数据库

连接数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const mongoose = require('mongoose');

// 连接数据库 mongodb://127.0.0.1:<端口号,默认27017可不写>/<数据库名,没有会自动创建>
mongoose.connect('mongodb://127.0.0.1/test');

// 设置回调
// 连接成功回调,使用once回调函数只执行一次
mongoose.connection.once('open', () => {
console.log('连接成功');
});
// 连接错误回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
// 连接关闭回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});

常用方法:
1、创建文档模型对象:

  1. mongoose.Schema() 创建文档的结构对象,约束文档字段的数据类型
  2. mongoose.model(<集合名称>, <结构对象>) 创建模型对象,对文档操作的封装对象,集合有则使用无则自动创建,集合名称会自动变为复数

2、插入、增加文档:

  1. create()) 创建并插入一个新文档
  2. insertMany() 传入文档数组批量添加新文档

3、删除文档:

  1. deleteOne() 删除一条文档
  2. deleteMany() 批量删除

4、更新文档:

  1. updateOne() 更新一条文档
  2. updateMany() 批量更新

5、查找、读取文档:

  1. findOne() 查找一条
  2. findById() 通过_id找一条
  3. find() 批量查找,无论找到多少条都返回数组

6、条件控制:

  1. > $gt
  2. < $lt
  3. >= $gte
  4. <= $lte
  5. !== $ne
  6. || $or 也可以用 ||
  7. && $and 也可以用 &&
  8. 正则匹配 {author: /鱼/}

7、规则控制:

  1. select() 字段筛选
  2. sort() 排序
  3. skip() 数据截取,跳过
  4. limit() 数据截取,限定

插入文档

插入新文档流程:在open事件的回调函数中操作

  1. mongoose.Schema() 创建文档的结构对象,约束文档字段的数据类型
  2. mongoose.model(<集合名称>, <结构对象>) 创建模型对象,对文档操作的封装对象,集合有则使用无则自动创建,集合名称会自动变为复数
  3. 模型对象.create({文档数据对象}).then(data=>{}) 创建新文档
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
// 在open事件的回调函数中操作
mongoose.connection.once('open', () => {
console.log('连接成功');
// 创建文档的结构对象BookSchema
// 并约束文档字段的数据类型
let BookSchema = mongoose.Schema({
name: String,
author: String,
price: Number
});
// 创建模型对象,对文档操作的封装对象,第一个参数是集合,会自动创建
let BookModel = mongoose.model('books', BookSchema);
// 使用模型对象去增删改查
// 创建新文档
BookModel.create({
name: '三体',
author: '刘慈欣',
price: 30
}).then(data=>{
console.log(data);
// {
// name: '三体',
// author: '刘慈欣',
// price: 30,
// _id: new ObjectId("6442809af5fae6b874c172f0"),
// __v: 0
// }
}).catch(err=>{
console.log(err);
})
// 关闭数据库连接(项目运行过程中不会添加该代码)
// mongoose.disconnect();
});

批量插入文档

create() 一次只能新增一个文档,使用 insertMany() 传入文档数组批量添加新文档

使用文档模型对象的insertMany()方法批量插入文档
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
mongoose.connection.once('open', () => {
// 文档结构对象
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
// 文档模型对象
let BookModel = mongoose.model('novels', BookSchema);
// 批量添加文档
BookModel.insertMany([{
name: '西游记',
author: '吴承恩',
price: 19.9,
is_hot: true
}, {
name: '红楼梦',
author: '曹雪芹',
price: 29.9,
is_hot: true
}, {
name: '三国演义',
author: '罗贯中',
price: 25.9,
is_hot: true
}, {
name: '水浒传',
author: '施耐庵',
price: 20.9,
is_hot: true
}, {
name: '活着',
author: '余华',
price: 19.9,
is_hot: true
}, {
name: '狂飙',
author: '徐纪周',
price: 68,
is_hot: true
}, {
name: '大魏能臣',
author: '黑男爵',
price: 9.9,
is_hot: false
},
{
name: '知北游',
author: '洛水',
price: 59,
is_hot: false
},{
name: '道君',
author: '跃千愁',
price: 59,
is_hot: false
},{
name: '七煞碑',
author: '游泳的猫',
price: 29,
is_hot: false
},{
name: '独游',
author: '酒精过敏',
price: 15,
is_hot: false
},{
name: '大泼猴',
author: '甲鱼不是龟',
price: 26,
is_hot: false
},
{
name: '黑暗王者',
author: '古羲',
price: 39,
is_hot: false
},
{
name: '不二大道',
author: '文刀手予',
price: 89,
is_hot: false
},
{
name: '大泼猴',
author: '甲鱼不是龟',
price: 59,
is_hot: false
},
{
name: '长安的荔枝',
author: '马伯庸',
price: 45,
is_hot: true
},
{
name: '命运',
author: '蔡崇达',
price: 59.8,
is_hot: true
},
{
name: '如雪如山',
author: '张天翼',
price: 58,
is_hot: true
},
{
name: '三体',
author: '刘慈欣',
price: 23,
is_hot: true
},
{
name: '秋园',
author: '杨本芬',
price: 38,
is_hot: true
},
{
name: '百年孤独',
author: '范晔',
price: 39.5,
is_hot: true
},
{
name: '在细雨中呼喊',
author: '余华',
price: 25,
is_hot: true
}]);

});

字段

文档结构可选的常用字段类型:

  1. String 字符串
  2. Number 数字
  3. Boolean 布尔值
  4. Array 数组,也可以使用 [] 来标识
  5. Date 日期
  6. Buffer Buffer 对象
  7. Mixed 任意类型,需要使用 mongoose.Schema.Types.Mixed 指定
  8. ObjectId 文档对象 ID,用于设置外键,保存其它文档的id,需要使用 mongoose.Schema.Types.ObjectId 指定
  9. Decimal128 高精度数字,需要使用 mongoose.Schema.Types.Decimal128 指定

字段值验证:一些内建验证器,可以对字段值进行验证,或者说进一步约束字段内容

  1. required: true 设置必填项
  2. default: ‘qx’ 设置默认值
  3. enum: [‘男’,’女’] 枚举值,字段值必须在数组中
  4. unique: true 设置值唯一,需要重建集合才有效果

在创建文档的结构对象时添加字段验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let BookSchema = mongoose.Schema({
name: {
type: String,
required: true, // 必填项
unique: true, // 唯一值
},
author: {
type: String,
default: '匿名' // 默认值
},
style: {
type: String,
required: true, // 必填项
enum: ['科幻','言情','玄幻'] // 枚举值
},
price: {
type: Number,
required: true // 必填项
},
});

删除文档

deleteOne() 删除一条文档,deleteMany() 批量删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 删除单条文档
BookModel.deleteOne({ _id: '64428b3a01df4070e7bc2df3' })
.then(data => {
console.log(data);// { acknowledged: true, deletedCount: 1 }
})
.catch(err => {
console.log('删除失败');
})

// 批量删除文档
BookModel.deleteMany({ is_hot: false })
.then(data => {
console.log(data);// { acknowledged: true, deletedCount: 9 }
})
.catch(err => {
console.log('删除失败');
})

更新文档

updateOne()updateMany()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 更新单条
BookModel.updateOne({name: '红楼梦'}, {price: 9.9})
.then(data=>{
console.log(data);
})
//更新多条
BookModel.updateMany({is_hot: true}, {is_hot: false})
.then(data=>{
console.log(data);
// {
// acknowledged: true,
// modifiedCount: 12,
// upsertedId: null,
// upsertedCount: 0,
// matchedCount: 12
// }
})

查找文档

findOne() 查找一条,findById() 通过_id找一条,find() 批量查找,无论找到多少条都返回数组

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
38
39
40
41
42
43
44
45
46
47
48
49
50
// 读取单条
BookModel.findOne({name: '狂飙'})
.then(data=>{
console.log(data);
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84f9a"),
// name: '狂飙',
// author: '徐纪周',
// price: 68,
// is_hot: true,
// __v: 0
// }
})
// 通过_id获取
BookModel.findById('6442a4a0f5bcc6fb8cc84fa7')
.then(data=>{
console.log(data);
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84fa7"),
// name: '三体',
// author: '刘慈欣',
// price: 23,
// is_hot: true,
// __v: 0
// }
})
//读取多条,没用参数则获取全部文档
BookModel.find({author: '余华'})
.then(data=>{
console.log(data);
// [
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84f99"),
// name: '活着',
// author: '余华',
// price: 19.9,
// is_hot: true,
// __v: 0
// },
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84faa"),
// name: '在细雨中呼喊',
// author: '余华',
// price: 25,
// is_hot: true,
// __v: 0
// }
// ]
})

条件控制

mongodb中没有条件运算符,必须使用替代符号

  1. > $gt
  2. < $lt
  3. >= $gte
  4. <= $lte
  5. !== $ne
  6. || $or 也可以用 ||
  7. && $and 也可以用 &&
1
2
3
4
5
6
7
8
// 价格高于20
BookModel.find({price: {$gte:20}})
// 价格高于20且低于30
BookModel.find({ $and: [{ price: { $gte: 20 }, price: { $lt: 30 } }] })
BookModel.find({ price: { $gte: 20 } && { $lt: 30 } })
// 查找甲鱼不是龟写的价格低于30的书
BookModel.find({ $and: [{ author: '甲鱼不是龟', price: { $lt: 30 } }] })

正则匹配:条件中可以直接使用 JS 的正则语法,通过正则可以进行模糊查询

1
2
// 搜索作者名带鱼写的书
BookModel.find({ author: /鱼/})

规则控制

1、字段筛选 select() 查找时只查找文档的某些字段,提高查找效率,1 需要,0 排除,_id默认为1

查找价格低于30的书,字段只包含书名、作者和价格
1
2
3
4
BookModel.find({price: { $lt: 30 }}).select({name:1,author:1,price:1})
.then(data=>{
console.log(data);
})

2、排序 sort(),按某个字段的升序(1)或降序(-1)排序

查找所有书,按价格升序排序,字段只包含书名、作者和价格
1
2
3
4
BookModel.find().sort({price:1}).select({name:1,author:1,price:1})
.then(data=>{
console.log(data);
})

3、数据截取skip() 跳过,limit() 限定

查找所有书,按价格降序排序,字段只包含书名和价格,只取前三本书
1
2
3
4
BookModel.find().sort({price:-1}).select({name:1,price:1}).limit(3)
.then(data=>{
console.log(data);
})
查找所有书,按价格降序排序,字段只包含书名和价格,跳过最贵的前三本书
1
2
3
4
BookModel.find().sort({price:-1}).select({name:1,price:1}).skip(3)
.then(data=>{
console.log(data);
})

代码模块化

mongoose的模块化:

  1. 将数据库连接部分独立,并放入一个函数中暴露出去
  2. 将数据库中不同的文档对象模型每个单独一个js文件独立到modules文件夹中,将文档对象模型暴露出去
  3. 将数据库的连接信息独立到config.js再暴露出去给db.js使用
  4. index.js导入数据库连接函数和文档对象模型,并将连接数据库成功后的操作,都放在数据库连接函数的第一个参数中,作为连接成功的回调函数
原来的文件
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
const mongoose = require('mongoose');

mongoose.set('strictQuery', true);

mongoose.connect('mongodb://127.0.0.1:27017/test');

mongoose.connection.once('open', () => {
// 文档结构对象
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
// 文档模型对象
let BookModel = mongoose.model('novels', BookSchema);
// 读取单条
BookModel.findOne({name: '狂飙'})
.then(data=>{
console.log(data);
})
// 通过_id获取

});

// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});

//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});

模块化后:

作用:当需要对数据库中一个集合操作时,只需要在models文件夹中新建该集合的文档对象模型,然后在index.js中导入,当要连接其它mongodb数据库时也只需要修改config.js

index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const mongoose = require('mongoose');
// 将数据库连接函数导入
const db = require('./db/db');
// 导入BookModel文档对象模型
const BookModel = require('./models/BookModel');
// 传入两个回调函数
db(() => {
// 连接成功则对数据库进行操作,增删改查
// 查找《狂飙》的信息
BookModel.findOne({ name: '狂飙' })
.then(data => {
console.log(data);
})
});

config.js
1
2
3
4
5
6
7
const config = {
HOST: '127.0.0.1',
PORT: 27017,
NAME: 'test'
}
module.exports = config;

./db/db.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 暴露一个函数,success数据库连接成功的回调,error连接失败的回调
module.exports = function (success, error = () => { console.log('连接失败'); }) {

const mongoose = require('mongoose');
// 导入数据库连接配置文件
const {HOST,PORT,NAME} = require('../config');

mongoose.connect(`mongodb://${HOST}:${PORT}/${NAME}`);

mongoose.connection.once('open', () => {
success();
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
error();
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});

}

./models/BookModel.js
1
2
3
4
5
6
7
8
9
10
11
12
13
const mongoose = require('mongoose');
// 文档结构对象
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
// 文档模型对象
let BookModel = mongoose.model('novels', BookSchema);
// 将文档模型对象暴露出去
module.exports = BookModel;

图形化管理

使用图形化软件更方便操控数据库 Robo3T Navicat

记账本优化

使用 mongodb 数据库替代 lowdb 文件管理,使用 moment 处理日期类型

主要文件:
数据库相关:

/db/db.js 数据库连接函数,连接成功后才能在回调函数中做后续业务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 暴露一个函数,success数据库连接成功的回调,error连接失败的回调
module.exports = function (success, error = () => { console.log('连接失败'); }) {

const mongoose = require('mongoose');
// 导入数据库连接配置文件
const { HOST, PORT, NAME } = require('../config');

mongoose.connect(`mongodb://${HOST}:${PORT}/${NAME}`);

mongoose.connection.once('open', () => {
success();
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
error();
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});

}

/models/AccountModel.js 账本的文档对象模型,用于约束和操控数据库集合中的文档,增删改查
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
const mongoose = require('mongoose');
// 文档结构对象
let AccountSchema = new mongoose.Schema({
matter: {
type: String,
required: true
},
date: {
type: Date,
required: true
},
type: {
type: String,
enum: ['支出', '收入'],
default: '支出'
},
account: {
type: Number,
required: true
},
remark: {
type: String,
default: '无'
}
});
// 文档模型对象
let AccountModel = mongoose.model('accounts', AccountSchema);
// 将文档模型对象暴露出去
module.exports = AccountModel;

config.js 数据库连接配置
1
2
3
4
5
6
7
const config = {
HOST: '127.0.0.1',
PORT: 27017,
NAME: 'test'
}
module.exports = config;

启动http服务的www文件:导入数据库连接函数,将http服务的代码都扔进db的回调函数中,待数据库连接成功再执行回调函数运行http服务

/bin/www
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/env node
// 导入数据库连接函数
const db = require('../db/db');
// 将http相关操作放到连接成功的回调中
db(()=>{
/**
* Module dependencies.
*/

var app = require('../app');
var debug = require('debug')('keepingbook:server');
var http = require('http');

/**
* Get port from environment and store in Express.
*/

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
* Create HTTP server.
*/

var server = http.createServer(app);

/**
* Listen on provided port, on all network interfaces.
*/

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
* Normalize a port into a number, string, or false.
*/

function normalizePort(val) {
var port = parseInt(val, 10);

if (isNaN(port)) {
// named pipe
return val;
}

if (port >= 0) {
// port number
return port;
}

return false;
}

/**
* Event listener for HTTP server "error" event.
*/

function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}

var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;

// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

/**
* Event listener for HTTP server "listening" event.
*/

function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

})

路由相关:index.js内有增删该查的页面、接口路由,在这个文件中引入文档对象模型,就能在对应路由内对数据库文档做对应操作

/route/index.js
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
var express = require('express');
var router = express.Router();

// 导入moment处理日期
const moment = require('moment');

// 导入文档模型对象
const AccountModel = require('../models/AccountModel');

// 记账本页面路由
router.get('/', function (req, res, next) {

// 读取所有数据,按日期降序
AccountModel.find().sort({ date: -1 })
.then(data => {
// 将数据数组传递过去遍历渲染,为了格式化日期,将moment传入
res.render('index', { content: data, moment });
})
.catch(err => {
console.log(err);
// 查找失败则返回500
res.status(500).render('tip', { msg: '查找失败!', url: '/' });
})

});

// 添加记录页面路由
router.get('/add', function (req, res, next) {
res.render('add');
});

// 添加记录post接口路由
router.post('/add', function (req, res, next) {

// 插入一条数据
AccountModel.create({
// 解构赋值
...req.body,
// 覆盖修改日期为日期对象
date: moment(req.body.date).toDate()
})
.then(data => {
console.log(data);
// 成功跳转到提示页
res.render('tip', { msg: '添加成功!', url: '/' });
})
.catch(err => {
console.log(err);
// 添加失败则返回500
res.status(500).render('tip', { msg: '添加失败!', url: '/' });
});

});

// 根据id删除数据,使用路由参数
router.get('/delete/:id', (req, res) => {
// 获取路由参数id
let id = req.params.id;
AccountModel.deleteOne({ _id: id })
.then(data => {
console.log(data);
// 删除成功跳转提示页
res.render('tip', { msg: '删除成功!', url: '/' });
})
.catch(err => {
console.log(err);
res.status(500).render('tip', { msg: '删除失败!', url: '/' });
})

});


module.exports = router;

除了index.ejs,其它页面无变化

index.ejs渲染日期时,调用moment去格式化
1
<div class="date"><%= moment(item.date).format('YYYY-MM-DD') %></div>