news 2026/9/14 11:38:32

refine useCan Hook 深度指南:基于 Access Control Provider 的权限校验、查询缓存与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
refine useCan Hook 深度指南:基于 Access Control Provider 的权限校验、查询缓存与源码解析

refine useCan Hook 深度指南:基于 Access Control Provider 的权限校验、查询缓存与源码解析

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

useCan是 refine 核心包中面向 Access Control Provider 的权限校验 Hook。它以accessControlProvider.can作为 TanStack Query(react-query)useQuery的查询函数,将“权限是否允许”这一判定纳入统一的数据查询体系,从而获得缓存、去重与状态管理能力。读完本文,你将掌握useCan的完整参数、返回值与性能优化手段,并通过仓库源码理解其底层实现与边界行为,能在 refine 3.x 项目中独立实现细粒度的 RBAC / ABAC 权限控制。

本文主体对应仓库文档 useCan.md,所有源码依据均来自当前仓库packages/coredocumentation/versioned_docs目录。

useCan 是什么:把「权限判定」变成一次数据查询

在 refine 的权限模型中,accessControlProvider只需实现一个异步方法can,用于回答“用户对某资源执行某动作是否被允许”。refine 刻意保持 API 无关性(agnostic),以便对接 RBAC、ABAC、ACL 等不同方案以及 Casbin、CASL、Cerbos 等库——can方法正是这些方案的统一入口,详见 accessControl-provider.md。

useCan的核心设计是:can函数当作useQuery的查询函数。它接受can所需的一切参数(resourceactionparams),并额外支持queryOptions用于配置useQuery,最终返回useQuery的查询结果。

从源码看,其签名与实现位于 packages/core/src/hooks/accessControl/useCan/index.ts:

