MongoDB在某些时候被人诟病,说字段过于灵活,数据结构不稳定,从而被排除在外。
例如,用户表,大部分情况下,都是固定格式:
| id | name | age | province |
类型是:
| ID | CHAR | INT | VARTXT |
如果使用MongoDB,因为是文档类型,字段增加修改灵活,这就会有一个问题,如果不小心被人insert了一个数据:
{
name: "Jerry",
age: "18",
province: "Guangdong"
}
大家看这个 age: "18",特别在当前NodeJS流行的环境下,WEB提交的表但都是string,就有可能出现。当然解决办法也是有的:
- 应用层面验证——就是自己写验证,例如Koa/expressjs中间,或koa-validate;
- 使用Mongoose schema验证/转换;
使用Mongoose Schema:
const mongoose = require('mongoose')
var UserSchema = mongoose.Schema({
_id: false,
name: {
type: String,
required: [true, "user name is required"]
},
age: {
type: Number
},
province: {
type: String
}
});
使用mongoose的好处是,不但可以验证,还可以转换类型,除此之外,还可以更贴合面向对象的方式使用数据,所以十分推荐引入Mongoose库。
那么问题来了,如果同一个数据库的使用这,不是NodeJS环境呢?大家都知道在微服务中,有可能使用Java来实现各种数据的处理。所以最好的方式就是像MYSQL那样在数据库里验证。
使用jsonSchema验证MongoDB字段
在MongoDB3.6后,支持jsonSchema的数据验证,通过如下方式可以配置,也可以修改。
//建表的时候配置
db.createCollection( <collection>, { validator: { $jsonSchema: <schema> } } )
//随时修改
db.runCommand( { collMod: <collection>, validator:{ $jsonSchema: <schema> } } )
配置的方式就是上面的2个语句,非常简单,使用的是标准JSON Schema规范。
JSON Schema规范内容,这里不铺开阐述,大家可以自己搜索。当然mongoDB也有自己的一些字段类型,这一点和Javascript有点不一样。例如int,那么10是合法的,但10.1是不合法的。还有一些细则,例如下面的year。
year: {
bsonType: "int",
minimum: 2017,
maximum: 3017,
description: "must be an integer in [ 2017, 3017 ] and is required"
},
这里仅做介绍,具体可以到官网去看,也不复杂。
效果
在设置好验证后,只要验证不通过,就会报错:
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 121,
"errmsg" : "Document failed validation"
}
})
在NodeJS里,可以通过代码来设置,只是如果使用mongoose,因为不提供mongodb connector内置对象暴露,需要我们稍稍处理一下即可。
const MongoClient = require("mongodb").MongoClient;
const URL = 'mongodb://@127.0.0.1:27017/mydb'
function connect () {
return new Promise((resolve, reject) => {
MongoClient.connect(URL, { useNewUrlParser: true }, (err, database) => {
if (err) {
console.log(`Unable to connect to the databse: ${err}`);
reject(err)
} else {
console.log('Connected to the database');
resolve(database)
}
});
})
}
async function init () {
let connection = await connect()
let db = connection.db()
//这里写jsonSchema
await db.createCollection(<collection>, { validator: { $jsonSchema: <schema> } })
//这里修改jsonSchema
await db.runCommand( { collMod: <collection>, validator:{ $jsonSchema: <schema> } } )
}
init().then(() => {})
我们写一些测试用例:
const chai = require('chai')
const { expect } = chai
const URL = 'mongodb://@127.0.0.1:27017/mydb'
const MongoClient = require("mongodb").MongoClient;
function connect () {
return new Promise((resolve, reject) => {
MongoClient.connect(URL, { useNewUrlParser: true }, (err, database) => {
if (err) {
console.log(`Unable to connect to the databse: ${err}`);
reject(err)
} else {
console.log('Connected to the database');
resolve(database)
}
});
})
}
describe('mongoose', function () {
it('createCollection', async () => {
let connection = await connect()
let db = connection.db()
await db.createCollection('students', {
validator: {
$jsonSchema: {
required: ["name"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
age: {
bsonType: "int"
},
score: {
bsonType: "number"
},
height: {
bsonType: "double"
},
address: {
bsonType: "object",
required: ["zipcode"],
properties: {
"street": { bsonType: "string" },
"zipcode": { bsonType: "string" }
}
}
}
}
}
})
})
it('jsonSchemaMod', async () => {
let connection = await connect()
let db = connection.db()
let rs = await db.command({
collMod: 'students',
validator: {
$jsonSchema: {
required: ["name"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
age: {
bsonType: "int"
},
score: {
bsonType: "number"
},
height: {
bsonType: "double"
},
address: {
bsonType: "object",
required: ["zipcode"],
properties: {
"street": { bsonType: "string" },
"zipcode": { bsonType: "string" }
}
}
}
}
}
})
expect(rs).eql({ ok: 1 })
// console.log('rs:', rs)
})
it('jsonSchemaModLess', async () => {
let connection = await connect()
let db = connection.db()
let rs = await db.command({
collMod: 'students',
validator: {
$jsonSchema: {
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
address: {
bsonType: "object",
properties: {
"street": { bsonType: "string" },
"zipcode": { bsonType: "string" }
}
}
}
}
}
})
expect(rs).eql({ ok: 1 })
})
it('insert', async () => {
let connection = await connect()
let db = connection.db()
let rs = await db.collection('students').insertOne({
name: 'tom',
age: 10,// 10.1 如果非整型,会失败
score: 100, //ok
height: 180, //ok
address: {
zipcode: 'code' //zipcode empty fail
},
otherField: 'an other field'
})
expect(rs.result).include({ ok: 1 })
})
it('emptyName', async () => {
let connection = await connect()
let db = connection.db()
// 如果name是必须,则会失败
let rs = await db.collection('students').insertOne({
age: 10
})
// console.log(rs)
await expect(rs.result).include({ ok: 1 })
})
it('found', async () => {
let connection = await connect()
let db = connection.db()
let found = await db.collection('students').find().toArray()
console.log('found:', found)
expect(found).to.be.an('array')
})
})
这样,我们就实现了类似MYSQL的数据验证。
所以呢,以后在也不要说MongoDB数据结构不稳定啦。
官方文档传送门:docs.mongodb.com/manual/refe…