DataBuilder(百度胜算)权限控制体系完整文档

基于 console-databuilder 前端代码深度分析 | 最后更新:2026-05-19


目录

  1. 产品概述
  2. 项目角色体系
  3. 项目权限体系(完整多级表格)
  4. 角色与权限对应关系
  5. 前端权限管理实现详解(含注释)
  6. 完整权限工作流

一、产品概述

DataBuilder(百度胜算) 是百度智能云旗下的数据智能平台,面向模型训练、AI应用及数据飞轮等场景,提供多模态数据管理、高性能计算、智能开发平台和丰富算子能力。支持一站式数据治理、数据加工和数据应用。

技术栈:React + TypeScript + Redux Toolkit + React Router 6 + @baidu/qianfan-antd-kit (antd)

部署模式:支持公有云 / 私有化双模式部署(通过 flags.DatabuilderPrivateSwitch 开关控制)

产品版本(通过 VERSION_NAME 枚举区分):

版本 枚举值 说明
免费版 TRIAL 基础功能,是产品默认开通的体验版本
标准版 STANDARD 提供完整的数据管理和开发能力
专业版 PROFESSIONAL 面向企业级场景,功能更丰富
企业版 ENTERPRISE 最高规格,支持高级定制和更大规模

版本信息的获取通过 getEditionInfo() API 调用,返回的数据包含版本名称、状态(RUNNING 正常运行 / STOPPED 欠费停服)、过期时间和资源ID。前端将版本信息存入 Redux Store 的 editionInfo 字段,业务代码可根据版本差异做功能开关。

权限架构:三层权限体系(路由级 + 组件级 + 数据级)


二、项目角色体系

2.1 角色类型

角色类型 枚举值 说明
用户自定义角色 RoleType.User = 'USER' 空间管理员在空间内创建的自定义角色,可自由分配权限
系统预设角色 RoleType.System = 'SYSTEM' 系统内置角色(如管理员、开发者等),在代码中硬编码,不可删除

2.2 主体类型(授权对象)

PrincipalType 定义了权限可以被授予的三类对象:

主体类型 枚举值 说明
用户 PrincipalType.User = 'USER' 直接对单个用户授予权限
用户组 PrincipalType.Group = 'GROUP' 对一组用户(用户组)批量授予权限
角色 PrincipalType.Role = 'ROLE' 将权限授予角色,用户通过获得角色来间接获得权限

在权限管理弹窗中,管理员可以选择任意主体类型进行授权,系统会通过 getWorkspaceUserList API 列出所有可选的主体供选择。

2.3 系统内置角色

角色 说明 权限级别
系统管理员 (systemAdmin) 由后端 verifySystemAdmin 接口判断。管理员可以访问空间外的元存储菜单,通常拥有所有空间的管理权限 最高
DataBuilderFullControl 专门用于公有云 Playground(体验/演示环境) 的完全控制权限。Playground 是一种无需真实购买产品即可体验的沙盒环境,系统预设了演示账户和演示工作空间。通过 checkFullControl() API 验证 完全控制
全部用户 (ALL) 特殊主体,名称为"全部用户",对应 Privilege.All 权限组,表示该权限对所有用户生效 可配置任意权限
元存储管理员 (metastoreAdmin) 元存储级别的管理员,后端返回值 ["admin"],用于判断用户是否有元存储管理能力 元存储管理

2.4 权限级别(PermissionType)

PermissionType 用于描述对某个具体资源的读写权限等级, 由后端 getWorkspacePermission(workspaceId) 接口自动计算返回不是在创建角色时手动设定的。它和 ResourceType 不在同一个层次: ResourceType 定义了"有哪些资源可被管理",PermissionType 定义了 "当前用户对这些资源有多大的操作权限"。,定义在 src/api/auth.ts 中:

// PermissionType 表示对资源的操作级别
export type PermissionType = 'readWrite' | 'readOnly' | undefined;
含义 使用场景
'readWrite' 可读写 用户对该资源(如表、卷、工作流)可以进行读取和修改操作
'readOnly' 只读 用户只能查看该资源,不能修改
undefined 无权限 用户完全无法访问该资源

在后端返回的 GetWorkspacePermissionResult 中,每个资源类型(catalog、table、volume 等)的值都是一个 PermissionType[] 数组,前端取第一项 [0] 存入 WorkspaceAuth Redux Store。

PermissionType 的使用位置: - src/store/WorkspaceAuth.ts:存储每个资源类型的读写权限 - src/api/auth.ts:定义 PermissionType 类型和 GetWorkspacePermissionResult 接口 - App.tsx 和 pages/index.tsx:进入工作空间时调用 getWorkspacePermission() 获取并 dispatch 到 store - 各业务页面:通过 useSelector(state => state.workspaceAuthSlice.xxx) 读取,决定编辑按钮是否可用


PermissionType 和 Privilege 权限点是否重复?

不是重复——它们是两个不同层次的设计:

维度Privilege 权限点PermissionType
粒度细粒度,75个具体操作(如 WRITE_TABLE)粗粒度,3档(readWrite/readOnly/undefined)
作用功能级——能不能看到按钮、能不能执行某操作资源级——对某类资源整体是只读还是可读写
判定方式在角色创建时由管理员勾选分配由后端根据用户身份直接计算返回
存储位置workspacePermission: { PRIV: boolean }workspaceAuth: { table: 'readWrite' }
当前使用广泛用于路由HOC、AuthButton、useWorkspaceAuth当前业务组件中使用较少,更偏向后端保底机制

打个比方:Privilege.WRITE_TABLE 是"你有没有资格去写表这个操作",是个开关; PermissionType = 'readWrite' 是"后端根据你的整体身份,判定你对表类资源可以进行读写"。 两者来源不同——权限点是角色配置的结果,PermissionType 是后端综合判断的结果。

2.5 页面状态

状态 枚举值 说明
有效 PageStatus.Valid 当前页面有权限,正常渲染
页面无权限 PageStatus.NoPagePermission 用户通过直接输入 URL 硬访问了一个没有分配权限的页面,HOC 拦截后显示"页面无权限"提示
空间无权限 PageStatus.NoWorkspacePermission 用户在空间内没有任何菜单权限,显示"空间无权限"提示

PageStatus 的触发时机和 UI 表现:当用户在地址栏直接输入 URL 硬访问时, WithPermissionHoc 先从 Redux 读取 workspacePermission 映射表。 如果连任一空间菜单权限都没有,dispatch NoWorkspacePermission, 此时 AppLayoutsrc/components/AppLayout/index.tsx:570-576) 完全隐藏侧边导航栏(因为没有可访问的菜单项); 如果用户有空间菜单权限但不含当前页面权限,dispatch NoPagePermission, 此时 AppLayout 仍然展示侧边导航栏(让用户可以导航到其他有权限的页面)。 这套机制确保即使用户绕过前端菜单直接输入 URL,权限系统仍能有效拦截并给出恰当提示。


三、项目权限体系(完整多级表格)

3.1 通用权限点

通用权限点定义了对资源的基本操作能力,它们之间有层级包含关系

Manage(管理) > Modify(修改) > Execute(执行) > View(查看)

例如,如果用户拥有 MANAGE 权限,则自动拥有 MODIFYEXECUTEVIEW 权限。这个层级判断逻辑在 checkSimplePrivilege() 工具函数中实现(位于 src/utils/auth.ts:142)。

通用权限点在使用上与功能权限点不同:功能权限点(如 PIPELINE_DESIGNER_CREATE)用于空间级别的菜单和按钮控制; 通用权限点(如 MANAGEVIEW)用于单个资源对象的 CRUD 级别控制。 典型使用场景在 数据管道模块PipelineBuilderListConfigPanelBase)和 API Key 组件中: 每条管道记录有自己的 privileges 数组,用 checkSimplePrivilege(record.privileges, Privilege.View) 判断用户对该条管道的查看/执行/修改/管理权限。 这使得同一个用户对不同管道可以有不同的操作级别,比空间级的统一权限更精细。

权限枚举值 中文名称 包含的权限 说明
ALL 全部权限 所有权限 权限集合,通常用于角色配置时一键赋予所有权限
FULL_CONTROL 完全控制 完全控制 最高权限,用于 Playground 管理员
MANAGE 管理 Modify + Execute + View 包含资源的全部管理权限
MODIFY 修改 Execute + View 可以修改资源内容
EXECUTE 执行 View 可以运行/执行操作
USE 使用 - 可以使用资源(如使用数据源)
VIEW 查看 - 最基础的查看权限

3.2 空间管理模块

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
空间管理 - WORKSPACES_MENU 空间管理菜单 菜单 可见左侧菜单"工作空间" 菜单隐藏
空间管理 - WORKSPACE_CREATE 新建空间 按钮 可点击"新建空间"按钮 按钮禁用+提示

3.3 元存储(空间外)

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
元存储 - METASTORE_MENU 元数据菜单 菜单 可见左侧菜单"元数据"(空间外) 菜单隐藏
元存储 - METASTORE_CREATE 创建元存储 按钮 可创建元存储 按钮禁用+提示

3.4 工作区模块

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
工作区 - WORKSPACE_MENU 工作区菜单 菜单 可见左侧菜单"工作台" 菜单隐藏
工作区 - PROJECT_CREATE 项目创建 按钮 可创建工作台项目 按钮禁用+提示
工作区 - DIRECTORY_CREATE 新建文件夹 按钮 可在工作区内新建文件夹 按钮禁用+提示
工作区 - FILE_IMPORT 导入文件 按钮 可在工作区内导入文件 按钮禁用+提示
工作区 - NOTEBOOK_CREATE 新建Notebook 按钮 可新建Jupyter Notebook 按钮禁用+提示

3.5 元数据目录(空间内)

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
元数据 - CATALOG_MENU 元数据菜单 菜单 可见左侧菜单"元数据" 菜单隐藏
元数据 目录 CREATE_CATALOG 创建目录 操作 可创建Catalog目录 按钮禁用
元数据 数据源 CREATE_CONNECTION 创建数据源 操作 可创建外部数据源连接 按钮禁用
元数据 数据源 USE_CONNECTION 使用数据源 操作 可使用已有数据源 不可使用
元数据 浏览 BROWSE 查看元数据 操作 可浏览元数据目录结构 不可浏览
元数据 Schema CREATE_SCHEMA 创建模式 操作 可在Catalog下创建Schema 按钮禁用
元数据 Volume CREATE_VOLUME 创建卷 操作 可创建数据卷 按钮禁用
元数据 Volume READ_VOLUME 读卷 操作 可读取数据卷内容 不可读取
元数据 Volume WRITE_VOLUME 写卷 操作 可写入数据卷 不可写入
元数据 Table CREATE_TABLE 创建表 操作 可创建数据表 按钮禁用
元数据 Table READ_TABLE 读表 操作 可读取表数据 不可读取
元数据 Table WRITE_TABLE 写表 操作 可写入表数据 不可写入
元数据 Dataset CREATE_DATASET 创建数据集 操作 可创建数据集 按钮禁用
元数据 Dataset READ_DATASET 读数据集 操作 可读取数据集 不可读取
元数据 Dataset WRITE_DATASET 写数据集 操作 可写入数据集 不可写入
元数据 Dataset CREATE_DATASET_VERSION 创建数据集版本 操作 可为数据集创建新版本 按钮禁用
元数据 Model CREATE_MODEL 创建模型 操作 可创建模型 按钮禁用
元数据 Model CREATE_MODEL_VERSION 创建模型版本 操作 可为模型创建新版本 按钮禁用
元数据 Operator CREATE_OPERATOR 创建算子 操作 可创建算子 按钮禁用
元数据 Operator CREATE_OPERATOR_VERSION 创建算子版本 操作 可为算子创建新版本 按钮禁用
元数据 表列 (TableColumn资源) 列权限 数据级 可访问特定列,通过列级鉴权实现 列被屏蔽
元数据 表行 (TableRow资源) 行权限 数据级 可访问特定行,通过SQL过滤规则实现 行被过滤

