180 lines
6.9 KiB
Python
180 lines
6.9 KiB
Python
import jingrow
|
|
import requests
|
|
import time
|
|
import hmac
|
|
import hashlib
|
|
import base64
|
|
import json
|
|
from urllib.parse import quote
|
|
import random
|
|
|
|
class AliyunSMSClient:
|
|
"""阿里云短信客户端,采用单例模式"""
|
|
_instance = None
|
|
|
|
@classmethod
|
|
def get_instance(cls):
|
|
"""获取单例实例"""
|
|
if cls._instance is None:
|
|
cls._instance = cls()
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
"""初始化阿里云短信配置"""
|
|
if AliyunSMSClient._instance is not None:
|
|
raise Exception("请使用 AliyunSMSClient.get_instance() 获取实例")
|
|
|
|
# 初始化配置
|
|
self.initialize()
|
|
|
|
def initialize(self):
|
|
"""初始化配置信息"""
|
|
try:
|
|
# 直接尝试获取 Jcloud Settings
|
|
settings = jingrow.get_single("Jcloud Settings")
|
|
if settings:
|
|
self.access_key_id = settings.get("aliyun_access_key_id")
|
|
self.access_secret = settings.get_password("aliyun_access_secret") if settings.get("aliyun_access_secret") else None
|
|
else:
|
|
# 设置默认值
|
|
self.access_key_id = None
|
|
self.access_secret = None
|
|
jingrow.log_error("阿里云SMS客户端: Jcloud Settings 尚未配置,请在设置中完成配置")
|
|
except Exception as e:
|
|
jingrow.log_error(f"阿里云SMS客户端初始化: {str(e)}")
|
|
self.access_key_id = None
|
|
self.access_secret = None
|
|
|
|
def send_sms(self, phone_numbers, template_code, template_param, sign_name):
|
|
"""发送短信的主方法"""
|
|
if not self.access_key_id or not self.access_secret:
|
|
jingrow.log_error("阿里云凭据未设置,无法发送短信")
|
|
return {"status": "error", "message": "缺少阿里云访问凭据"}
|
|
|
|
# 确保接收号码是字符串格式
|
|
if isinstance(phone_numbers, list):
|
|
phone_numbers = ','.join(phone_numbers)
|
|
|
|
# 确保模板参数是JSON字符串
|
|
if isinstance(template_param, dict):
|
|
template_param = json.dumps(template_param)
|
|
|
|
# 阿里云 API 请求参数
|
|
parameters = {
|
|
"Action": "SendSms",
|
|
"Version": "2017-05-25",
|
|
"RegionId": "cn-hangzhou",
|
|
"PhoneNumbers": phone_numbers,
|
|
"SignName": sign_name,
|
|
"TemplateCode": template_code,
|
|
"TemplateParam": template_param,
|
|
"AccessKeyId": self.access_key_id,
|
|
"Timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
"SignatureMethod": "HMAC-SHA1",
|
|
"SignatureVersion": "1.0",
|
|
"SignatureNonce": str(int(time.time() * 1000)),
|
|
"Format": "JSON"
|
|
}
|
|
|
|
# 计算签名并添加到参数中
|
|
parameters["Signature"] = self._compute_signature(parameters)
|
|
|
|
# 生成完整的请求 URL
|
|
url = "https://dysmsapi.aliyuncs.com/?" + "&".join(f"{key}={self._percent_encode(value)}" for key, value in parameters.items())
|
|
|
|
try:
|
|
response = requests.get(url)
|
|
try:
|
|
result = response.json()
|
|
except:
|
|
result = {"Code": "ParseError", "Message": "无法解析响应数据"}
|
|
jingrow.log_error(f"无法解析API响应: {response.text}")
|
|
|
|
# 判断发送结果
|
|
if result.get("Code") == "OK":
|
|
return {
|
|
"status": "success",
|
|
"message": "短信发送成功",
|
|
"receiver": phone_numbers,
|
|
"content": template_param
|
|
}
|
|
else:
|
|
jingrow.log_error(f"短信发送失败: {result}")
|
|
return {
|
|
"status": "failed",
|
|
"message": result.get("Message", "发送失败"),
|
|
"receiver": phone_numbers,
|
|
"content": template_param,
|
|
"response": result
|
|
}
|
|
except Exception as e:
|
|
jingrow.log_error(f"短信发送异常: {str(e)}")
|
|
return {
|
|
"status": "error",
|
|
"message": f"发送过程中发生错误: {str(e)}",
|
|
"receiver": phone_numbers,
|
|
"content": template_param
|
|
}
|
|
|
|
def _percent_encode(self, string):
|
|
"""对字符串进行URL编码"""
|
|
if not isinstance(string, str):
|
|
string = str(string)
|
|
return quote(string, safe='')
|
|
|
|
def _compute_signature(self, parameters):
|
|
"""计算签名"""
|
|
sorted_parameters = sorted(parameters.items())
|
|
query_string = '&'.join(f'{self._percent_encode(k)}={self._percent_encode(v)}' for k, v in sorted_parameters)
|
|
string_to_sign = f'GET&%2F&{self._percent_encode(query_string)}'
|
|
h = hmac.new((self.access_secret + "&").encode(), string_to_sign.encode(), hashlib.sha1)
|
|
signature = base64.b64encode(h.digest()).decode()
|
|
return signature
|
|
|
|
# 改为在首次调用时初始化
|
|
sms_client = None
|
|
|
|
def get_sms_client():
|
|
"""获取短信客户端实例,确保在首次调用时才初始化"""
|
|
global sms_client
|
|
if sms_client is None:
|
|
sms_client = AliyunSMSClient.get_instance()
|
|
return sms_client
|
|
|
|
def send_custom_sms(phone_numbers, message_content, sign_name, template_code):
|
|
|
|
client = get_sms_client()
|
|
result = client.send_sms(phone_numbers, template_code, message_content, sign_name)
|
|
|
|
return result
|
|
|
|
def generate_verification_code(length=4):
|
|
"""生成指定长度的随机数字验证码"""
|
|
return ''.join([str(random.randint(0, 9)) for _ in range(length)])
|
|
|
|
def send_verification_code(mobile_no, sign_name, template_code):
|
|
"""生成并发送验证码到指定手机号,并将验证码存储在缓存中"""
|
|
verification_code = generate_verification_code()
|
|
cache_key = f"verification_code:{template_code}:{mobile_no}"
|
|
jingrow.cache().set_value(cache_key, verification_code, expires_in_sec=600) # 验证码有效期10分钟
|
|
|
|
message_content = {"code": verification_code}
|
|
return send_custom_sms(mobile_no, message_content, sign_name, template_code)
|
|
|
|
def verify_code(mobile_no, verification_code, template_code):
|
|
"""验证用户输入的验证码是否正确"""
|
|
cache_key = f"verification_code:{template_code}:{mobile_no}"
|
|
cached_code = jingrow.cache().get_value(cache_key)
|
|
if cached_code and cached_code == verification_code:
|
|
jingrow.cache().delete_value(cache_key)
|
|
return True
|
|
return False
|
|
|
|
def send_renew_sms(phone_numbers, days_remaining, site_end_date):
|
|
|
|
template_code = "SMS_489640674" # 网站续费通知短信模板编码
|
|
sign_name = "向日葵网络" # 短信签名名称
|
|
message_content = {
|
|
"site_end_date": str(site_end_date)
|
|
}
|
|
return send_custom_sms(phone_numbers, message_content, sign_name, template_code) |