upload project source code
This commit is contained in:
190
后端源码/yifan.action-ai.cn/api/app/utils/sms_util.py
Normal file
190
后端源码/yifan.action-ai.cn/api/app/utils/sms_util.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user