Parasite 寄生登录
Parasite 是 oak-general-business 里一个很特别的设计。它不是普通的用户,也不是普通的 token,而是一种“先寄生在某个对象或流程上,后续再被激活成正式登录态”的中间身份。
这个能力通常出现在这些场景里:
- 用户还没正式注册,但已经开始参与流程;
- 需要先发一个临时访问入口给用户;
- 需要在某个对象上生成一次短期、可回收的临时身份。
主要对象
这一章的核心实体是 Parasite。
它定义了:
- 临时身份属于哪个用户;
- 作用在哪个
entity/entityId上; - 过期时间和是否允许重复使用;
- 唤醒后应该跳到哪个页面;
- 关联生成出来的 token。
从对象结构就能看出来,它更像一个“临时登录入口”而不是“长期账号”。
组件
现成组件主要有:
src/components/parasite/detailsrc/components/parasite/excesssrc/components/parasite/listsrc/components/parasite/upsert
其中 detail 组件会在 web 环境下直接生成寄生访问链接,excess 则更像实际落地页。
parasite/upsert 常用参数
这组组件是项目里最常直接包起来用的创建入口,关键参数有:
entityentityIdrelationredirectTomultiplenameLabelnameRequired
它当前的真实流程是:
- 先按昵称前缀搜索
shadow用户 - 如果选中了现有
shadow用户,就直接复用userId - 如果没选中用户,就创建一个新的
shadow用户,并自动补一条userRelation - 把
expiresAt和tokenLifeLength都设成“有效期天数换算后的毫秒数” - 创建成功后直接切换到
parasite/detail展示二维码和链接
也就是说,这个组件不是“只创建 parasite 记录”,而是已经把:
- 找人
- 补影子用户
- 建关系
- 生成分享入口
这一整段流程串起来了。
parasite/list 适合放在哪里
它的关键参数是:
entityentityIdnameLabel
真实行为则是:
- 只看当前
entity + entityId下的 parasite - 默认按创建时间倒序
- 表格操作里直接暴露
cancel和qrcode - 点“详情”时会在弹窗里包
parasite/detail
所以这组组件最适合放在:
- 某个业务对象的后台管理页
- 某条邀请关系的分享记录页
- 某个领取流程的二维码管理页
parasite/detail 的可调参数
除了自动生成链接,它还暴露了几组很实用的展示参数:
disableDownloadsizedisabledcolorbgColor
源码里它的真实链接生成方式也值得直接写进文档:
- web 环境下按
window.location.protocol + hostname + port - 自动拼
/parasite/excess?oakId=${parasite.id}
这意味着项目层如果部署域名已经稳定,parasite/detail 生成的链接就可以直接拿去复制、发二维码、放海报。
parasite/excess 的真实职责
这个组件真正干的是“消费寄生入口”,不是单纯展示页面。它进入后会:
- 先按
oakId查 parasite - 非法就标记
illegal - 过期就标记
expired - 先执行
features.token.removeToken() - 再执行
features.token.wakeupParasite(parasite.id!) - 最后按
redirectTo跳回业务页
而且它在跳转时还会额外把:
nameparasiteId
一起塞进路由参数。
所以如果项目层想在目标页感知“这是寄生入口进来的”,完全可以直接读 parasiteId。
前端入口与 aspect
这一章没有单独的 parasite feature,但它并不是没有前端入口。
真正的唤醒入口在:
src/aspects/token.ts的wakeupParasitefeatures.token.wakeupParasite(...)
也就是说,Parasite 的激活最终仍然走的是 token 体系,而不是自己另起一套登录机制。
后台规则
Parasite 的默认规则主要在:
src/checkers/parasite.tssrc/triggers/parasite.ts
默认行为包括:
- 创建时强制检查
expiresAt和tokenLifeLength不能为空; - 如果是挂到已有
userId上,对应用户必须还处于shadow状态; - 过期时,使关联 token 自动失效;
- 执行
cancel时,也同步使关联 token 失效。
而在 src/aspects/token.ts 的 wakeupParasite(...) 里,还会继续做两层限制:
- 已经过期的
parasite不允许再唤醒; - 只有
shadow用户才能被借用身份唤醒。
真正唤醒成功后,创建出来的 token 也不是长期有效的,它会按 tokenLifeLength 计算 disablesAt。
此外,src/triggers/user.ts 里还有一条很重要的规则:当用户被正式激活后,会把相关的 parasite 作废。
另外还有一个直接影响业务设计的点:
- 如果
multiple=false,wakeupParasite(...)会在创建 token 前先把当前 parasite 标记成失效
这就是一次性寄生入口的真实落地方式。
这说明寄生模式本质上是一段过渡态,而不是长期身份模型。
注入点
这一章的注入点分成两部分:
- 后端规则通过
ogb0Triggers、ogb0Checkers注入; - 前端激活入口通过
features.token暴露。
所以项目层通常不需要自己再做一次“寄生态转正式态”的底层逻辑。
项目中如何接入
Parasite 在项目里通常会拆成两端:
- 管理端或后台页面,负责创建/查看寄生记录;
- 消费端页面,负责拿到
oakId后调用features.token.wakeupParasite(...)激活寄生 token。
公共包里已经把这两端组件都写好了:
src/components/parasite/listsrc/components/parasite/detailsrc/components/parasite/upsertsrc/components/parasite/excess
所以项目层真正要做的,通常只是把路由接出来。
而且 redirectTo 本身就是实体字段,项目层通常只要在创建时配好:
pathnamepropsstate
激活成功后,公共组件就会按这组配置跳回业务页。
当前项目里的实际情况
从这次对 haina-busi 和 taicang 的源码检索来看,当前没有看到它们各自落了独立的 parasite 页面壳,更多还是保留了实体、i18n 和公共能力本身。
这说明一件事:
Parasite当前更像一组随时可接入的公共基础能力- 真正用不用、落在哪个业务对象上,取决于项目自己有没有邀请/临时访问/借用身份的场景
也就是说,新项目接这章时,不需要去找“现成业务页面”,而是应该按自己的业务流程把公共组件挂出来。
推荐的页面拆法
最稳的接法通常是:
- 后台对象详情页挂
parasite/list - 新建弹窗或侧边抽屉挂
parasite/upsert - 分享详情弹窗直接复用
parasite/detail /parasite/excess路由单独包parasite/excess
这样:
- 管理端负责生成入口
- 消费端负责激活入口
职责会很清晰。
使用示例
1. 生成寄生链接
src/components/parasite/detail/index.ts 会直接把寄生链接组装成:
/parasite/excess?oakId=<parasiteId>
因此项目里最常见的做法,就是在管理台展示这个链接或二维码,让目标用户去消费它。
1.1 创建寄生入口时常用的传参方式
项目层最常见的写法通常像这样:
<ParasiteUpsert
oakPath="$parasite-upsert"
entity="yourEntity"
entityId={entityId}
relation="viewer"
redirectTo={{
pathname: '/frontend/yourPage/detail',
props: { oakId: entityId },
}}
multiple={false}
nameLabel="访问者名称"
/>
这里最关键的其实不是 UI,而是:
relation要能在当前对象上找到redirectTo要指向项目里真实存在的页面
2. 在消费页激活寄生 token
src/components/parasite/excess/index.ts 的核心逻辑就是:
const { data: [parasite] } = await this.features.cache.refresh('parasite', {
data: {
id: 1,
expired: 1,
redirectTo: 1,
user: { id: 1, nickname: 1 },
},
filter: { id: oakId },
});
if (!parasite?.expired) {
this.features.token.removeToken();
await this.features.token.wakeupParasite(parasite.id!);
}
公共包里也是先移除当前 token,再唤醒寄生 token,最后按 redirectTo 跳转,这就是项目侧最标准的接入方式。
使用建议
对新手来说,最重要的一点是不要把 Parasite 当成“另一种用户表”。
更准确的理解是:
User代表正式用户;Token代表正式登录态;Parasite代表一段可以被唤醒或回收的临时身份流程。
再补四条开发时必须注意的细节:
- 被复用或新建出来的用户必须是
shadow,否则 checker 和wakeupParasite(...)都会直接拒绝。 redirectTo.pathname最好始终写项目真实路由,不要把跳转逻辑散落在消费页里硬编码。- 如果希望一个分享入口只能用一次,就把
multiple设成false。 - 一旦用户被正式
activate,关联 parasite 会被用户 trigger 自动作废,所以不要把 parasite 当成长期邀请链接。
只要这样分清楚,在设计邀请、领取、临时访问这类功能时,就会顺很多。