• 角色菜单权限 API

    用途与前置条件

    scoped.roles.menuPermissions 把后台管理员的菜单选择转换成角色上的可追踪授权来源。它使用 MenuConfigInput 保存后的配置资产,支持菜单、视图、加载接口、按钮操作和接口响应字段授权。

    前置条件:

    • 角色已存在。
    • 菜单配置已通过 scoped.menus.management.applyChanges()scoped.menus.configs/items/views/loadApis/actions/responses.*()scoped.menus.config.save() 保存。
    • 写入前先调用 preview(),执行时传回同一输入、expectedpreviewToken

    注意:这里保留显式 preview/execute 是有意的。菜单配置的普通增量保存可以自动预览并提交,但角色菜单授权会改变真实访问范围,可能影响大量用户,所以需要管理端先展示影响,再确认提交。

    我想做什么

    目标首选 API说明
    预览并提交授权preview() 后调用 grant() / deny() / revoke() / set()所有写入都使用预览证据和 revision 保护。
    读取直接授权getDirect(roleId)查看该角色自身保存的菜单授权和响应字段。
    分页读取授权listDirect(roleId, { first, after })管理后台列表页使用 first/after 翻页。
    读取有效授权getEffective(roleId)包含继承后的 grant,并按 deny-first 解析。
    生成授权树getAuthorizationTree(roleId, { configId })供角色编辑页展示已勾选、拒绝、继承和停用状态。

    签名

    roles.menuPermissions.preview(roleId: string, change: MenuBusinessPermissionChange, options?: PreviewOptions): Promise<ImpactPreview<MenuBusinessPermissionPlan>>
    roles.menuPermissions.grant(roleId: string, selection: MenuBusinessPermissionSelection, options: RequiredRevisionVectorOptions & PreviewExecutionOptions): Promise<MutationResult<MenuBusinessPermissionGrantResult>>
    roles.menuPermissions.deny(roleId: string, selection: MenuBusinessPermissionSelection, options: RequiredRevisionVectorOptions & PreviewExecutionOptions): Promise<MutationResult<MenuBusinessPermissionGrantResult>>
    roles.menuPermissions.revoke(roleId: string, input: { grantIds: readonly string[] }, options: RequiredRevisionVectorOptions & PreviewExecutionOptions): Promise<MutationResult<BatchMutationSummary>>
    roles.menuPermissions.set(roleId: string, assignments: readonly MenuBusinessPermissionAssignment[], options: RequiredRevisionVectorOptions & PreviewExecutionOptions): Promise<MutationResult<BatchMutationSummary>>
    roles.menuPermissions.getDirect(roleId: string): Promise<VersionedResult<MenuBusinessDirectPermissionSnapshot>>
    roles.menuPermissions.listDirect(roleId: string, query?: CursorQuery & { effect?: 'allow' | 'deny'; configId?: string }): Promise<PageResult<MenuBusinessGrantSnapshot>>
    roles.menuPermissions.getEffective(roleId: string): Promise<VersionedResult<MenuBusinessEffectivePermissionSnapshot>>
    roles.menuPermissions.getAuthorizationTree(roleId: string, options: { configId: string }): Promise<VersionedResult<MenuBusinessAuthorizationTree>>

    关键参数标记:change: MenuBusinessPermissionChangeselection: MenuBusinessPermissionSelectionassignments: readonly MenuBusinessPermissionAssignment[]

    参数对象

    字段类型必填/默认说明
    configIdstring必填目标菜单配置 ID。
    menusstring[]可选选择菜单分组或菜单项 ID。
    viewsstring[]可选选择视图 ID,例如页面 orders-list
    loadsApiResource[]可选精确选择加载接口资源。
    actionsstring[]可选精确选择 action ID。
    responseFieldsMenuBusinessResponseFieldSelection[]可选为指定接口选择可返回字段。
    include.descendantsboolean默认 false选择菜单时是否包含后代菜单和视图。
    include.loadsboolean默认 true选择视图时是否自动包含加载接口。
    include.actionsboolean默认 false选择视图时是否自动包含操作按钮。
    include.responseFields'none' | 'all'默认 'none'是否自动包含所选接口的全部响应字段。

    responseFields 的每一项形如:

    {
      apiResource: 'api:GET:/api/orders',
      target: 'items',
      fields: ['orderNo', 'status'],
    }

    fields 必须来自菜单配置中该接口已经声明的字段。分页响应建议写 target,例如 itemsdata.items;同一接口存在多个响应目标时,不写 target 会因为目标不明确而被拒绝。要授权所有字段,可以使用 include.responseFields: 'all';要精确控制字段,使用 'none' 加显式 responseFields

    operationpreview 输入执行方法语义
    grant{ operation: 'grant', selection }grant(roleId, selection, options)追加 allow 菜单授权。
    deny{ operation: 'deny', selection }deny(roleId, selection, options)追加 deny 菜单授权。
    revoke{ operation: 'revoke', grantIds }revoke(roleId, { grantIds }, options)删除指定 grant。
    set{ operation: 'set', assignments }set(roleId, assignments, options)替换该角色的完整直接菜单授权。
    字段类型说明
    effect'allow' | 'deny'本条 assignment 的效果。
    selectionMenuBusinessPermissionSelection要授权或拒绝的菜单选择。

    方法详解:预览与写入

    roles.menuPermissions.preview(roleId, change, options?)

    • 用途:把一次 grant、deny、revoke 或 set 展开成计划,提前暴露冲突、影响用户和将生成的来源。
    • 参数roleIdchange: MenuBusinessPermissionChange
    • 状态影响:只读,不写入 grant。
    • 原始返回ImpactPreview<MenuBusinessPermissionPlan>;重点检查 executableconflictsgrants.items[].selectedAssetsgrants.items[].selectedResponseFieldsexpectedpreviewToken

    roles.menuPermissions.grant(roleId, selection, options)

    • 用途:追加一组 allow 菜单授权。
    • 参数selection: MenuBusinessPermissionSelection 必须与 grant preview 一致;options 必须带 expected/previewToken
    • 状态影响:保存 grant,并生成视图、接口、按钮和响应字段的规则来源。
    • 原始返回MutationResult<MenuBusinessPermissionGrantResult>generatedSourcesgeneratedResponseFields 是本次生成数量。

    roles.menuPermissions.deny(roleId, selection, options)

    • 用途:追加一组 deny 菜单授权,用于显式禁止某些菜单能力。
    • 参数:必须先以 { operation: 'deny', selection } 预览。
    • 状态影响:保存 deny grant,不删除已有 allow。
    • 原始返回:同 grant(),但 effect 为 deny。

    roles.menuPermissions.revoke(roleId, input, options)

    • 用途:按 grant ID 精确撤销直接菜单授权。
    • 参数input.grantIds 来自 grant()getDirect()listDirect();执行前先 preview revoke。
    • 状态影响:移除指定 grant 及其生成来源。
    • 原始返回MutationResult<BatchMutationSummary>

    roles.menuPermissions.set(roleId, assignments, options)

    • 用途:保存完整角色菜单授权表单。
    • 参数assignments: readonly MenuBusinessPermissionAssignment[],每项含 effectselection
    • 状态影响:替换该角色全部直接菜单授权;不影响手工角色规则和用户角色绑定。
    • 原始返回MutationResult<BatchMutationSummary>

    方法详解:读取授权

    roles.menuPermissions.getDirect(roleId)

    • 用途:读取该角色自己拥有的菜单 grant。
    • 参数:角色 ID。
    • 状态影响:只读。
    • 原始返回VersionedResult<MenuBusinessDirectPermissionSnapshot>;每个 grant 含 selectionresponseFieldssourceStatus

    roles.menuPermissions.listDirect(roleId, query?)

    • 用途:分页读取角色自己的菜单 grant。
    • 参数:可按 effectconfigId 过滤,并支持 first/after
    • 状态影响:只读。
    • 原始返回PageResult<MenuBusinessGrantSnapshot>

    roles.menuPermissions.getEffective(roleId)

    • 用途:读取角色自身和父角色继承后的有效菜单授权。
    • 参数:角色 ID。
    • 状态影响:只读。
    • 原始返回VersionedResult<MenuBusinessEffectivePermissionSnapshot>;每项包含 sourceRoleId/inherited/depth 和冲突信息。

    roles.menuPermissions.getAuthorizationTree(roleId, options)

    • 用途:生成后台授权树,展示菜单、视图、加载接口、按钮和响应字段的 direct/inherited/conflict 状态。
    • 参数options.configId 指定配置。
    • 状态影响:只读。
    • 原始返回VersionedResult<MenuBusinessAuthorizationTree>;每个节点含 stateselectionchildren

    响应与副作用

    Grant/deny 会保存管理员选择,并生成可追踪来源。响应字段来源不会直接改变接口返回;只有当前用户调用 subject.menus.filterResponse() 或 Vext 自动响应投影时,才会按有效授权裁剪返回值。

    {
      "data": {
        "roleId": "order-operator",
        "grantIds": { "total": 1, "items": ["grant_..."] },
        "generatedSources": 3,
        "generatedResponseFields": 2,
        "removedSources": 0
      },
      "auditId": "audit_..."
    }

    失败与限制

    常见失败包括角色不存在、配置不存在、选择的 view/action/field 不存在、资源格式不合法、preview token 过期、revision 冲突和容量超限。set() 可以传空数组清空直接菜单授权,但不会删除手工规则或用户角色绑定。

    示例

    const selection = {
      configId: 'admin',
      views: ['orders-list'],
      responseFields: [{
        apiResource: 'api:GET:/api/orders',
        target: 'items',
        fields: ['orderNo', 'status'],
      }],
      include: { loads: true, actions: true, responseFields: 'none' },
    };
    
    const preview = await scoped.roles.menuPermissions.preview(
      'order-operator',
      { operation: 'grant', selection },
    );
    if (!preview.executable) throw new Error('resolve conflicts first');
    
    const result = await scoped.roles.menuPermissions.grant(
      'order-operator',
      selection,
      { ...preview.expected, previewToken: preview.previewToken },
    );
    {
      "roleId": "order-operator",
      "generatedSources": 3,
      "generatedResponseFields": 2
    }

    相关内容

    参见角色菜单授权管理菜单菜单 API