Skip to content
Mongoose plugin that tracking the fields you specified and automatically record the change time of them into DB.
TypeScript JavaScript Shell
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci perf: improve ci Oct 22, 2019
src chore: reword key name Oct 21, 2019
test
.editorconfig
.gitignore feat: the first commit Sep 20, 2019
.npmignore
.prettierrc
CHANGELOG.md
LICENSE
NPM_README.md
README.md docs: update changelog address Nov 10, 2019
README_en.md
commitlint.config.js
jest.config.js
npm_publish.sh
package.json
tsconfig.build.json
tsconfig.json feat: the first commit Sep 20, 2019
tslint.json
yarn.lock

README.md

mongoose-modified-at

CircleCI Coverage Status npm version code style: prettier license

Mongoose 插件 - 自动更新字段变化时间并记录到数据库中,类似 Mongoose 自带的 timestamps 功能。

中文 | English

目录

使用场景

考虑一个例子,我们有个文章发布与展示的需求,数据模型如下(为了简洁便于理解省去了长度、有效值等的校验,实际生产时还是必要加上的,以免出现脏数据)。

const schema = new mongoose.Schema({
  // 文章标题
  title: String,
  // 是否为草稿
  is_draft: Boolean,
  // 是否推荐
  is_recommended: Boolean,
  // 更多字段...
})

当我们在展示最新文章列表时,应该是以文章第一次发布的时间倒序展示,因为文章可以存为草稿,多次编辑,所以不能用 Mongoose 提供的 createdAtupdatedAt 作为第一次发布的时间,正确的做法是在每次文章创建或更新时,确定用户是发布文章而不是存为草稿,然后记录此次时间,用该时间作为第一次发布的时间。

要实现该功能我们需要在代码逻辑层进行处理,这样可行不过有点耦合,或者自己封装一个 Mongoose 中间件来做这件事,不过现在你可以把这件事交给一个经受测试、API 优雅的插件 ModifiedAt 来处理。

首先安装插件。

npm install mongoose-modified-at --save

然后在 Schema 初始化时做简单的配置即可,如下。

import modifiedAt from 'mongoose-modified-at'

// 在 mongoose.model 调用之前
schema.plugin(modifiedAt, {
  // 函数名将作为字段名写入数据库
  publishedAt(doc) {
    // 当函数返回值为 true 时,则记录该时间
    return !doc.is_draft
  },
  // 推荐文章也是如此
  recommendedAt(doc) {
    return doc.is_recommended
  },
})

const Article = mongoose.model('Article', schema)

当文档保存或更新携带着 is_draft 字段并且值为 false 时,插件就会记录此次时间到你声明的 publishedAt 字段上一起写入数据库。

示例如下:

await Article.create({
  title: 'Document Title',
  is_draft: false,
  is_recommended: true,
  // 更多字段...
})

结果如下(数据库):

{
  "title": "Document Title",
  "is_draft": false,
  "is_recommended": true,
  "publishedAt": ISODate("2019-09-27T03:11:07.880Z"),
  "recommendedAt": ISODate("2019-09-27T03:11:07.880Z"),
  // 更多字段...
}

附加案例

作为渐进式项目,我们的开发一般也是渐进式的,虽然我们会不自觉地超前考虑,但是还是不能完全考虑到未来需求的变化,假如我们对某个项目的功能已经完成并稳定上线了,后来比如我们需要做数据统计分析的工作,这项工作的分析维度对时间的精度要求比较高,所以要是我们在开发时并没有考虑到要添加这些时间字段(因为可能对业务不是必须的),而现在需要加上这些字段,要是去原来的代码基础上改动添加,如果改动的地方少还好,如果有完善的测试用例还好,否则这也许会改的心惊胆战,因为你需要确保每一处改动不会产生错误影响。所以此时,使用无侵入式的中间件插件 ModifiedAt 那就省心很多了,只需在模型出口简单配置,无需改动逻辑层代码,即可实现刚刚想要的功能。

API介绍

上面是 ModifiedAt 的富 API 形式,即对象格式,全部参数选项如下。

schema.plugin(modifiedAt, {
  // 设置监听字段
  fields: ['name', 'status', 'another'],
  // 设置后缀
  suffix: '_your_suffix',
  // 设置路径默认行为
  select: true,
  // 自定义字段
  customField(doc) {
    // 做一些你想做的事,然后返回 Boolean 值,告诉插件是否记录时间
  },
})