3.6 计算资源模块

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
计算资源 - COMPUTE_MENU 计算资源菜单 菜单 可见左侧菜单"计算资源" 菜单隐藏
计算资源 源连接与集成 INTEGRATION_COMPUTE_CREATE 新建集成实例 按钮 可创建集成计算资源 按钮禁用+提示
计算资源 常驻实例 ETL_COMPUTE_CREATE 新建常驻实例 按钮 可创建常驻实例 按钮禁用+提示
计算资源 任务模板 ETL_JOB_TEMPLATE_CREATE 新建任务模板 按钮 可创建任务实例模板 按钮禁用+提示
计算资源 分析/AI搜索 ANALYSIS_COMPUTE_CREATE 新建分析实例 按钮 可创建分析计算资源 按钮禁用+提示
计算资源 通用常驻实例 LOGIC_COMPUTE_CREATE 新建通用实例 按钮 可创建通用常驻实例 按钮禁用+提示
计算资源 资源池 RESOURCE_POOL_CREATE 新建资源池 按钮 可创建资源池 按钮禁用+提示
计算资源 资源组 RESOURCE_GROUP_CREATE 新建资源组 按钮 可创建资源组 按钮禁用+提示

3.7 数据集成模块

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
数据集成 - INTEGRATION_MENU 数据集成菜单 菜单 可见"数据集成" 菜单隐藏
数据集成 非结构化 UNSTRUCTURED_INTEGRATION_CREATE 新建 按钮 可创建非结构化集成任务 按钮禁用+提示
数据集成 非结构化 UNSTRUCTURED_INTEGRATION_EXECUTE 批量运行 按钮 可批量运行 按钮禁用+提示
数据集成 非结构化 UNSTRUCTURED_INTEGRATION_STOP 批量停止 按钮 可批量停止 按钮禁用+提示
数据集成 非结构化 UNSTRUCTURED_INTEGRATION_DELETE 批量删除 按钮 可批量删除 按钮禁用+提示
数据集成 结构化 STRUCTURED_INTEGRATION_CREATE 新建 按钮 可创建结构化集成任务 按钮禁用+提示
数据集成 结构化 STRUCTURED_INTEGRATION_EXECUTE 批量运行 按钮 可批量运行 按钮禁用+提示
数据集成 结构化 STRUCTURED_INTEGRATION_PUBLISH 批量发布 按钮 可发布任务 按钮禁用+提示
数据集成 结构化 STRUCTURED_INTEGRATION_DELETE 批量删除 按钮 可删除任务 按钮禁用+提示
数据集成 结构化 STRUCTURED_INTEGRATION_MODIFY 批量编辑 按钮 可编辑任务 按钮禁用+提示

3.8 工作流模块

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
工作流 - WORKFLOW_MENU 工作流菜单 菜单 可见"工作流"菜单 菜单隐藏
工作流 - WORKFLOW_CREATE 新建工作流 按钮 可创建DAG工作流 按钮禁用+提示
工作流 - WORKFLOW_IMPORT 导入工作流 按钮 可导入工作流JSON 按钮禁用+提示
工作流 运行记录 WORKFLOW_INSTANCE_MENU 运行记录菜单 菜单 可查看运行记录Tab Tab隐藏

3.9 数据质量模块

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
数据质量 - QUALITY_MENU 数据质量菜单 菜单 可见"数据质量"菜单 菜单隐藏
数据质量 规则模板 QUALITY_TEMPLATE_CREATE 创建自定义模板 按钮 可创建自定义质量模板 按钮禁用+提示
数据质量 质量规则 QUALITY_OBJECT_ADD 添加监控对象 按钮 可添加质量监控对象 按钮禁用+提示

3.10 其他功能模块(含白名单控制)

模块 子模块 权限枚举值 权限名称 类型 白名单 有权限时 无权限时
模型服务 - MODEL_SERVICE_MENU 模型服务菜单 菜单 可见菜单 菜单隐藏
模型服务 - MODEL_SERVICE_CREATE 创建服务 按钮 可创建模型服务 按钮禁用+提示
内容理解 - CONTENT_UNDERSTANDING_MENU 内容理解菜单 菜单 可见菜单 菜单隐藏
数据管道 - PIPELINE_DESIGNER_MENU 数据管道菜单 菜单 可见菜单 菜单隐藏
数据管道 - PIPELINE_DESIGNER_CREATE 新建管道 按钮 可创建Pipeline 按钮禁用+提示
数据血缘 - LINEAGE_MENU 数据血缘菜单 菜单 可见菜单 菜单隐藏
本体管理 - ONTOLOGY_MENU 本体管理菜单 菜单 可见菜单 菜单隐藏
本体管理 对象类型 ONTOLOGY_OBJECT_TYPE_CREATE 创建对象类型 按钮 可创建ObjectType 按钮禁用+提示
本体管理 关系类型 ONTOLOGY_LINK_TYPE_CREATE 创建关系类型 按钮 可创建LinkType 按钮禁用+提示
逻辑建模 - LOGIC_MENU 逻辑建模菜单 菜单 可见菜单 菜单隐藏
逻辑建模 - LOGIC_CREATE 新建逻辑 按钮 可创建逻辑模型 按钮禁用+提示
数据搜索 - DATASEARCH_MENU 数据搜索菜单 菜单 可见菜单 菜单隐藏
数据搜索 - DATASEARCH_CREATE 新建搜索 按钮 可创建搜索任务 按钮禁用+提示

3.11 数据架构模块

模块 子模块 权限枚举值 权限名称 类型 有权限时 无权限时
数据架构 全部 DATAFRAME_MENU 数据架构菜单 菜单 可见"数据架构"及所有子菜单(主题域/标准/模型/指标) 菜单隐藏
数据架构 全部 DATAFRAME_EDIT 数据架构编辑 按钮 可编辑主题域/标准/模型/指标 只读查看,编辑按钮禁用

四、角色与权限对应关系

4.1 系统管理员 (systemAdmin)

系统管理员由后端 verifySystemAdmin() API 判断,该接口是一个 POST 请求,返回布尔值。管理员拥有系统最高权限:

权限场景 是否拥有
访问所有页面
所有元数据操作
所有计算资源创建
所有数据集成操作
所有工作流操作
所有数据质量操作
所有数据架构编辑
查看元数据菜单(空间外) 是(系统管理员或拥有 metastore VIEW 权限的用户)

4.2 DataBuilderFullControl(Playground 管理员)

Playground 是百度智能云提供的产品体验/演示环境,用户无需真实购买产品即可体验 DataBuilder 功能。系统预先配置了演示账户和演示工作空间数据。

DataBuilderFullControl 权限用于区分 Playground 中的管理员和普通体验用户:

用户类型 权限场景 是否拥有
Playground 管理员 访问空间外页面(主路由)
Playground 管理员 所有空间内功能菜单
Playground 管理员 所有按钮级别操作
Playground 普通用户 访问空间外页面(主路由) 否(所有主路由被重定向到工作台固定页面)
Playground 普通用户 空间内功能菜单 取决于被分配的角色权限

FullControl 的判断通过 checkFullControl() API 调用实现,该接口在内部调用了 /api/auth/fullControl 端点,使用静默模式(silent=true),接口正常返回即表示用户有此权限。

4.3 白名单机制详解

什么是白名单?

白名单是百度智能云平台的一种灰度发布和功能管控机制。部分新功能模块在正式全量上线前,会先通过白名单的方式向特定用户/企业开放,用以收集反馈和控制风险。用户如果不在白名单中,则看不到对应功能模块的入口。

白名单如何工作?

  1. 后端接口 whitelistVerifyAll() 在应用启动时自动调用,返回用户在所有功能上的白名单状态
  2. 返回结果是一个 featureTypes 数组,每项包含 featureType(白名单类型名称)和 inWhitelist(是否在白名单中)
  3. 前端将结果处理后存入 Redux Store 的 whiteListSlice
  4. 在路由权限 HOC 中,如果检测到某个功能模块的菜单权限被禁用,且对应白名单为 false,则显示"白名单功能,开通请提工单"而不是普通无权限提示

白名单映射表

白名单 Key 中文名称 影响的功能模块 未开通时的用户提示
UnderstandingAndPipeline 理解与管道 内容理解 + 数据管道 "白名单功能,开通请提工单"
Ontology 本体管理 本体管理 同上
Logic 逻辑建模 逻辑建模 同上
DataSearch 数据搜索 数据搜索 同上
DataBuilder 产品白名单 产品访问权限
hideGpu 隐藏GPU 隐藏GPU相关按钮 按钮消失(理想汽车用户专用)
configModeDiJob 可配置化模式 集成-可配置化模式 功能不可用
computeLimit 计算资源限制 最多2个Ray+2个Doris实例 超过限制时不可创建

白名单与产品功能发布的关系:白名单是百度智能云平台的一种 灰度发布机制。新功能开发完成后并非直接全量上线,而是先通过白名单 向特定用户或企业开放。用户如果在白名单中,可以看到新功能菜单入口并正常使用; 如果不在白名单中,菜单入口不显示,硬输入 URL 访问时显示"白名单功能,开通请提工单"。 白名单独立于角色权限——即使角色给了所有权限,不在白名单中仍然看不到对应功能。 随着产品迭代,白名单范围逐渐扩大,最终全量开放时移除白名单限制。

4.4 空间内角色-权限参考映射表

以下展示三种典型角色的权限配置(实际权限由空间管理员通过角色灵活配置):

权限分类 权限枚举值 管理员 开发者 访客
工作台菜单 WORKSPACE_MENU
项目创建 PROJECT_CREATE
新建文件夹 DIRECTORY_CREATE
导入文件 FILE_IMPORT
新建Notebook NOTEBOOK_CREATE
元数据菜单 CATALOG_MENU
创建目录 CREATE_CATALOG
创建数据源 CREATE_CONNECTION
读表 READ_TABLE
写表 WRITE_TABLE
创建表 CREATE_TABLE
计算资源菜单 COMPUTE_MENU
创建各类计算资源 *_CREATE
数据集成菜单 INTEGRATION_MENU
数据集成CRUD *_CREATE/DELETE/...
工作流菜单 WORKFLOW_MENU
新建工作流 WORKFLOW_CREATE
导入工作流 WORKFLOW_IMPORT
数据质量菜单 QUALITY_MENU
创建质量模板 QUALITY_TEMPLATE_CREATE
模型服务菜单 MODEL_SERVICE_MENU
创建模型服务 MODEL_SERVICE_CREATE
数据血缘菜单 LINEAGE_MENU
数据架构菜单 DATAFRAME_MENU
数据架构编辑 DATAFRAME_EDIT
本体管理菜单 ONTOLOGY_MENU 是*
逻辑建模菜单 LOGIC_MENU 是*
数据搜索菜单 DATASEARCH_MENU 是*
内容理解菜单 CONTENT_UNDERSTANDING_MENU 是*
数据管道菜单 PIPELINE_DESIGNER_MENU 是*

是* 表示除了角色权限外,还需要用户在该功能的白名单中。


五、前端权限管理实现详解(含注释)

本章节对权限系统的每一层实现代码提供详细的中文注释,帮助理解每一段代码的作用和在整个权限体系中的位置。

5.1 权限枚举定义

文件: src/api/permission/type.ts

以下是权限系统的核心枚举和类型定义,是整个权限体系的"字典"。

// ========== 角色类型 ==========
// 用于区分角色是用户创建的还是系统内置的
export enum RoleType {
  User = 'USER',      // 用户自定义角色,空间管理员创建,可编辑可删除
  System = 'SYSTEM'    // 系统预设角色,系统内置,不可删除
}

