时间戳

Mongoose 结构支持 timestamps 选项。 如果你设置 timestamps: true,Mongoose 会将两个 Date 类型的属性添加到你的结构中:

  1. createdAt: 代表该文档创建时间的日期
  2. updatedAt: 表示本文档上次更新时间的日期

然后,Mongoose 将在首次插入文档时设置 createdAt,并在你使用 save()updateOne()updateMany()findOneAndUpdate()update()replaceOne()bulkWrite() 更新文档时更新 updatedAt

const userSchema = new Schema({ name: String }, { timestamps: true });
const User = mongoose.model('User', userSchema);

let doc = await User.create({ name: 'test' });

console.log(doc.createdAt); // 2022-02-26T16:37:48.244Z
console.log(doc.updatedAt); // 2022-02-26T16:37:48.244Z

doc.name = 'test2';
await doc.save();
console.log(doc.createdAt); // 2022-02-26T16:37:48.244Z
console.log(doc.updatedAt); // 2022-02-26T16:37:48.307Z

doc = await User.findOneAndUpdate({ _id: doc._id }, { name: 'test3' }, { new: true });
console.log(doc.createdAt); // 2022-02-26T16:37:48.244Z
console.log(doc.updatedAt); // 2022-02-26T16:37:48.366Z

createdAt 属性是不可变的,默认情况下,Mongoose 会覆盖任何用户指定的对 updatedAt 的更新。

let doc = await User.create({ name: 'test' });

console.log(doc.createdAt); // 2022-02-26T17:08:13.930Z
console.log(doc.updatedAt); // 2022-02-26T17:08:13.930Z

doc.name = 'test2';
doc.createdAt = new Date(0);
doc.updatedAt = new Date(0);
await doc.save();

// Mongoose blocked changing `createdAt` and set its own `updatedAt`, ignoring
// the attempt to manually set them.
console.log(doc.createdAt); // 2022-02-26T17:08:13.930Z
console.log(doc.updatedAt); // 2022-02-26T17:08:13.991Z

// Mongoose also blocks changing `createdAt` and sets its own `updatedAt`
// on `findOneAndUpdate()`, `updateMany()`, and other query operations
doc = await User.findOneAndUpdate(
  { _id: doc._id },
  { name: 'test3', createdAt: new Date(0), updatedAt: new Date(0) },
  { new: true }
);
console.log(doc.createdAt); // 2022-02-26T17:08:13.930Z
console.log(doc.updatedAt); // 2022-02-26T17:08:14.008Z

备用属性名称

出于这些文档的目的,我们将始终引用 createdAtupdatedAt。 但你可以覆盖这些属性名称,如下所示。

const userSchema = new Schema({ name: String }, {
  timestamps: {
    createdAt: 'created_at', // Use `created_at` to store the created date
    updatedAt: 'updated_at' // and `updated_at` to store the last updated date
  }
});

禁用时间戳

save()updateOne()updateMany()findOneAndUpdate()update()replaceOne()bulkWrite() 均支持 timestamps 选项。 设置 timestamps: false 以跳过为该特定操作设置时间戳。

let doc = await User.create({ name: 'test' });

console.log(doc.createdAt); // 2022-02-26T23:28:54.264Z
console.log(doc.updatedAt); // 2022-02-26T23:28:54.264Z

doc.name = 'test2';

// Setting `timestamps: false` tells Mongoose to skip updating `updatedAt` on this `save()`
await doc.save({ timestamps: false });
console.log(doc.updatedAt); // 2022-02-26T23:28:54.264Z

// Similarly, setting `timestamps: false` on a query tells Mongoose to skip updating
// `updatedAt`.
doc = await User.findOneAndUpdate({ _id: doc._id }, { name: 'test3' }, {
  new: true,
  timestamps: false
});
console.log(doc.updatedAt); // 2022-02-26T23:28:54.264Z

// Below is how you can disable timestamps on a `bulkWrite()`
await User.bulkWrite([{
  updateOne: {
    filter: { _id: doc._id },
    update: { name: 'test4' },
    timestamps: false
  }
}]);
doc = await User.findOne({ _id: doc._id });
console.log(doc.updatedAt); // 2022-02-26T23:28:54.264Z

