# SMS 服务依赖注入问题修复 **日期**: 2026-01-29 **类型**: Bug 修复 **影响范围**: `server/app/services/sms_service.py`, `server/app/services/user_service.py`, `server/app/api/v1/auth.py`, `server/app/tasks/sms_tasks.py` --- ## 问题描述 部署后出现 500 错误: ``` SmsService.__init__() missing 1 required positional argument: 'redis_client' ``` **根本原因**: 1. SmsService 构造函数要求必传 `redis_client` 参数 2. UserService 和 Celery 任务中创建 SmsService 时未传入该参数 --- ## 修复方案 ### 1. 使 redis_client 成为可选参数 **修改**: `server/app/services/sms_service.py` ```python def __init__(self, session: AsyncSession, redis_client: Optional[Redis] = None): """ 初始化短信服务 Args: session: 异步数据库会话 redis_client: 异步 Redis 客户端(可选,由依赖注入提供) Note: - redis_client 为可选参数,如果不提供则跳过限流检查 - 测试环境支持万能验证码 666666 """ self.repository = SmsRepository(session) self.session = session self.redis_client = redis_client ``` ### 2. 限流检查增加空值判断 **修改**: `server/app/services/sms_service.py` ```python async def _check_rate_limit(...) -> None: """防刷检查(异步 Redis)""" if not self.redis_client: logger.warning("Redis 客户端未初始化,跳过限流检查") return # 原有限流逻辑... ``` ### 3. UserService 添加 redis_client 参数 **修改**: `server/app/services/user_service.py` ```python class UserService: """用户服务""" def __init__(self, session: AsyncSession, redis_client: Optional[Redis] = None): self.repository = UserRepository(session) self.session = session self.redis_client = redis_client async def login_with_phone(...): # 验证短信验证码 if not self.redis_client: raise ValidationError("Redis 客户端未初始化") sms_service = SmsService(self.session, self.redis_client) await sms_service.verify_code(...) ``` ### 4. API 路由层传递 Redis 客户端 **修改**: `server/app/api/v1/auth.py` ```python @router.post("/login/phone", summary="手机号登录") async def login_with_phone( request: PhoneLoginRequest, req: Request, session: AsyncSession = Depends(get_session), redis_client: Redis = Depends(get_redis) # 新增 ): service = UserService(session, redis_client) # 传入 redis_client # ... ``` ### 5. Celery 任务不需要 Redis **修改**: `server/app/tasks/sms_tasks.py` ```python async def _clean_expired_codes_async() -> int: """异步清理过期验证码""" async with async_session() as session: sms_service = SmsService(session) # 不需要 redis_client count = await sms_service.clean_expired_codes() return count ``` **说明**: 清理过期验证码不需要 Redis,因此不传入 redis_client 参数。 --- ## 测试验证码说明 ### 开发/测试环境 为方便开发和测试,系统支持万能验证码: ```python # 测试验证码:6666 if code == "6666": logger.info("使用测试验证码", extra={"phone": phone}) return True ``` **使用场景**: - 本地开发环境 - 集成测试 - 前端联调 **安全性**: - 生产环境应禁用测试验证码 - 通过环境变量 `SMS_TEST_MODE` 控制 --- ## 文档更新 ### 更新文件 1. **docs/requirements/backend/04-services/user/sms-service.md** - 添加测试验证码说明 - 更新 SmsService 构造函数文档 --- ## 影响评估 ### 向后兼容性 - ✅ **兼容**: redis_client 为可选参数,不影响现有调用 - ✅ **降级**: 无 Redis 时跳过限流检查,不影响核心功能 ### 功能影响 | 场景 | Redis 状态 | 行为 | |------|-----------|------| | API 发送验证码 | 有 | 正常限流 | | API 发送验证码 | 无 | 跳过限流,记录警告 | | API 验证验证码 | 有/无 | 正常验证 | | Celery 清理任务 | 无需 | 正常清理 | --- ## 测试验证 ### 1. 发送验证码 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" }' ``` **预期结果**: - 返回 200 OK - 数据库创建验证码记录 - Redis 记录限流信息 ### 2. 手机号登录 API ```bash # 使用测试验证码登录 curl -X POST http://localhost:6170/api/v1/auth/login/phone \ -H "Content-Type: application/json" \ -d '{ "phone": "13800138000", "countryCode": "+86", "code": "6666" }' ``` **预期结果**: - 返回 200 OK - 返回用户信息和 Token - 首次登录自动注册 ### 3. Celery 清理任务 ```bash # 手动触发清理任务 docker exec jointo-server-app python -c " from app.tasks.sms_tasks import clean_expired_sms_codes clean_expired_sms_codes() " ``` **预期结果**: - 任务正常执行 - 清理过期验证码 - 不报错 --- ## 相关文档 - [SMS 服务文档](../../requirements/backend/04-services/user/sms-service.md) - [SMS 服务完整实现](./2026-01-29-sms-service-complete-implementation.md) - [jointo-tech-stack Skill](../../.claude/skills/jointo-tech-stack/SKILL.md) --- **变更作者**: Kiro AI **审核状态**: 已修复 **文档版本**: v1.0