// ========== 资源类型(共39种) ==========
// 定义了系统中所有可以被权限管理的资源对象类型
export enum ResourceType {
  Workspace = 'WORKSPACE',              // 工作空间(顶层资源)
  Metastore = 'METASTORE',              // 元存储(空间外的数据源注册中心)
  ResourcePool = 'RESOURCE_POOL',       // 资源池(通用资源队列)
  Workflow = 'WORKFLOW',                // 工作流 DAG
  Quality = 'QUALITY',                  // 数据质量检查
  Directory = 'DIRECTORY',              // 工作区文件夹
  File = 'FILE',                        // 工作区文件
  Notebook = 'NOTEBOOK',                // Jupyter Notebook
  Connection = 'CONNECTION',            // 外部数据源连接
  Catalog = 'CATALOG',                  // 元数据目录(Schema 的上级)
  Schema = 'SCHEMA',                    // 数据模式/数据库
  Table = 'TABLE',                      // 数据表
  Volume = 'VOLUME',                    // 数据卷(非结构化数据容器)
  Dataset = 'DATASET',                  // 数据集
  Model = 'MODEL',                      // 模型(ML 模型)
  Operator = 'OPERATOR',                // 算子(数据处理单元)
  UnstructuredIntegration = 'UNSTRUCTURED_INTEGRATION',  // 非结构化数据集成任务
  StructuredIntegration = 'STRUCTURED_INTEGRATION',      // 结构化数据集成任务
  IntegrationCompute = 'INTEGRATION_COMPUTE',    // 集成专用计算资源
  EtlCompute = 'ETL_COMPUTE',                    // ETL 常驻计算资源
  EtlJobTemplate = 'ETL_JOB_TEMPLATE',           // ETL 任务模板
  AnalysisCompute = 'ANALYSIS_COMPUTE',          // 分析计算资源
  LogicCompute = 'LOGIC_COMPUTE',                // 通用常驻计算资源
  TableColumn = 'TABLE_COLUMN',                  // 表列(列级权限)
  TableRow = 'TABLE_ROW',                        // 表行(行级权限)
  ModelService = 'MODEL_SERVICE',                // 模型在线服务
  ResourceGroup = 'RESOURCE_GROUP',              // 资源组
  StructuredData = 'STRUCTURED_DATA',            // 结构化数据(工作台右键创建)
  MediaSet = 'MEDIA_SET',                        // 媒体集(工作台非结构化数据)
  DataIntegration = 'DATA_INTEGRATION',          // 数据集成总类型
  Pipeline = 'PIPELINE',                         // 数据管道(已废弃?使用 PipelineDesigner)
  PipelineDesigner = 'PIPELINE_DESIGNER',        // 数据管道设计器
  ContentUnderstanding = 'CONTENT_UNDERSTANDING', // 内容理解/分析
  Ontology = 'ONTOLOGY',                          // 本体管理
  Logic = 'LOGIC',                                // 逻辑设计
  DataSearch = 'DATASEARCH'                       // 数据搜索
}

// ========== 权限点枚举(共75个) ==========
// 定义了系统中所有可操作的功能权限点
export enum Privilege {
  // --- 通用权限(用于资源级别的权限分配) ---
  All = 'ALL',              // 全部权限的集合,在角色创建时使用
  Manage = 'MANAGE',        // 管理权限,层级最高,包含 Modify + Execute + View
  Execute = 'EXECUTE',      // 执行权限,包含 View
  View = 'VIEW',            // 查看权限,最基本的读权限
  Modify = 'MODIFY',        // 修改权限,包含 Execute + View
  Use = 'USE',              // 使用权限(如"使用数据源")
  FullControl = 'FULL_CONTROL',  // 完全控制(Playground 管理员专用)

  // --- 空间管理相关 ---
  WorkspacesMenu = 'WORKSPACES_MENU',    // 左侧导航"工作空间"菜单是否显示
  WorkspaceCreate = 'WORKSPACE_CREATE',  // 是否允许创建新工作空间

  // --- 元存储(空间外) ---
  MetastoreMenu = 'METASTORE_MENU',      // 左侧导航"元数据"菜单(空间外)是否显示
  MetastoreCreate = 'METASTORE_CREATE',  // 是否允许创建元存储

  // --- 工作区(空间内) ---
  WorkspaceMenu = 'WORKSPACE_MENU',      // 工作台菜单是否显示
  ProjectCreate = 'PROJECT_CREATE',      // 是否允许创建工作台项目
  DirCreate = 'DIRECTORY_CREATE',        // 是否允许新建文件夹
  FileImport = 'FILE_IMPORT',            // 是否允许导入文件
  NotebookCreate = 'NOTEBOOK_CREATE',    // 是否允许新建 Jupyter Notebook

  // --- 元数据目录(空间内) ---
  CatalogMenu = 'CATALOG_MENU',          // 元数据菜单(空间内)是否显示
  CreateCatalog = 'CREATE_CATALOG',      // 是否允许创建 Catalog
  CreateConnection = 'CREATE_CONNECTION', // 是否允许创建外部数据源连接
  Browse = 'BROWSE',                      // 是否允许浏览元数据目录
  ReadVolume = 'READ_VOLUME',            // 是否允许读取卷内容
  ReadTable = 'READ_TABLE',              // 是否允许读取表数据
  ReadDataset = 'READ_DATASET',          // 是否允许读取数据集
  WriteVolume = 'WRITE_VOLUME',          // 是否允许写入卷
  WriteTable = 'WRITE_TABLE',            // 是否允许写入表数据
  WriteDataset = 'WRITE_DATASET',        // 是否允许写入数据集
  CreateSchema = 'CREATE_SCHEMA',        // 是否允许创建 Schema
  CreateVolume = 'CREATE_VOLUME',        // 是否允许创建卷
  CreateTable = 'CREATE_TABLE',          // 是否允许创建表
  CreateDataset = 'CREATE_DATASET',      // 是否允许创建数据集
  CreateDatasetVersion = 'CREATE_DATASET_VERSION',  // 是否允许创建数据集新版本
  CreateModel = 'CREATE_MODEL',                      // 是否允许创建模型
  CreateModelVersion = 'CREATE_MODEL_VERSION',       // 是否允许创建模型新版本
  CreateOperator = 'CREATE_OPERATOR',                // 是否允许创建算子
  CreateOperatorVersion = 'CREATE_OPERATOR_VERSION', // 是否允许创建算子新版本
  UseConnection = 'USE_CONNECTION',       // 是否允许使用已有数据源

  // --- 计算资源 ---
  ComputeMenu = 'COMPUTE_MENU',                       // 计算资源菜单是否显示
  IntegrationComputeCreate = 'INTEGRATION_COMPUTE_CREATE', // 是否允许创建集成计算资源
  EtlComputeCreate = 'ETL_COMPUTE_CREATE',                 // 是否允许创建 ETL 常驻实例
  EtlJobTemplateCreate = 'ETL_JOB_TEMPLATE_CREATE',       // 是否允许创建任务模板
  AnalysisComputeCreate = 'ANALYSIS_COMPUTE_CREATE',      // 是否允许创建分析实例
  LogicComputeCreate = 'LOGIC_COMPUTE_CREATE',            // 是否允许创建通用常驻实例
  ResourcePoolCreate = 'RESOURCE_POOL_CREATE',            // 是否允许创建资源池
  ResourceGroupCreate = 'RESOURCE_GROUP_CREATE',          // 是否允许创建资源组

  // --- 数据集成 ---
  IntegrationMenu = 'INTEGRATION_MENU',                   // 数据集成菜单是否显示
  UnstructuredIntegrationCreate = 'UNSTRUCTURED_INTEGRATION_CREATE',   // 创建非结构化集成任务
  UnstructuredIntegrationStop = 'UNSTRUCTURED_INTEGRATION_STOP',       // 停止非结构化集成任务
  UnstructuredIntegrationDelete = 'UNSTRUCTURED_INTEGRATION_DELETE',   // 删除非结构化集成任务
  UnstructuredIntegrationExecute = 'UNSTRUCTURED_INTEGRATION_EXECUTE', // 运行非结构化集成任务
  StructuredIntegrationCreate = 'STRUCTURED_INTEGRATION_CREATE',       // 创建结构化集成任务
  StructuredIntegrationExecute = 'STRUCTURED_INTEGRATION_EXECUTE',     // 运行结构化集成任务
  StructuredIntegrationPublish = 'STRUCTURED_INTEGRATION_PUBLISH',     // 发布结构化集成任务
  StructuredIntegrationDelete = 'STRUCTURED_INTEGRATION_DELETE',       // 删除结构化集成任务
  StructuredIntegrationModify = 'STRUCTURED_INTEGRATION_MODIFY',       // 编辑结构化集成任务

  // --- 工作流 ---
  WorkflowMenu = 'WORKFLOW_MENU',             // 工作流菜单是否显示
  WorkflowCreate = 'WORKFLOW_CREATE',         // 是否允许创建工作流
  WorkflowImport = 'WORKFLOW_IMPORT',         // 是否允许导入工作流 JSON
  WorkflowInstanceMenu = 'WORKFLOW_INSTANCE_MENU', // 运行记录 Tab 是否显示

  // --- 数据质量 ---
  DataQualityMenu = 'QUALITY_MENU',           // 数据质量菜单是否显示
  QualityTemplateCreate = 'QUALITY_TEMPLATE_CREATE', // 是否允许创建自定义质量模板
  QualityObjectAdd = 'QUALITY_OBJECT_ADD',           // 是否允许添加监控对象

  // --- 模型服务 ---
  ModelServiceMenu = 'MODEL_SERVICE_MENU',      // 模型服务菜单是否显示
  ModelServiceCreate = 'MODEL_SERVICE_CREATE',  // 是否允许创建模型在线服务

  // --- 内容理解(需白名单) ---
  ContentUnderstandingMenu = 'CONTENT_UNDERSTANDING_MENU',  // 内容理解菜单是否显示

  // --- 数据管道(需白名单) ---
  PipelineDesignerMenu = 'PIPELINE_DESIGNER_MENU',      // 数据管道菜单是否显示
  PipelineDesignerCreate = 'PIPELINE_DESIGNER_CREATE',  // 是否允许创建 Pipeline

  // --- 数据血缘 ---
  LineageMenu = 'LINEAGE_MENU',              // 数据血缘菜单是否显示

  // --- 本体管理(需白名单) ---
  OntologyMenu = 'ONTOLOGY_MENU',                       // 本体管理菜单是否显示
  OntologyObjectTypeCreate = 'ONTOLOGY_OBJECT_TYPE_CREATE', // 是否允许创建对象类型
  OntologyLinkTypeCreate = 'ONTOLOGY_LINK_TYPE_CREATE',     // 是否允许创建关系类型

  // --- 逻辑建模(需白名单) ---
  LogicMenu = 'LOGIC_MENU',      // 逻辑建模菜单是否显示
  LogicCreate = 'LOGIC_CREATE',  // 是否允许创建逻辑设计

  // --- 数据搜索(需白名单) ---
  DataSearchMenu = 'DATASEARCH_MENU',      // 数据搜索菜单是否显示
  DataSearchCreate = 'DATASEARCH_CREATE',  // 是否允许创建搜索

  // --- 数据架构 ---
  DataArchitectureMenu = 'DATAFRAME_MENU',  // 数据架构菜单是否显示(含主题域/标准/模型/指标)
  DataArchitectureEdit = 'DATAFRAME_EDIT'   // 数据架构编辑权限(是否能编辑主题域/标准/模型/指标)
}

// ========== 权限类型 ==========
// 区分单个权限点还是权限分组
export enum PrivilegeType {
  Group = 'GROUP',     // 权限分组,如 ALL 表示一组权限的集合
  Single = 'SINGLE'    // 单个权限点,一般传此类型
}

// ========== 主体类型(权限授予对象) ==========
// 定义权限可以被授予给用户、用户组还是角色
export enum PrincipalType {
  User = 'USER',    // 直接授权给单个用户
  Group = 'GROUP',  // 授权给用户组(组内所有成员继承权限)
  Role = 'ROLE'     // 授权给角色(拥有该角色的用户继承权限)
}

5.2 Redux 状态管理

权限数据在 Redux Store 中分为三个 Slice 来管理。

5.2.1 全局权限 Store(GlobalAuth)

文件: src/store/GlobalAuth.ts

这个 Slice 管理应用级别的权限状态,包括全局功能权限、空间内功能权限、页面状态等。

import {PermissionType, EditionInfo} from '@api/auth';
import {Privilege} from '@api/permission/type';
import {createSlice} from '@reduxjs/toolkit';

/**
 * PageStatus 页面状态枚举
 * 用于决定当前页面应该展示正常内容还是无权限提示
 */
