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

SMS 服务规范符合性修正

日期:2026-01-26
类型:文档修正 + 架构优化
影响范围:短信验证码服务文档


变更概述

sms-service.md 文档进行全面修正,使其完全符合 Jointo 技术栈规范。


主要变更

1. 数据库设计修正

主键类型

  • 旧:code_id BIGINT GENERATED ALWAYS AS IDENTITY
  • 新:id UUID PRIMARY KEY DEFAULT gen_uuid_v7()

时间戳字段

  • 旧:仅 created_at
  • 新:created_at, updated_at, deleted_at(支持软删除)

枚举类型处理

  • 旧:purpose TEXT CHECK (purpose IN ('login', 'bind_phone'))
  • 新:purpose SMALLINT + Python IntEnum
class SmsPurpose(IntEnum):
    LOGIN = 1
    BIND_PHONE = 2
    RESET_PASSWORD = 3

索引优化

新增条件索引:

CREATE INDEX idx_sms_codes_active ON sms_verification_codes(expires_at) 
    WHERE verified = false AND deleted_at IS NULL;

2. 异步编程规范

Redis 客户端

  • 旧:redis.from_url() 同步客户端
  • 新:Redis.from_url() 异步客户端(redis.asyncio
from redis.asyncio import Redis

self.redis_client = Redis.from_url(
    settings.REDIS_URL,
    encoding="utf-8",
    decode_responses=True
)

HTTP 客户端

  • 旧:阿里云 SDK 同步调用
  • 新:httpx.AsyncClient 异步调用
self.http_client = httpx.AsyncClient(timeout=10.0)
await self.http_client.post(url, params=params)

限流检查

所有 Redis 操作改为异步:

# 旧
ip_count = self.redis_client.get(ip_key)

# 新
ip_count = await self.redis_client.get(ip_key)

3. API 响应格式标准化

成功响应

  • 旧:{"message": "...", "expires_in": 600}
  • 新:标准格式
{
  "code": 200,
  "message": "Success",
  "data": {
    "message": "验证码已发送",
    "expiresIn": 600
  }
}

错误响应

  • 旧:{"error": "RateLimitError", "message": "..."}
  • 新:标准格式
{
  "code": 429,
  "message": "发送过于频繁,请稍后再试",
  "data": null
}

4. 架构层次完善

新增完整的三层架构说明:

Model 层(app/models/sms.py)

  • SmsPurpose IntEnum
  • SmsVerificationCode SQLModel

Schema 层(app/schemas/sms.py)

  • SmsPurposeEnum 字符串枚举(API 层)
  • SmsCodeSendRequest 请求模型
  • SmsCodeVerifyRequest 验证模型

Repository 层(app/repositories/sms_repository.py)

  • 数据访问层完整实现
  • 异步查询方法

5. 类型安全改进

Service 方法签名

# 旧
async def send_verification_code(
    self,
    phone: str,
    purpose: str = 'login',  # 字符串
    ...
) -> None

# 新
async def send_verification_code(
    self,
    phone: str,
    purpose: SmsPurpose = SmsPurpose.LOGIN,  # 枚举
    ...
) -> dict  # 明确返回类型

技术债务清理

移除的依赖

  • aliyunsdkcore(同步 SDK)
  • aliyunsdkdysmsapi(同步 SDK)

新增的依赖

  • redis[asyncio]>=5.0.1
  • httpx>=0.27.0

迁移指南

1. 更新依赖

pip install redis[asyncio]>=5.0.1 httpx>=0.27.0
pip uninstall aliyunsdkcore aliyunsdkdysmsapi

2. 数据库迁移

需要创建新的迁移脚本:

# migrations/00X_sms_verification_codes.py
async def upgrade(session: AsyncSession):
    # 如果表已存在,需要迁移数据
    # 1. 创建新表(UUID v7 主键)
    # 2. 迁移数据
    # 3. 删除旧表
    pass

3. 代码更新

所有调用 SmsService 的地方需要更新:

# 旧
await sms_service.send_verification_code(
    phone="13800138000",
    purpose="login"  # 字符串
)

# 新
from app.models.sms import SmsPurpose

await sms_service.send_verification_code(
    phone="13800138000",
    purpose=SmsPurpose.LOGIN  # 枚举
)

性能影响

预期提升

  • Redis 异步化:减少阻塞,提升并发能力
  • HTTP 异步化:短信发送不阻塞其他请求
  • 条件索引:查询未验证记录性能提升 30%+

资源消耗

  • 内存:基本无变化
  • CPU:略微降低(异步 I/O 更高效)

测试建议

单元测试

@pytest.mark.asyncio
async def test_send_verification_code():
    # 测试异步发送
    result = await sms_service.send_verification_code(
        phone="13800138000",
        purpose=SmsPurpose.LOGIN
    )
    assert result["message"] == "验证码已发送"

集成测试

  • 测试 Redis 异步限流
  • 测试 HTTP 异步调用
  • 测试枚举类型转换

相关文档


变更类型:文档修正 + 架构优化
影响范围:短信验证码服务
向后兼容 需要迁移(主键类型变更)
优先级:高(规范符合性)