news 2026/9/12 16:59:56

Refine v5 审计日志(Audit Logs)完整指南:从 AuditLogProvider 到 useLog / useLogList

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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在数据变更时自动记录审计事件、如何用useLoguseLogList手动写入与查询日志,并深入源码剖析create/update/get三个方法背后的调用链与事件参数结构。读完本文,你将能够在自己的 Refine 应用中搭建一套可追溯、可审计、面向合规要求的操作日志机制。

为什么需要审计日志

审计日志(Audit Logs)是 Web 应用中非常有用的工具,它为用户操作系统变更提供了一条可靠、可检索的记录。记录并存储这些日志能够保证系统行为的透明性与可问责性(accountability),这对**安全、合规(compliance)以及问题排查(debugging)**都至关重要。

在 Refine 中,审计日志的核心价值在于:CRUD 操作无需手动埋点即可自动记录。当你在<Refine>上提供auditLogProvider后,由useCreateuseUpdateuseDelete等数据 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: Record<number | string, any>; }; export type IAuditLogContext = { create?: (params: LogParams) => Promise<any>; get?: (params: { resource: string; action?: string; meta?: Record<number | string, any>; author?: Record<number | string, any>; }) => Promise<any>; update?: (params: { id: BaseKey; name: string; [key: string]: any; }) => Promise<any>; }; export type AuditLogProvider = Required<IAuditLogContext>;

可以看到AuditLogProvider就是IAuditLogContext必选版本creategetupdate三者缺一不可。

