UserEntityGrant 授权分享
UserEntityGrant 是 oak-general-business 里很有 Oak 味道的一块能力。它解决的不是“普通菜单授权”,而是“把某个对象上的一组关系权限,以链接或二维码的形式分享给另一个用户来认领”。
这类需求在客服转交、资源共享、邀请协作这些场景里很常见,而 oak-general-business 已经把它做成了一套完整的对象和规则。
主要对象
这一章最重要的实体是 UserEntityGrant。
它定义了:
- 权限作用在哪个
entity/entityId上; - 授权类型是
grant还是transfer; - 关系的选择规则
rule; - 行对象的选择规则
ruleOnRow; - 是否允许多人认领;
- 二维码类型
qrCodeType; - 过期时间、重定向页面和认领路由。
从运行时看,它还会和 wechatQrCode 以及 Oak 内建的认领关系数据一起工作。
组件
围绕这一能力,已经有四类常用组件:
src/components/userEntityGrant/listsrc/components/userEntityGrant/sharesrc/components/userEntityGrant/upsertsrc/components/userEntityGrant/claim
其中 claim 组件最值得认真读。它不是单纯展示一条授权,而是会根据 rule、ruleOnRow 和当前用户已有认领状态,组织“选择关系 + 选择对象行 + 执行认领”的完整前端流程。
userEntityGrant/claim 常用参数
oak-general-business/src/components/userEntityGrant/claim/index.ts 项目里最常用的参数有:
picker:自定义领取对象选择器hideInfohideTipafterClaim
其中最重要的是 picker。它决定“授权领取时,用户究竟从什么对象里挑关系和行”。taicang/src/pages/frontend/userEntityGrant/claim/web.tsx 的真实写法就是:
<UserEntityGrantClaim
oakId={oakId}
oakPath={oakFullpath}
picker={UbPicker}
/>
也就是说,这个组件本身已经把授权领取流程做好了,项目层真正需要补的是“如何挑选业务对象”的那块 picker。
claim 里的 picker 到底要满足什么接口
这一点在源码里其实写得很清楚,但如果文档不展开,新手很容易不知道该怎么自定义 picker。
userEntityGrant/claim 当前要求的 picker 组件签名是:
disabledentityentityFilterrelationIdsruleruleOnRowonPickRelations(ids)onPickRows(ids)pickedRowIdspickedRelationIdsoakPath
也就是说,项目层自定义 picker 时,不是只要“返回一组选中行”就够了,而是要同时处理:
- 关系怎么选
- 目标行怎么选
- 当前已经选中了什么
- 当前规则是不是单选 / 全选
claim 组件自己只负责在 pickedRelationIds + pickedRowIds 都具备时,自动把它们展开成 userEntityClaim$ueg.create 数据,再执行 claim。
默认 ubPicker 的真实行为
如果项目不传自定义 picker,最值得先参考的其实就是公共包自带的:
src/components/userEntityGrant/claim/ubPicker
它当前的真实行为包括:
entity()直接取授权上的relationEntity- 目标行 projection 会自动猜
name或title字段,没有就退回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、是否过期、过期时间
它还暴露了一组很适合项目层做样式定制的参数:
disableDownloadsizedisabledcolorbgColormaskColormaskTextmaskTextColormode: 'default' | 'simple'
所以项目里如果要做:
- 分享二维码弹窗
- 授权卡片页
- 领取入口海报区
通常不需要自己处理二维码 buffer 或图片转换,直接复用这个组件更稳。
userEntityGrant/list 的真实筛选能力
oak-general-business/src/components/userEntityGrant/list/index.ts 这组组件同样值得补出来,因为它不是简单把授权记录列出来。当前源码里的关键参数是:
entityentityIdrelationEntityrelationEntityFilter
组件会直接按这四个条件过滤 userEntityGrant,并默认按 $$createAt$$ desc 排序。也就是说,它更适合挂在“某个业务对象自己的授权记录列表”里,而不是全局授权台账。
从 web 端真实行为看,它还已经内置了两类非常常用的管理动作:
disable:如果当前行 legal action 里有disable,就直接把授权置失效二维码:弹出一个Modal,里面直接挂UserEntityGrantShare
所以项目层如果只是想给后台加一个“看历史分享、让某条分享失效、重新看二维码”的页,通常不需要自己再包一层复杂逻辑,直接用这组组件就够了。
userEntityGrant/upsert 的真实职责
userEntityGrant/upsert 不是一个通用大表单,它当前更像“生成一次分享授权”的专用入口。源码里最关键的输入参数有:
entityentityIdrelationEntityrelationEntityFilterrelationIdstyperedirectToAfterConfirmclaimUrlqrCodeTypemultipleruleruleOnRow
它在 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.tssrc/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/upsert、share、claim - 如果你本来就在做用户关系管理,更推荐直接接
src/components/userRelation/upsert/byUserEntityGrant
而 byUserEntityGrant 这条接法里,真正决定分享行为的关键输入有:
entity/entityIdrelationsredirectToAfterConfirmclaimUrlqrCodeTypemultiplerule
这样分享页、二维码页、认领页、过期失效逻辑会一起工作。
如果按组件职责来落页,更推荐这样拆:
- 关系管理页或对象详情页里挂
userEntityGrant/upsert - 历史分享记录页挂
userEntityGrant/list - 分享成功弹窗或分享海报区直接挂
userEntityGrant/share - 真正的领取页单独挂
userEntityGrant/claim
这样“生成授权”和“消费授权”会天然分层,不会在一个页面里把创建、二维码展示、认领三件事搅在一起。
真实项目里的入口组织
haina-busi 和 taicang 的做法都很接近:
- 后台或管理页里的关系维护组件,通常会把
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。