# -*- coding: utf-8 -*- import json import random import string from typing import Dict, Any from alibabacloud_dysmsapi20170525.client import Client as DysmsapiClient from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_dysmsapi20170525 import models as dysmsapi_models from alibabacloud_tea_util import models as util_models from app.core.logger import log from app.core.exceptions import CustomException class SMSUtil: """阿里云短信服务工具类""" def __init__(self): """初始化阿里云短信客户端""" # 阿里云短信配置信息 self.access_key_id = "LTAI5t7ox6LSot4bTXQiU39R" self.access_key_secret = "X4Z5K3ZSrZcXzcc5HgWZNmMUmTvK8N" self.region_id = "cn-hangzhou" self.sign_name = "山东实战派网络科技" self.international_sign_name = "Shando Tech" self.enable_international = True self.domestic_endpoint = "dysmsapi.aliyuncs.com" self.international_endpoint = "dysmsapi.aliyuncs.com" # 短信模板配置 self.templates = { # 国内短信模板 "register": "SMS_486390015", # 注册验证码模板 "resetpwd": "SMS_486445022", # 重置密码验证码模板 "changepwd": "SMS_486450016", # 修改密码验证码模板 "changemobile": "SMS_487250049", # 修改手机号验证码模板 "mobilelogin": "SMS_487410035", # 手机登录验证码模板 # 国际短信模板 "international_register": "SMS_INTL_486390015", "international_resetpwd": "SMS_INTL_486445022", "international_changepwd": "SMS_INTL_486450016", "international_changemobile": "SMS_INTL_487250049", "international_mobilelogin": "SMS_INTL_487410035" } def _create_client(self, is_international: bool = False) -> DysmsapiClient: """创建阿里云短信客户端""" config = open_api_models.Config( access_key_id=self.access_key_id, access_key_secret=self.access_key_secret ) # 设置访问的域名 if is_international: config.endpoint = self.international_endpoint else: config.endpoint = self.domestic_endpoint return DysmsapiClient(config) def _is_international_mobile(self, mobile: str) -> bool: """判断是否为国际手机号""" # 简单判断:中国大陆手机号以1开头,11位数字 if mobile.startswith('+'): return True if len(mobile) == 11 and mobile.startswith('1') and mobile.isdigit(): return False return True def generate_verification_code(self, length: int = 6) -> str: """生成验证码""" return ''.join(random.choices(string.digits, k=length)) async def send_sms( self, mobile: str, template_type: str, template_params: Dict[str, Any] = None ) -> bool: """ 发送短信 参数: - mobile: 手机号 - template_type: 模板类型 (register, resetpwd, changepwd, changemobile, mobilelogin) - template_params: 模板参数,如 {"code": "123456"} 返回: - bool: 发送是否成功 """ try: # 判断是否为国际手机号 is_international = self._is_international_mobile(mobile) # 获取对应的模板ID和签名 if is_international and self.enable_international: template_id = self.templates.get(f"international_{template_type}") sign_name = self.international_sign_name else: template_id = self.templates.get(template_type) sign_name = self.sign_name if not template_id: raise CustomException(msg=f"未找到模板类型: {template_type}") # 创建客户端 client = self._create_client(is_international) # 构建请求 send_sms_request = dysmsapi_models.SendSmsRequest( phone_numbers=mobile, sign_name=sign_name, template_code=template_id, template_param=json.dumps(template_params) if template_params else None ) # 发送短信 runtime = util_models.RuntimeOptions() response = client.send_sms_with_options(send_sms_request, runtime) # 检查响应 if response.body.code == "OK": log.info(f"短信发送成功: {mobile}, 模板: {template_type}") return True else: error_code = response.body.code error_message = response.body.message log.error(f"短信发送失败: {mobile}, 错误码: {error_code}, 错误信息: {error_message}") # 根据错误码抛出不同的异常信息 if error_code == "isv.BUSINESS_LIMIT_CONTROL": if "小时级流控" in error_message: raise CustomException(msg="发送过于频繁,同一手机号1小时内最多发送5条短信,请稍后再试") elif "天级流控" in error_message: raise CustomException(msg="今日短信发送次数已达上限,请明天再试") else: raise CustomException(msg="短信发送频率超限,请稍后再试") elif error_code == "isv.SMS_SIGNATURE_ILLEGAL": raise CustomException(msg="短信签名配置错误,请联系管理员") elif error_code == "isv.SMS_TEMPLATE_ILLEGAL": raise CustomException(msg="短信模板配置错误,请联系管理员") elif error_code == "isv.INVALID_PARAMETERS": raise CustomException(msg="手机号格式错误") elif error_code == "isv.MOBILE_NUMBER_ILLEGAL": raise CustomException(msg="手机号格式不正确或为空号") elif error_code == "isv.AMOUNT_NOT_ENOUGH": raise CustomException(msg="短信余额不足,请联系管理员") else: raise CustomException(msg=f"短信发送失败: {error_message}") return False except CustomException: # 重新抛出业务异常 raise except Exception as e: log.error(f"短信发送异常: {mobile}, 错误: {str(e)}") raise CustomException(msg="短信服务异常,请稍后重试") async def send_verification_code( self, mobile: str, code_type: str = "register" ) -> tuple[bool, str]: """ 发送验证码短信 参数: - mobile: 手机号 - code_type: 验证码类型 (register, resetpwd, changepwd, changemobile, mobilelogin) 返回: - tuple[bool, str]: (是否成功, 验证码) """ # 生成验证码 verification_code = self.generate_verification_code() # 发送短信(如果发生异常会直接抛出,不会返回False) try: success = await self.send_sms( mobile=mobile, template_type=code_type, template_params={"code": verification_code} ) return success, verification_code if success else "" except CustomException: # 重新抛出业务异常,让上层处理 raise