refactor: 优化短信验证服务
- 优化sms_service.py短信验证服务实现 - 更新.env.production配置
This commit is contained in:
@@ -22,6 +22,8 @@ TENCENT_SMS_SDK_APP_ID = os.getenv("TENCENT_SMS_SDK_APP_ID", "")
|
||||
TENCENT_SMS_SIGN_NAME = os.getenv("TENCENT_SMS_SIGN_NAME", "")
|
||||
# 统一使用一个短信模板ID(所有场景共用)
|
||||
TENCENT_SMS_TEMPLATE_ID = os.getenv("TENCENT_SMS_TEMPLATE_ID", "")
|
||||
# 模板参数配置:1=仅验证码,2=验证码+过期时间(默认2)
|
||||
TENCENT_SMS_TEMPLATE_PARAM_COUNT = int(os.getenv("TENCENT_SMS_TEMPLATE_PARAM_COUNT", "2"))
|
||||
|
||||
# 验证码配置
|
||||
CODE_LENGTH = 6 # 验证码长度
|
||||
@@ -39,7 +41,7 @@ def get_template_id_by_purpose(purpose: str) -> str:
|
||||
return TENCENT_SMS_TEMPLATE_ID
|
||||
|
||||
|
||||
def send_sms_via_tencent(phone: str, code: str, purpose: str) -> bool:
|
||||
def send_sms_via_tencent(phone: str, code: str, purpose: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
通过腾讯云发送短信验证码
|
||||
|
||||
@@ -49,43 +51,103 @@ def send_sms_via_tencent(phone: str, code: str, purpose: str) -> bool:
|
||||
purpose: 用途
|
||||
|
||||
Returns:
|
||||
bool: 是否发送成功
|
||||
Tuple[bool, str]: (是否发送成功, 错误消息)
|
||||
"""
|
||||
try:
|
||||
# 创建认证对象
|
||||
cred = credential.Credential(TENCENT_SMS_SECRET_ID, TENCENT_SMS_SECRET_KEY)
|
||||
|
||||
# 创建SMS客户端
|
||||
client = sms_client.SmsClient(cred, "ap-guangzhou")
|
||||
|
||||
# 创建请求对象
|
||||
req = sms_models.SendSmsRequest()
|
||||
req.SmsSdkAppId = TENCENT_SMS_SDK_APP_ID
|
||||
req.SignName = TENCENT_SMS_SIGN_NAME
|
||||
req.TemplateId = get_template_id_by_purpose(purpose)
|
||||
req.TemplateParamSet = [code, str(CODE_EXPIRE_MINUTES)] # 验证码和过期时间
|
||||
req.PhoneNumberSet = [f"+86{phone}"]
|
||||
|
||||
# 发送短信
|
||||
resp = client.SendSms(req)
|
||||
|
||||
# 检查发送结果
|
||||
if resp.SendStatusSet and len(resp.SendStatusSet) > 0:
|
||||
status = resp.SendStatusSet[0]
|
||||
if status.Code == "Ok":
|
||||
return True
|
||||
else:
|
||||
print(f"短信发送失败: {status.Code} - {status.Message}")
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
except TencentCloudSDKException as e:
|
||||
print(f"腾讯云SDK异常: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"发送短信异常: {e}")
|
||||
return False
|
||||
# 检查配置是否完整
|
||||
if not TENCENT_SMS_SECRET_ID or not TENCENT_SMS_SECRET_KEY:
|
||||
error_msg = "腾讯云短信服务配置不完整,请设置 TENCENT_SMS_SECRET_ID 和 TENCENT_SMS_SECRET_KEY 环境变量"
|
||||
print(f"错误: {error_msg}")
|
||||
return False, error_msg
|
||||
|
||||
if not TENCENT_SMS_SDK_APP_ID or not TENCENT_SMS_SIGN_NAME or not TENCENT_SMS_TEMPLATE_ID:
|
||||
error_msg = "腾讯云短信服务配置不完整,请设置 TENCENT_SMS_SDK_APP_ID、TENCENT_SMS_SIGN_NAME 和 TENCENT_SMS_TEMPLATE_ID 环境变量"
|
||||
print(f"错误: {error_msg}")
|
||||
return False, error_msg
|
||||
|
||||
# 创建认证对象和客户端(复用)
|
||||
cred = credential.Credential(TENCENT_SMS_SECRET_ID, TENCENT_SMS_SECRET_KEY)
|
||||
client = sms_client.SmsClient(cred, "ap-guangzhou")
|
||||
|
||||
# 尝试发送短信,如果参数不匹配则自动重试另一种配置
|
||||
param_configs = [
|
||||
(1, [code]), # 单参数:仅验证码
|
||||
(2, [code, str(CODE_EXPIRE_MINUTES)]) # 双参数:验证码+过期时间
|
||||
]
|
||||
|
||||
# 优先使用配置的参数数量
|
||||
if TENCENT_SMS_TEMPLATE_PARAM_COUNT == 1:
|
||||
param_configs = [param_configs[0], param_configs[1]] # 先试单参数,再试双参数
|
||||
else:
|
||||
param_configs = [param_configs[1], param_configs[0]] # 先试双参数,再试单参数
|
||||
|
||||
last_error = None
|
||||
for param_count, template_params in param_configs:
|
||||
try:
|
||||
# 创建请求对象
|
||||
req = sms_models.SendSmsRequest()
|
||||
req.SmsSdkAppId = TENCENT_SMS_SDK_APP_ID
|
||||
req.SignName = TENCENT_SMS_SIGN_NAME
|
||||
req.TemplateId = get_template_id_by_purpose(purpose)
|
||||
req.TemplateParamSet = template_params
|
||||
req.PhoneNumberSet = [f"+86{phone}"]
|
||||
|
||||
print(f"调试: 尝试发送短信 - 模板ID={req.TemplateId}, 参数数量={param_count}, 参数={template_params}")
|
||||
|
||||
# 发送短信
|
||||
resp = client.SendSms(req)
|
||||
|
||||
# 检查发送结果
|
||||
if resp.SendStatusSet and len(resp.SendStatusSet) > 0:
|
||||
status = resp.SendStatusSet[0]
|
||||
if status.Code == "Ok":
|
||||
print(f"调试: 短信发送成功,使用的参数数量={param_count}")
|
||||
return True, ""
|
||||
else:
|
||||
error_msg = f"短信发送失败: {status.Code} - {status.Message}"
|
||||
print(f"调试: {error_msg}")
|
||||
last_error = error_msg
|
||||
|
||||
# 如果是参数不匹配错误,尝试下一个配置
|
||||
if "TemplateParamSetNotMatchApprovedTemplate" in status.Code or "FailedOperation.TemplateParamSetNotMatchApprovedTemplate" in status.Code:
|
||||
print(f"调试: 参数不匹配,尝试下一个配置...")
|
||||
continue
|
||||
else:
|
||||
return False, error_msg
|
||||
|
||||
last_error = "短信发送失败: 未收到有效响应"
|
||||
print(f"调试: {last_error}")
|
||||
|
||||
except TencentCloudSDKException as e:
|
||||
error_msg = f"腾讯云SDK异常: {e}"
|
||||
print(f"调试: {error_msg}")
|
||||
last_error = error_msg
|
||||
|
||||
# 如果是参数不匹配错误,尝试下一个配置
|
||||
if "TemplateParamSetNotMatchApprovedTemplate" in str(e) or "FailedOperation.TemplateParamSetNotMatchApprovedTemplate" in str(e):
|
||||
print(f"调试: 参数不匹配,尝试下一个配置...")
|
||||
continue
|
||||
|
||||
# 其他错误直接返回
|
||||
if "SmsSdkAppIdVerifyFail" in str(e) or "UnauthorizedOperation.SmsSdkAppIdVerifyFail" in str(e):
|
||||
error_msg = "短信服务配置错误: SmsSdkAppId 验证失败,请检查 TENCENT_SMS_SDK_APP_ID 是否正确,以及该 AppId 是否属于当前 API 密钥对应的账户"
|
||||
elif "InvalidCredential" in str(e) or "secret id should not be none" in str(e).lower():
|
||||
error_msg = "短信服务配置错误: API 密钥无效,请检查 TENCENT_SMS_SECRET_ID 和 TENCENT_SMS_SECRET_KEY 是否正确"
|
||||
elif "UnauthorizedOperation" in str(e):
|
||||
error_msg = f"短信服务授权失败: {e.message if hasattr(e, 'message') else str(e)}"
|
||||
|
||||
return False, error_msg
|
||||
except Exception as e:
|
||||
error_msg = f"发送短信异常: {str(e)}"
|
||||
print(f"调试: {error_msg}")
|
||||
return False, error_msg
|
||||
|
||||
# 所有配置都失败了
|
||||
if "TemplateParamSetNotMatchApprovedTemplate" in str(last_error) or "FailedOperation.TemplateParamSetNotMatchApprovedTemplate" in str(last_error):
|
||||
error_msg = f"短信模板参数不匹配: 已尝试单参数和双参数配置,均失败。请检查腾讯云控制台中的模板配置(模板ID: {get_template_id_by_purpose(purpose)}),确认模板实际需要的参数数量和格式,然后设置正确的 TENCENT_SMS_TEMPLATE_PARAM_COUNT 环境变量(1=仅验证码,2=验证码+过期时间)"
|
||||
else:
|
||||
error_msg = last_error or "短信发送失败"
|
||||
|
||||
return False, error_msg
|
||||
|
||||
|
||||
async def check_rate_limit(db: AsyncSession, phone: str) -> Tuple[bool, int]:
|
||||
@@ -149,10 +211,13 @@ async def send_verification_code(
|
||||
code = generate_verification_code()
|
||||
|
||||
# 发送短信
|
||||
success = send_sms_via_tencent(phone, code, purpose)
|
||||
success, error_msg = send_sms_via_tencent(phone, code, purpose)
|
||||
|
||||
if not success:
|
||||
return False, "短信发送失败,请稍后重试", 0
|
||||
# 如果错误消息为空,使用默认消息
|
||||
if not error_msg:
|
||||
error_msg = "短信发送失败,请稍后重试"
|
||||
return False, error_msg, 0
|
||||
|
||||
# 保存到数据库
|
||||
expires_at = utc_now() + timedelta(minutes=CODE_EXPIRE_MINUTES)
|
||||
|
||||
Reference in New Issue
Block a user