Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UserEntityGrant 授权分享

UserEntityGrantoak-general-business 里很有 Oak 味道的一块能力。它解决的不是“普通菜单授权”,而是“把某个对象上的一组关系权限,以链接或二维码的形式分享给另一个用户来认领”。

这类需求在客服转交、资源共享、邀请协作这些场景里很常见,而 oak-general-business 已经把它做成了一套完整的对象和规则。

主要对象

这一章最重要的实体是 UserEntityGrant

它定义了:

  • 权限作用在哪个 entity/entityId 上;
  • 授权类型是 grant 还是 transfer
  • 关系的选择规则 rule
  • 行对象的选择规则 ruleOnRow
  • 是否允许多人认领;
  • 二维码类型 qrCodeType
  • 过期时间、重定向页面和认领路由。

从运行时看,它还会和 wechatQrCode 以及 Oak 内建的认领关系数据一起工作。

组件

围绕这一能力,已经有四类常用组件:

  • src/components/userEntityGrant/list
  • src/components/userEntityGrant/share
  • src/components/userEntityGrant/upsert
  • src/components/userEntityGrant/claim

其中 claim 组件最值得认真读。它不是单纯展示一条授权,而是会根据 ruleruleOnRow 和当前用户已有认领状态,组织“选择关系 + 选择对象行 + 执行认领”的完整前端流程。

userEntityGrant/claim 常用参数

oak-general-business/src/components/userEntityGrant/claim/index.ts 项目里最常用的参数有:

  • picker:自定义领取对象选择器
  • hideInfo
  • hideTip
  • afterClaim

其中最重要的是 picker。它决定“授权领取时,用户究竟从什么对象里挑关系和行”。taicang/src/pages/frontend/userEntityGrant/claim/web.tsx 的真实写法就是:

<UserEntityGrantClaim
  oakId={oakId}
  oakPath={oakFullpath}
  picker={UbPicker}
/>

也就是说,这个组件本身已经把授权领取流程做好了,项目层真正需要补的是“如何挑选业务对象”的那块 picker。

claim 里的 picker 到底要满足什么接口

这一点在源码里其实写得很清楚,但如果文档不展开,新手很容易不知道该怎么自定义 picker。

userEntityGrant/claim 当前要求的 picker 组件签名是:

  • disabled
  • entity
  • entityFilter
  • relationIds
  • rule
  • ruleOnRow
  • onPickRelations(ids)
  • onPickRows(ids)
  • pickedRowIds
  • pickedRelationIds
  • oakPath

也就是说,项目层自定义 picker 时,不是只要“返回一组选中行”就够了,而是要同时处理:

  • 关系怎么选
  • 目标行怎么选
  • 当前已经选中了什么
  • 当前规则是不是单选 / 全选

claim 组件自己只负责在 pickedRelationIds + pickedRowIds 都具备时,自动把它们展开成 userEntityClaim$ueg.create 数据,再执行 claim

默认 ubPicker 的真实行为

如果项目不传自定义 picker,最值得先参考的其实就是公共包自带的:

  • src/components/userEntityGrant/claim/ubPicker

它当前的真实行为包括:

  • entity() 直接取授权上的 relationEntity
  • 目标行 projection 会自动猜 nametitle 字段,没有就退回 id
  • entityFilter 直接用于列表过滤
  • 会先刷新 relationIds 对应的 relation,并校验它们都属于同一个 entity
  • rule='all' 时自动全选关系
  • rule='single' 且只有一个关系时自动选中
  • ruleOnRow='all' 时自动全选当前列表行
  • ruleOnRow='single' 且当前只有一行时自动选中

这意味着默认 ubPicker 已经够覆盖很多常见场景:

  • 授权对象本身就有 name / title
  • 关系和目标对象不需要复杂树形选择
  • 领取页只需要“勾关系 + 勾对象”

只有当你的对象选择逻辑明显更复杂时,才需要像 taicang 那样再包一层自己的 picker。