export enum PageStatus {
  Valid = 'valid',                           // 页面正常,渲染业务内容
  NoPagePermission = 'noPagePermission',     // 当前页面无权限,显示提示
  NoWorkspacePermission = 'noWorkspacePermission' // 空间内无任何菜单权限
}

/**
 * IGlobalAuthState 全局权限状态结构
 * 这个接口定义了整个应用层面与权限相关的所有状态字段
 */
export interface IGlobalAuthState {
  workspace: PermissionType;           
  // 工作空间级别的读写权限,来自 getWorkspacePermission 接口
  // 值为 'readWrite' | 'readOnly' | undefined

  metastore: PermissionType;           
  // 元存储级别的读写权限

  editionInfo: EditionInfo;            
  // 产品版本信息,包含版本名称(免费版/标准版/专业版/企业版)、
  // 运行状态(RUNNING/STOPPED)、过期时间、资源ID

  workspacePermission: Partial<Record<Privilege, boolean>>;
  // 【核心】空间内功能权限映射表
  // Key 是 Privilege 枚举值(如 WORKSPACE_MENU),Value 是布尔值(true=有权限)
  // 这个映射表由 getWorkspacePrivilege() 在进入工作空间时构建
  // 所有 UI 层的权限判断都查询这个映射表

  globalPermission: Partial<Record<Privilege, boolean>>;
  // 全局(空间外)功能权限映射表
  // 由 getGlobalPrivilege() 在应用启动时构建
  // 包含空间外菜单的权限(如 WORKSPACES_MENU、METASTORE_MENU)

  systemAdmin: boolean;
  // 是否是系统管理员,由 verifySystemAdmin() API 返回

  pageStatus: PageStatus;
  // 当前页面状态,用于决定是否显示无权限页面

  hasFullControl: boolean;
  // 是否有 DataBuilderFullControl 权限(Playground 管理员专用)
}

/**
 * 初始状态:所有权限默认为 null 或 false
 * 在权限数据加载完成之前,UI 展示 Loading 状态
 */
const initialState: IGlobalAuthState = {
  workspace: undefined,
  metastore: undefined,
  editionInfo: undefined,
  workspacePermission: null,
  globalPermission: null,
  systemAdmin: false,
  pageStatus: PageStatus.Valid,
  hasFullControl: false
};

/**
 * 创建 Redux Slice
 * 定义了 6 个 reducer 用于更新权限状态
 */
const globalAuthSlice = createSlice({
  name: 'globalAuth',
  initialState,
  reducers: {
    // 更新全局(空间外)权限映射表
    updateGlobalPermission: (state, action) => {
      state.globalPermission = action.payload;
    },

    // 更新空间内功能权限映射表
    // action.payload 格式: { WORKSPACE_MENU: true, CATALOG_MENU: false, ... }
    updateWorkspacePermission: (state, action) => {
      state.workspacePermission = action.payload;
    },

    // 更新系统管理员状态
    updateSystemAdmin: (state, action) => {
      state.systemAdmin = action.payload;
    },

    // 更新页面状态(Valid / NoPagePermission / NoWorkspacePermission)
    updatePageStatus: (state, action) => {
      state.pageStatus = action.payload;
    },

    // 更新产品版本信息(免费版/标准版/专业版/企业版)
    updateEditionInfo: (state, action) => {
      state.editionInfo = action.payload;
    },

    // 更新 FullControl 权限(Playground 管理员标记)
    updateHasFullControl: (state, action) => {
      state.hasFullControl = action.payload;
    }
  }
});

// 导出 actions 供 dispatch 使用
export const {
  updateGlobalPermission,
  updateWorkspacePermission,
  updateEditionInfo,
  updateHasFullControl
} = globalAuthSlice.actions;

export default globalAuthSlice.reducer;

5.2.2 工作空间资源权限 Store(WorkspaceAuth)

文件: src/store/WorkspaceAuth.ts

这个 Slice 管理与资源级别的读写权限,描述用户对每个资源类型的操作级别。

import {PermissionType} from '@api/auth';
import {createSlice} from '@reduxjs/toolkit';

/**
 * IWorkspaceAuthState 工作空间资源权限状态
 * 存储每种资源类型的读写权限等级
 * 值来源:getWorkspacePermission() API 返回的 GetWorkspacePermissionResult
 * 后端返回格式:{ catalog: ['readWrite'], table: ['readOnly'], ... }
 * 前端取 [0] 后存储
 */
export interface IWorkspaceAuthState {
  catalog: PermissionType;       // 目录的读写权限
  compute: PermissionType;       // 计算资源的读写权限
  file: PermissionType;          // 文件的读写权限
  job: PermissionType;           // 任务的读写权限
  schema: PermissionType;        // Schema 的读写权限
  table: PermissionType;         // 表的读写权限
  volume: PermissionType;        // 卷的读写权限
  workflow: PermissionType;      // 工作流的读写权限
  metastore: PermissionType;     // 元存储的读写权限
  workspace: PermissionType;     // 工作空间的读写权限
  metastoreAdmin: 'admin' | undefined; 
  // 是否是元存储管理员
  // 后端返回 ["admin"],前端取 [0] 得到 'admin'
}

/**
 * 初始状态:所有资源权限未加载时为 undefined
 */
const initialState: IWorkspaceAuthState = {
  catalog: undefined,
  compute: undefined,
  file: undefined,
  job: undefined,
  schema: undefined,
  table: undefined,
  volume: undefined,
  workflow: undefined,
  metastore: undefined,
  workspace: undefined,
  metastoreAdmin: undefined
};

/**
 * 创建 Slice
 * updateWorkspaceAuth reducer 从后端返回的权限数组中取第一项
 */
const workspaceAuthSlice = createSlice({
  name: 'workspaceAuth',
  initialState,
  reducers: {
    updateWorkspaceAuth: (state, action) => {
      // 遍历 11 个资源类型字段
      ['catalog', 'compute', 'file', 'job', 'schema',
       'table', 'volume', 'workflow', 'metastore',
       'workspace', 'metastoreAdmin'].forEach((key) => {
        // 后端返回格式如 action.payload.table = ['readWrite']
        // 取数组第一项 [0] 存入状态
        state[key] = action.payload[key]?.[0];
      });
    }
  }
});

export const {updateWorkspaceAuth} = workspaceAuthSlice.actions;
export default workspaceAuthSlice.reducer;

5.2.3 白名单 Store(WhiteList)

文件: src/store/whiteList.ts

白名单状态管理灰度功能是否对当前用户开放。

import {createSlice, PayloadAction} from '@reduxjs/toolkit';
import {IWhiteListResult, WhitelistName} from '@api/auth';

/**
 * 理想汽车用户白名单结果(用于隐藏 GPU 按钮)
 */
export interface LiAutoWhiteListResult {
  inWhitelist: boolean;
}

/**
 * 后端白名单字段名 -> 前端 Store 字段名的映射
 * 后端使用大写,前端使用 camelCase
 */
export const WebWhiteListName = {
  [WhitelistName.UnderstandingAndPipeline]: 'understandingAndPipeline',
  [WhitelistName.Ontology]: 'ontology',
  [WhitelistName.Logic]: 'logic',
  [WhitelistName.DataSearch]: 'dataSearch'
};

/**
 * WhiteListState 白名单状态结构
 */
export interface WhiteListState {
  liAutoWhiteList?: LiAutoWhiteListResult;  // 理想汽车用户专用
  understandingAndPipeline: boolean;         // 理解与管道白名单(控制内容理解+数据管道)
  ontology: boolean;                         // 本体管理白名单
  logic: boolean;                            // 逻辑建模白名单
  dataSearch: boolean;                       // 数据搜索白名单
  allWhitelistStatus?: IWhiteListResult[];   // 后端返回的所有原始白名单数据
}

/**
 * 初始状态:所有白名单默认为 true(乐观策略,先展示功能)
 * 接口加载后会用真实值覆盖
 */
const initialState: WhiteListState = {
  liAutoWhiteList: {inWhitelist: false},
  understandingAndPipeline: true,
  ontology: true,
  logic: true,
  dataSearch: true,
  allWhitelistStatus: undefined
};

const whiteListSlice = createSlice({
  name: 'whiteList',
  initialState,
  reducers: {
    // 更新理想汽车白名单
    updateLiAutoWhiteList: (state, action: PayloadAction<LiAutoWhiteListResult | undefined>) => {
      state.liAutoWhiteList = action.payload;
    },

    // 更新所有白名单状态(核心方法)
    // 在 whitelistVerifyAll() 接口返回后被调用
    updateAllWhitelistStatus: (state, action: PayloadAction<IWhiteListResult[] | undefined>) => {
      state.allWhitelistStatus = action.payload;
      // 遍历后端返回的每条白名单状态
      action.payload?.forEach((item) => {
        // 通过映射表找到对应的前端字段名
        if (WebWhiteListName[item.featureType]) {
          // 将白名单状态写入对应字段
          state[WebWhiteListName[item.featureType]] = item.inWhitelist;
        }
      });
    }
  }
});

export const {updateLiAutoWhiteList, updateAllWhitelistStatus} = whiteListSlice.actions;
export default whiteListSlice.reducer;

5.2.4 Store 组合入口

文件: src/store/index.ts

import {configureStore} from '@reduxjs/toolkit';

import sumSlice from './sumSlice';
import globalAuthSlice from './GlobalAuth';  // 全局权限
import workspaceAuthSlice from './WorkspaceAuth'; // 空间资源权限
import notebookSlice from './notebookSlice';
import cdcIntegrationSlice from './cdcIntegrationSlice';
import workflowSlice from './workflow';
import workflowNewSlice from './workflowSlice';
import workEditorSlice from './workEditorSlice';
import whiteListSlice from './whiteList';    // 白名单
import pipelineSlice from './pipelineSlice';
import pipelinePreviewSlice from './pipelinePreviewSlice';

// 使用 configureStore 合并所有 Slice
const store = configureStore({
  reducer: {
    sumSlice,
    globalAuthSlice,      // ← 全局权限(包含 workspacePermission 映射表)
    workspaceAuthSlice,   // ← 空间资源权限(包含各资源类型读写级别)
    whiteListSlice,       // ← 白名单(灰度功能开关)
    notebookSlice,
    workflowNewSlice,
    workflowSlice,
    cdcIntegrationSlice,
    workEditorSlice,
    pipelineSlice,
    pipelinePreviewSlice
  }
});

// 导出类型供 useSelector 和 useDispatch 使用
export type IAppState = ReturnType<typeof store.getState>;
export type IAppDispatch = typeof store.dispatch;

export default store;

5.3 权限 API 接口

文件: src/api/permission/index.ts

所有权限相关的 API 接口都定义在此文件中,按功能分为以下几类。

// ========== 角色管理 ==========
// 获取工作空间下的所有角色列表
getRolesList(workspaceId)
// 请求: GET /authorization/roles?workspaceId=xxx
// 返回: { success: true, result: RoleItem[] }
// RoleItem 结构: { name, description, privileges: Privilege[], type: RoleType, createdAt, createdBy }

// 获取指定角色的详细信息
getRoleDetail(workspaceId, roleName)
// 请求: GET /authorization/role?workspaceId=xxx&roleName=yyy
// 返回: { success: true, result: RoleDetail }

// 创建新角色
createRole(workspaceId, params)
// 请求: PUT /authorization/roles?workspaceId=xxx
// Body: { name: "自定义角色", description: "...", privileges: ["WORKSPACE_MENU", ...] }
// 返回: { success: true, result: RoleDetail }

// 删除角色
deleteRole(workspaceId, roleName)
// 请求: DELETE /authorization/roles?workspaceId=xxx&roleName=yyy
// 返回: { success: true, result: boolean }

// ========== 主体管理 ==========
// 获取工作空间下的用户/用户组/角色列表
getWorkspaceUserList({
  workspaceId,          // 空间 ID
  principalType,        // 可选,筛选主体类型:USER/GROUP/ROLE
  principalName,        // 可选,按名称搜索
  order,                // 可选,排序方式
  pageNo,               // 分页页码
  pageSize              // 每页条数
})
// 返回: {
//   success: true,
//   result: {
//     principalInfos: [{
//       principal: { id: "xxx", name: "张三", type: "USER" },
//       roles: [{ grantor: "admin", name: "开发者" }],
//       createdAt: "2024-01-01T00:00:00Z"
//     }],
//     totalCount: 10
//   }
// }

