人才解锁与配额管控
在 YouTeacher 的 profile(档案) 服务里,教师(talent)档案可被检索,但联系方式是被"锁住"的。招聘方的查看者——已认证的雇主(employer)或已批准的招聘顾问(recruiter)——需要消耗解锁配额(unlock quota),才能揭示某位教师的联系方式。本节讲的就是这道门:判定一次解锁是否被允许的领域规则,以及记录这次解锁的流程。
三条领域规则
判定逻辑集中在一个领域服务 UnlockService 里。它按固定顺序校验查看者与教师,返回第一条不满足的规则(以带错误码的结果返回,而非抛异常)。两类查看者走的是并行的检查:
- 资格 —— 雇主必须已认证,招聘顾问必须已批准。两者皆非,则谁也解锁不了(
employer_not_verified/recruiter_not_approved)。 - 可见性 —— 教师必须对该受众可见。雇主对照的是"对学校可见",招聘顾问对照的是"对招聘顾问可见"。对其隐藏的教师无法被解锁(
talent_not_visible)。 - 配额 —— 查看者必须仍有剩余解锁配额。用尽即拒绝(
quota_exceeded)。
只有三条全部通过,服务才报告 canUnlock: true。
作为值对象的配额
配额被建模为一个不可变值对象,持有 total(总额)与 used(已用)两个计数,remaining(剩余)由 total − used 推导得出——从不单独存储,保持单一真相来源。它的不变式在构造时强制:两个计数都不得为负,且 used 永远不得超过 total。消耗配额不会修改原对象,而是返回一个 used 加一的新配额对象;无余量可用时则拒绝消耗。另有一个调整操作,可重设总额或已用数。因为 remaining 始终是算出来的,这个对象永远不会漂移到不一致的"剩余"值上。
解锁流程
雇主与招聘顾问的应用层处理器几乎一样。每个 execute(userId, talentId) 都走同一序列:
- 用 user id 解析出查看者档案;没有则以 not-found 失败。
- 用 id 解析出教师;没有则 not-found。
- 幂等检查 —— 查这对(教师、查看者、查看者类型)是否已有解锁记录。若已存在,原样返回。同一位教师绝不会被重复扣费。
- 跑三条领域规则。失败则以 forbidden 响应返回,带上该规则的说明。
- 扣减该查看者的配额。
- 记录这次解锁并返回。
一条解锁记录是一个很小的事实:一个 id、教师 id、查看者 id、查看者类型(雇主或招聘顾问),以及一个时间戳。
持久化与并发安全
解锁记录存在自己的表里,以(教师、查看者、查看者类型)三元组作为唯一键。写入使用针对该唯一键的 upsert,因此同一对的两个并发解锁请求会收敛到同一条记录,而非产生重复。仓储还支持把一个查看者的解锁记录迁移给另一个查看者——迁移前会跳过目标方已持有的那些,再从源方删除。
一些说明与接缝
- 幂等检查加上配额扣减,意味着重复解锁一位已揭示的教师是免费的;配额只在首次揭示时消耗一次。
- 在处理器里,配额扣减与解锁记录写入是两个独立的持久化步骤;upsert 保护记录本身不重复,幂等检查则防止重复扣费。
- 资格(认证 / 批准)在上游决定——profile 服务在应用规则时只读取查看者当前的状态。
本组件内
- dual-auth-service-jwt-user-session —— 一次解锁请求如何被认证为真实用户
- architecture-ddd-cqrs-wiring —— 领域服务、处理器与 Prisma 仓储如何接线