您基本上是在寻找"pre" middleware 挂钩,该挂钩是通过在集合中创建新文档而触发的。这将检查当前文档内容并从值中提取“字符串”,以便为_id 创建“前缀”值。
还有另一部分,其中“前缀”需要添加数字计数器,当该特定“前缀”已经存在一个值以使其不同时。 "Generate an auto-incrementing sequence field" 在 MongoDB 中有一个常用技术,它基本上涉及保持一个“计数器”集合并在每次访问它时递增值。
作为一个完整且独立的演示,您将以下技术结合起来:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/warehouse');
var counterSchema = new Schema({
"type": { "type": String, "required": true },
"prefix": { "type": String, "required": true },
"counter": Number
});
counterSchema.index({ "type": 1, "prefix": 1 },{ "unique": true });
counterSchema.virtual('nextId').get(function() {
return this.prefix + this.counter;
});
var productSchema = new Schema({
"_id": "String",
"category": {
"object": { "type": String, "required": true },
"group": { "type": String, "required": true },
"name": { "type": String, "required": true }
}
},{ "_id": false });
productSchema.pre('save', function(next) {
var self = this;
if ( !self.hasOwnProperty("_id") ) {
var prefix = self.category.object.substr(0,1).toUpperCase()
+ self.category.group.substr(0,1).toUpperCase()
+ self.category.name.split(" ").map(function(word) {
return word.substr(0,1).toUpperCase();
}).join("");
mongoose.model('Counter').findOneAndUpdate(
{ "type": "product", "prefix": prefix },
{ "$inc": { "counter": 1 } },
{ "new": true, "upsert": true },
function(err,counter) {
self._id = counter.nextId;
next(err);
}
);
} else {
next(); // Just skip when _id is already there
}
});
var Product = mongoose.model('Product',productSchema),
Counter = mongoose.model('Counter', counterSchema);
async.series(
[
// Clean data
function(callback) {
async.each([Product,Counter],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
async.each(
[
{
"category": {
"object": "ring",
"group": "movies",
"name": "lord of the rings"
}
},
{
"category": {
"object": "ring",
"group": "movies",
"name": "four weddings and a funeral"
}
},
{
"category": {
"object": "ring",
"group": "movies",
"name": "lord of the rings"
}
}
],
function(data,callback) {
Product.create(data,callback)
},
callback
)
},
function(callback) {
Product.find().exec(function(err,products) {
console.log(products);
callback(err);
});
},
function(callback) {
Counter.find().exec(function(err,counters) {
console.log(counters);
callback(err);
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
)
这会给你这样的输出:
[ { category: { name: 'lord of the rings', group: 'movies', object: 'ring' },
__v: 0,
_id: 'RMLOTR1' },
{ category:
{ name: 'four weddings and a funeral',
group: 'movies',
object: 'ring' },
__v: 0,
_id: 'RMFWAAF1' },
{ category: { name: 'lord of the rings', group: 'movies', object: 'ring' },
__v: 0,
_id: 'RMLOTR2' } ]
[ { __v: 0,
counter: 2,
type: 'product',
prefix: 'RMLOTR',
_id: 57104cdaa774fcc73c1df0e8 },
{ __v: 0,
counter: 1,
type: 'product',
prefix: 'RMFWAAF',
_id: 57104cdaa774fcc73c1df0e9 } ]
要首先了解Counter 模式和模型,您基本上是在定义一些内容,您将在其中查找“唯一”键并在匹配时将数字字段附加到“增量”。为方便起见,它只有一个组成唯一组合的两个字段和一个定义的复合索引。如果需要,这也可以是一个复合 _id。
另一个方便之处是nextId 的virtual 方法,它只是将“前缀”和“计数器”值串联起来。在此处包含“类型”之类的内容也是最佳实践,因为您的Counter 模型可用于服务“计数器”以用于多个收集源。因此,在这里我们在访问 Product 模型的上下文时使用 "product" 以将其与您可能也保留类似序列计数器的其他模型区分开来。只是一个值得关注的设计点。
对于实际的Product 模型本身,我们要附加“预保存”中间件挂钩以填充_id 内容。因此,在确定了“前缀”的字符部分之后,该操作开始执行并在 Counter 模型集合中查找该“前缀”与“产品”类型数据的组合。
.findOneAndUpdate() 的功能是在 "counters" 集合中查找符合条件的文档,然后在已经找到文档的地方,它将通过使用 $inc 来“增加”当前的 counter 值更新运算符。如果未找到该文档,则"upsert" 选项意味着将创建一个新文档,并且无论如何,新文档中也会发生相同的“增量”。
这里的"new" 选项意味着我们希望返回“修改后的”文档(新的或更改的),而不是应用$inc 之前文档的样子。结果是“计数器”值将始终在每次访问时增加。
一旦完成并且Counter 的文档被递增或创建为它的匹配键,那么您现在可以使用一些东西来分配给Product 模型中的_id。如前所述,您可以在此处使用virtual 方便获取带有附加计数器值的前缀。
只要您的文档始终是由模型中的 .create() 方法或使用 new Product() 然后使用 .save() 方法创建的,那么附加到代码中“模型”的方法始终是执行。
请注意,既然您希望在_id 中使用它,那么作为主键,它是“不可变的”并且无法更改。因此,即使引用的字段中的内容后来被更改,_id 中的值也无法更改,因此这里的代码在 _id 值已经设置时不进行尝试。