Commit Graph

9 Commits

Author SHA1 Message Date
guke ed76820e97 feat(sms): 短信验证码可切换多 provider(极光/阿里云/创蓝),默认极光零改动 (#188)
读完了两份 spec + 分派器,这分支的全貌清晰了。下面是可直接粘进 Gitea PR 的 MR 说明(标题 + 正文):

建议标题

feat(sms): 短信验证码可切换多 provider(极光/阿里云/创蓝),默认极光零改动
MR 正文(复制以下 markdown):

## 概述
把原单文件 `app/integrations/sms.py` 重构为**可切换 provider 包** `app/integrations/sms/`,在保留极光(默认、行为零改动)的基础上,新增两家验证码短信 provider:
- **阿里云号码认证 dypns**(Mode A:阿里云生成/存储/校验验证码,核验免费)
- **创蓝云智 253**(Mode B:本服务自管码,httpx 直连 + HMAC 签名)

Provider 由 `SMS_PROVIDER` 按调用实时选择,默认 `jiguang`。短信=花钱 + 登录关键路径,故新 provider **opt-in、可灰度、秒级回退**,默认路径零变更。

## 为什么
现有极光路径本地内存存码(多 worker 不共享,已是技术债),且单一供应商无法灰度/切换。引入 provider 抽象后:阿里云托管码可消除存码债,创蓝作为备选降低单点依赖,三家随配置切换与回退。

## 改动内容

**架构(`app/integrations/sms/`)**
| 文件 | 说明 |
|---|---|
| `__init__.py` | 对外仍暴露 `send_code/verify_code/SmsError`(auth 导入不变);按 `SMS_PROVIDER` **每次调用**分派;未知值回退 `jiguang` |
| `base.py` | `SmsError`(status_code→HTTP) + provider 无关的 `mock_verify` |
| `jiguang.py` | 原 `sms.py` 逻辑**原样迁入**,行为零改动(git 识别为 rename) |
| `aliyun.py` | 新增,Mode A:`SendSmsVerifyCode` + `CheckSmsVerifyCode`,惰性加载 SDK |
| `chuanglan.py` | 新增,Mode B:自管码 + `tpl/send` + HMAC 签名 |

**两种验证码模式**
- Mode A(阿里云):不本地存码,阿里云 `##code##` 托管生成+校验;本地仅留 per-phone 失败计数防爆破。
- Mode B(极光/创蓝):`secrets` 生成 N 位 → 进程内存 → 供应商只下发;本地一次性校验 + 失败 N 次作废。创蓝**复制**极光存码机器(不重构极光,零回归风险)。

**配置(`config.py` + `.env.example`)**
- `SMS_PROVIDER = jiguang | aliyun | chuanglan`(默认 jiguang)
- `ALIYUN_SMS_*`(AK/签名/模板/方案名/时长…) + `aliyun_sms_configured` 门控
- `CHUANGLAN_SMS_*`(账号/密码/模板/签名/endpoint…) + `chuanglan_sms_configured` 门控
- 复用现有 `SMS_MOCK / SMS_CODE_LENGTH / SMS_CODE_TTL_SEC / SMS_SEND_INTERVAL_SEC / SMS_MAX_VERIFY_ATTEMPTS`
- 切到某 provider 却未配齐 → `send_code` 抛 `SmsError(503)`,不静默

**auth.py(最小改动)**
- `verify_code` 现在可能抛 `SmsError`(阿里云降级 503)→ `sms_login`、`wechat_bind_phone_sms` 两处各包 `try/except SmsError → HTTPException`,与 `send_code` 现有写法一致。

**依赖**
- `pyproject.toml` 增 `alibabacloud_dypnsapi20170525`(仅阿里云 provider 惰性 import;jiguang/chuanglan 不加载)。创蓝零新依赖(httpx + 标准库)。

**测试**
- 新增 `test_sms_aliyun.py` / `test_sms_chuanglan.py`(均 monkeypatch 网络接缝,不发真短信) + `test_sms_dispatch.py`(分派/回退)。
- `test_auth.py` 相应更新。
- 现有测试走 `SMS_MOCK=true` 在分派层短路,不受影响。

**文档**
- 设计 spec:`docs/superpowers/specs/2026-07-25-aliyun-sms-verify-design.md`、`2026-07-26-chuanglan-sms-verify-design.md`
- 接口调研:`docs/integrations/aliyun/*`、`docs/integrations/chuanglan/tpl-send.md`、`docs/integrations/sms.md`

## 兼容性 & 回退
- **默认 `SMS_PROVIDER=jiguang`,线上行为与现状完全一致**;不改极光逻辑、不动 API 层频控与测试账号短路。
- 切阿里云/创蓝仅改环境变量,出问题秒切回极光;未知 `SMS_PROVIDER` 一律回退极光,防误配打挂登录。

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #188
2026-07-28 09:20:57 +08:00
zhuzihao e9fd51d119 fix(auth): 发码防刷改为按成功计数 + 新增每设备每日发码上限 (#136)
- 修复:发码限流原为原子「判+记」,被单号 60s 冷却挡下的重发也占设备额度
  → 正常用户连点重发可能被误锁 1 小时。改为「先判后记、只对成功发码计数」:
  check 判在真发之前(超限直接 429、不真发),record 只在 send_code 成功后调;
  被单号冷却 / 供应商失败抛 429 时直接返回、不计数。
- 新增:同一设备(device_id)+ IP 每天最多 20 次发码上限,与原每小时 5 次两道闸并存,
  均按成功计数,叠一层日封顶挡低频长时间轰炸。
- 基建:ratelimit.py 新增 RateLimitRule + check_rate_limits / record_rate_limits
  (peek/commit 拆分);原子的 enforce_rate_limit 仍保留给登录爆破(失败也计)不变。
- 测试:补 2 个用例(冷却挡下不占额度 / 每日上限)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #136
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
2026-07-16 09:40:56 +08:00
zhuzihao 93f052535e 精简短信登录风控:保留单号冷却 + 单设备频控两道 (#109)
按 mentor 要求精简 SMS 登录链路的防刷策略,只保留两道主策略:
- 单号 60s 冷却(发码,SMS_SEND_INTERVAL_SEC)
- 单设备(device_id + IP)每小时 5 次频控(发码 + 登录,enforce_rate_limit)

删除两道:
- 单号每日发送上限(SMS_DAILY_LIMIT_PER_PHONE)
- 登录接口纯 IP 20 次/分钟限流(rate_limit 依赖)

验证码安全底线保留(一次性使用 + 输错 SMS_MAX_VERIFY_ATTEMPTS 次即作废),
属验证码正确性、不算限流策略,不在精简范围。

顺带清理:sms.py 移除 _daily_count/_today 及 datetime 导入;config.py 删常量;
auth.py 清理变为无用的 Depends/rate_limit 导入;同步 docs/integrations/sms.md 与
docs/后端技术实现.md 的防刷说明(并修正 sms.md 里已过时的 /sms/send rate_limit 描述)。

影响:单号发码上限由 10 条/天放宽为仅受 60s 冷却约束;登录撞库防护改由设备频控
+ 验证码错误次数上限兜底。test_auth.py 相应用例已删除/更新,auth 测试全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #109
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
2026-07-03 15:28:32 +08:00
zhuzihao 59fe715245 feat(analytics): 埋点事件接收接口 + admin 查询接口 + 埋点表 (#83)
Reviewed-on: #83
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
2026-06-26 23:56:29 +08:00
zhuzihao 2edebfad83 feat(auth): 短信登录防刷 — 同一设备id同IP 每小时限 5 次 (#78)
Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #78
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
2026-06-26 13:17:07 +08:00
zhuzihao c222137b2a feat(auth): 默认昵称加“用户”前缀(用户+9 位随机串) (#70)
新用户注册即分配的默认昵称由 9 位字母数字随机串改为“用户”+9 位随机串(如 用户aB3xK9mP2),更友好可读;长度 11 仍在 nickname(64)与改名上限(20)内。同步更新 test_login_assigns_username_and_nickname 断言。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #70
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
2026-06-24 03:53:58 +08:00
marco 9d93b70b9b feat(user): username 对外展示 + 默认昵称 + 存量回填
代提工作区既有的他人在制品(非本次 CPS);CPS 迁移链 cps_tables 依赖其
b3f1a2c4d5e6 迁移,需一并提交。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:00:29 +08:00
marco b50495bebe feat: 短信接入极光真实发送 + 新增运营 admin 后台子应用
短信(SMS_MOCK 切 mock/real):
- integrations/sms.py 重写: real 模式走极光短信 REST /v1/messages 自定义验证码(本服务 secrets
  生成 6 位码 + 进程内存 + 本地校验一次性/防爆破), 鉴权复用极光一键登录 JG_APP_KEY/MASTER_SECRET
  (同一极光应用, 上线只需 SMS_MOCK=false); mock 仍"任意6位通过"不动其余测试
- 防刷四层: 单号冷却 + 单号每日上限 + 单IP rate_limit(/sms/send 10/min、/sms/login 20/min)
  + 单码失败次数作废; SmsError 带 status_code 映射 429/503/400
- config 增 SMS_SEND_ENDPOINT/SIGN_ID/TEMPLATE_ID/CODE_LENGTH/DAILY_LIMIT/MAX_VERIFY_ATTEMPTS;
  test_auth 加 real 模式单测; sms.md/后端技术实现/待办账本同步

admin 后台(app/admin/ 独立子应用, uvicorn app.admin.main:admin_app :8771):
- 复用主仓 models/repositories/integrations + 同库, 鉴权完全隔离(ADMIN_JWT_SECRET≠JWT_SECRET_KEY
  + payload typ=admin + bcrypt 密码 + 可选 IP 白名单); 主 app 不 import 本包, admin 崩不影响主进程
- 路由: 登录 / 账号管理(RBAC: super_admin·finance·operator) / 用户列表+360详情+封禁+手动调币 /
  钱包流水 / 提现重试对账 / 反馈工单 / 数据大盘; 全写操作落 admin_audit_log(涉钱与业务写同事务)
- 涉钱逻辑(调微信/退款/对账)复用 app.repositories.wallet 不重写
- 新增 models/admin.py(AdminUser/AdminAuditLog) + admin_tables 迁移 + create_admin.py +
  deploy/shaguabijia-admin.service; 依赖加 bcrypt

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 03:02:41 +08:00
马润林 1aafc28621 feat: 正式 App 后端登录模块 v0.1.0
引入 JWT 认证、极光一键登录、短信 mock 登录与用户表,并补充技术实施文档与部署配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 17:37:18 +08:00