Files
----/后端源码/yifan.action-ai.cn/app/utils/sms_util.py

191 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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