11 KiB
11 KiB
sms_auth_event 表设计
日期:2026-04-21
1. 文档目的
这份文档用于完成 M2 的第六条任务:设计 sms_auth_event。
目标是把短信鉴权事件表固定成一张“短信发送与验证码校验的统计源表”,并明确:
- 哪些短信相关动作要写入事件
- 哪些动作不应该写进这张表
- 发送频控、失败次数限制、captcha 触发、风险保护分别依赖什么统计口径
- 它与
auth_risk_block、auth_audit_log的边界
2. 当前基线
当前 Node 后端已经存在 sms_auth_events 表,并有稳定的读写链路:
POST /api/auth/phone/send-code在短信发送成功后写入一条send_code事件POST /api/auth/phone/login在验证码校验成功或失败后写入verify_code事件POST /api/auth/wechat/bind-phone在绑定手机号时复用同一套verify_code事件POST /api/auth/phone/change在换绑手机号时复用同一套verify_code事件- 发送频控、失败次数限制、captcha 触发与风险保护,全部依赖这张表统计
当前 Node sms_auth_events 字段基线:
idphone_numbersceneactionsuccessipuser_agentcreated_at
当前已落地的 scene 基线:
loginbind_phonechange_phone
当前已落地的 action 基线:
send_codeverify_code
3. 表职责边界
3.1 sms_auth_event 负责
- 记录短信验证码发送成功事件
- 记录短信验证码校验成功或失败事件
- 作为手机号维度与 IP 维度的短信鉴权统计源
- 为发送频控、失败次数限制、captcha 触发、风险保护提供统一统计基础
3.2 它不负责
- 保存验证码明文或验证码 hash
- 保存阿里云短信 provider 的完整原始响应
- 记录当前风险保护是否仍生效
- 记录账号安全动作的长期审计历史
3.3 与其他表的边界
sms_auth_event负责“发生过哪些短信发送/校验事件”auth_risk_block负责“经过统计后当前是否处于保护状态”auth_audit_log负责“账号维度发生过哪些安全动作”
4. 访问级别
sms_auth_event 固定为 private table。
原因:
- 原始手机号、原始 IP、原始 UA 都属于敏感安全数据
- 这张表主要服务于后端风控与统计,不直接对前端暴露
- 前端不应该直接查询或订阅短信验证码操作明细
5. 字段设计
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
sms_event_id |
String |
是 | 主键,建议继续沿用 smsev_* 前缀。 |
phone_e164 |
String |
是 | 标准化后的 E.164 手机号。 |
scene |
String |
是 | 业务场景,枚举固定为 login、bind_phone、change_phone。 |
action |
String |
是 | 动作类型,枚举固定为 send_code、verify_code。 |
success |
bool |
是 | 当前动作是否成功。 |
ip |
Option<String> |
否 | 请求来源 IP;缺失时为 null。 |
user_agent |
Option<String> |
否 | 请求来源 UA;缺失时为 null。 |
created_at |
String |
是 | 事件发生时间,UTC RFC3339。 |
补充约束:
- 当前阶段不存
provider_request_id,因为这张表不是供应商排障日志表。 - 当前阶段不存
provider_code、provider_message,避免把供应商响应日志职责压进统计源表。 - 当前阶段不存
user_id,因为验证码发送和校验发生时,场景并不总是已经稳定归属到某个正式账号。
6. 枚举与语义设计
6.1 scene
当前阶段固定只支持:
loginbind_phonechange_phone
解释:
login对应手机号验证码登录bind_phone对应微信待激活账号绑定手机号change_phone对应正式账号更换手机号
6.2 action
当前阶段固定只支持:
send_codeverify_code
解释:
send_code表示验证码已成功发出verify_code表示一次验证码校验尝试已经结束
6.3 success
固定语义:
send_code + success = true表示供应商确认发送成功verify_code + success = true表示验证码校验通过verify_code + success = false表示验证码校验失败或已失效
当前阶段特别约束:
send_code + success = false暂不入表- 发送失败继续由应用日志、供应商日志与 tracing 承担排障,不混入当前统计口径
这样做的原因是:
- 现有发送频控按
action = send_code的总数统计 - Node 当前只在发送成功后写事件
- 若直接把发送失败也写入,会改变当前频控语义
7. 统计口径设计
7.1 发送频控
固定统计来源:
- 按手机号统计
action = send_code的事件数 - 按 IP 统计
action = send_code的事件数
当前 Node 兼容窗口:
- 单手机号:
过去 1 天 - 单 IP:
过去 1 小时
7.2 验证失败次数限制
固定统计来源:
- 按手机号统计
action = verify_code AND success = false - 按 IP 统计
action = verify_code AND success = false
当前 Node 兼容窗口:
- 单手机号:
过去 1 小时 - 单 IP:
过去 1 小时
7.3 captcha 触发
固定统计来源:
- 按手机号统计
verify_code失败次数 - 按 IP 统计
verify_code失败次数
说明:
captcha challenge自身不写进sms_auth_eventcaptcha_required审计事件继续写进auth_audit_log
7.4 风险保护触发
固定统计来源:
- 按手机号统计
verify_code失败次数 - 按 IP 统计
verify_code失败次数
说明:
- 风险保护命中后真正的当前态写进
auth_risk_block sms_auth_event只提供统计基础,不直接承载保护状态
8. 写入规则
8.1 POST /api/auth/phone/send-code
固定流程:
- 先检查当前手机号 / 当前 IP 是否存在活跃
auth_risk_block - 再根据
sms_auth_event统计发送频控 - 再根据
sms_auth_event统计决定是否需要 captcha - 短信 provider 返回发送成功后,写入一条:
scene = 当前请求场景action = send_codesuccess = true
8.2 POST /api/auth/phone/login
固定流程:
- 先检查当前手机号 / 当前 IP 是否存在活跃
auth_risk_block - 再根据
sms_auth_event统计失败次数限制 - 校验成功时写入:
scene = loginaction = verify_codesuccess = true
- 校验失败时写入:
scene = loginaction = verify_codesuccess = false
- 校验失败写入完成后,再按统计结果决定是否触发
auth_risk_block
8.3 POST /api/auth/wechat/bind-phone
固定流程:
- 与手机号登录复用同一套校验前检查
- 校验成功写入
bind_phone + verify_code + success = true - 校验失败写入
bind_phone + verify_code + success = false - 校验失败后再决定是否触发
auth_risk_block
8.4 POST /api/auth/phone/change
固定流程:
- 与手机号登录复用同一套校验前检查
- 校验成功写入
change_phone + verify_code + success = true - 校验失败写入
change_phone + verify_code + success = false - 校验失败后再决定是否触发
auth_risk_block
9. 查询索引与统计要求
9.1 必须具备的唯一约束
sms_event_id主键唯一
9.2 必须具备的查询索引
(phone_e164, action, created_at DESC)作用:支撑按手机号统计发送次数与失败次数(ip, action, created_at DESC)作用:支撑按 IP 统计发送次数与失败次数(phone_e164, action, success, created_at DESC)作用:支撑按手机号统计verify_code失败窗口(ip, action, success, created_at DESC)作用:支撑按 IP 统计verify_code失败窗口
说明:
- 当前阶段不强求单独按
scene建主索引,因为已有统计主要按手机号/IP 与动作窗口展开。 scene继续作为事件上下文字段保留,便于后续如果要细分某一场景的频控,再追加索引。
10. 读取规则
当前阶段 sms_auth_event 不直接对外暴露 DTO。
它只支撑后端内部这几类聚合查询:
count_since_by_phone(phone_e164, action, success?, since)count_since_by_ip(ip, action, success?, since)
读取约束:
ip = null时,按 IP 统计固定返回0- 统计窗口由 Axum 应用层提供,不把“过去 1 小时”“过去 1 天”写死进表层
- 表层只提供原子计数,不在表层拼装“是否需要 captcha / 是否需要封禁”的业务判断
11. 与其他鉴权表的协作规则
11.1 与 auth_risk_block
固定规则:
sms_auth_event先累积失败事实- Axum 根据失败统计决定是否写入或刷新
auth_risk_block - 不能直接从
auth_risk_block反推历史失败次数
11.2 与 auth_audit_log
固定规则:
sms_auth_event不承担安全操作审计展示职责captcha_required、risk_block_phone、risk_block_ip等用户可见安全事件,继续写进auth_audit_log- 不允许为了省表而把
sms_auth_event直接拿去充当账号操作记录
12. 与当前 Node sms_auth_events 的映射关系
| Node 列 | 新字段 | 迁移规则 |
|---|---|---|
id |
sms_event_id |
原样迁移。 |
phone_number |
phone_e164 |
重命名迁移,值原样保留。 |
scene |
scene |
原样迁移。 |
action |
action |
原样迁移。 |
success |
success |
原样迁移。 |
ip |
ip |
原样迁移。 |
user_agent |
user_agent |
原样迁移。 |
created_at |
created_at |
原样迁移。 |
13. reducer / service 落地约束
13.1 module-auth reducer 层
必须至少具备:
append_sms_auth_event
说明:
- 这是一张追加型统计源表,不做就地更新。
- 后续若要做归档或清理,再单独增加 maintenance reducer 或离线清理任务。
13.2 Axum 应用层
固定负责:
- 标准化手机号为
E.164 - 组织
scene、action、success - 在正确的时机写入事件
- 基于统计结果决定是否触发 captcha 与
auth_risk_block
14. 不允许的设计漂移
后续实现时禁止出现以下情况:
- 把验证码明文、验证码 hash 写进
sms_auth_event - 把阿里云 provider 的完整响应 JSON 直接写进
sms_auth_event - 把当前是否被保护的状态写进
sms_auth_event - 发送失败也无条件入表,却继续沿用当前“按
send_code总量限流”的统计口径 - 为了展示账号安全记录,直接把
sms_auth_event暴露给前端
15. 本任务完成定义
当以下条件满足时,设计 sms_auth_event 视为完成:
- 发送与校验事件的写入范围已经固定。
- 当前发送频控、失败限制、captcha 触发与风险保护的统计口径已经固定。
- 已和
auth_risk_block、auth_audit_log明确切开职责。 - 后续可以直接按这份文档编码 reducer、计数查询与 Axum 应用层判断逻辑。
16. 依据文件
server-node/src/repositories/smsAuthEventRepository.tsserver-node/src/auth/authService.tsserver-node/src/services/smsVerificationService.tsserver-node/src/routes/authRoutes.tsserver-node/src/db/migrations.tspackages/shared/src/contracts/auth.tsdocs/prd/ACCOUNT_SYSTEM_AND_LOGIN_ENTRY_PRD_2026-04-09.mddocs/prd/MY_TAB_SETTINGS_AND_SECURITY_PRD_2026-04-16.mddocs/technical/SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.mddocs/technical/SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md