Mongoose如何自动生成除默认_id外的另一套面向用户的ID?

MongoDB 生成10-12位对外展示publish_id的实现方案
下面提供两种常用的落地方式,可根据你的业务需求选择:
方案1:随机字符串生成(适合无需ID有序的场景)
如果不需要publish_id按新增顺序递增,优先选这个方案,实现简单,ID无规律不容易被恶意遍历。
实现步骤
- 给用户Schema新增
publish_id字段,配置唯一索引避免重复 - 编写10-12位随机字符串生成函数
- 配置Mongoose
pre('save')中间件,新文档入库前自动生成并校验ID唯一性
代码示例
const mongoose = require('mongoose'); // 1. 定义用户Schema const userSchema = new mongoose.Schema({ // 你原有的字段保留 username: { type: String, required: true }, email: { type: String, required: true }, // 新增publish_id字段 publish_id: { type: String, unique: true, index: true, required: true } }); // 2. 随机ID生成函数:10-12位大小写字母+数字组合 function generateRandomId() { const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const idLength = Math.floor(Math.random() * 3) + 10; // 长度10-12随机 let id = ''; for (let i = 0; i < idLength; i++) { id += charSet.charAt(Math.floor(Math.random() * charSet.length)); } return id; } // 3. 前置中间件:新文档保存前生成ID userSchema.pre('save', async function(next) { // 只有新增文档需要生成ID,更新操作跳过 if (!this.isNew) return next(); const maxRetry = 5; // 碰撞最多重试5次 let retryTimes = 0; while (retryTimes < maxRetry) { const tempId = generateRandomId(); // 校验ID是否已存在 const isExist = await mongoose.model('User').findOne({ publish_id: tempId }); if (!isExist) { this.publish_id = tempId; return next(); } retryTimes++; } next(new Error('publish_id生成失败,请稍后重试')); }); const User = mongoose.model('User', userSchema);
方案优势
- 实现简单,无需额外维护集合
- 62种字符的10位ID有8e17种组合,加上重试机制,碰撞概率可以忽略不计
方案2:自增序列生成(适合需要ID有序的场景)
如果需要publish_id是纯数字、按新增顺序递增,用原子自增计数器的方案,完全不会出现重复。
实现步骤
- 新增计数器集合,专门存储各业务的自增序列值
- 编写原子获取下一个序列值的函数
- 保存用户文档前调用函数生成ID
代码示例
const mongoose = require('mongoose'); // 1. 定义计数器Schema const counterSchema = new mongoose.Schema({ _id: { type: String, required: true }, // 初始值设为10亿,保证生成的ID最少10位 sequence: { type: Number, default: 1000000000 } }); const Counter = mongoose.model('Counter', counterSchema); // 2. 原子获取下一个publish_id async function getNextPublishId() { const res = await Counter.findOneAndUpdate( { _id: 'user_publish_id' }, { $inc: { sequence: 1 } }, // 原子自增1 { new: true, upsert: true } // 不存在则自动创建计数器 ); return res.sequence.toString(); } // 3. 用户Schema的pre中间件 userSchema.pre('save', async function(next) { if (!this.isNew) return next(); try { this.publish_id = await getNextPublishId(); next(); } catch (err) { next(err); } });
方案优势
- ID完全有序,无碰撞问题,生成效率稳定
- 适合高并发场景,不会出现重试消耗
通用注意事项
- 无论用哪种方案,都必须给
publish_id加上unique唯一索引,从数据库层面避免重复数据入库 - 如果对外暴露的ID不希望被遍历爬取,不要用方案2的连续自增ID
- 可根据业务需求调整ID生成规则,比如添加固定前缀、只保留数字等
内容的提问来源于stack exchange,提问作者Yellow Clock
相关产品推荐
相关产品推荐

