Node.js之Mongoose-Schema概述

概述

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 的工作流程

定义 Schema
创建 Model
创建 Document
保存到 MongoDB
数据验证
类型转换
默认值设置
索引定义

数据类型

基本数据类型

类型描述示例
String字符串name: String
Number数字age: Number
Date日期createdAt: Date
Boolean布尔值isActive: Boolean
Array数组tags: [String]
ObjectId对象IDauthor: 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 的设计和使用对于构建稳定、高效的应用至关重要。通过合理的字段定义、验证规则、索引设计和中间件使用,可以确保数据的完整性和应用的性能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值