export const useCan = ({ action, resource, params, queryOptions: hookQueryOptions, }: UseCanProps): UseQueryResult<CanReturnType> => { // 从 AccessControlContext 中取出 can 与全局 options const { can, options: globalOptions } = useContext(AccessControlContext); ... };

它从AccessControlContext读取can函数,再经由useQuery执行,因此useCan天然具备 react-query 的全部能力:查询状态机(loading / error / success)、缓存、重试控制、并发去重等。

版本说明:本仓库 version-3.xx.xx 文档中的示例使用@pankod/refine-core包名(refine 3.x 时代的命名),当前仓库较新版本中对应包为@refinedev/core,用法保持一致,按你所使用的版本选择导入来源即可。

前置准备:Access Control Provider 与 can 方法

useCan的输入输出严格对齐can的类型。在 packages/core/src/contexts/accessControl/types.ts 中可以确认三组核心类型:

export type CanResponse = { can: boolean; reason?: string; [key: string]: unknown; }; export type CanParams = { resource?: string; // 资源名,用于 API 数据交互 action: string; // 对资源的意图动作 params?: { resource?: IResourceItem & { children?: ITreeResource[] }; id?: BaseKey; [key: string]: any; }; }; export type CanReturnType = { can: boolean; reason?: string; }; export type CanFunction = ({ resource, action, params, }: CanParams) => Promise<CanReturnType>;

同时,IAccessControlContext除了can之外还支持全局options(见 types.ts):

type AccessControlOptions = { buttons?: { enableAccessControl?: boolean; hideIfUnauthorized?: boolean; }; queryOptions?: MakeOptional< UseQueryOptions<CanReturnType>, "queryFn" | "queryKey" >; }; export interface IAccessControlContext { can?: CanFunction; options?: AccessControlOptions; }

这意味着你既可以在单次调用时传queryOptions,也可以在 Provider 层配置全局的queryOptions(后文“源码深挖”会说明二者如何合并)。

基本用法

useCan的最基本用法如下(沿用原文档示例):

import { useCan } from "@pankod/refine-core"; const { data } = useCan({ resource: "resource-you-ask-for-access", action: "action-type-on-resource", params: { foo: "optional-params" }, });

调用后,data即为can方法的返回结果:{ can: boolean; reason?: string }。例如判断“当前用户能否创建 post”,结合 Provider 定义与 Hook 调用的完整示例为:

<Refine accessControlProvider={{ can: async ({ resource, action }) => { if (resource === "post" && action === "create") { return Promise.resolve({ can: false, reason: "Unauthorized", }); } return Promise.resolve({ can: true }); }, }} // ... />; // inside your component const { data: canCreatePost } = useCan({ action: "create", resource: "post", }); console.log(canCreatePost); // { can: false, reason: "Unauthorized" }

reason字段会随can一起返回,可用于向用户展示被拒原因;在 refine 内置按钮中,reason还会显示在按钮禁用态的工具提示(tooltip)中,参见 accessControl-provider.md。

Properties 详解

useCan接受四个属性,前三个原样透传给can,最后一个用于配置底层查询。

resource(必填)

传递给can函数的resource参数,表示要请求访问的资源名:

useCan({ resource: "resource-you-ask-for-access", });

action(必填)

传递给can函数的action参数,表示在资源上执行的意图动作(如listcreateeditshowdelete等):

useCan({ action: "resource-you-ask-for-access", });

原文档此处的示例字符串沿用了占位写法,实际使用时应填写动作类型而非资源名,例如action: "edit"

params

传递给can函数的params参数,通常用于携带记录级信息,例如id,或完整的资源对象以实现 ABAC:

useCan({ params: { foo: "optional-params" }, });

queryOptions

透传给 TanStack QueryuseQuery的查询配置,典型用途是调整staleTimecacheTimeenabledqueryKeyqueryFn等:

useCan({ queryOptions: { staleTime: 5 * 60 * 1000, // 5 minutes }, });

在 useCan/index.ts 中,UseCanProps定义为CanParamsqueryOptions的交集,其中queryOptions允许自定义queryKeyqueryFn

export type UseCanProps = CanParams & { queryOptions?: Omit<UseQueryOptions<CanReturnType>, "queryKey"> & { queryKey?: UseQueryOptions<CanReturnType>["queryKey"]; }; };

返回值

useCan的返回值就是useQuery的查询结果,类型为QueryObserverResult(数据部分为CanReturnType)。你可以直接使用 react-query 提供的全部字段,如dataisLoadingisErrorisFetchedrefetch等。

原文档给出的 API 对照如下:

属性描述
CanReturnType查询结果数据类型,即{ can: boolean; reason?: string }(原文档同时提及HttpError类型的错误场景,实际以你使用的 refine 版本接口定义为准)
描述类型
TanStack QueryuseQuery的查询结果QueryObserverResult<{ data: CanReturnType; }>(源码中为UseQueryResult<CanReturnType>

值得注意的一个兜底行为:在 useCan/index.ts 中,如果当前上下文中不存在can函数(例如未配置accessControlProvider),useCan不会抛错,而是直接返回{ data: { can: true } },即默认放行:

return typeof can === "undefined" ? ({ data: { can: true } } as typeof queryResponse) : queryResponse;

性能优化:善用 staleTime 与 cacheTime

随着应用内权限检查点的增加,尤其当权限判定依赖远程端点时,性能可能明显下降。由于 refine 基于 TanStack Query,缓存权限检查结果能带来巨大收益,最简单的方式就是配置staleTimecacheTime

import { useCan } from "@pankod/refine-core"; // inside your component const { data } = useCan({ resource: "resource-you-ask-for-access", action: "action-type-on-resource", params: { foo: "optional-params" } }, queryOptions: { staleTime: 5 * 60 * 1000, // 5 minutes } });
  • staleTime:数据在多少毫秒内被视为“新鲜”,期间命中缓存不会重新请求;
  • cacheTime:不再被使用的查询结果在缓存中保留的时长。

关于默认值,accessControl-provider.md 明确说明:refine 自带的权限检查点默认使用 5 分钟cacheTime、0staleTime。这意味着默认情况下每次挂载都可能重新请求(因为staleTime: 0),而缓存 5 分钟。如果你的权限变更不频繁,调大staleTime可以有效减少重复请求。

源码深挖:useCan 的实现细节

1. queryKey 的自动生成

在 useCan/index.ts 中,查询键由useKeys()生成,将resourceactionparams(含enabled状态)全部纳入键结构:

const queryResponse = useQuery<CanReturnType>({ queryKey: keys() .access() .resource(resource) .action(action) .params({ params: { ...paramsRest, resource: sanitizedResource }, enabled: mergedQueryOptions?.enabled, }) .get(), queryFn: () => can?.({ action, resource, params: { ...paramsRest, resource: sanitizedResource }, }) ?? Promise.resolve({ can: true }), enabled: typeof can !== "undefined", ...mergedQueryOptions, meta: { ...mergedQueryOptions?.meta, ...getXRay("useCan", resource, [ "useButtonCanAccess", "useNavigationButton", ]), }, retry: false, });

几个关键点:

  • enabled: typeof can !== "undefined":未配置权限 Provider 时查询不会执行,配合上面的兜底返回{ can: true }
  • retry: false:权限请求默认不自动重试(与一般数据请求的默认策略不同),避免权限接口失败时产生无意义的重复请求;
  • meta注入getXRay信息:为 refine devtools 提供 Hook 调用链路追踪(useButtonCanAccessuseNavigationButton等);
  • queryFn兜底canundefined时返回Promise.resolve({ can: true }),保证类型安全与行为一致。

2. 全局 queryOptions 与局部 queryOptions 合并

useCan支持在 Provider 层配置全局queryOptions(见AccessControlOptions.queryOptions)。源码中的合并逻辑是浅合并:全局配置作为基础,单次调用传入的配置覆盖之:

const { queryOptions: globalQueryOptions } = globalOptions || {}; const mergedQueryOptions = { ...globalQueryOptions, ...hookQueryOptions, };

在 index.spec.tsx 中有对应测试:当 Provider 配置options: { queryOptions: { enabled: false } }时,can不会被调用,验证了全局配置确实生效。

3. sanitizeResource:防止不可序列化的 icon 破坏 queryKey

由于 react-query 会对 queryKey 做字符串化处理,如果params.resource中带有icon等包含ReactNode的属性,可能引发循环依赖序列化错误。为此useCan在构建 queryKey 与调用can之前,会通过sanitizeResource清洗资源对象,见 sanitize-resource/index.ts:

export const sanitizeResource = (resource) => { const { list, edit, create, show, clone, children, meta, icon, ...restResource } = resource; const { icon: _metaIcon, ...restMeta } = meta ?? {}; return { ...restResource, ...(meta ? { meta: restMeta } : {}), }; };

该函数会剔除资源对象中不可序列化的list / edit / create / show / clone组件引用、icon(含meta.icon)等属性,只保留纯数据字段。相关行为在 index.spec.tsx 有专门测试:传入带meta.icon的资源对象后,can实际收到的params.resourceicon已被移除。

4. useCanWithoutCache:跳过缓存的直通变体

useCan外,refine 还提供了useCanWithoutCache(useCanWithoutCache.ts)。它不做任何查询缓存,直接从AccessControlContext取出can函数并返回,同时应用相同的sanitizeResource清洗逻辑:

export const useCanWithoutCache = (): IAccessControlContext => { const { can: canFromContext } = React.useContext(AccessControlContext); // 包装一层:对 params.resource 做 sanitize 后调用原始 can ... return { can }; };

适用场景:当某次权限判定必须“实时”执行、不能命中缓存时(例如刚完成角色变更,需要立即刷新判定结果),使用该变体可获得不带缓存的can函数引用。

5. 组件与框架层的联动

useCan是 refine 权限体系的底层 Hook,上层还有多个消费方:

  • <CanAccess />组件:内部直接调用useCan(见 canAccess/index.tsx),根据data.can决定渲染children还是fallback,并支持onUnauthorized回调;
  • 路由层@pankod/refine-nextjs-router@pankod/refine-react-router@pankod/refine-react-location会对 CRUD 页面[resource]/[action]做权限检查,失败时展示catchAll或标准错误页;
  • Sider 菜单:不可访问的资源不会出现在侧边栏;
  • 各类按钮(List / Create / Clone / Edit / Delete / Show):权限判定失败时按钮被禁用并展示reason

各检查点的具体{ resource, action, params }参数约定,可参阅 accessControl-provider.md 的 “List of Default Access Control Points” 章节。

测试用例验证

仓库为useCan提供了完整的单元测试,见 packages/core/src/hooks/accessControl/useCan/index.spec.tsx,覆盖了以下行为,可作为你理解和使用该 Hook 的参考清单:

测试场景验证结论
can返回{ can: true, reason: "Access granted" }正确透传data.candata.reason
can返回{ can: false, reason: "Access Denied" }拒绝场景数据透传正确
Provider 未提供can(undefined)返回兜底{ data: { can: true } },不抛错
params.resource携带meta.iconsanitizeResource生效,can收到清洗后的资源对象
queryOptions.enabled: falsecan不会被调用,查询被禁用
自定义queryOptions.queryKey覆盖默认 queryKey,缓存按自定义键存储
自定义queryOptions.queryFn覆盖默认查询函数,can不再被调用
Provider 全局options.queryOptions全局配置生效,可统一禁用查询

实践建议

  1. 优先使用useCan做组件内权限判断:它能复用 react-query 缓存,多个组件检查相同resource + action时只会发起一次请求;
  2. 合理设置staleTime:权限策略变化不频繁的应用可设置较大的staleTime(如 5 分钟)显著降低远程权限接口压力;需要即时响应的场景则保持默认或设为 0;
  3. 对 ABAC 场景善用params.resourcecan方法可通过params.resource拿到完整资源对象(含你在<Refine />中定义的meta等字段),实现基于属性的授权;
  4. 注意 queryKey 的序列化限制:不要在params.resource中携带icon等不可序列化内容,useCan会自动清洗,但了解这一点有助于排查异常 queryKey;
  5. 实时判定用useCanWithoutCache:需要绕过缓存、强制走最新权限逻辑时使用。

更多权限控制实战可参考仓库中的 examples/access-control-casbin 示例目录。至此,你已经掌握了useCan从参数、返回值到性能调优与源码原理的完整链路,可以在自己的 refine 项目中放心使用它构建细粒度、可缓存的权限控制。

【免费下载链接】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/14 11:31:39

构建 kt-kernel 时 CMake 报 “CUDA compiler not found“ 怎么修复?

构建 kt-kernel 时 CMake 报 "CUDA compiler not found" 怎么修复&#xff1f; 【免费下载链接】ktransformers A Flexible Framework for Experiencing Heterogeneous LLM Inference/Fine-tune Optimizations 项目地址: https://gitcode.com/GitHub_Trending/ktr/…

作者头像 李华