Fork me on GitHub

koa学习-建立数据库模型-4(上)

安装mongodb

之前其实我已经有写过关于怎么在ubuntu上安装mongodb了,这个可以去看一下之前的博客文章,然后这一次的话,主要是想试试在服务器上面安装MongoDB。

首先登录一下服务器,我的是:

1
ssh zoomdong@zoomdong.xin

当然如果想免密码登录服务器的话,可以参考一下 这篇文章

进入服务器里面之后的话,可以使用 lsb_release -a 来查看关于服务器的一系列有用的信息。

关于centOS的安装过程直接参考mongodb官网上面关于 Red Hat 的介绍来进行安装。

在第一段进行repo的复制的时候我们可以将 mongodb 的官方下载源切换成阿里的下载源。即修改一下baseURL即可,baseURL的地址如下:

阿里源mongodb 下载地址

由于我安装的是3.6版本的mongodb,所以这里就选择了3.6。

切换源之后,安装就一行命令的事情:

1
sudo yum install -y mongodb-org

安装完成之后,直接启动mongodb服务器:

1
sudo service mongod start

启动之后然后来连接一波数据库:

1
mongo --host 127.0.0.1:27017

然后在里面可以查看一下数据库:

1
> show dbs;

使用 MondoDB 来对数据进行导入

这里我们使用一个叫做mongoose的库来建议mongodb和代码的连接。这个库我之前的博客里面也有介绍过,(在使用express进行接口开发的时候有使用过)。

然后现在我们利用这个mongoose库来对数据库进行一波连接。它是一个封装好的数据建模库。然后在 server 下面新建一个 database 的文件夹,里面来放一些数据库的相关文件,首先新建一个init.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
const mongoose = require('mongoose');
const db = 'mongodb://localhost/douban-trailer';
exports.connect = () =>{
// 使用一个变量来计数数据库出现错误的次数,达到一定的限度之后将直接抛出错误,而不在对数据库进行重新连接
let maxConnectTimes = 0;
return new Promise((resolve,reject)=>{
if(process.env.NODE_ENV ! == 'production'){
mongoose.set('debug',true);
}
// 使用mongoose来连接本地 mongoDB 的地址
mongoose.connect(db);
// 如果数据库连接失败的话
mongoose.connection.on('disconnected',function(){
maxConnectTimes ++;
if(maxConnectTimes < 5){
mongoose.connect(db);
} else {
throw new Error('数据库挂了,赶快去修吧');
}
})
mongoose.connection.on('error',function(){
maxConnectTimes ++;
if(maxConnectTimes < 5){
mongoose.connect(db);
} else {
throw new Error('数据库挂了,赶快去修吧');
}
})
mongoose.connection.on('open',function(){
// 数据库连接成功的话将promise的状态改为成功了
resolve();
console.log('mongodb 数据库连接成功');
})
})
}

然后在server/index.js文件里面调用一下数据库初始连接的函数

1
2
3
4
const { connect } = require('./database/init');
(async()=>{
await connect();
})();

我们可以模拟一波往数据库里面存储数据的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 修改一下,用一个测试的数据库
const db = 'mongodb://localhost/douban-test';
mongoose.connection.once('open',function(){
// 先建立一个数据模型
const Dog = mongoose.model('Dog',{
name: String
});
const dogA = new Dog({ name:'阿尔法' });
dogA.save().then(()=>{
console.log('存储成功');
})
// 连接成功之后使用 resolve 来改变一下状态
resolve();
console.log('mongoDB connected successfully');
})

这个时候我们运行node server/index.js 来启动mongodb的使用脚本,然后就可以得到数据成功被存储的消息了,输出的信息如下:

1
Mongoose: dogs.insertOne({ _id: ObjectId("5d31c0490639dc474694a208"), name: '阿尔法', __v: 0 }, { session: null })

然后我们可以去本地的数据库里面看一波,首先使用下面的这些命令来操作

1
2
3
4
5
6
sudo service mongod start # 启动mongodb
mongo # 进入数据库
show dbs # 显示已有的数据库
use douban-test # 切换到这个我们测试用的数据库中
show tables # 看里面有多少个表格,这里会显示我们之前创建的dogs
db.dogs.find({}) # 查看里面的数据,里面会有一条数据

这里就证明了我们的数据库是没有问题的.

MongoDB 和 mongoose 里面的一些概念

