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.
9.6 KiB
9.6 KiB
SMS 服务完整实现与技术栈合规性优化
日期: 2026-01-29
类型: 功能实现 + 代码优化
影响范围: server/app/services/sms_service.py, server/app/repositories/sms_repository.py, server/app/schemas/sms.py, server/app/api/v1/auth.py
变更概述
完成短信验证码服务的完整实现,并优化代码以符合 jointo-tech-stack 技术栈规范,包括依赖注入、异常处理、日志记录、时区处理和统一响应格式。
主要变更
1. Service 层优化 (sms_service.py)
1.1 依赖注入优化
问题: Service 层直接创建 Redis 客户端,违反依赖注入原则
修正前:
def __init__(self, session: AsyncSession):
self.redis_client: Optional[Redis] = None
self.http_client = httpx.AsyncClient(timeout=10.0)
async def _get_redis(self) -> Redis:
if self.redis_client is None:
self.redis_client = Redis.from_url(settings.REDIS_URL)
return self.redis_client
修正后:
def __init__(self, session: AsyncSession, redis_client: Redis):
"""
Args:
session: 异步数据库会话
redis_client: 异步 Redis 客户端(由依赖注入提供)
"""
self.redis_client = redis_client
self._http_client: Optional[httpx.AsyncClient] = None
@property
def http_client(self) -> httpx.AsyncClient:
"""懒加载 HTTP 客户端"""
if self._http_client is None:
self._http_client = httpx.AsyncClient(timeout=10.0)
return self._http_client
1.2 时区处理修正
问题: 使用 datetime.utcnow() 而非 datetime.now(timezone.utc)
修正:
# 修正前
expires_at=datetime.utcnow() + timedelta(minutes=10)
# 修正后
from datetime import datetime, timezone
expires_at=datetime.now(timezone.utc) + timedelta(minutes=10)
1.3 日志系统统一
问题: 使用 loguru 而非项目标准的 logging 模块
修正:
# 修正前
from loguru import logger
# 修正后
from app.core.logging import logger
1.4 增强错误处理和日志
Service 层方法增强:
async def send_verification_code(...) -> dict:
try:
# 业务逻辑
logger.info(
f"验证码已发送",
extra={
"phone": phone,
"country_code": country_code,
"purpose": purpose.to_string(),
"ip_address": ip_address
}
)
return result
except (RateLimitError, ValidationError):
raise
except Exception as e:
logger.error(f"发送验证码失败: {str(e)}", exc_info=True)
raise ValidationError("发送验证码失败,请稍后重试")
1.5 资源管理优化
问题: 使用 close() 方法手动清理资源
修正: 实现异步上下文管理器
async def __aenter__(self):
"""异步上下文管理器入口"""
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""异步上下文管理器退出,清理资源"""
if self._http_client is not None:
await self._http_client.aclose()
1.6 测试验证码优化
修正: 统一测试验证码为 4 位
# 修正前
if code == "666666":
# 修正后
if code == "6666":
2. Repository 层优化 (sms_repository.py)
2.1 添加异常处理
所有数据库操作增加异常处理:
async def create(self, code: SmsVerificationCode) -> SmsVerificationCode:
try:
self.session.add(code)
await self.session.commit()
await self.session.refresh(code)
return code
except SQLAlchemyError as e:
await self.session.rollback()
logger.error(f"创建验证码失败: {str(e)}", exc_info=True)
raise DatabaseError("创建验证码失败")
2.2 时区处理统一
修正所有时间比较:
# 修正前
SmsVerificationCode.expires_at > datetime.utcnow()
code.updated_at = datetime.utcnow()
# 修正后
from datetime import datetime, timezone
SmsVerificationCode.expires_at > datetime.now(timezone.utc)
code.updated_at = datetime.now(timezone.utc)
2.3 软删除优化
批量软删除时使用统一时间戳:
async def delete_expired(self, cutoff_time: datetime) -> int:
try:
# ...
count = 0
now = datetime.now(timezone.utc) # 统一时间戳
for code in codes:
code.deleted_at = now
count += 1
# ...
except SQLAlchemyError as e:
await self.session.rollback()
logger.error(f"删除过期验证码失败: {str(e)}", exc_info=True)
return 0
3. Schema 层优化 (sms.py)
3.1 添加 ConfigDict
所有 Schema 添加配置:
from pydantic import BaseModel, Field, field_validator, ConfigDict
class SmsCodeSendRequest(BaseModel):
model_config = ConfigDict(populate_by_name=True)
phone: str = Field(..., min_length=11, max_length=11, description="手机号")
country_code: str = Field(default="+86", alias="countryCode", description="国家区号")
purpose: SmsPurposeEnum = Field(default=SmsPurposeEnum.LOGIN, description="用途")
3.2 响应 Schema 优化
统一字段命名:
class SmsCodeVerifyResponse(BaseModel):
model_config = ConfigDict(populate_by_name=True)
verified: bool = Field(..., description="验证是否成功") # 修正前: success
4. API 路由层优化 (auth.py)
4.1 依赖注入更新
添加 Redis 依赖注入:
from redis.asyncio import Redis
from app.api.deps import get_redis
@router.post("/sms/send", ...)
async def send_sms_code(
request: Request,
body: SmsCodeSendRequest,
session: AsyncSession = Depends(get_session),
redis_client: Redis = Depends(get_redis) # 新增
):
sms_service = SmsService(session, redis_client) # 传入 redis_client
4.2 统一响应格式
使用 ApiResponse 包装器:
from app.schemas.response import ApiResponse
from app.schemas.sms import SmsCodeSendResponse
@router.post(
"/sms/send",
response_model=ApiResponse[SmsCodeSendResponse], # 统一响应格式
summary="发送短信验证码"
)
async def send_sms_code(...):
result = await sms_service.send_verification_code(...)
return ApiResponse.success(
data=SmsCodeSendResponse(
message=result["message"],
expires_in=result["expires_in"]
)
)
4.3 枚举转换优化
简化枚举转换逻辑:
# 修正前
purpose_map = {
'login': SmsPurpose.LOGIN,
'bind_phone': SmsPurpose.BIND_PHONE,
'reset_password': SmsPurpose.RESET_PASSWORD
}
purpose_enum = purpose_map.get(request.purpose, SmsPurpose.LOGIN)
# 修正后
purpose_enum = SmsPurpose.from_string(body.purpose.value)
技术栈合规性检查
✅ 已符合规范
- 异步编程(
async/await、AsyncSession、asyncpg) - UUID v7 主键(
generate_uuid) - 枚举类型(
IntEnum+SMALLINT) - 仓储模式(Service/Repository 分层)
- 时间戳字段(
TIMESTAMPTZ+timezone.utc) - 索引设计(单列、组合、条件索引)
- 无物理外键
- Redis 异步客户端
- HTTP 异步客户端
✅ 新增合规项
- 依赖注入(Redis 客户端由 FastAPI 提供)
- 资源管理(异步上下文管理器)
- 错误处理(
try-except+ 异常转换) - 日志记录(结构化日志 +
extra字段) - Schema 配置(
ConfigDict+populate_by_name) - 字段验证(
@field_validator) - 统一响应格式(
ApiResponse+timestamp)
文件变更清单
修改的文件
-
server/app/services/sms_service.py
- 优化依赖注入(Redis 客户端)
- 修正时区处理
- 增强错误处理和日志
- 实现异步上下文管理器
- 统一测试验证码
-
server/app/repositories/sms_repository.py
- 添加异常处理
- 修正时区处理
- 优化软删除逻辑
- 添加日志记录
-
server/app/schemas/sms.py
- 添加
ConfigDict - 优化响应 Schema 字段命名
- 添加
-
server/app/api/v1/auth.py
- 添加 Redis 依赖注入
- 使用统一响应格式
- 简化枚举转换逻辑
测试建议
单元测试
# 运行 SMS 服务单元测试
docker exec jointo-server-app pytest tests/unit/test_sms_service.py -v
# 运行 SMS Repository 单元测试
docker exec jointo-server-app pytest tests/unit/test_sms_repository.py -v
集成测试
# 运行 SMS API 集成测试
docker exec jointo-server-app pytest tests/integration/test_sms_api.py -v
测试覆盖率
# 生成覆盖率报告
docker exec jointo-server-app pytest tests/ -v \
--cov=app/services/sms_service \
--cov=app/repositories/sms_repository \
--cov-report=html
后续优化建议
1. 性能优化
- 考虑使用 Redis Pipeline 批量操作
- 添加验证码发送成功率监控
- 优化数据库查询索引
2. 安全增强
- 添加验证码尝试次数限制
- 实现验证码加密存储
- 添加异常 IP 黑名单机制
3. 功能扩展
- 支持多短信服务商(腾讯云、华为云)
- 添加短信模板管理
- 实现短信发送队列
相关文档
变更作者: Kiro AI
审核状态: 待审核
文档版本: v1.0