
NocoBase 审计日志插件深度解析基于数据库事件钩子的操作变更追踪【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase导读NocoBase 是一个数据模型驱动的开源无代码平台其一切皆插件的微内核架构允许按需为业务系统附加审计能力。本文围绕 NocoBase 官方审计日志插件nocobase/plugin-audit-logs展开先说明插件的定位与元数据版本支持、免费/内置属性、废弃状态再深入其源码剖析数据库事件钩子 专用集合的审计实现原理最后结合官方测试用例给出开启审计、查看日志与开发注意事项的完整指引。读完本文你将掌握如何在 NocoBase 中通过logging配置对指定数据表开启操作留痕并理解审计数据从产生到落库、再到界面展示的完整链路。一、插件定位跟踪并记录系统内的用户活动与资源操作审计日志插件是 NocoBase 面向系统与安全package.json 中 keywords 为System security场景提供的插件其功能定位在中文文档 frontmatter 的 description 字段中写得很明确跟踪并记录系统内的用户活动和资源操作见 docs/docs/cn/plugins/nocobase/plugin-audit-logger/index.md。与大多数需要业务代码埋点的审计方案不同该插件在 NocoBase 的数据层nocobase/database通过全局事件钩子实现无侵入式记录任何数据表的增、删、改操作只要该表开启了日志开关就会自动生成审计记录业务侧无需编写任何审计代码。1.1 文档元数据速览该文档的 frontmatter 给出了插件的关键属性下表以当前仓库的文档与包配置为准属性文档声明仓库包配置package.json说明packageNamenocobase/plugin-audit-logger实际包名为nocobase/plugin-audit-logs文档路径与仓库目录名存在差异实际实现目录为packages/plugins/nocobase/plugin-audit-logs/supportedVersions1.x、2.x[1.x]文档声明支持 1.x 与 2.x当前仓库包配置仅声明 1.xisFreefalse—文档标注为非免费商业版能力builtInfalseinternal: true非内置插件属于内部包defaultEnabledfalse—默认不启用需手动安装/启用editionLevel30文档与包配置的等级标注存在出入license—Apache-2.0仓库包声明为 Apache-2.0需要特别说明的是当前仓库的 package.json 中明确将本插件标记为deprecated: true已废弃中文描述为该插件已废弃请勿使用未来将有新的审计日志插件。因此在 2.x 新项目中评估审计方案时应将这一点作为重要前提本文对源码的分析仍具有方法论参考价值同时建议关注后续版本推出的新审计插件。二、工作原理数据库事件钩子如何自动捕获增删改审计的核心机制不在 API 层而在数据库事件层。插件服务端入口 src/server/index.ts 在beforeLoad()阶段注册了三个全局事件监听export default class PluginAuditLogsServer extends Plugin { async beforeLoad() { this.db.on(afterCreate, afterCreate); this.db.on(afterUpdate, afterUpdate); this.db.on(afterDestroy, afterDestroy); } async load() { await this.importCollections(path.resolve(__dirname, collections)); this.db.addMigrations({ namespace: audit-logs, directory: path.resolve(__dirname, ./migrations), context: { plugin: this }, }); } }即任何模型实例Model在数据库中完成 create / update / destroy 之后钩子都会被触发。三个钩子实现位于 src/server/hooks/对应的日志类型由 src/server/constants.ts 定义export const LOG_TYPE_CREATE create; export const LOG_TYPE_UPDATE update; export const LOG_TYPE_DESTROY destroy;2.1 三个钩子的职责分工afterCreate新建记录新记录写入时的字段快照。从 after-create.ts 可以看到关键逻辑若本次操作的 options 中显式传入logging: false则直接跳过这是给不想被记录的操作留的逃生通道通过collection.options.logging判断该数据表是否开启审计未开启则跳过遍历model.changed()中发生变化的字段跳过hidden字段记录每个字段的after值新建时没有before写入一条type: create的日志。afterUpdate更新记录字段级的前后差异。从 after-update.ts 可以看到通过model.changed()拿到本次更新实际变化的字段集合未变化的字段不会出现在日志里避免噪音对每个变更字段同时记录before: model.previous(key)与after: model.get(key)同样过滤hidden字段并在没有有效变更时提前返回写入一条type: update的日志。afterDestroy删除在记录被删除前留档。从 after-destroy.ts 可以看到遍历model.get()的全部字段注意删除时没有 after 值因此只记录before快照写入一条type: destroy的日志用于追溯谁在什么时间删除了什么数据。2.2 写入细节防递归、同事务、记录操作人三个钩子在写入审计日志时遵循了三条一致的工程约束源码中均有体现防递归写入审计日志时显式传入hooks: false避免记录日志这个动作本身再次触发钩子形成无限循环同事务日志写入复用原操作的options.transaction保证业务操作 审计记录要么一起成功、要么一起回滚审计数据不会出现孤儿记录记录操作人通过options?.context?.state?.currentUser?.id读取当前登录用户存入日志的userId字段从而实现谁做了这个操作的追溯当操作来自系统内部无用户上下文时该字段为空。从源码结构看钩子内部对写入异常做了 try/catch 吞掉处理即审计失败不会阻断主业务操作这符合审计是旁路记录的设计取舍。三、数据模型auditLogs 主表与 auditChanges 明细表插件通过importCollections加载了两个集合src/server/collections/采用主表 明细表的两级结构存储审计数据。3.1 auditLogs审计主表定义见 auditLogs.ts字段类型说明createdAtdate操作发生时间取业务记录的 createdAttypestring操作类型create/update/destroyrecordIdstring带索引被操作记录的 ID用于快速检索某条记录的全部历史collectionNamestring被操作数据表的名称collectionbelongsTo →collections关联到元数据表foreignKey 即collectionNameconstraints 关闭changeshasMany →auditChanges该次操作的字段级变动明细userbelongsTo →users操作人集合层面还做了如下约定createdBy: false、updatedBy: false、updatedAt: false审计表自身不参与审计体系避免递归shared: true多应用共享dumpRules.group log备份时归入日志分组migrationRules: [schema-only, skip]迁移时仅同步表结构、跳过数据。3.2 auditChanges字段级变更明细定义见 auditChanges.ts字段类型说明fieldjson字段定义信息field.options含字段名、类型等beforejson变更前的值create 时为空afterjson变更后的值destroy 时为空logbelongsTo →auditLogs所属审计主记录before/after使用 json 类型存储天然支持任意字段类型字符串、数字、JSON 对象等的前后值对比这也是审计明细能精确到字段级 diff的原因。四、开启审计数据表的 logging 开关审计是否生效由每个数据表自身的logging选项决定。钩子中的collection.options.logging判断如 after-create.ts说明只有logging: true的表才会被记录。4.1 在代码中定义开启审计的表参考官方测试 hook.test.ts 中的集合定义方式db.collection({ name: posts, logging: true, // 开启审计 fields: [ { type: string, name: title }, { type: string, name: status, defaultValue: draft }, ], }); // 未开启审计的表操作不会被记录 db.collection({ name: users, logging: false, fields: [ { type: string, name: nickname }, { type: string, name: token }, ], });同一份测试也验证了logging: false的表不会产生日志测试中对users表执行了 create 操作但最终断言auditLogs中只包含posts表产生的 3 条日志。4.2 历史数据迁移老版本默认全部开启插件还附带了一个升级迁移 src/server/migrations/202206160949-logging.tsexport default class LoggingMigration extends Migration { appVersion 0.7.1-alpha.4; async up() { const result await this.app.version.satisfies(0.7.0-alpha.83); if (!result) return; const repository this.context.db.getRepository(collections); const collections await repository.find(); for (const collection of collections) { if (!collection.get(logging)) { collection.set(logging, true); await collection.save(); } } } }该迁移只作用于0.7.0-alpha.83及更早版本升级上来的老库遍历元数据表collections把尚未设置logging的数据表统一置为true保证升级后存量业务表默认纳入审计范围。五、界面侧在页面上以表格区块展示审计日志审计能力不仅在服务端客户端还提供了开箱即用的展示区块。从客户端目录src/client/看包含AuditLogsBlockInitializer / AuditLogsProvider页面区块初始化器与数据提供者在配置模式下可向页面拖入审计日志区块createAuditLogsBlockSchema区块的 Schema 定义createAuditLogsBlockSchema.tsx其核心配置为x-acl-action: auditLogs:list访问该区块需具备auditLogs:list权限、collection: auditLogs、action: list、pageSize: 20、rowKey: id以 TableV2 表格组件渲染并支持通过 initializer 配置操作列与列AuditLogsField / AuditLogsValue / AuditLogsViewActionInitializer审计字段的展示组件与查看详情动作初始化器用于查看单条日志的字段级变更明细changes对应 Schema 生成的测试见 src/client/tests/createAuditLogsBlockSchema.test.ts。这意味着启用插件后管理员可在页面设计器中直接添加审计日志区块基于auditLogs集合进行列表浏览、按操作类型筛选、查看每次操作的字段变更详情。六、行为验证官方测试如何断言审计结果插件自带的集成测试 server/tests/hook.test.ts 完整覆盖了创建 → 更新 → 删除的审计闭环是理解插件行为的最佳示例场景一模型Model层操作const Post db.getCollection(posts).model; const post await Post.create({ title: t1 }); await post.update({ title: t2 }); await post.destroy(); const auditLogs await db.getCollection(auditLogs).repository.find({ appends: [changes] }); expect(auditLogs.length).toBe(3); // create / update / destroy 各一条断言还验证了字段级 diff 的准确性第 1 条createbefore为nullafter为t1第 2 条updatebefore为t1after为t2第 3 条destroybefore为t2。场景二仓库Repository层操作并携带用户上下文const post await Post.repository.create({ values: { title: t1 }, context: { state: { currentUser: user } }, });断言生成的日志对象expect(log.toJSON()).toMatchObject({ collectionName: posts, type: create, userId: 1, // 从 context 中捕获的操作人 recordId: ${post.get(id)}, // recordId 以字符串存储 changes: [ { field: { name: title, type: string }, before: null, after: t1 }, ], });这两组测试精确印证了前文分析的钩子行为字段级 before/after 记录、操作人捕获、recordId的字符串化存储以及changes明细的字段结构。七、使用前提、限制与注意事项综合文档元数据与源码实现使用该插件时有以下要点需要留意废弃状态当前仓库包package.json已将其标记为deprecated文档声明支持 1.x/2.x但包配置仅声明支持 1.x新项目应关注未来替代插件存量项目升级前需评估迁移方案。默认关闭文档 frontmatter 声明defaultEnabled: false即安装后默认不启用需手动启用插件builtIn: false也说明它需要显式安装。按表开启审计只作用于logging: true的数据表敏感表如 users可通过logging: false排除或在单次操作中传logging: false跳过记录。字段过滤标记为hidden的字段不会被写入变更明细适合隐藏敏感字段。记录粒度update 仅记录真实发生变化的字段基于model.changed()delete 记录删除前的全量快照create 记录新建时的字段值。事务一致性审计日志与业务操作处于同一事务可保证数据一致审计写入异常会被捕获而不阻断主流程。权限控制界面区块受auditLogs:list权限约束应仅授予管理员角色防止普通用户窥探审计数据。结语NocoBase 审计日志插件的设计极具代表性它没有在业务代码中埋点而是通过数据库层的afterCreate/afterUpdate/afterDestroy全局钩子配合auditLogsauditChanges两张共享集合实现了对任意数据表增删改的字段级留痕logging开关、hidden字段过滤、同事务写入与防递归等细节则为生产环境的安全性和一致性提供了保障。虽然该插件在 2.x 已被标记废弃但其事件钩子驱动审计的架构思路、数据表设计与测试范式对理解 NocoBase 插件机制以及自行实现审计类扩展都具有直接的参考价值。读者可继续在仓库中阅读 服务端钩子、集合定义 与 集成测试 以深入探究。【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考