数据库是一个使用磁盘空间的程序,通过它可以来规划如何向磁盘里面读写数据,而我们的网站应用程序是不关心的,我们关心的是一个机制让我们可以在不跟磁盘直接打交道的前提下能够灵活方便的操作数据,这个时候就需要数据库的操作机制了,比如mysql和oracle这些关系型数据库,针对不同的上层语言例如java和ruby都有对应的驱动实现,在mongodb里面我们有mondoose,上面的代码我们已经跑通了mongoose的存储和链接。但里面还有一些概念是需要我们自己去理解一波的:

  1. document
  2. collection
  3. database

它和mysql本质上仍然是相同的,对于mongoDB来说,document里面的数据是由键值对来组成的,它是mongodb的核心单元,简单来说是关系型数据库里面的一行记录,而collection是多个文档,相当于是多个记录,多个用户可以组合成一个集合,相当于是关系型数据户的一个表。

mongoose是在MongoDB的事件驱动上面封装的一层模型驱动,通过mongoose可让我们在代码层面很直接的使用数据库,门槛比较低,关于mongoose也有三个概念是需要理解的:

  1. schema
  2. model
  3. entity

schema可以看成是mongoose里面的一种数据模式,可以参考mysql里面的每一个字段是什么类型,具体的表是什么解构,每一个schema都可以映射到mongoose里面的一个collection,但是schema里面不具备操作数据的能力的,他只是个纯定义,model是由schema发布的一个模型,它里面有一些行为,简单来理解,它是具备某张表操作函数的一个集合,函数集合的操作对象是整张表。最后是entity,他是model所创建的一个对象的实体,它指的是某条数据。

使用schema model 创建电影的数据模型

这里直接用mongoose里面自带的一系列 API 方法来完成电影数据库模型的建立:

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
// 电影的数据模型
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Mixed比较适用于数据类型变化比较频繁的数据
const Mixed = Schema.Types.Mixed;
const MovieSchema = new Schema({
doubanId: String,
rate: Number,
title: String,
// 简介
summary: String,
// url地址
video: String,
// 海报图
poster: String,
// 封面地址
cover: String,
// 图床上面的ID
videoKey: String,
posterKey: String,
coverKey: String,
rawTitle: String,
// 电影的类别,申明成数组,数组里面的每一个值都为字符串
movieTypes: [String],
pubdate: Mixed,
year: Number,
// 标签使用数组类型声明数组
tags: Array,
meta:{
// 这条数据被创建时的时间
createdAt: {
type: Date,
default: Date.now()
},
// 这条数据上传的时间
upDatedAt: {
type: Date,
default: Date.now()
}
}
});
// 先将这个模型建立起来,然后再将这个模型发布出去
// 传入两个参数
mongoose.model('Movie',MovieSchema)

用户模型的创建

接下来我们需要创建一个用户登录注册的模型,里面设计存储的数据有用的账户和密码,这里要注意密码的存储是不能用明文存储的,之前的博客里面也有介绍怎么来存储密码。
在schema目录下面新建一个user.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
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
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const Schema = mongoose.Schema;
const Mixed = Schema.Types.Mixed;
const SALT_WORK_FACTOR = 10;
// 设计最大的密码输入出错次数
const MAX_LOGIN_ATTEMPTS = 5;
// 锁定时间为2h
const LOCK_TIME = 2 * 60 * 60 * 1000
const userSchema = new Schema({
// 设置该字段为 唯一字段
username: {
unique: true,
type: String,
required: true,
},
email: {
unique: true,
type: String,
required: true
},
// 密码要进行一个比较精细的控制
password: {
unique: true,
required: true,
type: String
},
loginAttempts: {
type: Number,
required: true,
default: 0
// 默认值为0,必须要传
},
lockUntil: Number,
meta: {
createdAt: {
type: Date,
default: Date.now()
},
upDateAt: {
type: Date,
default: Date.now()
}
}
})
userSchema.pre('save', next => {
if (this.isNew) {
this.meta.createdAt = this.meta.upDateAt = Date.now();
} else {
this.meta.upDateAt = Date.now();
}
next();
});
// 使用mongoose里面的一个虚拟字段的使用方法,这个字段不会被存进数据库
userSchema.virtual('isLocked').get(() => {
return !!(this.lockUntil && this.lockUntil > Date.now());
})
// 在数据保存进数据库之前,对密码进行一个加密处理
userSchema.pre('save', next => {
if (!this.isModified('password')) return next();
bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
if (err) return next(err);
bcrypt.hash(this.password, salt, (error, hash) => {
if (error) return next(error);
this.password = hash;
next();
})
})
// 这里的这个next会让整个过程跳转到下一步去
// next();
});
// methods是实例方法
userSchema.methods = {
comparePassword: (_password, password) => {
// 来做一个明文密码与存储密码的比对
return new Promise((resolve, reject) => {
bcrypt.compare(_password, password, (err, isMatch) => {
if (!err) {
// 如果比对过程中没有出现问题,状态修改为完成
resolve(isMatch);
} else {
// 如果比对过程中出现问题,那么就抛出错误
reject(err);
}
})
})
},
// 判断用户是否超过登录次数然后将其进行一个锁定
// 但是如果锁定时间比until晚了的话证明我们已经过了这个有效期了
incLoginAttepts: (user) => {
return new Promise((resolve, reject) => {
if (this.lockUntil && this.lockUntil < Date.now()) {
// 如果当前用户已经被锁定
this.update({
$set: {
loginAttempts: 1
},
$unset: {
lockUntil: 1
}
}, (err) => {
if (!err) {
resolve(true);
} else reject(false);
})
} else {
let updates = {
$inc: {
loginAttempts: 1
}
}
if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) {
updates.$set = {
lockUntil: Date.now() + LOCK_TIME
}
}
this.update(updates, err => {
if (!err) {
resolve(true);
} else {
reject(false);
}
})
}
})
}
}
mongoose.model('User', userSchema);

