refactor: 优化API路由和响应模型 feat(admin): 添加App用户管理接口 feat(sms): 实现阿里云短信服务集成 feat(email): 添加SMTP邮件发送功能 feat(upload): 支持文件上传接口 feat(rate-limiter): 实现手机号限流器 fix: 修复计算步骤入库问题 docs: 更新API文档和测试计划 chore: 更新依赖和配置
99 lines
5.2 KiB
Markdown
99 lines
5.2 KiB
Markdown
## 目标与范围
|
||
- 接入阿里云短信服务,封装发送客户端
|
||
- 提供两类发送接口:验证码通知、报告生成通知,供 App 调用
|
||
- 支持模板动态调用与验证码变量 `${code}` 的正确替换
|
||
- 记录发送日志并融入现有审计体系
|
||
- 实现同一手机号每分钟不超过 1 条的频率限制
|
||
- 安全存储 AccessKey 等敏感信息(环境变量/配置)
|
||
|
||
## 技术选型
|
||
- 后端框架:FastAPI(现有工程)
|
||
- 短信 SDK:Alibaba Cloud SMS Python SDK(Tea/OpenAPI V2,`alibabacloud_dysmsapi20170525`)
|
||
- 端点(中国站):`dysmsapi.aliyuncs.com`
|
||
- 关键请求字段:`PhoneNumbers`、`SignName`、`TemplateCode`、`TemplateParam`
|
||
- 日志:沿用 `app/log` 的 Loguru 与审计中间件
|
||
- 频率限制:服务内共享的内存限流(后续可升级为 Redis)
|
||
- 安全:通过环境变量注入凭证,Pydantic Settings 读取
|
||
- 参考文档:
|
||
- Alibaba Cloud SDK V2(Python)示例(SendSms):https://www.alibabacloud.com/help/en/sdk/developer-reference/v2-python-integrated-sdk
|
||
- 短信服务 SendSms 接口(2017-05-25):https://help.aliyun.com/zh/sms/developer-reference/api-dysmsapi-2017-05-25-sendsms
|
||
|
||
## 代码改动
|
||
- 新增:`app/services/sms_client.py`
|
||
- 初始化 Dysms 客户端(读取 `ALIBABA_CLOUD_ACCESS_KEY_ID`、`ALIBABA_CLOUD_ACCESS_KEY_SECRET`、`ALIYUN_SMS_SIGN_NAME`、`ALIYUN_SMS_ENDPOINT`)
|
||
- 方法:`send_by_template(phone, template_code, template_param_json)`
|
||
- 方法:`send_code(phone, code)`(模板:`SMS_498190229`)
|
||
- 方法:`send_report(phone)`(模板:`SMS_498140213`)
|
||
- 新增:`app/services/rate_limiter.py`
|
||
- 类:`PhoneRateLimiter`,键为手机号,值为最近一次发送时间戳;判定 60s 内拒绝
|
||
- 新增路由:`app/api/v1/sms/sms.py`
|
||
- `POST /api/v1/sms/send-code`(无鉴权,用于登录场景)
|
||
- `POST /api/v1/sms/send-report`(需要鉴权,防滥用)
|
||
- 统一返回结构:`{status, message, request_id}`
|
||
- 路由聚合:在 `app/api/v1/__init__.py` 注册 `sms_router(prefix="/sms", tags=["短信服务"])`
|
||
- 配置:扩展 `app/settings/config.py`(Pydantic Settings)增加短信相关字段并从环境读入
|
||
|
||
## 接口设计
|
||
- `POST /api/v1/sms/send-code`
|
||
- 请求体:`{ "phone": "1390000****", "code": "123456" }`
|
||
- 处理:限流校验 → 构造 `TemplateParam` 为 `{"code": "123456"}` → 调用 `SMS_498190229`
|
||
- 成功:`{ "status": "OK", "message": "sent", "request_id": "..." }`
|
||
- 失败:`{ "status": "ERROR", "message": "..." }`
|
||
- `POST /api/v1/sms/send-report`
|
||
- 请求体:`{ "phone": "1390000****" }`
|
||
- 处理:鉴权(`DependAuth`)→ 限流校验 → 调用 `SMS_498140213`
|
||
- 返回同上
|
||
- 校验:手机号格式(支持无前缀或 `+86`),`code` 为 4–8 位数字(可按需约束)
|
||
|
||
## 模板与变量替换
|
||
- 验证码模板:`SMS_498190229`
|
||
- `TemplateParam`:`{"code": "<动态验证码>"}` 与 `${code}` 正确对应
|
||
- 报告通知模板:`SMS_498140213`
|
||
- 不含变量,可传空对象 `{}` 或不传 `TemplateParam`
|
||
- 签名:`ALIYUN_SMS_SIGN_NAME` 读取为“成都文化产权交易所”且不在代码中硬编码
|
||
|
||
## 日志与审计
|
||
- 路由层:审计中间件自动记录请求/响应(`module=短信服务`,`summary=验证码发送/报告通知发送`)
|
||
- 服务层:`from app.log import logger`
|
||
- 发送开始、Provider 请求入参(不含敏感信息)、返回码、`RequestId`、耗时、失败异常
|
||
- 敏感信息不入日志:AccessKey、完整模板内容不打印
|
||
|
||
## 频率限制
|
||
- 策略:同一手机号在 60 秒内全模板合并限 1 次(共享窗口)
|
||
- 实现:进程内 `dict[phone]=last_ts`;进入路由先校验再发送;返回 429(或业务错误码)
|
||
- 进阶:如需多实例一致性,后续接入 Redis,键:`sms:limit:{phone}` TTL=60s
|
||
|
||
## 安全与配置
|
||
- 环境变量:
|
||
- `ALIBABA_CLOUD_ACCESS_KEY_ID`
|
||
- `ALIBABA_CLOUD_ACCESS_KEY_SECRET`
|
||
- `ALIYUN_SMS_SIGN_NAME`
|
||
- `ALIYUN_SMS_ENDPOINT`(默认 `dysmsapi.aliyuncs.com`)
|
||
- Pydantic Settings 统一读取,避免硬编码,并在 `/docs` 与审计中隐藏敏感字段
|
||
|
||
## 依赖与安装
|
||
- 在 `pyproject.toml` 添加:
|
||
- `alibabacloud_dysmsapi20170525`
|
||
- `alibabacloud_tea_openapi`
|
||
- `alibabacloud_tea_util`
|
||
- 与 `requirements.txt` 保持一致版本钉死策略;Python 3.11 兼容性验证
|
||
|
||
## 测试与验证
|
||
- 单元测试:
|
||
- Mock SDK 客户端,校验 `TemplateCode` 与 `TemplateParam` 的正确构造
|
||
- 限流:同号 60s 内第二次返回限制错误
|
||
- 集成测试:
|
||
- 使用 `httpx.AsyncClient` 调用两个接口并断言响应结构
|
||
- 在预设测试手机号上进行真实发送,观察到达与模板内容正确
|
||
- 观测:
|
||
- 查看应用日志与审计表(`AuditLog`)记录
|
||
|
||
## 风险与回滚
|
||
- 进程内限流仅在单实例有效,多实例需 Redis(后续迭代)
|
||
- SDK 版本冲突,采用独立最小版本并逐项验证;必要时锁版本
|
||
- 若出现发送失败,保留错误码与 `RequestId`,按官方错误码表排查(见 SendSms 文档)
|
||
|
||
## 交付物
|
||
- 新增短信客户端与路由模块
|
||
- 两个可调用接口(验证码发送、报告通知发送)
|
||
- 限流与日志落地,配置基于环境变量 |