from fastapi import APIRouter, HTTPException, status from app.config import settings from app.schemas.sms import SmsSendRequest, SmsVerifyRequest, SmsResponse from app.services.sms import generate_and_send_sms, verify_sms_code router = APIRouter(prefix="/sms", tags=["短信验证码"]) def _validate_captcha_token(captcha_token: str | None) -> None: if settings.SMS_MOCK: return if not (req_captcha := captcha_token): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="需要验证码", ) from app.services.auth import decode_access_token token_sub = decode_access_token(req_captcha) if not token_sub or not token_sub.startswith("captcha:"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="验证码无效", ) @router.post( "/send", response_model=SmsResponse, summary="发送短信验证码", description="客户端发送短信验证码。scene=register 用于注册,scene=login 用于短信登录,scene=set_password 用于设置密码。正式环境会按配置校验图形验证码。", ) async def send_sms_code(req: SmsSendRequest): _validate_captcha_token(req.captcha_token) try: ok, code = await generate_and_send_sms(req.phone, req.scene.value) except ValueError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), ) from exc if not ok: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="短信发送失败,请稍后重试", ) message = f"验证码已发送:{code}" if settings.SMS_MOCK else "验证码已发送" return SmsResponse(message=message, success=True) @router.post( "/verify", response_model=SmsResponse, summary="校验短信验证码", description="校验指定手机号、场景下的短信验证码。业务接口一般会内部校验,本接口主要用于前端调试或单独校验。", ) async def verify_sms(req: SmsVerifyRequest): ok = await verify_sms_code(req.phone, req.code, req.scene.value) if not ok: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="验证码错误或已过期", ) return SmsResponse(message="验证成功", success=True)