// 更新工作空间用户的角色(添加或移除角色)
updateWorkspaceUser(workspaceId, [
  {
    principal: { id: "xxx", name: "张三", type: "USER" },
    addRoleNames: ["开发者"],         // 要添加的角色名
    removeRoleNames: []              // 要移除的角色名
  }
])
// 请求: PATCH /authorization/principals?workspaceId=xxx
// 返回: { success: true, result: boolean }

// ========== 资源权限查询与修改 ==========
// 获取某个资源上的所有权限授权记录
getResourceList(resourceType, resourceId, workspaceId)
// 请求: GET /authorization/acl?resourceType=TABLE&resourceId=xxx&workspaceId=yyy
// 返回: { success: true, result: ResourcePermissionInfo[] }
// ResourcePermissionInfo: {
//   principal: { id, name, type },      // 授权主体
//   privilege: { type: "SINGLE", privilege: "READ_TABLE" },  // 权限类型和权限点
//   inheritedFrom: { id, name, type },   // 继承来源(如果权限来自父级资源)
//   grantor: "admin"                     // 授权者
// }

// 更新资源权限(批量添加或移除权限)
updateResourcePermission([
  {
    principal: { id: "xxx", name: "张三", type: "USER" },
    addPrivileges: [{ privilege: "READ_TABLE", type: "SINGLE" }],
    removePrivileges: [],
    resource: { type: "TABLE", id: "table-id" }
  }
], workspaceId)
// 请求: PATCH /authorization/acl?workspaceId=xxx
// 返回: { success: true, result: boolean }

// ========== 鉴权(验证是否有权限) ==========
// 单个资源鉴权
verifyAuth({
  privileges: [{ privilege: Privilege.VIEW, type: PrivilegeType.Single }],
  resource: { id: "resource-id", type: ResourceType.Table }
})
// 请求: POST /authorization/verify
// 说明: privileges 数组中多个权限是"或"的关系,只要满足一个即返回 true
// 返回: { success: true, result: boolean }

// 批量资源鉴权
batchVerifyAuth([
  { privileges: [...], resource: { id: "id1", type: ResourceType.Table } },
  { privileges: [...], resource: { id: "id2", type: ResourceType.Volume } }
])
// 请求: POST /authorization/verifyBatch
// 返回: { success: true, result: [true, false, ...] }

// 校验当前用户是否是系统管理员
verifySystemAdmin()
// 请求: POST /authorization/verifySystemAdmin
// 返回: { success: true, result: boolean }

// ========== 行/列权限(表级别的细粒度权限) ==========
// 查询表的行过滤规则
getTableRowFilterRule(workspaceId, tableName)
// 返回: [{ name: "rule1", expression: "region = 'cn'", principal: {...} }]

// 设置行权限过滤规则
updateTableRowFilterRule(workspaceId, tableName, {
  principals: [{ id: "...", type: "USER" }],
  expression: "region = 'cn'",      // SQL 过滤表达式
  extraAcl: [...]
})

// 删除行权限过滤规则
deleteTableRowFilterRule(workspaceId, [
  { tableName: "...", rowFilterName: "rule1" }
])

// 检查用户对表的列是否有权限
checkTablePermission(workspaceId, tableName, [
  { principal: { id: "...", type: "USER" } }
])
// 返回: [true, false, ...] 对应每个 principal 是否有权限

5.4 权限工具函数

文件: src/utils/auth.ts

这些工具函数负责将后端返回的权限数据转换为前端 Redux Store 需要的格式。

import {batchVerifyAuth, verifyAuth, verifySystemAdmin} from '@api/permission';
import {Privilege, PrivilegeType, ResourceType} from '@api/permission/type';
import {queryMetastore} from '@api/metastore';
import {queryWorkspaceDetail} from '@api/workspace';

/**
 * 空间内所有可分配的功能权限列表(共约 50 个)
 * 这个列表定义了 workspacePermission 映射表中包含的所有权限 key
 * 在 getWorkspacePrivilege() 中用于从后端返回的 privileges 数组构建布尔映射
 */
export const WorkspacePrivileges = [
  // --- 菜单权限 ---
  Privilege.WorkspaceMenu,         // 工作台菜单
  Privilege.CatalogMenu,           // 元数据菜单
  Privilege.ComputeMenu,           // 计算资源菜单
  Privilege.IntegrationMenu,       // 数据集成菜单
  Privilege.WorkflowMenu,          // 工作流菜单
  Privilege.WorkflowInstanceMenu,  // 运行记录菜单
  Privilege.DataQualityMenu,       // 数据质量菜单
  Privilege.ContentUnderstandingMenu, // 内容理解菜单
  Privilege.PipelineDesignerMenu,  // 数据管道菜单
  Privilege.PipelineDesignerCreate, // 数据管道创建
  Privilege.ModelServiceMenu,      // 模型服务菜单
  Privilege.LineageMenu,           // 数据血缘菜单
  Privilege.OntologyMenu,          // 本体管理菜单
  Privilege.DataArchitectureMenu,  // 数据架构菜单
  Privilege.LogicMenu,             // 逻辑建模菜单
  Privilege.DataSearchMenu,        // 数据搜索菜单

  // --- 按钮/操作权限 ---
  Privilege.ResourceGroupCreate,
  Privilege.IntegrationComputeCreate,
  Privilege.EtlComputeCreate,
  Privilege.EtlJobTemplateCreate,
  Privilege.AnalysisComputeCreate,
  Privilege.LogicComputeCreate,
  Privilege.ResourcePoolCreate,
  Privilege.UnstructuredIntegrationCreate,
  Privilege.UnstructuredIntegrationStop,
  Privilege.UnstructuredIntegrationDelete,
  Privilege.UnstructuredIntegrationExecute,
  Privilege.StructuredIntegrationCreate,
  Privilege.StructuredIntegrationExecute,
  Privilege.StructuredIntegrationPublish,
  Privilege.StructuredIntegrationDelete,
  Privilege.StructuredIntegrationModify,
  Privilege.WorkflowCreate,
  Privilege.WorkflowImport,
  Privilege.DataArchitectureEdit,
  Privilege.Modify,
  Privilege.QualityObjectAdd,
  Privilege.QualityTemplateCreate,
  Privilege.ProjectCreate,
  Privilege.ModelServiceCreate,
  Privilege.OntologyObjectTypeCreate,
  Privilege.OntologyLinkTypeCreate,
  Privilege.LogicCreate,
  Privilege.DataSearchCreate
];

/**
 * getGlobalPrivilege - 获取全局(空间外)权限
 * 在 App.tsx 应用启动时调用
 * 返回全局权限映射表,包含空间外菜单是否可见
 *
 * 实现细节:
 * 1. 同时调用 verifySystemAdmin() 和 queryMetastore() 两个接口
 * 2. 空间列表菜单始终可见(workspacesMenu = true)
 * 3. 元存储菜单仅对系统管理员或有 VIEW 权限的用户可见
 *
 * 返回格式示例:
 * {
 *   WORKSPACES_MENU: true,
 *   METASTORE_MENU: false  // 非管理员,不含 VIEW 权限
 * }
 */
export const getGlobalPrivilege = async (): Promise<
  Partial<Record<Privilege, boolean>>
> => {
  const [systemAdminRes, metastoreRes] = await Promise.all([
    verifySystemAdmin(),           // 检查是否为系统管理员
    queryMetastore(true)           // 静默模式查询元存储信息
  ]);
  const isSystemAdmin = systemAdminRes.result;
  const privileges = metastoreRes.result?.privileges;
  return {
    [Privilege.WorkspacesMenu]: true,  // 空间列表始终可见
    [Privilege.MetastoreMenu]:         // 元存储菜单条件可见
      isSystemAdmin || privileges?.includes(Privilege.View)
  };
};

/**
 * getWorkspacePrivilege - 获取空间内所有功能权限
 * 在 pages/index.tsx 的 Main 组件中进入空间时调用
 * 返回完整的空间内权限映射表
 *
 * 实现细节:
 * 1. 同时查询工作空间详情和元存储信息
 * 2. 将 workspaceDetail 返回的 privileges 数组与 WorkspacePrivileges
 *    列表交叉,构建 { Privilege: boolean } 的键值对映射
 * 3. 额外从 metastore 查询 CreateCatalog 和 CreateConnection 权限
 *
 * 输入:workspaceId - 当前空间 ID
 *
 * 返回格式示例:
 * {
 *   WORKSPACE_MENU: true,
 *   CATALOG_MENU: false,
 *   COMPUTE_MENU: true,
 *   CREATE_TABLE: false,
 *   ...共约50个键值对
 * }
 */
export const getWorkspacePrivilege = async (workspaceId) => {
  const [workspaceRes, metastoreRes] = await Promise.all([
    queryWorkspaceDetail({id: workspaceId}),  // 查询空间详情
    queryMetastore(true, workspaceId)         // 查询空间内元存储
  ]);
  const metaPrivileges = metastoreRes.result?.privileges;

  // 从后端返回的 privileges 字符串数组构建布尔映射
  // workspaceRes.result.privileges 示例: ["WORKSPACE_MENU", "COMPUTE_MENU", ...]
  // Object.fromEntries 将其转换为: { WORKSPACE_MENU: true, COMPUTE_MENU: true, ... }
  return {
    ...Object.fromEntries(
      WorkspacePrivileges.map((key) => [
        key,
        // 后端 privileges 数组中包含该 key 则表示用户有此权限
        workspaceRes.result?.privileges.includes(key)
      ])
    ),
    // 以下两个权限不来自空间详情,而来自元存储接口
    [Privilege.CreateCatalog]:
      metaPrivileges?.includes(Privilege.CreateCatalog),
    [Privilege.CreateConnection]:
      metaPrivileges?.includes(Privilege.CreateConnection)
  };
};

/**
 * checkSimplePrivilege - 简单权限判断(层级包含关系)
 * 用于在权限管理弹窗中判断权限选项的启用/禁用
 *
 * 权限层级:Manage > Modify > Execute > View
 * 如果用户拥有 Manage 权限,则自动拥有 Modify、Execute、View 权限
 *
 * 参数:
 *   privileges: 用户拥有的权限列表(如 ["MANAGE", "VIEW"])
 *   auth:       需要检查的目标权限(如 Privilege.View)
 *
 * 返回:用户是否有权限执行该操作
 *
 * 示例:
 *   checkSimplePrivilege(["MANAGE"], Privilege.View)  → true
 *   checkSimplePrivilege(["VIEW"], Privilege.Modify)  → false
 */
export const checkSimplePrivilege = (
  privileges: string[],
  auth: Privilege
): boolean => {
  const privilegesMap = {
    [Privilege.Manage]: [
      Privilege.Manage, Privilege.Modify,
      Privilege.Execute, Privilege.View
    ],
    [Privilege.Modify]: [
      Privilege.Modify, Privilege.Execute, Privilege.View
    ],
    [Privilege.Execute]: [Privilege.Execute, Privilege.View],
    [Privilege.View]: [Privilege.View]
  };
  // 遍历用户拥有的权限,检查是否包含了目标权限
  return privileges.some((item) => {
    const allowed = privilegesMap[item as Privilege];
    return allowed?.includes(auth) ?? false;
  });
};

5.5 应用初始化 - 权限加载全流程

文件: src/App.tsx

应用启动时按以下顺序加载权限数据:

第一步:产品激活和版本检查(init 函数内)

公有云用户需要先验证产品是否激活和购买。如果是 Playground 体验用户,则检查 FullControl 权限。