userEntityGrant/share 的真实职责

创建完授权之后,真正给用户看的通常不是原始数据,而是 userEntityGrant/share。这个组件当前的真实行为包括:

  • 直接读取关联的 wechatQrCode$entity
  • 优先使用二维码的 url
  • 如果只有 buffer,会在前端把二进制内容转成 base64 图片
  • 同时展示 relationIds、是否过期、过期时间

它还暴露了一组很适合项目层做样式定制的参数:

  • disableDownload
  • size
  • disabled
  • color
  • bgColor
  • maskColor
  • maskText
  • maskTextColor
  • mode: 'default' | 'simple'

所以项目里如果要做:

  • 分享二维码弹窗
  • 授权卡片页
  • 领取入口海报区

通常不需要自己处理二维码 buffer 或图片转换,直接复用这个组件更稳。

userEntityGrant/list 的真实筛选能力

oak-general-business/src/components/userEntityGrant/list/index.ts 这组组件同样值得补出来,因为它不是简单把授权记录列出来。当前源码里的关键参数是:

  • entity
  • entityId
  • relationEntity
  • relationEntityFilter

组件会直接按这四个条件过滤 userEntityGrant,并默认按 $$createAt$$ desc 排序。也就是说,它更适合挂在“某个业务对象自己的授权记录列表”里,而不是全局授权台账。

从 web 端真实行为看,它还已经内置了两类非常常用的管理动作:

  • disable:如果当前行 legal action 里有 disable,就直接把授权置失效
  • 二维码:弹出一个 Modal,里面直接挂 UserEntityGrantShare

所以项目层如果只是想给后台加一个“看历史分享、让某条分享失效、重新看二维码”的页,通常不需要自己再包一层复杂逻辑,直接用这组组件就够了。

userEntityGrant/upsert 的真实职责

userEntityGrant/upsert 不是一个通用大表单,它当前更像“生成一次分享授权”的专用入口。源码里最关键的输入参数有:

  • entity
  • entityId
  • relationEntity
  • relationEntityFilter
  • relationIds
  • type
  • redirectToAfterConfirm
  • claimUrl
  • qrCodeType
  • multiple
  • rule
  • ruleOnRow

它在 ready() 里会把这些参数自动灌进当前创建数据,并默认补:

  • granterId = 当前用户
  • type = 'grant'
  • rule = 'single'
  • ruleOnRow = 'single'

而当前 web 端表单真正让用户手填的核心只有一个:

  • period,也就是有效期天数,范围 1 到 30 天

点提交后,组件会先算出 expiresAt,执行创建;创建成功后不会立刻跳走,而是直接在当前页切成 UserEntityGrantShare 展示二维码,并给一个“重新生成”按钮。

这意味着它非常适合做:

  • 关系分享弹窗
  • 后台快速生成邀请二维码
  • 某个对象详情页里的“生成领取链接”侧栏

而不太像一个需要项目层深度定制字段的后台表单。

aspect / endpoint / feature

这一章有一个很容易让人误判的地方:

  • 没有单独的 frontend feature;
  • 没有单独的对外 aspect;
  • 也没有专门的 HTTP endpoint。

UserEntityGrant 的主入口其实是实体动作本身,尤其是 claim

但是它又不是孤立的,因为微信回调 endpoint 在扫描对应二维码时,会间接触发这套流程。

后台规则

这一章的核心逻辑集中在:

  • src/checkers/userEntityGrant.ts
  • src/triggers/userEntityGrant.ts

默认规则包括:

  • 创建前检查 relationIds 至少选了一个关系;
  • 创建授权时自动补授权人,并默认把 expired 置为 false
  • 没有显式传 expiresAt 时,默认 5 分钟后过期;
  • 自动创建关联的 wechatQrCode,而且二维码默认跳转到 claimUrl || '/userEntityGrant/claim'
  • 执行 claim 时,checker 会把 userEntityClaim$ueg 自动展开成真正的 userRelation 创建数据;
  • 授权过期时,使关联二维码也过期;
  • 执行 claim 时,如果是单次授权,则自动失效。