创建分类以及初始化所有的Schema

继续在schema目录下面新建一个叫做category.js的文件.

里面存放关于电影的分类的数据库 schema
代码如下:

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
// 电影的数据模型
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const { ObjectId } = Schema.Types;
const categorySchema = new Schema({
name: {
type: String,
unique: true,
},
movies: [{
type: ObjectId,
ref: 'Movie'
}],
// meta几乎是每个schema模型里面都要有的东西
meta: {
// 这条数据被创建时的时间
createdAt: {
type: Date,
default: Date.now()
},
// 这条数据上传的时间
upDatedAt: {
type: Date,
default: Date.now()
}
}
});
categorySchema.pre('save', function(next){
if (this.isNew) {
this.meta.createdAt = this.meta.upDatedAt = Date.now();
} else {
this.meta.upDatedAt = Date.now();
}
next();
})
// 先将这个模型建立起来,然后再将这个模型发布出去
// 传入两个参数
mongoose.model('Category', categorySchema)

movies 对象处是使用了ObejctId 模式字段来创建两个文档/模型实例间一对一的引用,(一组ObjectIds可创建一对多的引用)。该字段存储相关模型的id.

而这里 ref 的作用是告知模式分配哪个模型给该字段。

因而这里在movie.js 里面还是要加上和这里关联的字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
const mongoose = requrire('mongoose');
const Schema = mongoose.Schema;
const { Mixed,ObjectId } = Schema.Types;
const movieSchema = new Schema({
// ... some codes
category: [{
type: ObjectId,
ref: 'Category'
}]
// ... some codes
})
// ... some codes
mongoose.model('Movie',movieSchema);

添加完分类的schema数据库模型之后,我们在database的init.js中添加上初始化schema的代码。

我们需要require之前我们已经定义好的那些schema模块。这里我们使用一个叫做glob的 npm 库来完成操作

node的glob模块允许你使用 *等符号, 来写一个glob规则,像在shell里一样,获取匹配对应规则的文件.

于是我们在 init.js 里面使用glob这个库来引用所有的 schemas.

1
2
3
4
5
6
7
8
9
// ...some codes
const glob = require('glob');
const { resolve } = require('path');
// ... some codes
exports.initSchemas = () => {
glob.sync(resolve(__dirname,'./schema/','**/*.js')).forEach(require);
}
// ... some codes

然后在server文件夹的根入口文件夹下面调用这个函数即可:

1
2
3
4
5
6
7
8
9
10
11
12
const mongoose = require('mongoose');
const { connect,initSchemas } = require('./database/init');
(async()=>{
// 数据库链接的函数
await connect();
initSchemas();
// 下面可以添加一些代码来做一波测试
// 来创建一个Movie的schema对象,然后查看里面的所有字段
const Movie = mongoose.model('Movie');
const movies = await Movie.find();
console.log('movies: ', movies);
})();

然后使用 node server/index.js 来运行,会发现控制台会输出”[]”,这样我们的数据库schema初始化也就建立完成了。

到了这里我们的数据库的基础模型已经建立完成了,下面就来利用这些数据模型,将之前爬取到的数据所导入到数据库里面去吧。

-------------本文结束感谢您的阅读-------------