async function init() {
  setInitLoading(true);

  if (!isPrivate && !isPlayGroundUser) {
    // ===== 公有云普通用户 =====
    // 1. 检查 IAM STS 角色是否创建(产品开通的前置条件)
    const iamRoleInfo = await queryIamStsRole();

    // 2. 检查产品是否已激活
    const isEdapActive = await queryEdapActive();
    if (isEdapActive.result?.isActivated) {
      // 已激活,设置产品激活状态
      appDispatch({ type: AppContextActionType.ACTIVATE_PRODUCT });
    } else {
      // 未激活,跳转到产品激活页面
      window.location.href = `${baseUrl}/edap/#/active`;
    }

    // 3. 检查产品版本信息(免费版/标准版/专业版/企业版)
    const editionRes = await getEditionInfo();
    if (editionRes.result.name) {
      // 有版本信息 → 存储到 Redux
      store.dispatch({
        type: 'globalAuth/updateEditionInfo',
        payload: editionRes.result
      });
      // result 结构: { name: "STANDARD", status: "RUNNING", expiredTime: "...", resourceId: "..." }
    } else {
      // 无版本 → 尚未购买,跳转到购买页面
      window.location.href = `${baseUrl}/edap/#/billing?type=OPEN`;
    }

  } else if (isPlayGroundUser) {
    // ===== Playground 体验用户 =====
    // 检查是否拥有 DataBuilderFullControl 权限
    const res = await checkFullControl(true);  // silent=true 静默模式
    if (res.success) {
      store.dispatch({ type: 'globalAuth/updateHasFullControl', payload: true });
    }
  }

  setInitLoading(false);
}

第二步:获取全局权限(useEffect)

useEffect(() => {
  // 获取空间外的菜单权限
  getGlobalPrivilege().then((res) => {
    // res 格式: { WORKSPACES_MENU: true, METASTORE_MENU: false }
    store.dispatch({
      type: 'globalAuth/updateGlobalPermission',
      payload: res
    });
  });
}, []);

第三步:获取白名单状态(useEffect)

useEffect(() => {
  // 理想汽车用户专用:检查是否需要隐藏 GPU 按钮
  checkLiAutoWhiteList().then((res) => {
    store.dispatch({ type: 'whiteList/updateLiAutoWhiteList', payload: res.result });
  });

  // 获取所有功能的用户白名单状态
  whitelistVerifyAll().then((res) => {
    // res.result.featureTypes 示例:
    // [{ featureType: "UnderstandingAndPipeline", inWhitelist: true },
    //  { featureType: "Ontology", inWhitelist: false }, ...]
    store.dispatch(updateAllWhitelistStatus(res.result.featureTypes));
  });
}, []);

第四步:进入工作空间时获取空间内权限(在 pages/index.tsx Main 组件中)

// 每当 workspaceId 变化时重新加载空间内权限
useEffect(() => {
  if (workspaceId) {
    // 1. 获取空间内功能权限(菜单+按钮级别的)
    getWorkspacePrivilege(workspaceId).then((res) => {
      store.dispatch({
        type: 'globalAuth/updateWorkspacePermission',
        payload: res  // { WORKSPACE_MENU: true, CATALOG_MENU: false, ... }
      });
    });

    // 2. 获取空间内资源级别权限(读/写级别的)
    getWorkspacePermission(workspaceId).then((res) => {
      store.dispatch({
        type: 'workspaceAuth/updateWorkspaceAuth',
        payload: res.result
        // res.result: { table: ['readWrite'], volume: ['readOnly'], ... }
      });
    });
  }
}, [workspaceId]);

5.6 路由级权限控制

5.6.1 菜单配置中绑定权限

文件: src/pages/index.tsx(约 1847 行)

每一个菜单项都通过 privilege 字段绑定对应的权限枚举值。

// ========== 空间外菜单 ==========
// 这些菜单在工作空间选择页面(未进入具体空间时)的左侧导航显示
export const menus: MenuItem[] = [
  {
    menuName: '工作空间',
    key: urls.manageWorkspace,        // 路由路径
    isNavMenu: true,                   // 是否显示在左侧导航
    Component: WorkspaceList,          // 对应页面组件
    privilege: Privilege.WorkspacesMenu // ← 绑定的权限 key
  },
  {
    menuName: '元数据',
    key: urls.metastore,
    isNavMenu: true,
    Component: Metastore,
    privilege: Privilege.MetastoreMenu  // ← 绑定的权限 key
  }
];

// ========== 空间内菜单 ==========
// 这些菜单在进入具体工作空间后的左侧导航显示
export const workspaceMenus: MenuItem[] = [
  {
    menuName: '工作台',
    key: urls.workArea,
    isNavMenu: true,
    Component: WorkArea,
    privilege: Privilege.WorkspaceMenu  // ← 绑定的权限 key
  },
  {
    menuName: '元数据',
    key: urls.metaData,
    isNavMenu: true,
    Component: MetaData,
    privilege: Privilege.CatalogMenu     // ← 绑定的权限 key
  },
  // ... 更多菜单配置(共约 80 个菜单项)
];

// 空间内所有菜单权限列表
// 用于在 WithPermissionHoc 中判断用户是否有任一空间菜单权限
export const WorkspaceMenuPrivileges = [
  Privilege.WorkspaceMenu,
  Privilege.CatalogMenu,
  Privilege.ComputeMenu,
  Privilege.IntegrationMenu,
  Privilege.WorkflowMenu,
  Privilege.WorkflowInstanceMenu,
  Privilege.DataQualityMenu,
  Privilege.ContentUnderstandingMenu,
  Privilege.PipelineDesignerMenu,
  Privilege.ModelServiceMenu,
  Privilege.LineageMenu,
  Privilege.OntologyMenu,
  Privilege.DataArchitectureMenu,
  Privilege.LogicMenu,
  Privilege.DataSearchMenu
];

5.6.2 路由生成

文件: src/router/router.tsx

// 主路由(空间外):每个菜单项生成一条路由
// privilege 仅作为元数据标记,主路由不通过 HOC 做权限拦截
// 权限过滤是在 Main 组件内部的 filteredMenus 中完成的
const mainRouter = flattenedMenuList.map((menu) => ({
  path: menu.key,
  element: <Main menus={menus} component={menu.Component} />,
  privilege: menu.privilege
}));

// 工作空间路由(空间内):通过 withPermissionHoc 包裹每个子页面
// 每个子页面的权限由 HOC 在渲染时实时检查
const workspaceRouter = [{
  path: '/workspace',
  element: <Main menus={workspaceMenus} />,
  children: workspaceFlattenedMenuList.map((menu) => ({
    path: menu.key,
    // withPermissionHoc 的第二个参数是菜单的 privilege 值
    // HOC 内部会从 Redux 读取 workspacePermission 映射表来判断权限
    element: withPermissionHoc(
      <LazyComponent component={menu.Component} />,
      menu.privilege          // ← 传递权限 key 给 HOC
    ),
    privilege: menu.privilege
  }))
}];

// Playground 用户路由处理
// 无 FullControl 的体验用户 → 所有主路由重定向到预设的工作台页面
const router = useMemo(() => {
  const list = [
    ...(isPlayGroundUser && !hasFullControl
      ? playgroundMainRouter   // 全部重定向路由
      : mainRouter),           // 正常路由
    ...workspaceRouter,
    { path: '*', element: <Navigate to={urls.manageWorkspace} replace /> }
  ];
  return createHashRouter(list);
}, [appState.isActivated, isPlayGroundUser, hasFullControl]);

5.6.3 权限 HOC 核心逻辑

文件: src/router/WithPermissionHoc.tsx

withPermissionHoc 是整个路由级权限控制的核心。它在每个子页面渲染前执行权限检查。

import store, {IAppState} from '@store/index';
import {useSelector} from 'react-redux';
import {WorkspaceMenuPrivileges} from '../pages/index';
import {WorkspaceNoPermission} from '@components/WithoutPermissionPage/WorkspaceNoPermission';
import {PageNoPermission, PageNoPermissionIcon} from '@components/WithoutPermissionPage/PageNoPermission';
import {Loading} from 'acud';
import {PageStatus} from '@store/GlobalAuth';
import {Privilege} from '@api/permission/type';

/**
 * withPermissionHoc - 路由级权限高阶组件
 *
 * 工作原理:
 * 1. 接收要渲染的页面元素和该页面对应的权限 key
 * 2. 从 Redux Store 的 workspacePermission 映射表中查找用户是否有对应权限
 * 3. 根据查找结果决定渲染正常页面还是无权限提示
 *
 * 参数:
 *   element:        要渲染的页面组件
 *   needPermission: 该页面对应的 Privilege 枚举值(如 Privilege.CatalogMenu)
 */
export const withPermissionHoc = (element, needPermission) => {
  function WithPermissionComponents() {

    // ===== 步骤1:从 Redux 读取当前空间的功能权限映射表 =====
    // workspacePermission 格式:
    // { WORKSPACE_MENU: true, CATALOG_MENU: false, COMPUTE_MENU: true, ... }
    const permission = useSelector(
      (state: IAppState) => state.globalAuthSlice.workspacePermission
    );

    // ===== 步骤2:从 Redux 读取白名单状态 =====
    // 这四个白名单用于判断部分功能模块是否对当前用户开放
    const understandingAndPipeline = useSelector(
      (state: IAppState) => state.whiteListSlice.understandingAndPipeline
    );
    const ontology = useSelector(
      (state: IAppState) => state.whiteListSlice.ontology
    );
    const logic = useSelector(
      (state: IAppState) => state.whiteListSlice.logic
    );
    const dataSearch = useSelector(
      (state: IAppState) => state.whiteListSlice.dataSearch
    );

    // ===== 步骤3:权限未加载 → 显示 Loading =====
    // 首次进入空间时 workspacePermission 为 null,等接口返回后再判断
    if (!permission) {
      return <Loading />;
    }

    // ===== 步骤4:检查用户是否有任一空间内菜单权限 =====
    // 遍历 WorkspaceMenuPrivileges 列表,只要有一个为 true 就表示有权限
    // 如果用户连任一菜单权限都没有,直接显示"空间无权限",不再检查具体页面
    const hasWorkspaceMenu = WorkspaceMenuPrivileges.some(
      (item) => permission?.[item]
    );
    if (!hasWorkspaceMenu) {
      store.dispatch({
        type: 'globalAuth/updatePageStatus',
        payload: PageStatus.NoWorkspacePermission
      });
      return <WorkspaceNoPermission />;
    }

    // ===== 步骤5:检查当前页面是否有权限 =====
    // needPermission 为空(未设置 privilege 字段)或映射表中为 true → 有权限
    if (!needPermission || permission?.[needPermission]) {
      store.dispatch({
        type: 'globalAuth/updatePageStatus',
        payload: PageStatus.Valid
      });
      return element; // ← 正常渲染页面
    }

    // ===== 步骤6:无权限 → 区分是白名单功能还是常规无权限 =====
    store.dispatch({
      type: 'globalAuth/updatePageStatus',
      payload: PageStatus.NoPagePermission
    });

    // 白名单功能判断:如果某个功能模块的菜单权限被禁用,且用户不在对应白名单中
    // 则提示"白名单功能,开通请提工单",否则提示常规的无权限文案
    if (!understandingAndPipeline &&
        (needPermission === Privilege.ContentUnderstandingMenu ||
         needPermission === Privilege.PipelineDesignerMenu)) {
      return <PageNoPermission
        content="白名单功能,开通请提工单"
        icon={PageNoPermissionIcon.whiteList}
      />;
    }
    if (!ontology && needPermission === Privilege.OntologyMenu) {
      return <PageNoPermission
        content="白名单功能,开通请提工单"
        icon={PageNoPermissionIcon.whiteList}
      />;
    }
    if (!logic && needPermission === Privilege.LogicMenu) {
      return <PageNoPermission
        content="白名单功能,开通请提工单"
        icon={PageNoPermissionIcon.whiteList}
      />;
    }
    if (!dataSearch && needPermission === Privilege.DataSearchMenu) {
      return <PageNoPermission
        content="白名单功能,开通请提工单"
        icon={PageNoPermissionIcon.whiteList}
      />;
    }

    // 常规无权限(用户角色中没有分配该页面的菜单权限)
    return <PageNoPermission
      content="您当前暂无该页面权限,请联系空间管理员进行授权"
    />;
  }
  return <WithPermissionComponents />;
};

