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

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

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 控制

文档更新

更新文件

  1. 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