讲师中心 微信公众号
AI工具推荐 视频效率加速

Mongoose _id字段自定义为Number类型:实现与验证

雨芳姑娘_2789

雨芳姑娘_2789

发布时间:2025-11-12 21:24:07

|

838人浏览过

|

来源于php中文网

原创

Mongoose _id字段自定义为Number类型:实现与验证

本教程详细指导如何在mongoose中将`_id`字段自定义为`number`类型,并实现严格的正整数验证。通过创建自定义schematype,确保`_id`的数据完整性。同时,文章将深入探讨mongodb/mongoose环境下`_id`字段自增长的实现策略,指出仅定义类型无法自动生成序列号,需要额外的业务逻辑支持,例如利用独立的计数器集合。

引言

Mongoose默认使用ObjectId作为文档的_id字段类型,它是一个12字节的唯一标识符。然而,在某些业务场景下,开发者可能希望将_id设置为Number类型,并且期望它能像关系型数据库的主键一样自动递增。直接在Schema中定义_id: Number并不能实现自动递增的功能,它只会将_id字段的类型限制为数字,但仍会由MongoDB生成ObjectId,或者要求用户手动提供一个数字。为了实现_id为数字类型并进行有效验证,我们需要创建自定义的Mongoose SchemaType。

自定义 NumberId SchemaType

Mongoose允许开发者定义自己的SchemaType,以扩展其内置类型的功能。这里,我们将创建一个名为NumberId的自定义类型,它将强制_id字段为正整数。

  1. 继承 mongoose.SchemaType: 自定义SchemaType需要继承自mongoose.SchemaType。
  2. 实现 cast() 方法: cast() 方法是自定义类型中的核心,它负责将输入值转换为期望的类型,并在转换失败或值不符合要求时抛出错误。在这个方法中,我们将验证输入值是否为数字、整数且为非负数。
  3. 注册新的SchemaType: 将自定义类型注册到mongoose.Schema.Types中,使其可以在Schema定义中被引用。

以下是实现NumberId自定义SchemaType的代码:

const mongoose = require('mongoose');

// 1. 定义自定义SchemaType:NumberId
function NumberId(key, options) {
  // 调用父类构造函数,并指定类型名称为 'NumberId'
  mongoose.SchemaType.call(this, key, options, 'NumberId');
}

// 2. 继承mongoose.SchemaType的原型
NumberId.prototype = Object.create(mongoose.SchemaType.prototype);

// 3. 实现cast()方法进行类型转换和验证
NumberId.prototype.cast = function (val) {
  if (typeof val !== 'number') {
    throw new Error('NumberId: ' + val + ' is not a number');
  }
  if (val % 1 !== 0) { // 检查是否为整数
    throw new Error('NumberId: ' + val + ' is not an integer');
  }
  if (val < 0) { // 检查是否为非负数
    throw new Error('NumberId: ' + val + ' is negative');
  }
  return val;
};

// 4. 注册新的SchemaType
mongoose.Schema.Types.NumberId = NumberId;

上述代码定义了一个名为NumberId的SchemaType,它强制要求字段值必须是正整数。任何不符合这些条件的值都将在保存时抛出错误。

在Schema中应用自定义 NumberId 类型

一旦自定义类型被注册,就可以像使用Mongoose内置类型一样,在Schema定义中引用它。下面是创建一个模型并使用NumberId类型作为_id字段的示例:

const mongoose = require('mongoose');

// ... (此处省略NumberId SchemaType的定义和注册代码,假设已完成) ...
// 注册新的SchemaType
// mongoose.Schema.Types.NumberId = NumberId; // 确保此行已执行

// 创建一个使用我们自定义NumberId类型的Schema
const mySchema = new mongoose.Schema(
  {
    _id: { type: mongoose.Schema.Types.NumberId, required: true, unique: true },
    name: String, // 示例:其他字段
  },
  {
    autoIndex: false // 生产环境建议关闭自动创建索引,以手动管理索引
  }
);

// 创建模型
const MyModel = mongoose.model('MyModel', mySchema);

// 连接MongoDB并进行示例操作
const mongoUri = 'mongodb://localhost:27017/test_db';

