Refine v5 审计日志(Audit Logs)完整指南:从 AuditLogProvider 到 useLog / useLogList
Refine v5 审计日志Audit Logs完整指南从 AuditLogProvider 到 useLog / useLogList【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本篇指南围绕 Refine v5 的 Audit Logs 体系展开讲解如何通过auditLogProvider在数据变更时自动记录审计事件、如何用useLog与useLogList手动写入与查询日志并深入源码剖析create/update/get三个方法背后的调用链与事件参数结构。读完本文你将能够在自己的 Refine 应用中搭建一套可追溯、可审计、面向合规要求的操作日志机制。为什么需要审计日志审计日志Audit Logs是 Web 应用中非常有用的工具它为用户操作与系统变更提供了一条可靠、可检索的记录。记录并存储这些日志能够保证系统行为的透明性与可问责性accountability这对**安全、合规compliance以及问题排查debugging**都至关重要。在 Refine 中审计日志的核心价值在于CRUD 操作无需手动埋点即可自动记录。当你在Refine上提供auditLogProvider后由useCreate、useUpdate、useDelete等数据 hook 发起的每一次成功变更都会自动生成一条审计事件并附带来自useGetIdentityhook 的当前用户信息。Audit Log Provider 是什么Refine 通过 Audit Log Provider 来集中、统一地获取与写入应用中的审计日志。它本质上是一个普通对象包含三个方法create向审计日志写入一条事件get返回一组审计事件列表查询update更新一条审计事件其 TypeScript 接口定义在 packages/core/src/contexts/auditLog/types.ts 中核心类型如下export type LogParams { resource: string; action: string; data?: any; author?: { name?: string; [key: string]: any; }; previousData?: any; meta: Recordnumber | string, any; }; export type IAuditLogContext { create?: (params: LogParams) Promiseany; get?: (params: { resource: string; action?: string; meta?: Recordnumber | string, any; author?: Recordnumber | string, any; }) Promiseany; update?: (params: { id: BaseKey; name: string; [key: string]: any; }) Promiseany; }; export type AuditLogProvider RequiredIAuditLogContext;可以看到AuditLogProvider就是IAuditLogContext的必选版本create、get、update三者缺一不可。在运行时这些方法通过AuditLogContext注入到整个应用中其实现见 packages/core/src/contexts/auditLog/index.tsxAuditLogContextProvider接收create、get、update三个 prop并将其放入 React Context。所有内置 hookuseLog、useLogList以及各数据 mutation hook都通过useContext(AuditLogContext)拿到这三个方法。一个最小可用的 Provider 示例下面是一个完整的auditLogProvider示例对应 documentation/docs/audit-logs/audit-log-provider/index.md 中的实战写法import { AuditLogProvider } from refinedev/core; export const auditLogProvider: AuditLogProvider { get: async (params) { const { resource, meta, action, author } params; const response await fetch( https://example.com/api/audit-logs/${resource}/${meta.id}, { method: GET, }, ); const data await response.json(); return data; }, // 理想情况下审计日志应该在服务端创建。 // 因为它可以被用户篡改客户端创建的结果并不是可靠的真相来源。 create: async (params) { const { resource, meta, action, author, data, previousData } params; console.log(resource); // products, posts, 等 console.log(meta); // { id: 1 }, { id: 2 }, 等 console.log(action); // create, update, delete // author 对象是 useGetIdentity hook 的返回值 console.log(author); // { id: 1, name: John Doe } console.log(data); // { name: Product 1, price: 100 } console.log(previousData); // { name: Product 1, price: 50 } await fetch(https://example.com/api/audit-logs, { method: POST, body: JSON.stringify(params), }); return { success: true }; }, update: async (params) { const { id, name, ...rest } params; console.log(id); // 1 console.log(name); // Created Product 1 console.log(rest); // { foo: bar } await fetch(https://example.com/api/audit-logs/${id}, { method: PATCH, body: JSON.stringify(params), }); return { success: true }; }, };注册到Refine将 Provider 传给Refine组件的auditLogProviderprop 即可启用全局审计能力import { Refine } from refinedev/core; import { auditLogProvider } from ./audit-log-provider; export const App () { return Refine auditLogProvider{auditLogProvider}{/* ... */}/Refine; };逐方法实现get / create / updateget查询审计事件列表get方法用于获取一组审计日志事件。例如通过useLogListhook 按某个记录 id 列出该资源的所有活动时会向get发送如下事件{ resource: posts, meta: { id: 1 } }get收到的参数结构为{ resource, action?, meta?, author? }你的实现可以基于这些字段向服务端发起查询如上面的 fetch 示例。create写入审计事件create方法在以下两种时机被触发一次成功的 mutation 之后由数据 hook 自动触发手动调用useLog的log方法时。传入的参数表示即将创建的新记录的值。根据 mutation 类型不同Refine 会为create组装不同的参数规则如下见 documentation/docs/audit-logs/audit-log-provider/index.mdpreviousData来自 react-query 缓存若能找到则返回旧值否则为undefined在 create 类 mutation 中如果请求响应包含id字段该id会被加入到meta对象如果 auth provider 中定义了getUserIdentity事件中会追加author对象其值为getUserIdentity的返回值。各 mutation 类型的create事件参数如下createuseCreate{ action: create, resource: posts, data: { title: Hello World, content: Hello World }, meta: { dataProviderName: simple-rest, // 如果请求响应有 id 字段会被加入 meta id: 1 } }updateuseUpdate{ action: update, resource: posts, data: { title: New Hello World, content: New Hello World }, previousData: { title: Hello World, content: Hello World }, meta: { dataProviderName: simple-rest, id: 1 } }deleteuseDelete{ action: delete, resource: posts, meta: { dataProviderName: simple-rest, id: 1 } }createManyuseCreateMany{ action: createMany, resource: posts, data: [ { title: Hello World 1 }, { title: Hello World 2 } ], meta: { dataProviderName: simple-rest, // 如果请求响应有 id 字段会被加入 meta ids: [1, 2] } }updateManyuseUpdateMany{ action: updateMany, resource: posts, data: { status: published }, previousData: [ { status: draft }, { status: archived } ], meta: { dataProviderName: simple-rest, ids: [1, 2] } }deleteManyuseDeleteMany{ action: deleteMany, resource: posts, meta: { dataProviderName: simple-rest, id: [1, 2] } }在 Provider 的create中你可以解构这些字段并发送到服务端export const auditLogProvider: AuditLogProvider { create: (params) { const { resource, meta, action, author, data, previousData } params; console.log(resource); // products, posts, 等 console.log(meta); // { id: 1 }, { id: 2 }, 等 console.log(action); // create, update, delete // author 对象是 useGetIdentity hook 的返回值 console.log(author); // { id: 1, name: John Doe } console.log(data); // { name: Product 1, price: 100 } console.log(previousData); // { name: Product 1, price: 50 } await fetch(https://example.com/api/audit-logs, { method: POST, body: JSON.stringify(params), }); return { success: true }; }, };安全提醒由于客户端数据可被用户篡改官方建议将审计日志的实际落库放在服务端完成。客户端的create只是上报事件不应被视为可靠的数据真相来源source of truth。update更新审计事件update方法用于更新一条既有的审计事件。例如通过useLog的rename方法会给update发送如下参数{ id: 1, name: event name }对应实现export const auditLogProvider: AuditLogProvider { update: async (params) { const { id, name, ...rest } params; console.log(id); // 1 console.log(name); // Created Product 1 console.log(rest); // { foo: bar } await fetch(https://example.com/api/audit-logs/${id}, { method: PATCH, body: JSON.stringify(params), }); return { success: true }; }, };自动审计的底层原理数据 hook 如何在成功后写日志Refine 的自动审计并非黑魔法而是内建于各个数据 mutation hook 的成功回调中。以useCreate为例在 packages/core/src/hooks/data/useCreate.ts 中可以看到当 mutation 成功后Refine 会先执行通知notification与缓存失效invalidate随后调用const { fields: _fields, operation: _operation, variables: _variables, ...rest } combinedMeta || {}; log?.mutate({ action: create, resource: resource.name, data: values, meta: { ...rest, dataProviderName, id: data?.data?.id ?? undefined, }, });这段代码印证了文档中的两个细节meta中会带上dataProviderName并且当响应包含id时会把id塞进meta。log?.mutate(...)中log正是useLog返回的 mutation通过内部 hook 组合注入因此只要auditLogProvider存在create方法就会被自动调用。useUpdate、useDelete、useCreateMany、useUpdateMany、useDeleteMany的实现位于 packages/core/src/hooks/data/ 目录逻辑一致。再看useLog的源码packages/core/src/hooks/auditLog/useLog/index.ts它内部会调用useGetIdentity获取当前用户并在调用create时把author: identityData ?? authorData?.data合并进参数——这就是事件中author字段的来源它还读取resource?.meta?.audit并调用hasPermission进行按 mutation 类型的过滤详见下文按资源启用/禁用审计一节log使用useMutation包装auditLogContext.createrename使用useMutation包装auditLogContext.update并定义了各自的 mutationKey。Hook 集成useLog 与 useLogList除自动审计外Refine 还提供两个 hook 让你在任意组件中手动读写审计日志见 useLog 与 useLogList。useLog手动创建 / 重命名审计事件useLog返回两个 mutationlog与rename。import { useLog } from refinedev/core; const { log, rename } useLog();log创建事件底层调用auditLogProvider的create方法。const { log } useLog(); const { mutate } log; mutate({ resource: posts, action: create, author: { username: admin, }, data: { id: 1, title: New post, }, meta: { id: 1, }, });logmutation 的属性如下PropertyType说明resource必填string资源名称如postsaction必填string动作名称如createauthorRecordstring, any操作者信息metaRecordstring, any附加元数据如记录 iddataRecordstring, any变更后的数据previousDataRecordstring, any变更前的数据可选log与rename的类型参数均支持TData继承BaseRecord、TError继承HttpError、TVariables默认值分别为BaseRecord、HttpError、{}返回值均为 TanStack Query 的UseMutationResult。rename更新事件底层调用auditLogProvider的update方法。const { rename } useLog(); const { mutate } rename; mutate({ id: 1, name: Updated Name, });renamemutation 的属性PropertyType说明id必填BaseKey要更新的事件 idname必填string新的事件名称值得注意的源码细节rename的onSuccess中如果返回值包含resource字段Refine 会通过queryClient.invalidateQueries使对应资源的list审计查询失效从而让useLogList的列表自动刷新见 useLog/index.ts。因此你的update方法如果返回{ resource: posts }之类的对象就能触发关联列表的重新拉取。useLogList查询审计事件列表useLogList底层调用auditLogProvider的get方法对应文档 use-log-listimport { useLogList } from refinedev/core; const postAuditLogResults useLogList({ resource: posts, });useLogList的入参属性PropertyType默认值resource必填string从路由读取的 actionactionstringauthorRecordstring, anymetaRecordstring, anyqueryOptionsUseQueryOptionsTQueryFnData, TError, TData其返回值是 TanStack Query 的UseQueryResult{ data: TData }。从源码 useLogList/index.ts 可以看到它使用useQuery包装get查询 key 由keys().audit().resource(resource).action(list).params(meta)生成当get未定义即未配置 auditLogProvider时enabled: false且 queryFn 返回Promise.resolve([])——这意味着即使忘记配置 Provider页面也不会报错。同时retry: false避免审计查询在失败时无限重试。自动记录审计的 Supported Hooks当 mutation 成功后以下 hook 会自动调用 Audit Log Provider 的create方法见 documentation/docs/audit-logs/audit-log-provider/index.md 的 Supported Hooks 小节PackageHooksrefinedev/coreuseFormrefinedev/antduseForm、useModalForm、useDrawerForm、useStepsFormrefinedev/mantineuseForm、useModalForm、useDrawerForm、useStepsFormrefinedev/react-hook-formuseForm、useModalForm、useStepsForm以及核心数据 hookuseCreate、useCreateMany、useUpdate、useUpdateMany、useDelete、useDeleteMany源码位于 packages/core/src/hooks/data/。以useUpdate为例其发送给create的参数会同时包含data新值与previousData缓存旧值便于服务端做 diffconst { mutate } useUpdate(); mutate({ id: 1, resource: posts, values: { title: Updated New Title, }, }); // 发送给 Audit Log Provider create 的参数 { action: update, resource: posts, data: { title: Updated New Title, status: published, content: New Post Content }, previousData: { title: Title, status: published, content: New Post Content }, meta: { id: 1 } }useDeleteMany则只发送action与带ids数组的metaconst { mutate } useDeleteMany(); mutate({ ids: [1, 2], resource: posts, }); // 发送给 Audit Log Provider create 的参数 { action: deleteMany, resource: posts, meta: { ids: [1, 2] } }按资源按 mutation 类型启用 / 禁用审计默认情况下某个资源的create、update、delete操作都会被记录。若只想记录特定类型的操作可以在资源的meta.audit中声明允许记录的 action 白名单Refine dataProvider{dataProvider(API_URL)} resources{[ { name: posts, meta: { audit: [create], }, }, ]} /上面的配置表示对于posts资源只有create操作会生成审计事件update与delete均不会。该机制在源码中的落点是useLog的 mutationFn它通过pickResource找到资源定义读取resource?.meta?.audit再用hasPermission(logPermissions, params.action)判断当前 action 是否被允许未通过时直接return不调用create见 useLog/index.ts。从零跑通参考示例仓库在 examples/audit-log-provider/ 提供了一个可直接运行的完整示例对应文档末尾的 CodeSandbox 示例包含完整的auditLogProvider实现create/get/update三个方法注册 Provider 的Refine配置通过useLog/useLogList手动记录与展示审计事件的界面代码。该示例中的 hook 组合、Provider 方法与本文讲解一一对应是理解整套审计机制的最佳实践参照。另外useLog与useLogList的单元测试位于 packages/core/src/hooks/auditLog/useLog/index.spec.ts 与 packages/core/src/hooks/auditLog/useLogList/index.spec.ts可作为验证行为边界的参考。总结Refine v5 的审计日志体系由三层构成Provider 层auditLogProvider的create/get/update经AuditLogContext注入、自动层数据 hook 成功回调中自动调用log?.mutate自动附带用户身份与previousData、手动层useLog的log/rename与useLogList查询。配合资源级meta.audit白名单你可以精确控制哪些资源的哪些操作需要留痕而审计落库放在服务端的建议则提醒我们客户端上报只是采集可信的审计真相应保存在服务端。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考