refactor: 优化短信验证服务

- 优化sms_service.py短信验证服务实现
- 更新.env.production配置
This commit is contained in:
iammm0
2026-01-27 14:30:12 +08:00
parent 12a9f85146
commit 0925c6f15a
2 changed files with 110 additions and 41 deletions

View File

@@ -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_IDTENCENT_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)