概述
Schema 是 [[Concept-Mongoose]] 中用于定义文档结构的核心概念。它类似于传统数据库的表结构定义,但更加灵活。Schema 定义了文档的字段类型、验证规则、默认值、索引等重要信息。
核心概念
什么是 Schema?
[!info] 文档结构蓝图
Schema 是一个配置对象,定义了 MongoDB 集合中文档的结构和规则。它不是 MongoDB 原生的概念,而是 Mongoose 提供的抽象层。
const mongoose = require('mongoose');
// 定义一个用户 Schema
const userSchema = new mongoose.Schema({
username: String,
email: String,
age: Number,
isActive: Boolean,
createdAt: Date
});
Schema 的工作流程
数据类型
基本数据类型
| 类型 | 描述 | 示例 |
|---|---|---|
| String | 字符串 | name: String |
| Number | 数字 | age: Number |
| Date | 日期 | createdAt: Date |
| Boolean | 布尔值 | isActive: Boolean |
| Array | 数组 | tags: [String] |
| ObjectId | 对象ID | author: mongoose.Schema.Types.ObjectId |
| Mixed | 任意类型 | data: mongoose.Schema.Types.Mixed |
复杂数据类型示例
const blogSchema = new mongoose.Schema({
// 字符串类型
title: String,
// 数字类型
views: Number,
// 日期类型
publishedAt: Date,
// 布尔类型
isPublished: Boolean,
// 数组类型
tags: [String],
comments: [{
user: String,
message: String,
createdAt: Date
}],
// 对象ID类型(引用)
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
// 嵌套对象
meta: {
likes: Number,
shares: Number,
favorites: Number
},
// 混合类型
customData: mongoose.Schema.Types.Mixed
});
字段选项
通用选项
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true, // 必填
unique: true, // 唯一
trim: true, // 去除前后空格
lowercase: true, // 转为小写
maxlength: 50, // 最大长度
minlength: 3, // 最小长度
default: 'guest' // 默认值
},
email: {
type: String,
required: [true, '邮箱是必需的'], // 自定义错误消息
match: [/\S+@\S+\.\S+/, '邮箱格式不正确'], // 正则验证
index: true // 创建索引
},
age: {
type: Number,
min: [0, '年龄不能为负数'],
max: [150, '年龄不能超过150'],
validate: {
validator: function(v) {
return v % 1 === 0; // 必须是整数
},
message: '年龄必须是整数'
}
}
});
字符串特有选项
const productSchema = new mongoose.Schema({
name: {
type: String,
trim: true, // 去除前后空格
lowercase: true, // 转为小写
uppercase: true, // 转为大写
minlength: 2, // 最小长度
maxlength: 100, // 最大长度
match: /^[a-zA-Z]+$/, // 正则匹配
enum: ['small', 'medium', 'large'] // 枚举值
}
});
数字特有选项
const priceSchema = new mongoose.Schema({
amount: {
type: Number,
min: 0, // 最小值
max: 99999, // 最大值
get: v => Math.round(v * 100) / 100, // getter
set: v => Math.round(v * 100) / 100 // setter
}
});
日期特有选项
const eventSchema = new mongoose.Schema({
startDate: {
type: Date,
default: Date.now, // 默认当前时间
min: '2020-01-01', // 最小日期
max: '2030-12-31' // 最大日期
}
});
验证器
内置验证器
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true, // 必填验证
unique: true, // 唯一性验证
match: /\S+@\S+\.\S+/ // 正则验证
},
age: {
type: Number,
min: 0, // 最小值验证
max: 150 // 最大值验证
},
role: {
type: String,
enum: ['user', 'admin', 'moderator'] // 枚举验证
}
});
自定义验证器
const userSchema = new mongoose.Schema({
password: {
type: String,
validate: {
validator: function(password) {
// 密码强度验证
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/.test(password);
},
message: '密码必须包含至少8个字符,包含大小写字母和数字'
}
},
confirmPassword: {
type: String,
validate: {
validator: function(confirmPassword) {
return confirmPassword === this.password;
},
message: '确认密码与密码不匹配'
}
}
});
异步验证器
const userSchema = new mongoose.Schema({
username: {
type: String,
validate: {
validator: async function(username) {
// 异步检查用户名是否已存在
const user = await this.constructor.findOne({ username });
return !user || user._id.equals(this._id);
},
message: '用户名已存在'
}
}
});
默认值
静态默认值
const articleSchema = new mongoose.Schema({
title: {
type: String,
default: '无标题'
},
status: {
type: String,
default: 'draft'
},
createdAt: {
type: Date,
default: Date.now
}
});
动态默认值
const orderSchema = new mongoose.Schema({
orderNumber: {
type: String,
default: function() {
return 'ORD-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}
},
items: {
type: [String],
default: []
}
});
索引
单字段索引
const userSchema = new mongoose.Schema({
email: {
type: String,
index: true, // 普通索引
unique: true // 唯一索引
},
username: {
type: String,
index: { unique: true, sparse: true } // 稀疏唯一索引
}
});
复合索引
const postSchema = new mongoose.Schema({
title: String,
author: String,
category: String,
createdAt: Date
});
// 在 Schema 上定义复合索引
postSchema.index({ author: 1, createdAt: -1 });
postSchema.index({ category: 1, title: 'text' });
文本索引
const articleSchema = new mongoose.Schema({
title: String,
content: String,
tags: [String]
});
// 全文搜索索引
articleSchema.index({
title: 'text',
content: 'text',
tags: 'text'
});
虚拟属性
基本虚拟属性
const personSchema = new mongoose.Schema({
firstName: String,
lastName: String
});
// 虚拟属性 - 不存储在数据库中
personSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
personSchema.virtual('fullName').set(function(name) {
const parts = name.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
});
虚拟属性选项
const userSchema = new mongoose.Schema({
email: String
});
// 虚拟属性配置
userSchema.virtual('domain').get(function() {
return this.email.split('@')[1];
});
// 在 JSON 中包含虚拟属性
userSchema.set('toJSON', { virtuals: true });
userSchema.set('toObject', { virtuals: true });
Schema 选项
常用选项
const userSchema = new mongoose.Schema({
name: String,
email: String
}, {
// 自动添加 createdAt 和 updatedAt
timestamps: true,
// 版本控制
versionKey: false, // 禁用 __v 字段
// 集合名称
collection: 'users',
// 严格模式
strict: true,
// 转换选项
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
高级选项
const advancedSchema = new mongoose.Schema({
data: mongoose.Schema.Types.Mixed
}, {
// 自动创建索引
autoIndex: true,
// 缓冲命令
bufferCommands: true,
// 上限集合
capped: { size: 1024, max: 1000 },
// 类型转换
typecast: true,
// 严格查询
strictQuery: true
});
中间件钩子
Pre 钩子
const userSchema = new mongoose.Schema({
username: String,
email: String,
password: String
});
// 保存前执行
userSchema.pre('save', function(next) {
// 密码加密
if (this.isModified('password')) {
this.password = hashPassword(this.password);
}
next();
});
// 查询前执行
userSchema.pre(/^find/, function(next) {
this.populate('profile');
next();
});
Post 钩子
userSchema.post('save', function(doc, next) {
console.log(`用户 ${doc.username} 已保存`);
next();
});
userSchema.post('remove', function(doc, next) {
// 清理相关数据
console.log(`用户 ${doc.username} 已删除`);
next();
});
TODO-Schema设计练习
练习1:基础 Schema 定义
// 创建一个电商产品的 Schema,包含以下字段:
// - name: 必填字符串,长度3-100字符
// - price: 必填数字,最小值0
// - description: 可选字符串,最大长度1000字符
// - category: 必填枚举值 ['electronics', 'clothing', 'books', 'home']
// - inStock: 布尔值,默认true
// - tags: 字符串数组,默认空数组
// - createdAt: 日期,默认当前时间
// 在此编写你的代码
练习2:复杂验证规则
// 为上面的产品 Schema 添加复杂验证:
// - name: 不能包含特殊字符,只允许字母、数字、空格、中文
// - price: 必须是两位小数
// - description: 如果提供,不能全是空格
// - 自定义验证:electronics 类别的产品价格不能低于10元
// 在此编写你的代码
练习3:虚拟属性和中间件
// 为产品 Schema 添加:
// - 虚拟属性 priceWithTax (价格 * 1.13)
// - 虚拟属性 isExpensive (价格 > 1000)
// - pre save 中间件:自动生成产品编号 (PRD-年月日-随机数)
// - post save 中间件:记录创建日志
// 在此编写你的代码
练习4:关联和索引
// 扩展产品 Schema:
// - 添加 seller 字段,引用 User 模型
// - 添加 reviews 数组,包含评论信息
// - 为 category 和 price 创建复合索引
// - 为 name 和 description 创建文本搜索索引
// 在此编写你的代码
练习5:高级 Schema 特性
// 创建一个订单 Schema,展示高级特性:
// - 使用 discriminator 实现订单类型继承
// - 实现软删除功能
// - 添加版本控制
// - 使用 populate 虚拟属性
// 在此编写你的代码
[!abstract] 本节总结
本节深入介绍了 Mongoose Schema 的核心概念和高级特性。Schema 是 Mongoose 应用的基础,掌握 Schema 的设计和使用对于构建稳定、高效的应用至关重要。通过合理的字段定义、验证规则、索引设计和中间件使用,可以确保数据的完整性和应用的性能。
323

被折叠的 条评论
为什么被折叠?



