巨鹅追踪器
巨鹅追踪器 是一个多功能的 Mongoose 插件,可以自动跟踪文档的创建和更新。 它仔细地记录了特定字段的更改,包括嵌入字段,数组,并引用了其他文档,提供了全面的修改历史. 此插件会增强您 MongoDB 收藏中的数据完整性和可审计性 .
受到 动物可追踪 软件包, 巨鹅追踪器 提供更好的功能和定制 与你的Mongoose计划无缝结合.
目录
特征
- 跟踪您 Mongoose 文档中的字段变化.
- 支持嵌入对象.
- 支持数组元素(检测已添加/删除的项目).
- 支持引用( I)
ObjectId)到其他Mongoose文件(如果有的话,将存储“显示”值). - 允许忽略某些字段(例如:.
_id, (中文(简体) ),__v(见a/c.5/49/70,附件). - 保持历史条目可配置的最大长度.
安装
安装 巨鹅追踪器 通过 npm :
npm install @expander/mongoose-tracker
或
yarn add @expander/mongoose-tracker
使用量
插件配置
import mongoose, { Schema } from "mongoose";
import mongooseTracker from "@expander/mongoose-tracker"; // Adjust import based on your actual package name
const YourSchema = new Schema({
title: String,
orders: [
{
orderId: String,
timestamp: Date,
items: [ { name: String, price:Number, .... }, ],
// ...other fields...
}
],
user: {
firstName: String,
lastName:String,
// ...other fields...
}
// ...other fields...
});
// Apply the plugin with options
YourSchema.plugin(mongooseTracker, {
name: "history",
fieldsToTrack: [
"title",
"user.firstName",
"user.lastName",
"orders.$.items.$.price",
"orders.$.items.$.name",
"orders.$.timestamp",
],
fieldsNotToTrack: ["history", "_id", "__v", "createdAt", "updatedAt"],
limit: 50,
instanceMongoose: mongoose, //optional.
logLevel: 'info', // Optional: 'debug' | 'info' | 'warn' | 'error' | 'none' (default: 'none')
});
export default mongoose.model("YourModel", YourSchema);
它会做什么
-
添加历史字段: 添加一个调用字段
history(默认)到您的计划, 存储变化的历史 . -
监视文档更改: 监视期间的变化
save业务和基于特定查询的更新findOneAndUpdate, (中文(简体) ),updateOne, (中文(简体) ),updateMany) (中文(简体) ).说明: 当前, 插件最符合
save用于跟踪变化的方法。 我们正积极努力加强对其他更新钩的支持,以确保在所有更新行动中全面跟踪变化. -
详细更改:每次更改时记录一个条目,存储更改的用户/系统(
_changedBy如果提供的话.
选项
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
name | string | 'history' | 存储历史记录的阵列字段名称 . |
fieldsToTrack | string[] | [] (空虚) | 要跟踪的字段模式列表 。 若为空相, 所有字段 (单位:千美元) fieldsNotToTrack)被跟踪. |
fieldsNotToTrack | string[] | ['history', '_id', '_v', '__v', 'createdAt', 'updatedAt', 'deletedAt', '_display'] | 字段/路径 排除 从跟踪. |
limit | number | 50 | 要保留在历史阵列中的最大历史项数 . |
instanceMongoose | mongoose | 默认导入 mongoose 实例 | 如果你有另外的Mongoose实例的话,就取消. |
logLevel | string | 'none' | 日志级别 : 'debug', (中文(简体) ), 'info', (中文(简体) ), 'warn', (中文(简体) ), 'error',或 'none'。 。 。 . |
logger | Logger | 默认控制台日志 | 自定义日志实例(如温斯顿,皮诺等). |
字段图案
- A 类 点 (单位:千美元)
.匹配子域.- 譬如说.
user.address.city音轨更改city内字段user.address。 。 。 .
- 譬如说.
- A 类 美元数 签名( e)
$与“任何阵列索引”相匹配- 譬如说.
contacts.$.phone音轨更改phone字段为 任意 参数contacts阵列.
- 譬如说.
使用量
使用任何 Mongoose 插件 :
const mongoose = require("mongoose");
const mongooseTracker = require("@expander/mongoose-tracker");
const { Schema } = mongoose.Schema;
const CarsSchema = new Schema({
tags: [String],
description: String,
price: { type: Number, default: 0 },
});
CarsSchema.plugin(mongooseTracker, {
limit: 50,
name: "metaDescriptions",
fieldsToTrack: ["price", "description"],
});
module.exports = mongoose.model("Cars", CarsSchema);
使用 _changedBy 记录更改
这个 _changedBy 字段允许跟踪对文档进行特定修改的人.
您可以在更新文档前直接设置此字段.
建议使用 用户标识,但任何字符串值都可以指定.
示例
async function foo() {
// Create a new document
const doc = await SomeModel.find({ name: "Initial Name" });
doc.name = "New Name";
// Set the user or system responsible for the creation
doc._changedBy = "creator"; // Replace 'creator' with the user's ID or identifier
await doc.save();
}
正在生成历史日志
[
{
action: "updated",
at: 1734955271622,
changedBy: "creator",
changes: [
{
field: "name",
before: "Initial Name",
after: "New Name",
},
],
},
];
关键说明
-
changedBy字段是可选的,但高度推荐问责.
-
您可以根据当前用户的ID,用户名或其他独特的标识符,动态设置 changedBy.
《公约》的重要性 _display 外地
这个 _display 字段对于提高历史日志的可读性至关重要。 而不是用数组索引记录原始的字段路径(例如, orders.0.items.1.price,该插件将使用 _display 字段,以显示更有意义的标识符.
如何运作
-
是否在场
_display数字 :- 确保每个子文件(例如命令内的项目)包括a
_display字段. - 此字段应当包含一个唯一标识对象的字符串值,例如名称或可读标签.
- 确保每个子文件(例如命令内的项目)包括a
-
连接机制 :
- 当跟踪字段更新时(例如,
orders.$.items.$.price,插件检索_display相应的项目值. - 然后它会把这个调好
_display与已更改的字段名称一起为历史日志构成可读字符串. - 示例
- Raw 字段路径 :
orders.0.items.1.price - 与
_display数字 :"Test Item 2 price"
- Raw 字段路径 :
- 当跟踪字段更新时(例如,
-
处理对象 :
- 如果
_display字段包含一个ObjectId引用另一个文档, 插件会通过引用获取_display父文档的值. - 这种递归解析持续到获得字符串值,确保历史日志保持信息性.
- 如果
福利
- 清晰度 : 提供了清晰而简洁的变化表述,使人们更容易理解修改的内容.
- 可读性 : 避免从数组索引中产生混乱,特别是在有多个嵌入式数组的文档中.
- 相关性: 重点是在应用程序背景下具有重要意义的有意义的标识符.
示例
- 考虑以下方案:
interface Item extends Document {
name: string;
price: number;
_display: string;
}
const ItemSchema = new Schema<Item>({
name: { type: String, required: true },
price: { type: Number, required: true },
_display: { type: String, required: true },
});
interface Order extends Document {
orderNumber: string;
date: Date;
items: Item[];
_display:string;
}
const OrderSchema = new Schema<Order>({
orderNumber: { type: String, required: true, unique: true },
date: { type: Date, required: true, default: Date.now },
items: { type: [ItemSchema], required: true },
_display: { type: String },
});
interface PurchaseDemand extends Document {
pdNumber: string;
orders: Order[];
}
const PurchaseDemandSchema = new Schema<PurchaseDemand>({
pdNumber: { type: String, required: true, unique: true },
orders: [OrderSchema],
});
PurchaseDemandSchema.plugin(mongooseTracker, {
fieldsToTrack: ["orders.$.date", "orders.$.items.$.price"], //The Fields I want to track.
});
const PurchaseDemandModel = mongoose.model<PurchaseDemand>(
"PurchaseDemand",
PurchaseDemandSchema
);
const purchaseDemand = new PurchaseDemand({
pdNumber: "PD-001",
orders: [
{
orderNumber: "ORD-001",
items: [
{ name: "Test Item 1", price: 100, _display: "Test Item 1" },
{ name: "Test Item 2", price: 200, _display: "Test Item 2" },
],
_display: "Order 1",
},
],
});
// Update an item's price
purchaseDemand._changedBy = 'system';
purchaseDemand.orders[0].items[1].price = 250;
await purchaseDemand.save();
历史日志条目 :
{
"action": "updated",
"at": 1734955271622,
"changedBy": "system",
"changes": [
{
"field": "Test Item 2 price", // instead of "orders.0.items.1.price"
"before": 200,
"after": 250
}
]
}
跟踪阵列元素中的嵌入物体
当跟踪数组元素内的嵌入对象时,插件会智能地解决 _display 从数组元素本身,而不是从嵌入对象。 这对有巢状结构的复杂计划特别有用.
示例:追踪供应商尺寸
const BusinessPartnerSchema = new Schema({
name: { type: String, required: true },
_display: { type: String, required: true }
});
const ExpanderProductSchema = new Schema({
name: { type: String, required: true },
availableSuppliers: [
{
name: { type: String, required: true },
supplierId: {
type: Schema.Types.ObjectId,
ref: 'BusinessPartner',
required: true
},
dimensions: {
boxUnitLengthSide: { type: Number, default: 0 },
boxUnitWidthSide: { type: Number, default: 0 },
boxUnitHeightSide: { type: Number, default: 0 },
boxUnitWeight: { type: Number, default: 0 }
},
cost: { type: Number, default: 0 },
_display: {
type: Schema.Types.ObjectId,
ref: 'BusinessPartner' // References BusinessPartner
}
}
]
});
// Track nested dimension fields
ExpanderProductSchema.plugin(mongooseTracker, {
fieldsToTrack: [
'availableSuppliers.$.dimensions.boxUnitLengthSide',
'availableSuppliers.$.dimensions.boxUnitWidthSide',
'availableSuppliers.$.dimensions.boxUnitHeightSide',
'availableSuppliers.$.dimensions.boxUnitWeight'
]
});
用法 :
const supplier = await BusinessPartner.create({
name: 'Xinlong Plastic Trunking Co Ltd',
_display: 'Xinlong Plastic Trunking Co Ltd'
});
const product = await ExpanderProduct.create({
name: 'Test Product',
availableSuppliers: [{
name: supplier.name,
supplierId: supplier._id,
dimensions: {
boxUnitLengthSide: 10,
boxUnitWidthSide: 20,
boxUnitHeightSide: 30,
boxUnitWeight: 5
},
cost: 50,
_display: supplier._id // ObjectId reference
}]
});
// Update dimensions
product.availableSuppliers[0].dimensions.boxUnitLengthSide = 15;
product.availableSuppliers[0].dimensions.boxUnitWeight = 7;
await product.save();
历史日志条目 :
{
"action": "updated",
"at": 1734955271622,
"changedBy": null,
"changes": [
{
"field": "Xinlong Plastic Trunking Co Ltd boxUnitLengthSide", // Resolved from BusinessPartner
"before": 10,
"after": 15
},
{
"field": "Xinlong Plastic Trunking Co Ltd boxUnitWeight", // Resolved from BusinessPartner
"before": 5,
"after": 7
}
]
}
说明: 插件解决 _display 从 availableSuppliers[0]._display 而不是来自 availableSuppliers[0].dimensions._display这提供了有意义的背景,表明哪些供应商的维度被修改了.
日志
插件包括内置的日志系统来帮助调试和监测. 您可以使用 logLevel 选项或提供自定义日志.
使用内建登录
YourSchema.plugin(mongooseTracker, {
name: "history",
fieldsToTrack: ["title", "status"],
logLevel: 'info' // Set log level
});
可用的日志级别 :
'debug'- 调试的详细信息(实地变化、阵列操作、参考文献)'info'- 一般性信息信息(提示、历史更新)'warn'- 警告信息(丢失文件,无效引用)'error'- 错误消息(无效模式、搜索失败)'none'- 禁用所有记录( 默认)
日志输出示例
# Info level
[mongoose-tracker] [INFO] Initializing mongoose-tracker plugin with options: { ... }
[mongoose-tracker] [INFO] Pre-save hook triggered for document 507f1f77bcf86cd799439011
# Debug level (includes all info + detailed tracking)
[mongoose-tracker] [DEBUG] Tracking changes for path: title, displayField: title
[mongoose-tracker] [DEBUG] Primitive value changed for title: "Old Title" => "New Title"
[mongoose-tracker] [DEBUG] Array elements added to orders: 2
# Warn level
[mongoose-tracker] [WARN] Referenced document not found for model: Category, id: 507f1f77bcf86cd799439012
使用自定义记录器
您可以通过执行该程序提供自己的记录器(例如Winston, Pino) Logger 接口 :
import winston from 'winston';
const customLogger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'mongoose-tracker.log' })
]
});
// Adapt Winston to mongoose-tracker Logger interface
const loggerAdapter = {
debug: (message, ...args) => customLogger.debug(message, args),
info: (message, ...args) => customLogger.info(message, args),
warn: (message, ...args) => customLogger.warn(message, args),
error: (message, ...args) => customLogger.error(message, args)
};
YourSchema.plugin(mongooseTracker, {
name: "history",
fieldsToTrack: ["title"],
logger: loggerAdapter
});
使用皮诺
import pino from 'pino';
const pinoLogger = pino({
level: 'debug',
transport: {
target: 'pino-pretty'
}
});
const loggerAdapter = {
debug: (message, ...args) => pinoLogger.debug({ args }, message),
info: (message, ...args) => pinoLogger.info({ args }, message),
warn: (message, ...args) => pinoLogger.warn({ args }, message),
error: (message, ...args) => pinoLogger.error({ args }, message)
};
YourSchema.plugin(mongooseTracker, {
name: "history",
logger: loggerAdapter
});
跟踪阵列字段
当指定字段 ToTrack 中的数组字段时, 如“ 顺序 ”, 巨鹅追踪器 将监视该数组内的任何增删。 这意味着:
- 增加数: 当新元素被添加到数组中时, 插件会记录历史数组中的这个变化 .
- 删除: 当一个已有元素从数组中去除时, 插件会记录历史数组中的这个去除 .
有引用的矩阵元素 _display
巨鹅追踪器 也支持跟踪数组 _display 字段是一个 ObjectId 参考另一种模式。 插件将自动解析引用并使用引用文档的 _display 历史日志中的值.
示例:跟踪供应商阵列
const BusinessPartnerSchema = new Schema({
name: { type: String, required: true },
type: { type: String },
_display: { type: String, required: true }
});
const ExpanderProductSchema = new Schema({
name: { type: String, required: true },
availableSuppliers: [
{
name: { type: String, required: true },
supplierId: {
type: Schema.Types.ObjectId,
ref: 'BusinessPartner',
required: true
},
cost: { type: Number, default: 0 },
// _display references the BusinessPartner document
_display: {
type: Schema.Types.ObjectId,
ref: 'BusinessPartner'
}
}
]
});
// Track changes to availableSuppliers array
ExpanderProductSchema.plugin(mongooseTracker, {
fieldsToTrack: ['availableSuppliers']
});
const BusinessPartner = mongoose.model('BusinessPartner', BusinessPartnerSchema);
const ExpanderProduct = mongoose.model('ExpanderProduct', ExpanderProductSchema);
添加供应商 :
const supplier = await BusinessPartner.create({
name: 'Supplier A',
type: 'manufacturer',
_display: 'Supplier A Display'
});
const product = await ExpanderProduct.findOne({ name: 'My Product' });
product.availableSuppliers.push({
name: supplier.name,
supplierId: supplier._id,
cost: 100,
_display: supplier._id // ObjectId reference
});
await product.save();
历史日志条目 :
{
"action": "added",
"at": 1734955271622,
"changedBy": null,
"changes": [
{
"field": "availableSuppliers",
"before": null,
"after": "Supplier A Display" // Resolved from BusinessPartner._display
}
]
}
删除供应商 :
product.availableSuppliers = product.availableSuppliers.filter(
s => s.supplierId.toString() !== supplier._id.toString()
);
await product.save();
历史日志条目 :
{
"action": "removed",
"at": 1734955271622,
"changedBy": null,
"changes": [
{
"field": "availableSuppliers",
"before": "Supplier A Display", // Resolved from BusinessPartner._display
"after": null
}
]
}
操作 :
添加元素( 命令) :
PurchaseDemandSchema.plugin(mongooseTracker, {
fieldsToTrack: ["orders"],
});
const purchaseDemand = await PurchaseDemandModel.create({
pdNumber: "PD-TEST-002",
orders: [],
});
// Adding a new order
purchaseDemand.orders.push({
orderNumber: "ORD-TEST-002",
date: new Date(),
items: [{ name: "Test Item 3", price: 300, _display: "Test Item 3" }],
_display: "ORD-TEST-002",
});
await purchaseDemand.save();
历史日志条目添加后 :
{
"action": "added",
"at": 1734955271622,
"changedBy": null,
"changes": [
{
"field": "orders",
"before": null,
"after": 'ORD-TEST-002' // the name of _display.
}
]
}
删除元素( 命令) :
purchaseDemand.orders.pop(); // we remove the last element that insert in orders. (ORD-TEST-002)
await purchaseDemand.save();
删除后的历史日志条目 :
{
"action": "removed",
"at": 1734955271622,
"changedBy": null,
"changes": [
{
"field": "orders",
"before": 'ORD-TEST-002'
"after": null
}
]
}
捐款
- 使用 Eslint 插入您的代码 .
- 为任何新的或已更改的功能添加测试 .
- 如果添加或更改任何功能,则用示例更新读取器.
法律
- 作者:罗尼·杰克·维图利
- 许可证: Apache-2.0