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

@@ -38,8 +38,12 @@ ACCESS_TOKEN_EXPIRE_MINUTES=120
TENCENT_SMS_SECRET_ID=AKIDmXEoYQCypUyFI9nH7tWzGOJG7e3ING0Y TENCENT_SMS_SECRET_ID=AKIDmXEoYQCypUyFI9nH7tWzGOJG7e3ING0Y
TENCENT_SMS_SECRET_KEY=Vq22tb7UghhV6WnskRyQjroY5QDqHUXO TENCENT_SMS_SECRET_KEY=Vq22tb7UghhV6WnskRyQjroY5QDqHUXO
# 短信应用 SDK AppID # 短信应用 SDK AppID
TENCENT_SMS_SDK_APP_ID=1319381411 TENCENT_SMS_SDK_APP_ID=1401010099
# 短信签名内容(不包含【】符号) # 短信签名内容(不包含【】符号)
TENCENT_SMS_SIGN_NAME=上海华嘎科技有限公司 TENCENT_SMS_SIGN_NAME=上海华嘎科技有限公司
# 短信模板 ID # 短信模板 ID
TENCENT_SMS_TEMPLATE_ID=2592163 TENCENT_SMS_TEMPLATE_ID=2592163
# 短信模板参数数量1=仅验证码2=验证码+过期时间)
# 如果遇到 TemplateParamSetNotMatchApprovedTemplate 错误,请检查腾讯云控制台中的模板配置
# 并根据实际模板参数数量设置此值
TENCENT_SMS_TEMPLATE_PARAM_COUNT=1

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", "") TENCENT_SMS_SIGN_NAME = os.getenv("TENCENT_SMS_SIGN_NAME", "")
# 统一使用一个短信模板ID所有场景共用 # 统一使用一个短信模板ID所有场景共用
TENCENT_SMS_TEMPLATE_ID = os.getenv("TENCENT_SMS_TEMPLATE_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 # 验证码长度 CODE_LENGTH = 6 # 验证码长度
@@ -39,7 +41,7 @@ def get_template_id_by_purpose(purpose: str) -> str:
return TENCENT_SMS_TEMPLATE_ID 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,23 +51,48 @@ def send_sms_via_tencent(phone: str, code: str, purpose: str) -> bool:
purpose: 用途 purpose: 用途
Returns: Returns:
bool: 是否发送成功 Tuple[bool, str]: (是否发送成功, 错误消息)
""" """
try: # 检查配置是否完整
# 创建认证对象 if not TENCENT_SMS_SECRET_ID or not TENCENT_SMS_SECRET_KEY:
cred = credential.Credential(TENCENT_SMS_SECRET_ID, TENCENT_SMS_SECRET_KEY) error_msg = "腾讯云短信服务配置不完整,请设置 TENCENT_SMS_SECRET_IDTENCENT_SMS_SECRET_KEY 环境变量"
print(f"错误: {error_msg}")
return False, error_msg
# 创建SMS客户端 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") 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 = sms_models.SendSmsRequest()
req.SmsSdkAppId = TENCENT_SMS_SDK_APP_ID req.SmsSdkAppId = TENCENT_SMS_SDK_APP_ID
req.SignName = TENCENT_SMS_SIGN_NAME req.SignName = TENCENT_SMS_SIGN_NAME
req.TemplateId = get_template_id_by_purpose(purpose) req.TemplateId = get_template_id_by_purpose(purpose)
req.TemplateParamSet = [code, str(CODE_EXPIRE_MINUTES)] # 验证码和过期时间 req.TemplateParamSet = template_params
req.PhoneNumberSet = [f"+86{phone}"] req.PhoneNumberSet = [f"+86{phone}"]
print(f"调试: 尝试发送短信 - 模板ID={req.TemplateId}, 参数数量={param_count}, 参数={template_params}")
# 发送短信 # 发送短信
resp = client.SendSms(req) resp = client.SendSms(req)
@@ -73,19 +100,54 @@ def send_sms_via_tencent(phone: str, code: str, purpose: str) -> bool:
if resp.SendStatusSet and len(resp.SendStatusSet) > 0: if resp.SendStatusSet and len(resp.SendStatusSet) > 0:
status = resp.SendStatusSet[0] status = resp.SendStatusSet[0]
if status.Code == "Ok": if status.Code == "Ok":
return True print(f"调试: 短信发送成功,使用的参数数量={param_count}")
return True, ""
else: else:
print(f"短信发送失败: {status.Code} - {status.Message}") error_msg = f"短信发送失败: {status.Code} - {status.Message}"
return False print(f"调试: {error_msg}")
last_error = error_msg
return False # 如果是参数不匹配错误,尝试下一个配置
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: except TencentCloudSDKException as e:
print(f"腾讯云SDK异常: {e}") error_msg = f"腾讯云SDK异常: {e}"
return False 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: except Exception as e:
print(f"发送短信异常: {e}") error_msg = f"发送短信异常: {str(e)}"
return False 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]: 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() 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: 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) expires_at = utc_now() + timedelta(minutes=CODE_EXPIRE_MINUTES)