5.7 组件级权限控制

组件级权限控制通过三个 React 组件实现:AuthButton(按钮)、AuthComponents(通用组件)、AuthMenuItem(菜单项)。它们的核心机制是:无权限时不是隐藏组件,而是将其置为 disabled 状态并用 Tooltip 包裹显示提示信息。

5.7.1 AuthButton(按钮权限控制)

文件: src/components/AuthComponentsAntd/AuthButton.tsx

import {Button, Tooltip} from '@baidu/qianfan-antd-kit';
import type {ButtonProps} from '@baidu/qianfan-antd-kit';
import {isNil} from 'lodash';
import React, {memo, useMemo} from 'react';
import {TooltipConfig, TooltipType} from './constants';

/**
 * AuthButton Props 定义
 * 继承 Button 的所有属性,额外增加权限控制相关属性
 */
interface AuthButtonProps extends ButtonProps {
  isAuth: boolean;             // 是否有权限(核心:从 Redux 传入)
  isDisabled?: boolean;        // 额外的禁用条件(如业务逻辑判断)
  tooltipType?: TooltipType;   // 无权限时 Tooltip 的提示类型
  disabledTooltip?: string;    // 自定义 Tooltip 提示文案(优先级最高)
}

/**
 * AuthButton 权限控制按钮组件
 *
 * 行为:
 * - 有权限(isAuth=true):正常渲染按钮,可点击
 * - 无权限(isAuth=false):按钮变为 disabled 状态,hover 显示权限不足提示
 * - 按钮始终可见(不隐藏),让用户知道此功能存在
 */
const AuthButton: React.FC<AuthButtonProps> = ({
  isAuth,
  tooltipType = TooltipType.Resource,  // 默认为资源权限不足
  disabledTooltip,
  children,
  disabled: propsDisabled,  // 外部传入的 disabled 属性
  ...props
}) => {
  // 计算最终 disabled 状态
  // 1. 外部没传 disabled → 纯粹由 isAuth 决定
  // 2. 外部传了 disabled → 取"外部禁用 OR 无权限"的并集
  const disabled = useMemo(() => {
    if (isNil(propsDisabled)) return !isAuth;  // 无权限即禁用
    return propsDisabled || !isAuth;            // 合并禁用条件
  }, [isAuth, propsDisabled]);

  // 构建按钮元素
  const button = (
    <Button {...props} disabled={disabled}>
      {children}
    </Button>
  );

  // 确定 Tooltip 文案:自定义文案 > 类型预设文案
  const tooltipTitle = disabledTooltip || TooltipConfig[tooltipType];

  // 有权限直接返回按钮;无权限用 Tooltip 包裹后返回
  return <>
    {isAuth
      ? button
      : <Tooltip title={tooltipTitle}>{button}</Tooltip>
    }
  </>;
};

// 使用 memo 避免不必要的重渲染
export default memo(AuthButton);

使用示例:

import {AuthButton} from '@components/AuthComponentsAntd';
import {Privilege} from '@api/permission/type';
import {useSelector} from 'react-redux';

function MyPage() {
  // 从 Redux 读取权限映射表
  const permission = useSelector(
    state => state.globalAuthSlice.workspacePermission
  );

  return (
    <AuthButton
      // isAuth: 从权限映射表中查询 DATAFRAME_EDIT 权限
      isAuth={permission?.[Privilege.DataArchitectureEdit]}
      // tooltipType: 功能权限不足时提示"请向空间管理员申请"
      tooltipType={TooltipType.Function}
    >
      编辑
    </AuthButton>
  );
}

5.7.2 AuthComponents(通用组件权限包裹)

文件: src/components/AuthComponentsAntd/AuthComponents.tsx

import {Tooltip} from '@baidu/qianfan-antd-kit';
import {TooltipPlacement} from 'antd/lib/tooltip';
import React, {cloneElement, memo, ReactElement} from 'react';
import {TooltipConfig, TooltipType} from './constants';

/**
 * AuthComponents Props
 * 适用于任意 ReactElement(不限于 Button,也包括 Select、Input 等)
 */
interface AuthComponentsProps {
  isAuth: boolean;             // 是否有权限
  children: ReactElement;      // 要包裹的任意 React 组件
  tooltipType?: TooltipType;   // 提示类型
  disabledTooltip?: string;    // 自定义提示
  placement?: TooltipPlacement; // Tooltip 弹出方向
}

/**
 * AuthComponents 通用权限包裹组件
 *
 * 与 AuthButton 不同,此组件通过 cloneElement 方法
 * 动态向子组件注入 disabled: true 属性,适用于任何表单组件
 *
 * 使用场景:需要对 Select、Input、Switch 等非按钮组件进行权限控制时
 */
const AuthComponents: React.FC<AuthComponentsProps> = ({
  isAuth,
  tooltipType = TooltipType.Resource,
  disabledTooltip,
  children,
  placement = 'top'
}) => {
  // 有权限:原样返回子组件,不做任何修改
  if (isAuth) return children;

  // 无权限:克隆子元素并强制注入 disabled: true
  const controlledChild = cloneElement(children, {
    disabled: !isAuth
  });

  return (
    <Tooltip
      title={disabledTooltip || TooltipConfig[tooltipType]}
      placement={placement}
    >
      {controlledChild}
    </Tooltip>
  );
};

export default memo(AuthComponents);

5.7.3 AuthMenuItem(菜单项权限控制)

文件: src/components/AuthComponentsAntd/AuthMenuItem.tsx

import {Menu, Tooltip} from '@baidu/qianfan-antd-kit';
import {MenuItemProps} from 'antd/lib/menu/MenuItem';
import React, {memo} from 'react';
import {TooltipConfig, TooltipType} from './constants';

/**
 * AuthMenuItem Props
 * 继承 MenuItem 属性,增加权限控制
 */
interface AuthMenuItemProps extends MenuItemProps {
  isAuth: boolean;             // 是否有权限
  tooltipType?: TooltipType;   // 提示类型
  disabledTooltip?: string;    // 自定义提示
}

/**
 * AuthMenuItem 菜单项权限控制
 *
 * 用于下拉菜单中的菜单项权限控制
 * 无权限时菜单项变为 disabled 状态并显示提示
 */
const AuthMenuItem: React.FC<AuthMenuItemProps> = ({
  isAuth,
  tooltipType = TooltipType.Resource,
  disabledTooltip,
  children,
  disabled,
  ...props
}) => {
  const isNotAuthOrDisabled = !isAuth || disabled;
  const tooltipTitle = disabledTooltip || TooltipConfig[tooltipType];

  return isNotAuthOrDisabled ? (
    <Menu.Item {...props} disabled={isNotAuthOrDisabled}>
      <Tooltip title={tooltipTitle}>
        {children}
      </Tooltip>
    </Menu.Item>
  ) : (
    <Menu.Item {...props}>{children}</Menu.Item>
  );
};

export default memo(AuthMenuItem);

5.7.4 Tooltip 类型定义

文件: src/components/AuthComponentsAntd/constants.ts

// Tooltip 提示类型:区分权限不足的原因
export enum TooltipType {
  Function = 'function',     // 功能权限不足(按钮级),提示"请向空间管理员申请"
  Resource = 'resource',     // 资源权限不足(数据级),提示"无权限执行该操作"
  NotSupport = 'notSupport'  // 功能暂不支持
}

// 各类型对应的中文提示文案
export const TooltipConfig = {
  [TooltipType.Function]: '您暂无该按钮权限,请向空间管理员申请',
  [TooltipType.Resource]: '您无权限执行该操作',
  [TooltipType.NotSupport]: '暂不支持'
};

5.8 数据级权限控制 Hooks

5.8.1 useWorkspaceAuth - 同步权限查询

文件: src/hooks/useWorkspaceAuth.ts

不需要 API 调用,直接从 Redux Store 读取权限映射表。

import {useSelector} from 'react-redux';
import {IAppState} from '@store/index';
import {Privilege} from '@api/permission/type';

/**
 * useWorkspaceAuth - 同步查询空间内功能权限
 *
 * 纯同步操作,从 Redux 中读取已加载的权限映射表
 * 适用于 UI 组件的条件渲染(如表单按钮是否可点击)
 *
 * 参数:
 *   privileges: 需要查询的权限列表
 *   (如 [Privilege.ReadTable, Privilege.WriteTable])
 *
 * 返回:
 *   权限键值对,key 为权限枚举值,value 为布尔值
 *   例:{ ReadTable: true, WriteTable: false }
 *
 * 注意:如果权限数据尚未加载(null),所有权限默认为 false
 */
export default function useWorkspaceAuth(
  privileges: Privilege[]
): Partial<Record<Privilege, boolean>> {
  const permission = useSelector(
    (state: IAppState) => state.globalAuthSlice.workspacePermission
  );
  // 遍历请求的权限列表,从映射表中取值,未加载则返回 false
  return privileges.reduce(
    (pre, cur) => ({
      ...pre,
      [cur]: permission ? permission?.[cur] : false
    }),
    {}
  );
}

5.8.2 useVerifyAuth - 异步实时鉴权

文件: src/hooks/useVerifyAuth.ts

需要调用后端 API 进行实时鉴权,适用于资源级权限验证。

import {batchVerifyAuth, verifyAuth} from '@api/permission';
import {VerifyAuthParams} from '@api/permission/type';
import {useCallback, useEffect, useState} from 'react';

/**
 * useVerifyAuth - 异步实时鉴权 Hook
 *
 * 通过调用后端 /authorization/verify 接口进行实时的权限验证
 * 适用于需要对特定资源进行鉴权的场景(如检查用户是否可读取某张表)
 *
 * 参数:
 *   resourceAuth: 鉴权参数数组,每项包含权限列表和资源信息
 *     示例:[{ privileges: [{ privilege: VIEW, type: SINGLE }],
 *              resource: { id: "table-123", type: ResourceType.Table } }]
 *
 * 返回:
 *   [auth]: boolean 数组,每个元素对应输入数组中该资源的鉴权结果
 *
 * 鉴权逻辑:
 *   - 单个资源:调用 verifyAuth()
 *   - 多个资源:调用 batchVerifyAuth() 批量鉴权
 *   - privileges 数组中多个权限是"或"关系,只要满足一个即返回 true
 */
export function useVerifyAuth(resourceAuth: VerifyAuthParams[]) {
  const [auth, setAuth] = useState<boolean[]>([]);

  const getVerifyAuth = useCallback(async () => {
    if (resourceAuth?.length > 1) {
      // 批量鉴权
      const res = await batchVerifyAuth(resourceAuth);
      setAuth(res.result);  // result 是 boolean[],如 [true, false, true]
    } else {
      // 单个鉴权
      const res = await verifyAuth(resourceAuth[0]);
      setAuth([res.result]);  // 包装成数组 [boolean]
    }
  }, [resourceAuth]);

  // resourceAuth 变化时自动重新鉴权
  useEffect(() => {
    getVerifyAuth();
  }, [getVerifyAuth]);

  return [auth];
}

5.8.3 useHasFullControl - FullControl 检查

文件: src/hooks/useHasFullControl.ts

import {useSelector} from 'react-redux';
import {IAppState} from '@store/index';

/**
 * useHasFullControl - 检查用户是否有 DataBuilderFullControl 权限
 *
 * 专门用于 Playground 场景:区分管理员和普通体验用户
 * Playground 管理员拥有 FullControl,可以访问所有功能
 *
 * 返回:true 表示是 Playground 管理员
 */
export default function useHasFullControl(): boolean {
  const hasFullControl = useSelector(
    (state: IAppState) => state.globalAuthSlice.hasFullControl
  );
  return hasFullControl;
}

5.9 权限管理弹窗组件

文件: src/components/PermissionManage/index.tsx

权限管理弹窗允许空间管理员对特定资源(如表、卷、工作流等)进行权限分配。

主要功能:

  1. 权限类型切换:对于表资源,支持切换"整表权限 / 列权限 / 行权限"三种授权维度
  2. 授权操作:新增用户/用户组/角色对该资源的权限
  3. 取消授权:移除已授权的权限记录
  4. 权限继承显示:显示权限是从哪个父级资源继承来的
  5. 系统角色保护:系统预设角色不可撤销
  6. 继承权限保护:从父级继承来的权限不可在当前层级撤销