也就是说,你创建的并不是一条“静态授权记录”,而是一条会自动派生二维码、自动处理过期、自动处理单次领取的动态业务数据。

注入点

这一章没有专门的 feature 注入点,它的注入点在后端:

  • ogb0Triggers 注入 userEntityGrant 的派生逻辑;
  • ogb0Checkers 注入 claim 的校验逻辑。

只要你的项目初始化时合并了 oak-general-business 的 trigger / checker,这些规则就已经生效。

项目中如何接入

UserEntityGrant 这章在项目里的典型接法,不是手写二维码逻辑,而是直接复用公共组件和默认 trigger:

  • 先在项目初始化时合并 ogb0Triggers / ogb0Checkers
  • 在页面里复用 src/components/userEntityGrant/upsertshareclaim
  • 如果你本来就在做用户关系管理,更推荐直接接 src/components/userRelation/upsert/byUserEntityGrant

byUserEntityGrant 这条接法里,真正决定分享行为的关键输入有:

  • entity / entityId
  • relations
  • redirectToAfterConfirm
  • claimUrl
  • qrCodeType
  • multiple
  • rule

这样分享页、二维码页、认领页、过期失效逻辑会一起工作。

如果按组件职责来落页,更推荐这样拆:

  • 关系管理页或对象详情页里挂 userEntityGrant/upsert
  • 历史分享记录页挂 userEntityGrant/list
  • 分享成功弹窗或分享海报区直接挂 userEntityGrant/share
  • 真正的领取页单独挂 userEntityGrant/claim

这样“生成授权”和“消费授权”会天然分层,不会在一个页面里把创建、二维码展示、认领三件事搅在一起。

真实项目里的入口组织

haina-busitaicang 的做法都很接近:

  • 后台或管理页里的关系维护组件,通常会把 claimUrl 直接设成 /userEntityGrant/claim
  • 领取页本身再去包 userEntityGrant/claim
  • 如果默认 picker 不够用,就像 taicang 一样传一个项目自己的 UbPicker

从这次源码比对看,taicang 前台领取页其实也给了一条很典型的最小包法:

  • 页面壳只负责从路由里拿 oakId
  • 直接把公共 UserEntityGrantClaim 挂出来
  • picker 先用公共 @oak-general-business/components/userEntityGrant/claim/ubPicker

也就是说,即使项目后续准备自定义 picker,第一版通常也可以先直接落公共 ubPicker,等业务规则真的复杂了再替换。

这套分法很实用。关系管理页只负责“生成授权”,领取页只负责“消费授权”,职责很清楚。

使用示例

1. 在关系管理页里创建授权分享

userRelation/upsert/byUserEntityGrant 就是现成的项目接法,它会在创建授权后把生成的 userEntityGrantId 回传给上层:

<UserRelationUpsert
  mode="byUserEntityGrant"
  onUserEntityGrantCreated={(id) => this.setState({ grantId: id })}
/>

2. 拿到授权后直接展示分享组件

src/components/userRelation/upsert/byUserEntityGrant/web.tsx 的真实做法,就是继续挂 UserEntityGrantShare

<UserEntityGrantShare
  oakId={grantId}
  oakPath="$userRelation/upsert/byUserEntityGrant-userEntityGrant/detail"
/>

这里不需要项目层自己生成二维码。创建授权记录后,默认 trigger 会自动派生 wechatQrCode

使用建议

这一能力最适合的场景,不是“做一个自己的权限系统”,而是:

  • 基于已有 relation 体系,把授权分享出去;
  • 让别人通过二维码或链接来领取;
  • 把共享行为表达成一次明确的业务动作。

因此在使用之前,最好先把对象上的 relation 设计好,再来使用 UserEntityGrant