我在为 CabloyJS 全栈框架开发 SMS 套件(通用底座 + 验证码 + 认证)时,输入的提示词和 Claude Code 返回的开发方案非常具有典型性。现在整理出来,供大家参考。
提示词如下:
有如下开发任务,请你仔细分析,并整理出合理的方案:
- 目标:先实现 SMS 系统底座,然后基于 SMS 系统底座实现多个业务能力。
- 系统底座:SMS provider / clients / scene 。
- 业务能力:
- sms captcha:短信验证码。
- sms auth:用户注册与登录。
- 套件 / 模块结构:
- 套件:cabloy-sms 。
- 模块:
- cabloy-sms:提供 SMS 系统底座。
- sms-captcha:短信验证码。
- sms-auth:用户注册与登录。
- sms-mock:提供 Mock SMS Provider ,在控制台打印验证码,并把验证码发送给前端,让前端将验证码直接渲染到输入框中。
Claude Code 返回的“开发方案”如下:
一、总体建议
建议保留你提出的 cabloy-sms 套件 + 四个模块,采用以下分层:
- cabloy-sms:短信传输底座,解决“通过哪个供应商账号、使用哪个模板发送短信”。
- sms-captcha:短信验证能力,解决“验证码发给谁、用于什么操作、如何安全验证”。
- sms-auth:手机号认证能力,解决“验证成功后登录哪个账号,是否创建新账号”。
- sms-mock:开发和测试用短信 Provider ,替代真实短信传输,但不替代验证码校验及认证流程。
最重要的设计原则是:
底座不依赖验证码,验证码不依赖用户认证,Mock 不改变真实业务流程。
当前仓库为 Cabloy Basic 。结合现有源码,建议复用 Vona 的 provider/client/scene 扩展方式、a-captcha 验证码体系和 a-auth 认证链,不另建短信验证码缓存体系或会话系统。
不过,现有 a-captcha 尚不具备手机号绑定和原子消费等短信登录必需的保障,需要做一组有界、向后兼容的扩展。
二、套件与模块结构
你给出的名称符合当前命名规则,可以直接保留。
| 层级 | 名称 | 说明 |
|---|---|---|
| Suite | cabloy-sms | providerId=cabloy ,suiteName=sms |
| Module | cabloy-sms | 通用短信底座 |
| Module | sms-captcha | 短信验证码 |
| Module | sms-auth | 短信注册与登录 |
| Module | sms-mock | Mock 短信 Provider |
Suite 与其中一个 Module 同名没有问题,完整包名分别是 vona-suite-cabloy-sms 、vona-module-cabloy-sms ,不会混淆。
后端模块统一放在:
vona/src/suite/cabloy-sms/modules/
前端有实际代码的对应模块放在:
zova/src/suite/cabloy-sms/modules/
不要求四个模块都建立空的前端对应模块。 首期前端主要需要 sms-captcha 的验证码交互和 sms-auth 的登录表单; sms-mock 的回填不必另造一套前端 Mock 系统。
依赖方向
- sms-captcha 必须依赖短信底座及现有验证码体系。
- sms-auth 必须依赖短信验证码及现有认证体系。
- sms-mock 只需依赖短信底座。
- 底座、验证码、认证模块都不能反向强依赖 sms-mock 。
Mock 是否参与运行,应由应用组合和服务端配置决定,而不是业务模块默认强制启用。
三、cabloy-sms:provider / clients / scene 如何划分
1. Provider:供应商协议实现
Provider 表示发送协议的实现,例如:
- sms-mock:mock
- 将来的阿里云短信 Provider
- 将来的腾讯云短信 Provider
Provider 负责:
- 将统一发送请求转换为供应商请求。
- 调用 SDK 或 HTTP API 。
- 将供应商结果转换为统一结果。
- 隐藏供应商错误和敏感信息。
Provider 不负责生成验证码、判断注册登录、查找用户。
建议建立 smsProvider 和 smsScene 两类扩展,沿用现有 decorator 、类型注册、onion metadata 和 CLI 模板机制,而不是自建独立插件管理器。
现有参考:
vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payProvider.ts:6
2. Client:某个 Provider 下的命名账号配置
Client 不是新的业务服务,也不必单独成为 bean scene ,而是 Provider 下的命名配置。
例如,同一个真实 Provider 可以有:
- default:默认账号。
- marketing:另一供应商账号或专用发送配置。
Client 保存:
- 凭据。
- 区域、接入地址。
- 必要的供应商默认选项。
- 是否启用等配置。
建议采用 Provider 下 base + clients 的方式,保留 Provider 与 Client 的类型关联。
不建议首期建立供应商账号管理表、凭据管理后台或持久化 Client 注册中心;配置驱动足以支持当前消费者。
3. Scene:业务发送场景
Scene 表示稳定的业务发送意图,例如:
- sms-auth:signin
- 将来的订单通知、配送通知。
Scene 配置:
- 使用哪个 Provider 、Client 。
- 短信签名、供应商模板。
- 模板参数约束。
- 必要的服务端路由规则。
服务端调用语义可以设计为:
// 拟议接口,非当前已有 API
await this.bean.sms.send({
scene: 'sms-auth:signin',
recipient: normalizedPhone,
params: {
code,
minutes: 5,
},
});
调用方传业务参数,底座解析传输配置。公开请求不得直接指定 Provider 、Client 、模板 ID 、签名或凭据。
另外需要区分:
| 概念 | 作用 |
|---|---|
| SMS Scene | 选择传输渠道、账号、模板 |
| Captcha Scene | 界定验证码可以证明哪个业务操作 |
两者可以由同一业务模块声明和关联,但不能相互替代,也不需要再引入第三套“短信验证场景”注册体系。
4. 实例隔离与发送结果
Provider 和 Scene 都必须按当前 instance 解析有效配置,不能把首个请求的凭据、手机号或场景配置缓存到共享 bean 字段。
现有 onion 的动态配置合并会加入 instance 配置,直接读取静态 beanOptions.options 则不等价:
vona/src/suite-vendor/a-vona/modules/a-onion/src/service/onion_.ts:252
首期发送建议保持同步,明确区分:
- 供应商已接受。
- 明确拒绝。
- 超时等结果不确定。
“供应商接受”不等于“手机已收到”。 暂不做消息队列、投递回执、自动故障切换和自动重试,尤其不能在超时后盲目重发。
四、sms-captcha:复用验证码体系,但补齐短信语义
1. 已有能力与缺口
现有 a-captcha 已经提供:
- challenge ID 。
- Provider 、Scene 。
- Redis 存储及 TTL 。
- 验证 DTO 。
- 表单校验接入。
但当前缓存只有 scene/provider/token/token2 ,没有接收手机号;验证成功采用分开的读取、比较、删除操作,存在并发重复成功的可能。
依据:
vona/src/suite-vendor/a-captcha/modules/a-captcha/src/bean/bean.captcha.ts:19 、:79
因此,不能只新增一个“发送短信”的 CaptchaProvider 就认为短信验证完成。
2. 推荐的职责分配
a-captcha 增加通用、可选的能力:
- 创建时传入可信上下文。
- 保存验证绑定信息。
- 消费时核对服务端计算的预期绑定。
- 原子状态转换,保证单次消费。
- Provider/Scene 可声明受控签发、刷新和公开 token 策略。
- 去除通用验证码日志中的明文 token 。
sms-captcha 负责短信领域规则:
- 手机号规范化和格式校验。
- 允许的用途及场景映射。
- 安全随机验证码生成。
- 手机号、用途和必要主体的绑定。
- 发送冷却、配额、验证失败次数。
- 调用短信底座。
- 短信专用表单交互。
这样既不重复建设 OTP 系统,也不把手机号规则塞进通用验证码模块。
3. 推荐验证规则
首期建议:
| 项目 | 建议 |
|---|---|
| 验证码 | 6 位数字,按字符串处理,安全随机生成 |
| 有效期 | 5 分钟,可配置 |
| 重发冷却 | 60 秒,可配置 |
| 错误次数 | 明确上限,例如 5 次 |
| 时间规则 | 查询、输错均不延长原到期时间 |
| 重发 | 新 challenge 替换旧 challenge ,旧凭据失效 |
| 消费 | 正确验证码最多成功一次 |
| 绑定 | 当前 instance + 规范化手机号 + 用途;按业务增加主体绑定 |
| 防滥用 | 手机号冷却/配额 + IP 限流 + 验证尝试限制 |
手机号不能只作为发送参数。例如,发给手机号 A 的验证码,即使验证成功,也不能用于手机号 B 的登录。
首期可以继续使用现有 Redis 短期秘密存储,不为摘要存储额外建设一套缓存。但必须有短 TTL 、访问隔离、无明文日志和无持久化验证码历史;若后续要求摘要存储,需要明确拆分“存储校验材料”和“Mock 回填值”,当前接口并未天然支持这一点。
4. 必须关闭通用入口绕过
现有通用 captcha 的创建、刷新、即时验证接口都是公开入口:
vona/src/suite-vendor/a-captcha/modules/a-captcha/src/controller/captcha.ts:15
因此,安全规则必须落在共用服务边界,而不是只加在新短信 Controller 上。
建议首期明确:
- 普通 create(scene) 不允许无可信上下文创建短信验证码。
- 普通 refresh(id, scene) 不允许绕过短信冷却重发。
- 过期或缺失 ID 不得通过 refresh 自动回退成无约束发送。
- 短信场景暂不支持 verifyImmediate → token2 ,避免引入另一种可重放证明。
- 短信验证缺少预期手机号/用途绑定时一律失败。
五、sms-mock:模拟传输,不模拟业务成功
1. 正确的运行方式
Mock 应走与真实短信相同的链路:
- sms-captcha 生成验证码、建立绑定。
- 短信底座解析到 sms-mock Provider 。
- Mock 不访问外部供应商。
- 在开发控制台打印验证码,手机号脱敏。
- 验证码层在允许的条件下将验证码返回前端。
- 用户提交后,仍走正常过期、限次、绑定和单次消费验证。
不能用固定万能验证码,也不能让 Mock 登录直接返回成功。
2. 前端回填可以复用现有响应字段
已有 captcha 响应支持可选 token ,当前表单也会将其写入输入框:
zova/src/suite/cabloy-basic/modules/basic-captcha/src/component/formFieldCaptcha/controller.tsx:96
因此不必引入第二套验证码响应协议。
不过当前 showToken 默认在所有非生产环境开启,不能直接沿用到真实短信:
vona/src/suite-vendor/a-captcha/modules/a-captcha/src/config/config.ts:3
短信返码应同时满足:
- 本次实际使用的是 sms-mock 。
- 非生产环境。
- 显式开启开发回填配置。
真实短信即使运行在 dev/test ,也不能因为全局 showToken 而返回验证码。生产环境选择 Mock 应拒绝运行或发送,不能静默回退。
3. 前端必须有短信专用交互
现有图形验证码组件会初始化自动创建、提交失败自动刷新,并将 payload 渲染为图片,不能原样套用到付费短信。
短信组件应提供:
- 手机号输入。
- 显式“发送验证码”按钮。
- 按服务端时间展示冷却。
- Mock 成功后回填。
- 手机号修改后清除 challenge 和验证码。
- 发送失败不自动重试。
- 页面初始化和普通表单错误不自动发送。
验证码只保存在当前交互状态,不进入 localStorage 、SSR HTML 或持久化用户状态。
六、sms-auth:复用认证链,建立可信手机号身份
1. 推荐产品语义
建议首期采用:
手机号验证成功后登录;该短信身份不存在时自动注册。
它能用一个表单同时完成注册与登录,符合现有通用认证链的基本行为。但这是产品选择,仍需确认;如果要求“注册”和“登录”严格分离,就需要明确已有身份和不存在身份各自的处理规则。
2. 认证链路
新增非 OAuth 的 SMS AuthProvider ,复用:
验证并消费 OTP → 获取或创建身份与用户 → 现有 Passport signin
现有框架已经支持 provider.verify → issuePassport → passport.signin:
vona/src/suite-vendor/a-auth/modules/a-auth/src/service/auth.ts:27
关键要求:
- OTP 消费必须发生在 Provider 的验证边界,不能只放在新增 Controller 。
- Provider 、Client 、intention 由服务端控制。
- 不把请求整体合并为认证配置。
- 不自行生成另一套 JWT 、Cookie 或会话存储。
3. 不按旧 mobile 字段自动认领账号
现有 homeUser.mobile 只是可选字符串,没有“已验证手机号”的语义:
vona/src/suite/a-home/modules/home-user/src/entity/user.ts:24
建议使用已有认证身份映射:
SMS 身份命名空间 + 规范化手机号 → auth 映射 → userId
由此得到:
- 已有可信 SMS 映射:登录其关联用户。
- 没有映射:按确认的策略注册或拒绝。
- 旧账号的 mobile 相同:不能自动关联、合并或接管。
- 已有映射却缺失用户:报错,不静默重建账号。
首期不做手机号绑定、换绑、找回、账号合并,并在 Provider 内明确拒绝 associate/migrate ,避免通用入口触发这些操作。
4. 独立处理首次注册并发
同一验证码原子消费,不能替代身份唯一性保障:同一手机号可能持有两个独立的有效验证请求。
需要按当前 instance 和短信身份串行化,事务内重新查询并原子完成:
- 用户创建。
- 正常角色关系。
- auth 映射。
- 必要的 mobile 资料投影。
事务提交后,再进入既有 signin 。不能仅靠事务或不存在行的锁来防止重复创建,也不应添加跨租户业务唯一约束。
首期优先复用已有身份映射,不新增身份表。若确需新增 mobileVerifiedAt 等持久化字段,实施前再确认所属模块的 fileVersion 策略。
5. 前端登录接入
- sms-auth 提供短信登录表单及业务封装。
- 现有 Home Login 页面做最薄的入口接入。
- 成功后复用 Passport 的登录后处理,不复制 token 写入和 returnTo 逻辑。
- Web/Admin 共享认证身份,不因页面或短信供应商 Client 不同而重复创建用户。
- 新用户沿用明确的普通用户角色策略;“短信注册成功”不等于授予管理权限。
七、推荐交付顺序与验收
| 阶段 | 交付内容 | 退出条件 |
|---|---|---|
| 1. 底座 + Mock | Provider/Client/Scene 、发送门面、Mock | 普通模板短信可通过 Mock 发送;实例配置隔离;失败语义明确 |
| 2. 短信验证码 | captcha 必需扩展、签发/重发、防滥用、短信组件 | 回填可用;手机号/用途绑定;原子消费;通用入口不可绕过 |
| 3. 短信注册登录 | SMS AuthProvider 、身份创建边界、登录入口 | 可注册及再次登录;不认领旧 mobile ;并发不重复开户;复用会话 |
| 4. 首个真实 Provider | 按选定供应商接入 | Mock 与真实传输遵守相同契约;真实短信不返码、不泄露日志 |
虽然 Mock 在模块分工中是第四项,实施上应与底座一起完成,让验证码和认证从一开始就有可运行的消费者链路。
关键验收不必铺成庞大矩阵,但必须覆盖:
- 跨手机号、跨用途、跨 instance 、过期、重放验证失败。
- 同一 challenge 并发消费最多一次成功。
- 两个独立有效 OTP 并发首登,最终只有一个用户及身份映射。
- 通用 captcha 和认证入口不能绕过安全规则。
- 手机号变化清空旧证明。
- 真实短信在任何环境均不公开验证码。
- Mock 仅在明确开发门禁下打印及回填。
- disabled 用户不能登录,旧密码/GitHub 登录不回归。
- 发送超时不会触发盲目重试。
八、首期范围与待确认事项
建议首期不包含:供应商管理后台、发送历史后台、营销群发、队列、回执、自动重试/切换、手机号换绑、找回和账号合并。
需要集中确认的产品决策只有三组:
- 注册语义:是否采用“短信登录即自动注册”?推荐采用。
- 号码范围:首期仅中国大陆,还是支持国际号码?无论哪种,都应明确区号并统一规范化,不能默认对所有输入补 +86 。
- 投放范围:短信登录先开放 Web ,还是同时开放 Admin ;首期是否只交付 Mock 闭环,暂不接真实供应商。
名称已经合理,无需另行改名;如有品牌或命名偏好,也可以继续按同一规则调整。
确认后,先通过现有 CLI 创建底座及 Mock ,而不是手工搭目录,例如:
npm run vona :create:suite cabloy-sms
npm run vona :create:module cabloy-sms -- --suite=cabloy-sms
npm run vona :create:module sms-mock -- --suite=cabloy-sms
之后逐阶段生成其余模块、执行聚焦后端测试和关键流程 E2E 。后端契约变化先生成前端消费者;涉及前端反向依赖时,先构建受影响的 Web/Admin ,再运行 npm run deps:vona 。
本轮仅完成分析,没有修改文件。CLI 命令源码已确认存在,但实际执行 npm run vona 、npm run zova 均因缺少 @cabloy/cli 失败;当前 Node 为 v24.8.0 ,低于仓库要求的 >=24.11.0 。实施前需准备运行时与依赖,本轮未安装依赖、构建或运行测试。
结语
Claude Code 要求我确认三组产品决策:注册语义、号码范围和投放范围,再据此制定进一步的“开发方案”。限于篇幅,本文先整理到这里。如果想看后续,欢迎留言,我会继续整理。