async function run() {
  await mongoose.connect(mongoUri, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  });
  console.log('MongoDB connected.');

  try {
    // 尝试创建文档,_id需要手动提供
    await MyModel.create({ _id: 1, name: 'Item One' });
    await MyModel.create({ _id: 2, name: 'Item Two' });
    await MyModel.create({ _id: 3, name: 'Item Three' });
    console.log('Documents created successfully with custom Number _id.');

    // 尝试创建无效_id的文档,这些操作将抛出错误
    // await MyModel.create({ _id: -1, name: 'Invalid Item' });
    // await MyModel.create({ _id: 1.5, name: 'Invalid Item' });
    // await MyModel.create({ _id: 'abc', name: 'Invalid Item' });

  } catch (error) {
    console.error('Error creating documents:', error.message);
  } finally {
    await mongoose.disconnect();
    console.log('MongoDB disconnected.');
  }
}

run();

在上述示例中,_id字段被明确指定为mongoose.Schema.Types.NumberId类型,并且required: true和unique: true确保了其唯一性和存在性。需要注意的是,尽管我们定义了NumberId类型,但在创建文档时,_id的值仍然需要手动提供。

mongodb-query
mongodb-query

用于调试和故障排查,查询MongoDB数据库。用户需要时可以: (1) 列出所有数据库; (2) 列出数据库中的集合; (3) 执行MongoDB查询或命令。

下载

关于 _id 自增长的实现策略

虽然我们已经成功地将_id定义为Number类型并实现了验证,但MongoDB和Mongoose本身并没有提供像关系型数据库那样内置的自动递增Number类型_id机制。如果需要实现_id的自增长,通常需要额外的逻辑。以下是两种常见的实现策略:

  1. 使用独立的计数器集合 (Counters Collection): 这是MongoDB中实现自增长序列最常见且推荐的方法。

    • 创建一个专门的集合(例如counters),其中每个文档存储一个序列名称和对应的当前值。
    • 在每次需要生成新_id时,原子性地查找并更新(findOneAndUpdate)该计数器文档,获取递增后的值。
    • 将获取到的值赋给新文档的_id。

    示例伪代码(结合Mongoose的pre('save')钩子):

    // 假设MyModel的Schema定义如上
    mySchema.pre('save', async function(next) {
      // 仅在新文档且_id未手动指定时执行自增长逻辑
      if (this.isNew && !this._id) {
        try {
          // 原子性地查找并更新计数器
          const counter = await mongoose.connection.db.collection('counters').findOneAndUpdate(
            { _id: 'myModelId' }, // 使用模型名称作为计数器ID
            { $inc: { sequence_value: 1 } }, // 递增sequence_value
            { returnDocument: 'after', upsert: true } // 返回更新后的文档,如果不存在则创建
          );
          this._id = counter.value.sequence_value; // 将递增后的值赋给_id
          next();
        } catch (error) {
          next(error); // 捕获并传递错误
        }
      } else {
        next(); // 如果是更新操作或_id已指定,则跳过
      }
    });

    这种方法确保了在并发环境下的序列号唯一性和正确性。

  2. 利用外部服务或库: 对于更复杂的分布式系统,可以考虑使用专门的ID生成服务(如UUID、Snowflake算法)或第三方库来生成唯一的数字ID。但请注意,这些方法生成的ID通常不是连续的序列号。

重要提示: 上述自定义NumberId SchemaType主要用于_id的类型验证,确保其为有效的正整数。它本身不提供自增长功能。实现自增长需要结合Mongoose的pre('save')钩子和MongoDB的原子操作来管理序列。

总结与注意事项

本教程展示了如何通过自定义Mongoose SchemaType将_id字段设置为Number类型并

热门AI工具

更多
音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

超级麦吉

一款通过数字员工协作完成调研、数据分析与文档制作的AI办公平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

相关专题

更多
什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

2113

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

3414

2023.10.07

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

16360

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1183

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

8950

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

3562

2025.08.07

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

6536

2023.08.14

mongodb和mysql的区别
mongodb和mysql的区别

mongodb和mysql的区别:1、数据模型;2、查询语言;3、扩展性和性能;4、可靠性。本专题为大家提供mongodb和mysql的区别的相关的文章、下载、课程内容,供大家免费下载体验。

875

2023.07.18

Uvicorn使用教程
Uvicorn使用教程

本专题汇总了Uvicorn使用教程与启动运行指南,涵盖其作为Python ASGI服务器的定位与特点,以及命令行启动(uvicorn main:app --reload)、参数配置(host/port/workers)、程序内嵌运行与Gunicorn结合部署等核心用法。同时整理了热重载调试、日志配置与生产环境部署优化等技巧,助你快速掌握Uvicorn的运行与配置。

0

2026.11.02

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn