guzhi/.trae/documents/接入阿里云短信并提供两个接口.md
邹方成 cc352d3184 feat: 重构后端服务并添加新功能
refactor: 优化API路由和响应模型
feat(admin): 添加App用户管理接口
feat(sms): 实现阿里云短信服务集成
feat(email): 添加SMTP邮件发送功能
feat(upload): 支持文件上传接口
feat(rate-limiter): 实现手机号限流器
fix: 修复计算步骤入库问题
docs: 更新API文档和测试计划
chore: 更新依赖和配置
2025-11-19 19:36:03 +08:00

99 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 目标与范围
- 接入阿里云短信服务,封装发送客户端
- 提供两类发送接口:验证码通知、报告生成通知,供 App 调用
- 支持模板动态调用与验证码变量 `${code}` 的正确替换
- 记录发送日志并融入现有审计体系
- 实现同一手机号每分钟不超过 1 条的频率限制
- 安全存储 AccessKey 等敏感信息(环境变量/配置)
## 技术选型
- 后端框架FastAPI现有工程
- 短信 SDKAlibaba Cloud SMS Python SDKTea/OpenAPI V2`alibabacloud_dysmsapi20170525`
- 端点(中国站):`dysmsapi.aliyuncs.com`
- 关键请求字段:`PhoneNumbers``SignName``TemplateCode``TemplateParam`
- 日志:沿用 `app/log` 的 Loguru 与审计中间件
- 频率限制:服务内共享的内存限流(后续可升级为 Redis
- 安全通过环境变量注入凭证Pydantic Settings 读取
- 参考文档:
- Alibaba Cloud SDK V2Python示例SendSmshttps://www.alibabacloud.com/help/en/sdk/developer-reference/v2-python-integrated-sdk
- 短信服务 SendSms 接口2017-05-25https://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` 为 48 位数字(可按需约束)
## 模板与变量替换
- 验证码模板:`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 文档)
## 交付物
- 新增短信客户端与路由模块
- 两个可调用接口(验证码发送、报告通知发送)
- 限流与日志落地,配置基于环境变量