在运行时,这些方法通过AuditLogContext注入到整个应用中,其实现见 packages/core/src/contexts/auditLog/index.tsx:AuditLogContextProvider接收creategetupdate三个 prop,并将其放入 React Context。所有内置 hook(useLoguseLogList以及各数据 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 / update

get:查询审计事件列表

get方法用于获取一组审计日志事件。例如,通过useLogListhook 按某个记录 id 列出该资源的所有活动时,会向get发送如下事件:

{ "resource": "posts", "meta": { "id": "1" } }

get收到的参数结构为{ resource, action?, meta?, author? },你的实现可以基于这些字段向服务端发起查询(如上面的 fetch 示例)。

create:写入审计事件

create方法在以下两种时机被触发:

  1. 一次成功的 mutation 之后(由数据 hook 自动触发);
  2. 手动调用useLoglog方法时

传入的参数表示即将创建的新记录的值。根据 mutation 类型不同,Refine 会为create组装不同的参数,规则如下(见 documentation/docs/audit-logs/audit-log-provider/index.md):

  • previousData来自 react-query 缓存,若能找到则返回旧值,否则为undefined
  • 在 create 类 mutation 中,如果请求响应包含id字段,该id会被加入到meta对象;
  • 如果 auth provider 中定义了getUserIdentity,事件中会追加author对象,其值为getUserIdentity的返回值。

各 mutation 类型的create事件参数如下:

create(useCreate

{ "action": "create", "resource": "posts", "data": { "title": "Hello World", "content": "Hello World" }, "meta": { "dataProviderName": "simple-rest", // 如果请求响应有 `id` 字段,会被加入 `meta` "id": 1 } }

update(useUpdate

{ "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 } }

delete(useDelete

{ "action": "delete", "resource": "posts", "meta": { "dataProviderName": "simple-rest", "id": 1 } }

createMany(useCreateMany

{ "action": "createMany", "resource": "posts", "data": [ { "title": "Hello World 1" }, { "title": "Hello World 2" } ], "meta": { "dataProviderName": "simple-rest", // 如果请求响应有 `id` 字段,会被加入 `meta` "ids": [1, 2] } }

updateMany(useUpdateMany

{ "action": "updateMany", "resource": "posts", "data": { "status": "published" }, "previousData": [ { "status": "draft" }, { "status": "archived" } ], "meta": { "dataProviderName": "simple-rest", "ids": [1, 2] } }

deleteMany(useDeleteMany

{ "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方法用于更新一条既有的审计事件。例如,通过useLogrename方法会给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塞进metalog?.mutate(...)log正是useLog返回的 mutation(通过内部 hook 组合注入),因此只要auditLogProvider存在,create方法就会被自动调用。useUpdateuseDeleteuseCreateManyuseUpdateManyuseDeleteMany的实现位于 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返回两个 mutation:logrename

import { useLog } from "@refinedev/core"; const { log, rename } = useLog();

log(创建事件):底层调用auditLogProvidercreate方法。

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资源名称,如"posts"
action(必填)string动作名称,如"create"
authorRecord<string, any>操作者信息
metaRecord<string, any>附加元数据(如记录 id)
dataRecord<string, any>变更后的数据
previousDataRecord<string, any>变更前的数据(可选)

logrename的类型参数均支持TData(继承BaseRecord)、TError(继承HttpError)、TVariables,默认值分别为BaseRecordHttpError{};返回值均为 TanStack Query 的UseMutationResult

rename(更新事件):底层调用auditLogProviderupdate方法。

const { rename } = useLog(); const { mutate } = rename; mutate({ id: 1, name: "Updated Name", });

renamemutation 的属性:

PropertyType说明
id(必填)BaseKey要更新的事件 id
name(必填)string新的事件名称

值得注意的源码细节:renameonSuccess中,如果返回值包含resource字段,Refine 会通过queryClient.invalidateQueries使对应资源的list审计查询失效,从而让useLogList的列表自动刷新(见 useLog/index.ts)。因此你的update方法如果返回{ resource: "posts" }之类的对象,就能触发关联列表的重新拉取。

useLogList:查询审计事件列表

useLogList底层调用auditLogProviderget方法(对应文档 use-log-list):

import { useLogList } from "@refinedev/core"; const postAuditLogResults = useLogList({ resource: "posts", });

useLogList的入参属性:

PropertyType默认值
resource(必填)string从路由读取的 action
actionstring
authorRecord<string, any>
metaRecord<string, any>
queryOptionsUseQueryOptions<TQueryFnData, 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 小节):

PackageHooks
@refinedev/coreuseForm
@refinedev/antduseFormuseModalFormuseDrawerFormuseStepsForm
@refinedev/mantineuseFormuseModalFormuseDrawerFormuseStepsForm
@refinedev/react-hook-formuseFormuseModalFormuseStepsForm

以及核心数据 hookuseCreateuseCreateManyuseUpdateuseUpdateManyuseDeleteuseDeleteMany(源码位于 packages/core/src/hooks/data/)。

useUpdate为例,其发送给create的参数会同时包含data(新值)与previousData(缓存旧值),便于服务端做 diff:

const { 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数组的meta

const { mutate } = useDeleteMany(); mutate({ ids: [1, 2], resource: "posts", }); // 发送给 Audit Log Provider `create` 的参数: { "action": "deleteMany", "resource": "posts", "meta": { "ids": [1, 2] } }

按资源按 mutation 类型启用 / 禁用审计

默认情况下,某个资源的createupdatedelete操作都会被记录。若只想记录特定类型的操作,可以在资源的meta.audit中声明允许记录的 action 白名单:

<Refine dataProvider={dataProvider(API_URL)} resources={[ { name: "posts", meta: { audit: ["create"], }, }, ]} />

上面的配置表示:对于posts资源,只有create操作会生成审计事件updatedelete均不会。

该机制在源码中的落点是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 方法与本文讲解一一对应,是理解整套审计机制的最佳实践参照。另外,useLoguseLogList的单元测试位于 packages/core/src/hooks/auditLog/useLog/index.spec.ts 与 packages/core/src/hooks/auditLog/useLogList/index.spec.ts,可作为验证行为边界的参考。

总结

Refine v5 的审计日志体系由三层构成:Provider 层auditLogProvidercreate/get/update,经AuditLogContext注入)、自动层(数据 hook 成功回调中自动调用log?.mutate,自动附带用户身份与previousData)、手动层useLoglog/renameuseLogList查询)。配合资源级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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 16:47:11

使用 ESLint `no-eq-null` 规则:杜绝无类型检查的 `null` 比较

使用 ESLint no-eq-null 规则&#xff1a;杜绝无类型检查的 null 比较 【免费下载链接】eslint Find and fix problems in your JavaScript code. 项目地址: https://gitcode.com/GitHub_Trending/es/eslint 在 JavaScript 中&#xff0c;foo null 这类比较看似无害&am…

作者头像 李华
网站建设 2026/9/12 16:42:20

AI与文学创作:人机协作模式探索与实践

1. 项目背景与创作动机《AI元人文&#xff1a;悟空而行》是一部探讨人工智能与人类文化交融的实验性文学作品。作为作者&#xff0c;我试图通过这部作品构建一个跨越技术与人文的叙事空间。创作初衷源于对当前AI技术爆发式发展下人文精神处境的思考——当机器能够模仿人类创作时…

作者头像 李华