5.10 无权限页面组件

文件: src/components/WithoutPermissionPage/

三种无权限状态的展示:

状态 组件 展示内容 触发场景
页面无权限 PageNoPermission "页面无权限" + "请联系空间管理员" 用户角色没有分配当前页面的菜单权限
白名单未开通 PageNoPermission(icon=whiteList) "页面无权限" + "白名单功能,开通请提工单" 功能模块需要白名单但用户不在白名单中
空间无权限 WorkspaceNoPermission 用户没有空间内任何菜单的权限 用户被添加到空间但未分配任何角色
路由错误 ErrorBoundary "无法找到页面" 404 或路由匹配失败

六、完整权限工作流

6.1 应用启动阶段的权限加载流程

当用户打开 DataBuilder 应用时,权限系统按照以下顺序逐步完成初始化:

第一阶段:产品状态检查。 应用首先判断当前是公有云环境还是私有化环境。如果是公有云环境,需要依次检查 IAM STS 角色是否已创建(这是产品开通的前置条件),然后检查产品是否已激活。如果尚未激活,用户会被重定向到产品激活页面。激活检查通过后,还会查询当前产品的版本信息(免费版 TRIAL、标准版 STANDARD、专业版 PROFESSIONAL 还是企业版 ENTERPRISE),以及版本状态是否正常。如果是 Playground 体验环境,则跳过这些步骤,直接检查体验用户是否拥有 DataBuilderFullControl 管理员权限。

第二阶段:全局权限加载。 在初始化完成后,应用会并行发起两个关键请求:一是调用 verifySystemAdmin() 判断当前用户是否为系统管理员;二是调用 queryMetastore() 查询元存储的权限信息。这两个请求的结果被 getGlobalPrivilege() 函数合并处理,生成全局权限映射表并 dispatch 到 Redux Store。此时,空间列表菜单始终可见,而元存储菜单仅在用户是系统管理员或拥有 VIEW 权限时才显示。

第三阶段:白名单状态获取。 同时,应用还会发起白名单查询请求。whitelistVerifyAll() 接口返回用户在所有灰度功能上的白名单状态,前端将结果存入 Redux 的 whiteListSlice。白名单状态将影响后续路由 HOC 中无权限提示的文案(区分"白名单功能,开通请提工单"和常规的"请联系空间管理员")。

第四阶段:进入工作空间。 当用户选择一个工作空间进入后,Main 组件检测到 URL 中的 workspaceId 参数变化,触发空间内权限的加载。这个过程包含两个并行请求:getWorkspacePrivilege() 从工作空间详情的 privileges 字段中提取所有功能权限,构建 { Privilege: boolean } 映射表,存入 workspacePermissiongetWorkspacePermission() 获取资源级别的读写权限,如 { table: ['readWrite'], volume: ['readOnly'] },存入 workspaceAuth

以上四个阶段完成之后,所有权限数据均已就位,路由开始渲染。

6.2 页面路由匹配与权限拦截

当用户导航到某个具体页面时,React Router 匹配到对应的路由,然后 withPermissionHoc 高阶组件开始执行权限拦截。

首先,HOC 从 Redux Store 读取 workspacePermission 权限映射表。如果映射表为 null(权限数据尚未加载),则显示 Loading 加载动画,等待权限数据就绪。

权限数据就绪后,HOC 首先检查用户是否拥有空间内任一菜单的权限。这是通过遍历 WorkspaceMenuPrivileges 列表(包含所有菜单权限如 WORKSPACE_MENU、CATALOG_MENU 等)来完成的。如果用户连一个菜单权限都没有,说明用户虽然被添加到了工作空间,但没有被分配任何功能角色,此时显示"空间无权限"页面。

如果用户至少有一个菜单权限,HOC 继续检查当前访问的具体页面是否有权限。检查方式很简单:查看传入的 needPermission 参数(即该页面在菜单配置中绑定的 privilege 值)是否在权限映射表中为 true。如果为 true 或者 needPermission 本身为空(表示该页面不需要权限检查),则正常渲染页面。

如果权限检查失败,说明用户角色没有被分配该页面的权限。此时 HOC 进一步区分失败原因:检查该页面对应的功能模块是否需要白名单(内容理解、数据管道需要 UnderstandingAndPipeline 白名单;本体管理需要 Ontology 白名单;逻辑建模需要 Logic 白名单;数据搜索需要 DataSearch 白名单)。如果功能需要白名单且用户不在白名单中,显示"白名单功能,开通请提工单";如果是常规的角色权限不足,显示"您当前暂无该页面权限,请联系空间管理员进行授权"。

6.3 页面内部的组件级权限控制

当页面正常渲染后,页面内部的按钮、表单、菜单等 UI 组件也受到权限控制。

按钮权限: 使用 AuthButton 组件包裹需要权限控制的按钮。AuthButton 接收 isAuth 参数,该参数通常从 Redux 的 workspacePermission 中查询对应的 Privilege 值获得。如果 isAuth=true,按钮正常可点击;如果 isAuth=false,按钮变为 disabled 状态,鼠标悬停时显示 Tooltip 提示。按钮不会隐藏,让用户知道这个功能存在,只是当前没有权限使用。

通用组件权限: 使用 AuthComponents 包裹任意 React 元素(如 Select 下拉框、Input 输入框等)。通过 cloneElement 技术动态向子组件注入 disabled 属性,实现与 AuthButton 相同的禁用效果。

菜单项权限: 使用 AuthMenuItem 包裹下拉菜单中的菜单项,无权限时菜单项变为灰色不可点击状态。

6.4 数据级别的细粒度权限控制

除了路由和组件级别的权限控制,DataBuilder 还支持对具体数据资源的细粒度权限管理。

功能权限查询(同步): 使用 useWorkspaceAuth Hook 可以从 Redux 中同步读取已加载的权限映射表。例如,在数据表的操作栏中,需要判断用户是否有 READ_TABLEWRITE_TABLE 权限,来决定是否显示"查看数据"和"编辑表"按钮。

资源鉴权(异步): 使用 useVerifyAuth Hook 可以向后端发起实时的权限验证请求。这通常用于一些需要动态判断的场景,比如用户尝试打开某张表时,需要调用 verifyAuth 接口实时检查用户对该表的访问权限。批量鉴权 batchVerifyAuth 支持一次请求验证多个资源的权限。

行级和列级权限: 对于表资源,DataBuilder 支持更精细的行权限和列权限。行权限通过 SQL 过滤表达式实现(如 region = 'cn' 限制只能看特定区域的数据),列权限则可以限制用户只能访问特定的列。这些在权限管理弹窗中通过切换"整表权限 / 列权限 / 行权限"来配置。

6.5 权限判断的总优先级

当用户尝试访问某个功能或资源时,系统按照以下优先级依次判断:

  1. 白名单检查(最高优先级):功能模块是否在用户的白名单中?不在白名单中则直接拒绝访问,提示提工单开通。
  2. 系统管理员检查:用户是否是系统管理员?管理员拥有所有功能权限,跳过后续检查。
  3. FullControl 检查(Playground 专用):Playground 体验用户是否拥有 FullControl 权限?管理员可访问全部功能。
  4. 路由级权限检查(菜单权限 XXX_MENU):用户角色是否有该页面的菜单访问权限?
  5. 组件级权限检查(操作权限 XXX_CREATE/XXX_DELETE 等):用户角色是否有执行该操作的权限?
  6. 数据级权限检查(读写权限 READ_TABLE/WRITE_TABLE 等):用户对具体资源是否有读或写的权限?
  7. 资源读写级别检查PermissionType):对资源是 readWrite、readOnly 还是无权限?

6.6 权限加载的三个批次与时机详解

权限数据并非一次性全部加载,而是按照三个批次在不同时间点逐步获取。需要注意区分两类概念:权限配置的加载(管理员在弹窗里看到谁有什么权限)和权限的生效(普通用户访问数据时后端自动过滤)。下面以用户从打开空间到操作元数据模块的完整路径为例说明。

批次一:进入空间时 — 模块级功能权限(一次性加载,存入 Redux)

用户点击某个工作空间进入时,Main 组件检测到 workspaceId 变化,并行发起两个请求:

此时已确定:① 元数据左侧菜单是否可见;② 创建 Catalog、创建 Schema 等按钮是否可用。尚未加载:具体某个 Catalog 下有哪些 Schema、某张表谁有读写权限。

批次二:进入元数据模块浏览目录树 — 不发权限请求(后端 SQL 层过滤)

用户点击左侧"元数据"菜单后,浏览 Catalog → Schema → Table 的目录树。此过程的前端行为:

批次三:进入具体资源详情页 — 随数据 API 返回该资源的操作权限

当用户点击某个 Schema(如 catalog.default)进入详情页时,PanelSchema 组件调用 getSchemaDetail(workspaceId, 'catalog.default')。这个数据查询接口的返回结果中直接包含了当前用户对此 Schema 的权限列表:

// getSchemaDetail 返回结构
res.result.schema.privileges = ["MANAGE", "VIEW"]
// 存入组件 state: const [authList, setAuthList] = useState<Privilege[]>([]);

authList 数组随后用于:

同样的模式适用于 Table 详情页(PanelTable.tsx)、Catalog 详情页等——进入页面时随数据 API 返回该资源上的 privileges 数组,无需额外权限请求。

批次补充:权限管理 Tab — 管理员查看/修改授权配置(完全独立于数据访问)

用户若拥有 MANAGE 权限,可以看到"权限管理"Tab。点击后触发 getResourceList(ResourceType.Schema, 'catalog.default'),查询该资源上所有主体(用户/用户组/角色)被授予的权限记录

这些数据存入 PermissionManage 组件本地 useState,用于管理员增删授权。切换"列权限/行权限"Radio 时分别调 getResourceList(TABLE_COLUMN, ...)getTableRowFilterRule()

关键区分:此处的权限配置加载与普通用户的数据访问完全独立。普通用户访问表数据时,行列权限已经在后端 SQL 查询层自动生效(SELECT 只返回有权限的列、WHERE 自动拼接行过滤条件)。前端全程不感知权限过滤逻辑——不调 verifyAuth,不调 checkTablePermission,只负责渲染后端返回的已过滤数据。

完整时间线(以用户操作元数据为例)

  1. 进入空间getWorkspacePrivilege() 加载 CATALOG_MENU、CREATE_CATALOG 等功能开关 → Redux workspacePermission
  2. 点击元数据菜单useWorkspaceAuth 同步读 Redux 控制按钮,后端 listCatalogs 自动过滤无权限资源
  3. 点击某个 Catalog → SchemalistSchemas/listTables 后端过滤,前端不加载权限
  4. 点击某个 Schema 进入详情getSchemaDetail() 随数据返回 privileges: ["MANAGE"] → 决定"权限管理"Tab 可见 + "重命名"可用
  5. 点击权限管理 Tab(仅管理员) → getResourceList(SCHEMA, name) 拉取所有主体授权记录 → 组件本地 state
  6. 切换列/行权限 Radio(仅管理员) → getResourceList(TABLE_COLUMN, ...) / getTableRowFilterRule() → 组件本地 state
  7. 普通用户预览表数据 → 普通数据 API,后端 SQL 层自动应用行列权限过滤,前端不感知

文档说明:本文档基于 console-databuilder 前端代码深度分析生成,涵盖文件包括: src/api/permission/type.tssrc/api/permission/index.tssrc/api/auth.tssrc/store/GlobalAuth.tssrc/store/WorkspaceAuth.tssrc/store/whiteList.tssrc/store/index.tssrc/utils/auth.tssrc/router/router.tsxsrc/router/WithPermissionHoc.tsxsrc/pages/index.tsxsrc/App.tsxsrc/components/AuthComponentsAntd/ 全部文件、src/components/PermissionManage/src/components/WithoutPermissionPage/src/hooks/useVerifyAuth.tssrc/hooks/useWorkspaceAuth.tssrc/hooks/useHasFullControl.ts 等。