上一篇中,我整理了为 CabloyJS 全栈框架开发 SMS 套件(通用底座 + 验证码 + 认证)时,输入的提示词和 Claude Code 返回的开发方案。在方案结尾,Claude Code 要求我确认三组产品决策:注册语义、号码范围和投放范围。
其中,"短信登录即自动注册"让我有些疑问:如果用户不需要填写用户名,那么 userName 应该怎么确定?我把这个问题和另外两组决策一起补充给 Claude Code,让它进一步调整方案。现在继续整理出来,供大家参考。
提示词如下:
- 注册语义:是否采用"短信登录即自动注册"?存疑。如果是自动注册,那么
userName怎么确定?- 号码范围:首期仅中国大陆,还是支持国际号码?支持国际号码。
- 投放范围:
- 短信登录同时开放 Web/Admin。
- 首期交付 Mock 闭环,同时接真实供应商:
- 中国大陆:阿里云 / 腾讯云。
- 国际号码:请推荐 1 到 2 家最流行的服务。
Claude Code 返回的"开发方案"如下:
这几项确认后,方案可以进一步收敛。先回答用户名问题:自动注册并非不能实现,但当前账号模型下,显式注册更自然。我建议首期调整为"短信注册与短信登录分离",不默认自动注册。
另外,国际短信服务推荐 Twilio、Vonage;首期建议接入 Twilio,将 Vonage 作为第二选择,而不是两家都必须实现。
一、注册语义:建议显式注册,由用户确定 userName
1. 为什么不直接自动生成用户名?
进一步核实源码后,当前 user.name 不只是一个内部技术标识:
- 注册时从
profile.username写入。 - 用于用户展示。
- 也是现有密码登录的用户名查找字段。
- 当前资料编辑还允许修改它。
因此,不能简单把它解释为"自动生成、永不变化的内部 ID"。当前也没有一个独立持久化的 nickname 字段来承担展示名称。
对应源码:
- 用户资料映射:
vona/src/suite/a-home/modules/home-user/src/service/userAdapter.ts:13 - 用户注册:
vona/src/suite-vendor/a-vona/modules/a-user/src/bean/bean.user.ts:27 - 密码登录查找:
vona/src/suite-vendor/a-auth/modules/auth-simple/src/bean/authProvider.simple.ts:58
自动注册当然可以生成类似 sms_<随机标识> 的用户名,但会带来后续产品问题:是否允许修改、展示什么、以后设置密码时使用哪个名字等。为实现短信登录,首期不必同时改造整个账号命名模型。
2. 推荐的注册与登录契约
| 操作 | 输入 | 行为 |
|---|---|---|
| 短信注册 | 国际手机号、用户名、短信验证码 | 验证手机号,创建用户及短信身份映射,成功后登录 |
| 短信登录 | 国际手机号、短信验证码 | 只登录已存在的短信身份,不创建新用户 |
注册首期不强制要求密码或邮箱,也不创建虚假的密码凭据。
用户名规则建议:
- 沿用并补齐现有用户名校验。
- 去除首尾空白,按统一的大小写等价规则检查。
- 在当前 instance 内唯一。
- 禁止 admin 等系统保留名称。
- 名称被占用时返回字段错误,不静默追加随机后缀。
- 并发注册同一手机号、同一用户名时,仍必须保证最终唯一。
这里还需注意:当前资料编辑中的"Display name"实际上会修改同一个 user.name,但没有与注册一致的查重规则。实施时应让注册与改名共享必要的用户名约束,否则注册时保证唯一、改名时却可以制造冲突。
3. 手机号身份与用户名分开
建议保持三个概念独立:
| 字段/概念 | 用途 |
|---|---|
user.id |
内部用户标识 |
user.name |
用户选择的账号名称 |
| SMS 身份映射 | 已验证的规范化手机号与 userId 的关联 |
因此:
- 用户改名,不影响手机号登录。
- 手机号不能直接作为默认用户名,避免在用户展示位置暴露完整号码。
- 注册时用户名与旧账号相同,不自动关联。
- 旧账号 mobile 与本次手机号相同,也不自动认领。
还需要显式关闭现有认证链的自动注册行为。 当前框架对 login 和 register 都可能创建用户,不能仅传入 intention: 'login' 就认为变成了"仅登录"。
对于不存在的短信身份,应在验证手机号归属后提示注册;发送验证码接口本身不公开账号是否存在。
二、号码范围:支持国际号码,统一为 E.164
建议前后端采用明确的国际号码契约:
- 前端提供国家/地区选择器。
- 服务端使用成熟号码解析库,例如基于 libphonenumber 的实现。
- 最终统一保存、比较和发送为 E.164,例如 +86138...、+447...。
- 发送与验证使用同一套规范化逻辑。
- 不依赖简单正则判断所有国家号码。
- 不通过界面语言、浏览器地区或 IP 猜测接收号码国家。
路由规则
| 接收目的地 | 默认发送渠道 |
|---|---|
| 中国大陆 | 阿里云或腾讯云 |
| 港澳台及其他国际目的地 | Twilio,或配置选择 Vonage |
| 显式开发/测试配置 | sms-mock |
不能把港澳台归入中国大陆短信渠道,也不能根据页面语言选择供应商。
"支持国际号码"不等于"默认开放全球发送"。 建议按国家/地区配置允许发送范围、配额和预算限制。不同目的地可能有 Sender ID、号码注册、模板或用途限制,需要逐个开通与验收。
三、国际服务商:推荐 Twilio 和 Vonage
这两家都是成熟、广泛使用的国际通信 API 服务商。这里是工程选型推荐,不声称它们存在可核实的全球市场份额第一、第二排名。
| 服务商 | 推荐定位 | 接入方式 |
|---|---|---|
| Twilio | 首期国际短信首选 | Programmable Messaging |
| Vonage,原 Nexmo | 第二选择或后续替代渠道 | SMS API |
1. Twilio:建议首期接入
适合当前架构的原因:
- 提供通用短信发送 API。
- 使用 E.164 接收号码。
- 支持通过发送号码或 Messaging Service 配置发送方。
- 有较完整的目的地权限及国家合规文档。
2. Vonage:适合作为第二选择
Vonage SMS API 提供通用的接收方、发送方、正文发送能力,适合实现另一个遵守相同契约的 SMS Provider。
最终在具体目标国家如何选择,仍应看:
- 实际可开通的发送身份。
- 到达率和时延测试。
- 单价与短信分段计费。
- 账号主体和当地合规要求。
3. 不选择托管 Verify 产品作为本次默认方案
Twilio 和 Vonage 都有托管验证码产品,但本次建议接入:
- Twilio Programmable Messaging
- Vonage SMS API
而不是直接使用它们的 Verify 产品。
因为本次已经计划由 sms-captcha 统一管理:
- 验证码生成。
- 有效期。
- 手机号及用途绑定。
- 尝试次数。
- 单次消费。
供应商只负责传输,才能保证 Mock、阿里云、腾讯云和国际 Provider 使用同一套验证语义。
四、底座需要兼容两类供应商发送模式
新增国际供应商后,不能把统一 SMS 接口设计成"必须传供应商模板 ID",因为各家的接口并不相同:
| Provider | 典型发送方式 |
|---|---|
| 阿里云 | 签名 + 模板编号 + 命名参数 JSON |
| 腾讯云 | 签名 + 模板编号 + 有序参数数组 |
| Twilio | 发送方/Messaging Service + 短信正文 |
| Vonage | 发送方 + 短信正文 |
因此建议保持业务调用统一:
ts
// 拟议接口
await this.bean.sms.send({
scene: 'sms-auth:signup',
recipient: normalizedPhone,
params: {
code,
minutes: 5,
},
});
由服务端 Scene 配置和 Provider 适配完成差异转换:
- 阿里云:
{ code, minutes }转为模板参数 JSON。 - 腾讯云:按明确配置的参数顺序转换为数组。
- Twilio/Vonage:使用服务端维护的本地化文本模板生成正文。
参数顺序不能依赖对象枚举;短信正文、供应商模板和发送方也不能由公开请求自由传入。
同时区分:
- 接收号码地区:决定合规及可用渠道。
- 短信内容语言:由业务语言策略决定。
- 供应商账号区域:由 Client 配置决定。
三者不能混成一个 region 字段。
五、模块结构:保留四个核心模块,真实 Provider 独立扩展
既然首期明确包含多家真实供应商,建议保留原有四模块,并为供应商分别建模块:
| 模块 | 首期建议 |
|---|---|
| cabloy-sms | 必需:传输底座 |
| sms-captcha | 必需:验证码 |
| sms-auth | 必需:注册登录 |
| sms-mock | 必需:开发测试 |
| sms-aliyun | 必需:中国大陆 Provider |
| sms-tencent | 必需:中国大陆 Provider |
| sms-twilio | 推荐纳入首期:国际 Provider |
| sms-vonage | 备选;需要两家国际渠道时再纳入 |
这些模块都归属 cabloy-sms 套件。独立模块可以隔离 SDK、凭据类型和供应商变化,不让所有使用者都加载所有 SDK。
如果希望严格保持只有四个模块,也可以把真实 Provider 放进 cabloy-sms,但既然现在已经确定存在多个真实供应商消费者,独立 Provider 模块更合适。
默认路由只选择一家发送。例如:
- 中国大陆默认阿里云,可配置切换为腾讯云。
- 国际默认 Twilio。
- 不同时向多个 Provider 发送。
- 超时结果不确定时,不自动换另一家重发。
六、Web/Admin 同时开放的含义
可以同时提供短信注册、登录入口,并共享同一套:
- 用户。
- SMS 身份映射。
- Provider 配置机制。
- Passport 与会话处理。
不能因从 Web 或 Admin 注册而生成两套同手机号用户。
但"登录入口开放"与"业务权限开放"必须分开:
- 短信注册只获得普通注册用户权限。
- 不因从 Admin 页面注册就赋予管理权限。
七、更新后的首期交付建议
首期范围建议定为:
- 底座 + Mock:先跑通统一发送契约。
- 阿里云、腾讯云、Twilio Provider:接入统一契约。
- 短信验证码:国际号码、用途绑定、限流冷却、单次消费和 Mock 回填。
- 显式短信注册 + 仅已有身份短信登录。
- Web/Admin 入口和权限回归验证。
真实 Provider 的验收需要区分:
- 代码接入完成:请求映射、响应解析、错误处理已验证。
- 真实短信验收完成:已批准账号、签名/模板/发送身份,向授权测试号码实际发送并确认收到。
前者不能代替后者,也不能以 Mock 闭环代替真实供应商验收。
我的最终建议是:显式注册确定 userName;国际号码统一 E.164;首期接阿里云、腾讯云、Twilio,Vonage 作为第二国际渠道备选。这能覆盖你明确的首期业务范围,同时避免把用户名体系改造、全球渠道开通和自动故障切换一起扩展进来。
Sources
- Twilio Messages API:接收号码、正文与发送方参数
- Twilio 国际短信目的地权限与合规限制
- Vonage SMS API
- 阿里云 SendSms API
- 腾讯云 SendSms API
本轮未修改代码。供应商资料依据官方文档搜索结果核对;页面全文抓取受网络限制,未据此承诺具体国家的可开通性、价格或实际到达率。
这一轮交流后,Claude Code 对注册语义、国际号码处理和真实供应商接入给出了更具体的建议,也明确了 Web/Admin 共用身份与权限控制的边界。限于篇幅,本文先整理到这里。如果想看后续实现过程,欢迎留言,我会继续整理。