import random import json from extensions import redis_client from config import Config # 阿里云号码认证服务 SDK try: from alibabacloud_dypnsapi20170525.client import Client as Dypnsapi20170525Client from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_dypnsapi20170525 import models as dypnsapi_20170525_models from alibabacloud_tea_util import models as util_models ALIYUN_SDK_AVAILABLE = True except ImportError: ALIYUN_SDK_AVAILABLE = False print("⚠️ 阿里云号码认证服务 SDK 未安装,请运行: pip install alibabacloud_dypnsapi20170525") class SMSService: @staticmethod def _create_client(): """创建号码认证服务客户端""" config = open_api_models.Config( access_key_id=Config.ALIBABA_CLOUD_ACCESS_KEY_ID, access_key_secret=Config.ALIBABA_CLOUD_ACCESS_KEY_SECRET ) config.endpoint = 'dypnsapi.aliyuncs.com' return Dypnsapi20170525Client(config) @staticmethod def send_code(phone): # 检查是否在冷却期内 cooldown_key = f"sms_cooldown:{phone}" if redis_client.exists(cooldown_key): ttl = redis_client.ttl(cooldown_key) return False, f"请{ttl}秒后再试" # 开发模式:使用固定验证码 if Config.DEV_MODE: code = Config.DEV_SMS_CODE redis_client.set(f"sms_code:{phone}", code, ex=1800) redis_client.set(cooldown_key, "1", ex=60) print(f"🟡 [开发模式] 验证码: {code} (手机: {phone})") return True, "验证码已发送(开发模式)" # 生产模式:真实发送 # 调用阿里云号码认证服务(自动生成验证码) success, biz_id = SMSService._call_aliyun(phone, None) if success: # 发送成功后存储 BizId 用于校验 redis_client.set(f"sms_biz_id:{phone}", biz_id, ex=1800) redis_client.set(cooldown_key, "1", ex=60) print(f"✅ 短信已发送至 {phone}, BizId: {biz_id}") return True, "发送成功" else: print(f"❌ 短信发送失败: {biz_id}") # biz_id 此时是错误信息 return False, biz_id @staticmethod def verify_code(phone, code): """校验验证码 - 统一使用本地 Redis 校验""" # 从 Redis 获取验证码 cached_code = redis_client.get(f"sms_code:{phone}") if not cached_code: print(f"⚠️ 未找到验证码,手机号: {phone}") return False # 比对验证码 stored_code = cached_code.decode('utf-8') if isinstance(cached_code, bytes) else str(cached_code) input_code = str(code) print(f"🔍 校验验证码: 输入={input_code}, 存储={stored_code}") if stored_code == input_code: # 校验成功,删除验证码(一次性使用) redis_client.delete(f"sms_code:{phone}") redis_client.delete(f"sms_biz_id:{phone}") # 也删除 BizId print(f"✅ 验证码校验成功") return True else: print(f"❌ 验证码不匹配") return False @staticmethod def _call_aliyun(phone, code): """调用阿里云号码认证服务发送验证码""" if not ALIYUN_SDK_AVAILABLE: return False, "SDK未安装" try: client = SMSService._create_client() # 使用 SendSmsVerifyCode 接口 request = dypnsapi_20170525_models.SendSmsVerifyCodeRequest( phone_number=phone, sign_name=Config.SMS_SIGN_NAME, template_code=Config.SMS_TEMPLATE_CODE, # 模板需要 code 和 min 两个参数 template_param='{"code":"##code##","min":"30"}', code_type=1, # 1=纯数字 code_length=6, # 6位验证码 interval=60, # 60秒重发间隔 valid_time=1800, # 30分钟有效期(秒) return_verify_code=True # 返回验证码用于本地校验 ) runtime = util_models.RuntimeOptions() resp = client.send_sms_verify_code_with_options(request, runtime) if resp.body.code == "OK": # 获取阿里云生成的验证码 verify_code = resp.body.model.verify_code if hasattr(resp.body.model, 'verify_code') else None biz_id = resp.body.model.biz_id if verify_code: # 将验证码存储到 Redis 用于本地校验 redis_client.set(f"sms_code:{phone}", verify_code, ex=1800) print(f"✅ 短信已发送至 {phone}, 验证码: {verify_code}, BizId: {biz_id}") else: # 如果没有返回验证码,只存储 BizId redis_client.set(f"sms_biz_id:{phone}", biz_id, ex=1800) print(f"✅ 短信已发送至 {phone}, BizId: {biz_id}") return True, biz_id else: return False, resp.body.message except Exception as e: import traceback error_detail = traceback.format_exc() print(f"❌ 详细错误信息:\n{error_detail}") return False, str(e)