🍎 参数解释:

  • fields: 设置监听字段,在文档创建或更新时,如果存在被监听的字段,则自动以 字段名 + 后缀 的形式作为字段,并记录此次更新时间到该字段上。可选,Array 类型。
  • suffix: 设置后缀,默认值为 _modifiedAt。可选,String 类型。
  • select: 设置路径默认行为,默认为 true参考 Mongoose 文档。可选,Boolean 类型。
  • customField: 自定义字段,此字段不会加后缀,以函数形式添加到参数中,用于自定义功能,函数接收唯一文档参数,当函数返回值为真值时,则记录此次时间到该字段上。

🌟 1、 你可以在应用程序初始化时设置全局后缀,它将应用于每个插件实例,如下。

import modifiedAt from 'mongoose-modified-at'
modifiedAt.suffix = '_your_suffix'

🚀 2、 为了增加 API 的简洁易用同时避免过度重载,ModifiedAt 只增加了一种简化传参格式,如下。

schema.plugin(modifiedAt, ['name', 'status'])

意思是将 fields 选项提取出来作为参数,写入数据库的结果如下。

{
  "name": "Tom",
  "status": 1,
  "name_modifiedAt": ISODate("2019-09-27T03:13:17.888Z"),
  "status_modifiedAt": ISODate("2019-09-27T03:13:17.888Z"),
}

支持异步(Async)

你需要 Node.js 版本支持 async/await 即可。

import P from 'bluebird'

const petSchema = new mongoose.Schema({
  name: String,
  age: Number,
  sex: String,
  // 1:表示采购中,2:已购买,3:已售出
  status: Number,
})

petSchema.plugin(modifiedAt, {
  // 记录购买于哪时
  async boughtAt(doc) {
    // 延时 1s
    await P.delay(1000)
    return doc.status === 2
  },
  // 记录售出于哪时
  soldAt(doc) {
    return doc.status === 3
  },
})

细节说明

👍 1、 对于 update 系列操作,可通过在 options 里加上 { modifiedAt: false } 来跳过插件功能,对于此次更新。

JavaScript 示例如:Model.updateOne({}, { status: 2 }, { modifiedAt: false })

TypeScript 示例如:Model.updateOne({}, { status: 2 }, { modifiedAt: false } as any)


🙌 2、 如果要为 Model.create() 指定 options,文档需要是数组,如 Mongoose 的描述

[options] «Object» Options passed down to save(). To specify options, docs must be an array, not a spread.

🤟 3、 对于 replace 系列操作,ModifiedAt 功能默认是关闭的,因为替换操作可能是想换成纯粹的数据,当然如果也需要 ModifiedAt 功能,则可以在 options 里加上 { modifiedAt: true } 来为此次操作开启插件功能。

示例如:Model.findOneAndReplace({}, { status: 2 }, { modifiedAt: true })

相关 API 列表如下:

  • Model.findOneAndReplace()
  • Model.replaceOne()
  • Query.prototype.findOneAndReplace()
  • Query.prototype.replaceOne()
  • Document.prototype.replaceOne()

🖐 4、 暂不支持 Model.bulkWrite() 操作,如官方文档所描述,该操作不会触发任何中间件,如果需要触发 save() 中间件请使用 Model.create() 替代。

虽然结果相同,但性能不同,如果同时要兼顾性能,可自行在 bulkWrite() 数据里加上时间。


🖐 5、 暂不支持 MongoDB 原生操作符,如 $set, $inc, $currentDate 等(下个版本支持)。

示例如:Model.updateOne({}, { $inc: { quantity: 5 } })


🖐 6、 插件不支持 Schema 的默认值,因为无法监听获取;示例如下:

const schema = new mongoose.Schema({
  name: String,
  age: {
    type: Number,
    default: 1,
  },
})

schema.plugin(modifiedAt, ['name', 'age'])

const Cat = mongoose.model('Cat', schema)

const kitty = await Cat.create({ name: 'Kitty' })

// 结果如下:
// kitty.name => 'Kitty'
// kitty.name_modifiedAt => ISODate("2019-09-27T03:13:17.888Z")
// kitty.age => 1
// kitty.age_modifiedAt => 不存在

如果希望 age 被监听到,可以在 create() 里指定 age 属性,设置为默认值即可。

版本支持

该插件支持于 Mongoose 5.x 的版本,如果你使用的是 Mongoose 4.x 的版本,请安装插件 1.x 版本,文档请点击这里

npm install mongoose-modified-at@1 --save

更新日志

版本详细的更新日志请查看 CHANGELOG.md

协议

MIT licensed.

You can’t perform that action at this time.