你还可以将 timestamps 选项设置为对象以分别配置 createdAtupdatedAt。 例如,在下面的代码中,Mongoose 在 save() 上设置 createdAt 但跳过 updatedAt

const doc = new User({ name: 'test' });

// Tell Mongoose to set `createdAt`, but skip `updatedAt`.
await doc.save({ timestamps: { createdAt: true, updatedAt: false } });
console.log(doc.createdAt); // 2022-02-26T23:32:12.478Z
console.log(doc.updatedAt); // undefined

禁用时间戳还可以让你自己设置时间戳。 例如,假设你需要更正文档的 createdAtupdatedAt 属性。 你可以通过自行设置 timestamps: falsecreatedAt 来实现此目的,如下所示。

let doc = await User.create({ name: 'test' });

// To update `updatedAt`, do a `findOneAndUpdate()` with `timestamps: false` and
// `updatedAt` set to the value you want
doc = await User.findOneAndUpdate({ _id: doc._id }, { updatedAt: new Date(0) }, {
  new: true,
  timestamps: false
});
console.log(doc.updatedAt); // 1970-01-01T00:00:00.000Z

// To update `createdAt`, you also need to set `strict: false` because `createdAt`
// is immutable
doc = await User.findOneAndUpdate({ _id: doc._id }, { createdAt: new Date(0) }, {
  new: true,
  timestamps: false,
  strict: false
});
console.log(doc.createdAt); // 1970-01-01T00:00:00.000Z

子文档的时间戳

Mongoose 还支持在子文档上设置时间戳。 请记住,子文档的 createdAtupdatedAt 代表子文档的创建或更新时间,而不是顶层文档。 覆盖子文档也会覆盖 createdAt

const roleSchema = new Schema({ value: String }, { timestamps: true });
const userSchema = new Schema({ name: String, roles: [roleSchema] });

const doc = await User.create({ name: 'test', roles: [{ value: 'admin' }] });
console.log(doc.roles[0].createdAt); // 2022-02-27T00:22:53.836Z
console.log(doc.roles[0].updatedAt); // 2022-02-27T00:22:53.836Z

// Overwriting the subdocument also overwrites `createdAt` and `updatedAt`
doc.roles[0] = { value: 'root' };
await doc.save();
console.log(doc.roles[0].createdAt); // 2022-02-27T00:22:53.902Z
console.log(doc.roles[0].updatedAt); // 2022-02-27T00:22:53.902Z

// But updating the subdocument preserves `createdAt` and updates `updatedAt`
doc.roles[0].value = 'admin';
await doc.save();
console.log(doc.roles[0].createdAt); // 2022-02-27T00:22:53.902Z
console.log(doc.roles[0].updatedAt); // 2022-02-27T00:22:53.909Z

引擎盖下

对于带有时间戳的查询,Mongoose 为每个更新查询添加 2 个属性:

  1. updatedAt 添加到 $set
  2. createdAt 添加到 $setOnInsert

例如,如果你运行以下代码:

mongoose.set('debug', true);

const userSchema = new Schema({
  name: String
}, { timestamps: true });
const User = mongoose.model('User', userSchema);

await User.findOneAndUpdate({}, { name: 'test' });

你将看到 Mongoose 调试结构的以下输出:

Mongoose: users.findOneAndUpdate({}, { '$setOnInsert': { createdAt: new Date("Sun, 27 Feb 2022 00:26:27 GMT") }, '$set': { updatedAt: new Date("Sun, 27 Feb 2022 00:26:27 GMT"), name: 'test' }}, {...})

请注意 $setOnInsert 对应 createdAt$set 对应 updatedAt。 仅当新文档为 upserted 时,MongoDB 的 $setOnInsert 运算符 才会应用更新。 因此,例如,如果你只想在创建新文档时设置 updatedAt,则可以禁用 updatedAt 时间戳并自行设置,如下所示:

await User.findOneAndUpdate({}, { $setOnInsert: { updatedAt: new Date() } }, {
  timestamps: { createdAt: true, updatedAt: false }
});