自定义类型与扩展
自定义类型与扩展
Mongoose 提供了灵活的自定义类型机制,允许开发者根据需求创建专属的数据类型。这种能力在处理特殊数据结构或业务逻辑时特别有用,能够突破内置类型的限制。
基础类型扩展
最简单的自定义类型方式是通过继承 mongoose.SchemaType 类。下面示例创建一个简单的邮政编码类型:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const SchemaType = mongoose.SchemaType;
class ZipCode extends SchemaType {
constructor(key, options) {
super(key, options, 'ZipCode');
}
cast(val) {
if (!/^\d{6}$/.test(val)) {
throw new Error('ZipCode must be 6 digits');
}
return val;
}
}
// 注册类型
mongoose.Schema.Types.ZipCode = ZipCode;
// 使用示例
const addressSchema = new Schema({
zip: { type: ZipCode }
});
复杂自定义类型
对于需要处理嵌套数据的场景,可以创建更复杂的自定义类型。下面实现一个支持自动格式化的电话号码类型:
class PhoneNumber extends SchemaType {
constructor(key, options) {
super(key, options, 'PhoneNumber');
this.countryCode = options?.countryCode || '86';
}
cast(val) {
if (typeof val !== 'string') {
throw new Error('PhoneNumber must be a string');
}
// 移除所有非数字字符
const cleaned = val.replace(/\D/g, '');
// 检查长度
if (cleaned.length < 7 || cleaned.length > 15) {
throw new Error('Invalid phone number length');
}
// 自动添加国家代码
return cleaned.startsWith(this.countryCode)
? `+${cleaned}`
: `+${this.countryCode}${cleaned}`;
}
}
// 使用示例
const contactSchema = new Schema({
phone: {
type: PhoneNumber,
countryCode: '1' // 北美区号
}
});
类型验证扩展
自定义类型可以集成复杂的验证逻辑。下面创建一个带验证的RGB颜色类型:
class RGBColor extends SchemaType {
constructor(key, options) {
super(key, options, 'RGBColor');
}
cast(val) {
if (typeof val === 'string') {
// 处理 #RRGGBB 格式
if (/^#([0-9a-f]{3}){1,2}$/i.test(val)) {
return val.toLowerCase();
}
// 处理 rgb(r,g,b) 格式
const rgbMatch = val.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (rgbMatch) {
const toHex = c => parseInt(c).toString(16).padStart(2, '0');
return `#${toHex(rgbMatch[1])}${toHex(rgbMatch[2])}${toHex(rgbMatch[3])}`;
}
}
throw new Error('Invalid RGB color format');
}
}
// 使用示例
const themeSchema = new Schema({
primaryColor: { type: RGBColor }
});
混合类型扩展
Mongoose 的 Mixed 类型可以进一步扩展,创建具有特定行为的动态类型:
class DynamicConfig extends mongoose.Schema.Types.Mixed {
constructor(key, options) {
super(key, options);
this.allowedKeys = options?.allowedKeys || [];
}
cast(val) {
if (typeof val !== 'object') {
throw new Error('DynamicConfig must be an object');
}
// 过滤不允许的键
return Object.keys(val).reduce((acc, key) => {
if (this.allowedKeys.includes(key)) {
acc[key] = val[key];
}
return acc;
}, {});
}
}
// 使用示例
const appSchema = new Schema({
config: {
type: DynamicConfig,
allowedKeys: ['theme', 'layout', 'features']
}
});
继承内置类型
直接继承内置类型可以复用现有功能:
class TruncatedString extends mongoose.Schema.Types.String {
constructor(key, options) {
super(key, options);
this.maxLength = options?.maxLength || 255;
}
cast(val) {
const strVal = super.cast(val);
return strVal.length > this.maxLength
? strVal.substring(0, this.maxLength)
: strVal;
}
}
// 使用示例
const postSchema = new Schema({
title: {
type: TruncatedString,
maxLength: 100
},
content: {
type: TruncatedString,
maxLength: 1000
}
});
类型转换器
实现类型转换器可以在保存前自动转换数据格式:
class Timestamp extends mongoose.Schema.Types.Date {
constructor(key, options) {
super(key, options);
this.precision = options?.precision || 'millisecond';
}
cast(val) {
const date = super.cast(val);
switch (this.precision) {
case 'second':
return new Date(Math.floor(date.getTime() / 1000) * 1000);
case 'minute':
return new Date(Math.floor(date.getTime() / 60000) * 60000);
case 'hour':
return new Date(Math.floor(date.getTime() / 3600000) * 3600000);
default:
return date;
}
}
}
// 使用示例
const eventSchema = new Schema({
createdAt: {
type: Timestamp,
precision: 'minute',
default: Date.now
}
});
虚拟类型扩展
虚拟类型也可以自定义,实现复杂计算逻辑:
class GeometricMean extends mongoose.Schema.Types.Virtual {
constructor(key, options) {
super(key, options);
this.fields = options?.fields || [];
}
get() {
return function() {
const values = this.fields.map(f => this[f]);
if (values.some(v => v === undefined || v === null)) return null;
const product = values.reduce((acc, val) => acc * val, 1);
return Math.pow(product, 1 / values.length);
};
}
}
// 使用示例
const statsSchema = new Schema({
values: [Number]
});
statsSchema.virtual('gmean', new GeometricMean(null, {
fields: ['values']
}));
数组类型扩展
自定义数组元素类型可以实现特定元素的数组:
class UniqueStringArray extends mongoose.Schema.Types.Array {
constructor(key, options) {
super(key, options);
this.maxLength = options?.maxLength || 100;
}
cast(val) {
const arr = super.cast(val);
// 去重
const uniqueArr = [...new Set(arr)];
// 截断
return uniqueArr.length > this.maxLength
? uniqueArr.slice(0, this.maxLength)
: uniqueArr;
}
}
// 使用示例
const userSchema = new Schema({
tags: {
type: UniqueStringArray,
maxLength: 10
}
});
查询条件扩展
自定义类型可以扩展查询条件处理:
class CaseInsensitiveString extends mongoose.Schema.Types.String {
constructor(key, options) {
super(key, options);
}
cast(val) {
return super.cast(val)?.toLowerCase();
}
checkRequired(val) {
return super.checkRequired(val) && val.trim().length > 0;
}
}
// 使用时会自动转换查询条件
const productSchema = new Schema({
name: { type: CaseInsensitiveString, required: true }
});
// 查询时不需要考虑大小写
Product.find({ name: 'TEST' }); // 会匹配 "test", "Test" 等
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:性能调优与查询优化