""" 阿里云短信服务模块 提供短信验证码发送功能。 """ import random import string from typing import Optional from alibabacloud_dysmsapi20170525.client import Client as DysmsapiClient from alibabacloud_dysmsapi20170525 import models as dysmsapi_models from alibabacloud_tea_openapi import models as open_api_models from core.config import settings from core.redis import RedisService from logger.logging import get_logger logger = get_logger(__name__) # 验证码有效期(秒) SMS_CODE_EXPIRE = 300 # 5分钟 # 验证码发送间隔(秒) SMS_CODE_INTERVAL = 60 # 1分钟 class SmsService: """短信服务类""" _client: Optional[DysmsapiClient] = None @classmethod def _get_client(cls) -> DysmsapiClient: """获取阿里云短信客户端""" if cls._client is None: config = open_api_models.Config( access_key_id=settings.sms_access_key_id, access_key_secret=settings.sms_access_key_secret, ) config.endpoint = "dysmsapi.aliyuncs.com" cls._client = DysmsapiClient(config) return cls._client @staticmethod def _generate_code(length: int = 6) -> str: """生成随机验证码""" return ''.join(random.choices(string.digits, k=length)) @staticmethod def _get_code_key(phone: str, scene: str = "login") -> str: """获取验证码存储键""" return f"sms:code:{scene}:{phone}" @staticmethod def _get_interval_key(phone: str, scene: str = "login") -> str: """获取发送间隔存储键""" return f"sms:interval:{scene}:{phone}" @classmethod async def send_code(cls, phone: str, scene: str = "login") -> dict: """ 发送短信验证码 Args: phone: 手机号 scene: 场景(login/register/reset) Returns: dict: {"success": bool, "message": str} """ # 检查发送间隔 interval_key = cls._get_interval_key(phone, scene) if await RedisService.exists(interval_key): ttl = await RedisService.ttl(interval_key) return {"success": False, "message": f"请{ttl}秒后再试"} # 生成验证码 code = cls._generate_code() # 发送短信 try: client = cls._get_client() request = dysmsapi_models.SendSmsRequest( phone_numbers=phone, sign_name=settings.sms_sign_name, template_code=settings.sms_template_code, template_param=f'{{"code":"{code}"}}' ) response = client.send_sms(request) if response.body.code != "OK": logger.error(f"短信发送失败: {response.body.message}") return {"success": False, "message": "短信发送失败,请稍后重试"} # 存储验证码 code_key = cls._get_code_key(phone, scene) await RedisService.set(code_key, code, SMS_CODE_EXPIRE) # 设置发送间隔 await RedisService.set(interval_key, "1", SMS_CODE_INTERVAL) logger.info(f"短信验证码已发送: phone={phone}, scene={scene}") return {"success": True, "message": "验证码已发送"} except Exception as e: logger.exception(f"短信发送异常: {e}") return {"success": False, "message": "短信发送失败,请稍后重试"} @classmethod async def verify_code(cls, phone: str, code: str, scene: str = "login") -> bool: """ 验证短信验证码 Args: phone: 手机号 code: 验证码 scene: 场景 Returns: bool: 验证是否成功 """ code_key = cls._get_code_key(phone, scene) stored_code = await RedisService.get(code_key) if stored_code is None: return False if stored_code != code: return False # 验证成功后删除验证码 await RedisService.delete(code_key) return True