# 短信验证码服务实现 > **变更日期**:2026-01-27 > **变更类型**:新功能 > **影响范围**:后端服务、数据库、API --- ## 变更概述 实现完整的短信验证码服务,支持登录、绑定手机、重置密码等场景。包含防刷机制、测试模式、定时清理等功能。 --- ## 主要变更 ### 1. 数据模型层 **新增文件**:`app/models/sms.py` - `SmsPurpose` IntEnum:用途枚举(SMALLINT 存储) - `LOGIN = 1`:登录 - `BIND_PHONE = 2`:绑定手机 - `RESET_PASSWORD = 3`:重置密码 - `SmsVerificationCode` SQLModel:验证码表模型 - UUID v7 主键 - 支持多种用途 - 包含过期时间、验证状态、IP 地址等字段 **数据库表结构**: ```sql CREATE TABLE sms_verification_codes ( id UUID PRIMARY KEY DEFAULT gen_uuid_v7(), phone VARCHAR(20) NOT NULL, country_code VARCHAR(10) NOT NULL DEFAULT '+86', code VARCHAR(10) NOT NULL, purpose SMALLINT NOT NULL DEFAULT 1, expires_at TIMESTAMPTZ NOT NULL, verified BOOLEAN DEFAULT false, ip_address INET, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); ``` **索引优化**: - 单列索引:phone, purpose, ip_address - 组合索引:(phone, country_code, purpose, verified, expires_at) - 条件索引:expires_at WHERE verified = false AND deleted_at IS NULL ### 2. 数据访问层 **新增文件**:`app/repositories/sms_repository.py` 核心方法: - `create()` - 创建验证码 - `get_valid_code()` - 查询有效验证码 - `mark_as_verified()` - 标记已验证 - `delete_expired()` - 软删除过期记录 ### 3. 业务逻辑层 **新增文件**:`app/services/sms_service.py` 核心功能: #### 3.1 发送验证码 - 生成 6 位随机数字验证码 - 防刷检查(Redis 限流) - 调用阿里云短信 API(支持测试模式) - 存储验证码到数据库 - 有效期 10 分钟 #### 3.2 验证验证码 - 查询未验证且未过期的验证码 - 验证码匹配检查 - 验证成功后立即失效 #### 3.3 防刷机制 - **IP 限流**:1分钟内最多 3 次 - **手机号限流**:1分钟内最多 1 次,1小时内最多 5 次 - 使用 Redis 实现分布式限流 #### 3.4 测试模式 - 环境变量 `SMS_TEST_MODE=True` 启用测试模式 - 测试模式跳过真实短信发送,仅打印日志 - 生产模式调用阿里云 API ### 4. API 层 **新增文件**:`app/schemas/sms.py` Schema 定义: - `SmsPurposeEnum`:API 层用途枚举(字符串) - `SmsCodeSendRequest`:发送验证码请求 - `SmsCodeVerifyRequest`:验证验证码请求 - `SmsCodeSendResponse`:发送验证码响应 - `SmsCodeVerifyResponse`:验证验证码响应 **更新文件**:`app/api/v1/auth.py` 新增接口: #### 4.1 发送短信验证码 ``` POST /api/v1/auth/sms/send ``` 请求体: ```json { "phone": "13800138000", "countryCode": "+86", "purpose": "login" } ``` 响应: ```json { "code": 200, "message": "Success", "data": { "message": "验证码已发送", "expiresIn": 600 } } ``` #### 4.2 验证短信验证码 ``` POST /api/v1/auth/sms/verify ``` 请求体: ```json { "phone": "13800138000", "countryCode": "+86", "code": "123456", "purpose": "login" } ``` 响应: ```json { "code": 200, "message": "Success", "data": { "success": true } } ``` ### 5. 定时任务 **新增文件**:`app/tasks/sms_tasks.py` - `clean_expired_sms_codes()`:清理过期验证码 - 执行频率:每小时 - 保留策略:保留最近 24 小时的记录用于审计 ### 6. 配置更新 **更新文件**:`app/core/config.py` 新增配置项: ```python SMS_TEST_MODE: bool = True # 测试模式开关 SMS_ACCESS_KEY_ID: str = "" SMS_ACCESS_KEY_SECRET: str = "" SMS_SIGN_NAME: str = "道研组" SMS_TEMPLATE_CODE: str = "" ``` **更新文件**:`.env.example` ```env # 短信服务配置(阿里云) SMS_TEST_MODE=True SMS_ACCESS_KEY_ID= SMS_ACCESS_KEY_SECRET= SMS_SIGN_NAME=道研组 SMS_TEMPLATE_CODE= ``` ### 7. 数据库迁移 **新增文件**: - `app/migrations/011_sms_service_tables.py` - `app/migrations/sql/011_sms_service_tables.sql` 迁移内容: - 创建 `sms_verification_codes` 表 - 创建相关索引 - 添加表和列注释 --- ## 技术亮点 ### 1. 全异步实现 - 使用 `asyncpg` 异步数据库访问 - 使用 `httpx.AsyncClient` 异步 HTTP 请求 - 使用 `redis.asyncio` 异步 Redis 操作 ### 2. 枚举类型优化 - 数据库层使用 SMALLINT 存储(节省空间) - API 层使用字符串枚举(易读性) - 提供双向转换方法 ### 3. 防刷机制 - 多维度限流(IP + 手机号) - 分布式限流(Redis) - 灵活的时间窗口控制 ### 4. 测试友好 - 支持测试模式,避免开发阶段产生短信费用 - 测试模式下打印验证码到日志 - 环境变量控制,无需修改代码 ### 5. 安全性 - 验证码有效期 10 分钟 - 验证成功后立即失效 - 记录请求 IP 地址 - 软删除保留审计记录 --- ## 部署步骤 ### 1. 更新环境变量 编辑 `.env` 文件: ```env # 测试环境 SMS_TEST_MODE=True # 生产环境 SMS_TEST_MODE=False SMS_ACCESS_KEY_ID=your_access_key_id SMS_ACCESS_KEY_SECRET=your_access_key_secret SMS_SIGN_NAME=道研组 SMS_TEMPLATE_CODE=SMS_123456789 ``` ### 2. 执行数据库迁移 ```bash cd server python app/migrations/011_sms_service_tables.py ``` ### 3. 配置 Celery 定时任务 在 Celery Beat 配置中添加: ```python from celery.schedules import crontab beat_schedule = { 'clean-expired-sms-codes': { 'task': 'sms.clean_expired_codes', 'schedule': crontab(minute=0), # 每小时执行 }, } ``` ### 4. 重启服务 ```bash # 重启 API 服务 docker-compose restart api # 重启 Celery Worker docker-compose restart celery-worker # 重启 Celery Beat docker-compose restart celery-beat ``` --- ## 使用示例 ### 测试模式(开发环境) 1. 设置 `SMS_TEST_MODE=True` 2. 调用发送接口 3. 查看日志获取验证码: ``` [测试模式] 跳过短信发送: phone=13800138000, code=123456 ``` 4. 使用日志中的验证码进行验证 ### 生产模式 1. 在阿里云短信服务控制台配置签名和模板 2. 获取 AccessKey ID 和 Secret 3. 设置环境变量 4. 调用发送接口,用户手机收到验证码 5. 用户输入验证码进行验证 --- ## API 测试 ### 发送验证码 ```bash curl -X POST http://localhost:6170/api/v1/auth/sms/send \ -H "Content-Type: application/json" \ -d '{ "phone": "13800138000", "countryCode": "+86", "purpose": "login" }' ``` ### 验证验证码 ```bash curl -X POST http://localhost:6170/api/v1/auth/sms/verify \ -H "Content-Type: application/json" \ -d '{ "phone": "13800138000", "countryCode": "+86", "code": "123456", "purpose": "login" }' ``` --- ## 错误处理 ### 常见错误码 | 错误码 | 说明 | 解决方案 | |--------|------|----------| | 429 | 发送过于频繁 | 等待限流时间窗口过期 | | 400 | 验证码不存在或已过期 | 重新发送验证码 | | 400 | 验证码错误 | 检查输入的验证码 | | 500 | 短信发送失败 | 检查阿里云配置和余额 | --- ## 监控建议 ### 关键指标 1. **发送成功率**:监控短信发送成功/失败比例 2. **验证成功率**:监控验证码验证成功/失败比例 3. **限流触发次数**:监控防刷机制触发频率 4. **验证码过期率**:监控未使用验证码的比例 ### 日志关键字 - `验证码已发送`:发送成功 - `验证码验证成功`:验证成功 - `短信发送失败`:发送失败 - `发送过于频繁`:触发限流 --- ## 后续优化 1. **多渠道支持**:支持腾讯云、华为云等多个短信服务商 2. **语音验证码**:支持语音播报验证码 3. **国际短信**:支持国际手机号验证 4. **验证码模板管理**:支持多种验证码模板 5. **发送记录查询**:提供管理后台查询发送记录 --- ## 相关文档 - [短信服务设计文档](../../requirements/backend/04-services/user/sms-service.md) - [用户服务重构](./2026-01-27-user-service-refactor.md) - [API 设计规范](../../requirements/backend/05-api-design.md) --- **变更人**:Kiro AI **审核人**:待审核 **变更日期**:2026-01-27