""" 短信发送服务模块 ============================================================= 封装阿里云短信 SDK 提供短信发送能力,供业务模块和定时任务调用。 使用方式: from application.services import sms_svc # 模板短信(推荐) sms_svc.send_sms( phone='13800138000', template_code='SMS_123456', template_params={'code': '123456'} ) # 指定签名(覆盖默认签名) sms_svc.send_sms( phone='13800138000', template_code='SMS_123456', template_params={'code': '123456'}, sign_name='我的应用' ) # 群发(多号码) sms_svc.send_sms( phone=['13800138000', '13900139000'], template_code='SMS_123456', template_params={'name': '张三'} ) """ import json import logging from typing import Dict, List, Optional, Union from config import ( SMS_ALIYUN_ACCESS_KEY_ID, SMS_ALIYUN_ACCESS_KEY_SECRET, SMS_ALIYUN_CONNECT_TIMEOUT, SMS_ALIYUN_ENDPOINT, SMS_ALIYUN_READ_TIMEOUT, SMS_ALIYUN_SIGN_NAME, SMS_PROVIDER, ) logger = logging.getLogger(__name__) # ============================================================ # 阿里云短信客户端(延迟初始化) # ============================================================ _client = None def _get_client(): """ 获取阿里云短信客户端(单例,延迟初始化) Returns: dysmsapi.Client: 阿里云短信客户端实例 Raises: ImportError: 阿里云 SDK 未安装 Exception: 客户端创建失败 """ global _client if _client is not None: return _client try: from alibabacloud_dysmsapi20170525.client import Client from alibabacloud_tea_openapi.models import Config except ImportError: raise ImportError( "阿里云短信 SDK 未安装,请执行: " "pip install alibabacloud-dysmsapi20170525 alibabacloud-tea-openapi" ) config = Config( access_key_id=SMS_ALIYUN_ACCESS_KEY_ID, access_key_secret=SMS_ALIYUN_ACCESS_KEY_SECRET, endpoint=SMS_ALIYUN_ENDPOINT, connect_timeout=SMS_ALIYUN_CONNECT_TIMEOUT, read_timeout=SMS_ALIYUN_READ_TIMEOUT, ) _client = Client(config) return _client # ============================================================ # 短信发送 # ============================================================ def send_sms( phone: Union[str, List[str]], template_code: str, template_params: Optional[Dict] = None, sign_name: Optional[str] = None, ) -> bool: """ 发送短信(阿里云模板短信) Args: phone: 手机号,字符串或列表(群发时传入多个号码) template_code: 短信模板CODE(在阿里云控制台获取,如 SMS_123456) template_params: 模板变量,如 {'code': '123456'} 对应模板中的 ${code} sign_name: 短信签名(可选,默认使用配置中的 SMS_ALIYUN_SIGN_NAME) Returns: bool: 全部发送成功返回 True,任一失败返回 False """ if not phone: logger.warning("发送短信失败:手机号不能为空") return False if not template_code: logger.warning("发送短信失败:模板CODE不能为空") return False if not sign_name: sign_name = SMS_ALIYUN_SIGN_NAME if not sign_name: logger.warning("发送短信失败:短信签名未配置") return False # 统一转为列表 phones = [phone] if isinstance(phone, str) else phone # 检查 AccessKey 配置 if not SMS_ALIYUN_ACCESS_KEY_ID or not SMS_ALIYUN_ACCESS_KEY_SECRET: logger.warning("发送短信失败:阿里云 AccessKey 未配置") return False # 模板变量序列化 params_str = json.dumps(template_params, ensure_ascii=False) if template_params else '' # 逐条发送(阿里云短信 API 不支持批量同发) all_success = True for p in phones: if not _send_one(p, sign_name, template_code, params_str): all_success = False return all_success def _send_one(phone: str, sign_name: str, template_code: str, template_params: str) -> bool: """ 发送单条短信 Args: phone: 手机号 sign_name: 短信签名 template_code: 模板CODE template_params: 模板变量 JSON 字符串 Returns: bool: 发送成功返回 True """ try: from alibabacloud_dysmsapi20170525 import models as dysms_models client = _get_client() request = dysms_models.SendSmsRequest( phone_numbers=phone, sign_name=sign_name, template_code=template_code, template_param=template_params, ) response = client.send_sms(request) body = response.body if body.code == 'OK': logger.info(f"短信发送成功: phone={phone}, template={template_code}, biz_id={body.biz_id}") return True else: logger.warning( f"短信发送失败: phone={phone}, template={template_code}, " f"code={body.code}, message={body.message}" ) return False except ImportError as e: logger.error(f"阿里云短信 SDK 未安装: {str(e)}") return False except Exception as e: logger.error(f"短信发送异常: phone={phone}, template={template_code}, error={str(e)}") return False