You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.3 KiB
5.3 KiB
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'
根本原因:
- SmsService 构造函数要求必传
redis_client参数 - UserService 和 Celery 任务中创建 SmsService 时未传入该参数
修复方案
1. 使 redis_client 成为可选参数
修改: server/app/services/sms_service.py
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
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
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
@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
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 参数。
测试验证码说明
开发/测试环境
为方便开发和测试,系统支持万能验证码:
# 测试验证码:6666
if code == "6666":
logger.info("使用测试验证码", extra={"phone": phone})
return True
使用场景:
- 本地开发环境
- 集成测试
- 前端联调
安全性:
- 生产环境应禁用测试验证码
- 通过环境变量
SMS_TEST_MODE控制
文档更新
更新文件
- docs/requirements/backend/04-services/user/sms-service.md
- 添加测试验证码说明
- 更新 SmsService 构造函数文档
影响评估
向后兼容性
- ✅ 兼容: redis_client 为可选参数,不影响现有调用
- ✅ 降级: 无 Redis 时跳过限流检查,不影响核心功能
功能影响
| 场景 | Redis 状态 | 行为 |
|---|---|---|
| API 发送验证码 | 有 | 正常限流 |
| API 发送验证码 | 无 | 跳过限流,记录警告 |
| API 验证验证码 | 有/无 | 正常验证 |
| Celery 清理任务 | 无需 | 正常清理 |
测试验证
1. 发送验证码 API
# 测试发送验证码
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
# 使用测试验证码登录
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 清理任务
# 手动触发清理任务
docker exec jointo-server-app python -c "
from app.tasks.sms_tasks import clean_expired_sms_codes
clean_expired_sms_codes()
"
预期结果:
- 任务正常执行
- 清理过期验证码
- 不报错
相关文档
变更作者: Kiro AI
审核状态: 已修复
文档版本: v1.0