|
|
@@ -0,0 +1,224 @@
|
|
|
+#!/usr/bin/env python
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+"""
|
|
|
+阿里云国际短信发送 Demo
|
|
|
+
|
|
|
+功能说明:
|
|
|
+- 使用阿里云 SMS 服务发送国际短信(2018-05-01 版本)
|
|
|
+- 服务地址:新加坡(ap-southeast-1)
|
|
|
+- 支持发送到美国、加拿大、新加坡等国家/地区
|
|
|
+- 支持 OTP(验证码)、NOTIFY(通知)、MKT(推广)三种类型
|
|
|
+
|
|
|
+使用前准备:
|
|
|
+1. 安装依赖: pip install alibabacloud-dysmsapi20180501
|
|
|
+2. 确保已注册 Sender ID(美国、加拿大需 10 DLC 注册,新加坡需 IMDA SSIR 注册)
|
|
|
+
|
|
|
+注意: AccessKey 已直接配置在脚本中
|
|
|
+
|
|
|
+参考文档:
|
|
|
+https://next.api.aliyun.com/api/Dysmsapi/2018-05-01/SendMessageToGlobe
|
|
|
+"""
|
|
|
+
|
|
|
+import sys
|
|
|
+import json
|
|
|
+from typing import Optional, Dict, Any
|
|
|
+
|
|
|
+try:
|
|
|
+ from alibabacloud_dysmsapi20180501.client import Client as Dysmsapi20180501Client
|
|
|
+ from alibabacloud_tea_openapi import models as open_api_models
|
|
|
+ from alibabacloud_dysmsapi20180501 import models as dysmsapi_20180501_models
|
|
|
+ from alibabacloud_tea_util import models as util_models
|
|
|
+except ImportError:
|
|
|
+ print("❌ 缺少依赖包,请先安装:")
|
|
|
+ print(" pip install alibabacloud-dysmsapi20180501")
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+
|
|
|
+class InternationalSMSClient:
|
|
|
+ """阿里云国际短信发送客户端(2018-05-01 版本)"""
|
|
|
+
|
|
|
+ # 固定配置(根据文档要求,不可修改)
|
|
|
+ REGION_ID = 'ap-southeast-1' # 新加坡
|
|
|
+ ENDPOINT = 'dysmsapi.ap-southeast-1.aliyuncs.com'
|
|
|
+
|
|
|
+ # 短信类型
|
|
|
+ TYPE_OTP = 'OTP' # 验证码
|
|
|
+ TYPE_NOTIFY = 'NOTIFY' # 短信通知
|
|
|
+ TYPE_MKT = 'MKT' # 推广短信
|
|
|
+
|
|
|
+ def __init__(self, access_key_id: str, access_key_secret: str):
|
|
|
+ """
|
|
|
+ 初始化客户端
|
|
|
+
|
|
|
+ Args:
|
|
|
+ access_key_id: 阿里云 AccessKey ID
|
|
|
+ access_key_secret: 阿里云 AccessKey Secret
|
|
|
+ """
|
|
|
+ # 配置客户端(直接使用 AccessKey)
|
|
|
+ config = open_api_models.Config(
|
|
|
+ access_key_id=access_key_id,
|
|
|
+ access_key_secret=access_key_secret
|
|
|
+ )
|
|
|
+ config.endpoint = self.ENDPOINT
|
|
|
+
|
|
|
+ self.client = Dysmsapi20180501Client(config)
|
|
|
+
|
|
|
+ def send_sms(
|
|
|
+ self,
|
|
|
+ to: str,
|
|
|
+ message: str,
|
|
|
+ sender_id: str,
|
|
|
+ sms_type: str = TYPE_OTP,
|
|
|
+ task_id: Optional[str] = None,
|
|
|
+ validity_period: Optional[int] = None,
|
|
|
+ channel_id: Optional[str] = None
|
|
|
+ ) -> Dict[str, Any]:
|
|
|
+ """
|
|
|
+ 发送国际短信
|
|
|
+
|
|
|
+ Args:
|
|
|
+ to: 接收短信号码,格式为国际区号+号码,例如: "8521245567****"
|
|
|
+ message: 短信的完整内容(美国、加拿大需要填写报备的内容)
|
|
|
+ sender_id: 发送方标识(Sender ID)
|
|
|
+ - 支持数字+字母,含有字母标识最长11位,纯数字标识支持15位
|
|
|
+ - 美国、加拿大:需填写 10 DLC 注册后运营商提供的 Sender ID
|
|
|
+ - 新加坡:需填写在 IMDA 完成 SSIR 注册的 Sender ID
|
|
|
+ sms_type: 短信类型,可选值: OTP/NOTIFY/MKT,默认 OTP
|
|
|
+ task_id: 任务 ID(可选),长度不超过 255 个字符
|
|
|
+ validity_period: 短信有效时长,单位:秒(可选)
|
|
|
+ channel_id: 通道 ID(可选)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ 包含发送结果的字典,格式:
|
|
|
+ {
|
|
|
+ "success": bool,
|
|
|
+ "response_code": str, # 短信提交状态码,如 "OK"
|
|
|
+ "message_id": str, # 成功时返回消息ID
|
|
|
+ "request_id": str, # 请求ID
|
|
|
+ "segments": str, # 短信计费条数
|
|
|
+ "response_description": str, # 状态码描述
|
|
|
+ "to": str, # 接收号码
|
|
|
+ "from": str, # 发送方标识
|
|
|
+ "number_detail": { # 号码明细(可选)
|
|
|
+ "carrier": str, # 运营商
|
|
|
+ "country": str, # 国家
|
|
|
+ "region": str # 地区
|
|
|
+ },
|
|
|
+ "error": str # 失败时返回错误信息
|
|
|
+ }
|
|
|
+ """
|
|
|
+ # 验证短信类型
|
|
|
+ if sms_type not in [self.TYPE_OTP, self.TYPE_NOTIFY, self.TYPE_MKT]:
|
|
|
+ return {
|
|
|
+ "success": False,
|
|
|
+ "error": f"无效的短信类型: {sms_type},支持的类型: OTP/NOTIFY/MKT"
|
|
|
+ }
|
|
|
+
|
|
|
+ # 创建请求对象
|
|
|
+ request = dysmsapi_20180501_models.SendMessageToGlobeRequest()
|
|
|
+ request.to = to
|
|
|
+ request.from_ = sender_id
|
|
|
+ request.message = message
|
|
|
+ request.type = sms_type
|
|
|
+
|
|
|
+ if task_id:
|
|
|
+ request.task_id = task_id
|
|
|
+ if validity_period is not None:
|
|
|
+ request.validity_period = validity_period
|
|
|
+ if channel_id:
|
|
|
+ request.channel_id = channel_id
|
|
|
+
|
|
|
+ # 运行时选项
|
|
|
+ runtime = util_models.RuntimeOptions()
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 发送请求
|
|
|
+ response = self.client.send_message_to_globe_with_options(request, runtime)
|
|
|
+
|
|
|
+ # 解析响应
|
|
|
+ body = response.body
|
|
|
+
|
|
|
+ # 检查响应状态码
|
|
|
+ if body.response_code == 'OK':
|
|
|
+ # 成功
|
|
|
+ result = {
|
|
|
+ "success": True,
|
|
|
+ "response_code": body.response_code,
|
|
|
+ "response_description": body.response_description or '',
|
|
|
+ "message_id": body.message_id or '',
|
|
|
+ "request_id": body.request_id or '',
|
|
|
+ "segments": body.segments or '',
|
|
|
+ "to": body.to or '',
|
|
|
+ "from": body.from_ or '',
|
|
|
+ }
|
|
|
+
|
|
|
+ # 号码明细(可选)
|
|
|
+ if body.number_detail:
|
|
|
+ result["number_detail"] = {
|
|
|
+ "carrier": body.number_detail.carrier or '',
|
|
|
+ "country": body.number_detail.country or '',
|
|
|
+ "region": body.number_detail.region or ''
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+ else:
|
|
|
+ # 失败
|
|
|
+ return {
|
|
|
+ "success": False,
|
|
|
+ "response_code": body.response_code or '',
|
|
|
+ "response_description": body.response_description or '',
|
|
|
+ "request_id": body.request_id or '',
|
|
|
+ "error": body.response_description or '未知错误'
|
|
|
+ }
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ return {
|
|
|
+ "success": False,
|
|
|
+ "error": f"发送失败: {str(e)}"
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ """主函数 - 示例用法"""
|
|
|
+
|
|
|
+ # AccessKey 配置(直接写死在脚本中)
|
|
|
+ ACCESS_KEY_ID = 'LTAI5tQ6iFj9XMSZygRhKL4F'
|
|
|
+ ACCESS_KEY_SECRET = 'eXEfRLenQfOCLWkj307jv48rBUWNHK'
|
|
|
+
|
|
|
+ # 创建客户端
|
|
|
+ sms_client = InternationalSMSClient(ACCESS_KEY_ID, ACCESS_KEY_SECRET)
|
|
|
+
|
|
|
+ # 发送验证码短信
|
|
|
+ print("📤 发送验证码短信")
|
|
|
+ print("-" * 50)
|
|
|
+
|
|
|
+ result = sms_client.send_sms(
|
|
|
+ to="447900613287", # 接收号码(国际区号+号码)
|
|
|
+ message="Lyrria,Your verification code is 123456. Valid for 5 minutes.",
|
|
|
+ sender_id="Lyrria", # Sender ID
|
|
|
+ sms_type=InternationalSMSClient.TYPE_OTP
|
|
|
+ )
|
|
|
+
|
|
|
+ if result["success"]:
|
|
|
+ print(f"✅ 发送成功!")
|
|
|
+ print(f" 状态码: {result.get('response_code', '')}")
|
|
|
+ print(f" 状态描述: {result.get('response_description', '')}")
|
|
|
+ print(f" 消息ID: {result.get('message_id', '')}")
|
|
|
+ print(f" 请求ID: {result.get('request_id', '')}")
|
|
|
+ print(f" 计费条数: {result.get('segments', '')}")
|
|
|
+ if 'number_detail' in result:
|
|
|
+ detail = result['number_detail']
|
|
|
+ print(f" 运营商: {detail.get('carrier', '')}")
|
|
|
+ print(f" 国家: {detail.get('country', '')}")
|
|
|
+ print(f" 地区: {detail.get('region', '')}")
|
|
|
+ else:
|
|
|
+ print(f"❌ 发送失败: {result.get('error', '未知错误')}")
|
|
|
+ if result.get('response_code'):
|
|
|
+ print(f" 状态码: {result.get('response_code')}")
|
|
|
+ if result.get('response_description'):
|
|
|
+ print(f" 状态描述: {result.get('response_description')}")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main()
|
|
|
+
|