smssend/smssend/smssend.py
2026-03-21 09:58:35 +08:00

235 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
SMS Send Module v1.0
=====================
Features:
- 百度短信发送
- 验证码生成与验证
- 配置从文件读取
"""
import os
import datetime
import random
import string
from appPublic.jsonConfig import getConfig
from baidubce.bce_client_configuration import BceClientConfiguration
from baidubce.auth.bce_credentials import BceCredentials
import baidubce.services.sms.sms_client as sms
import baidubce.exception as ex
from sqlor.dbpools import DBPools
from appPublic.uniqueID import getID as uuid
from ahserver.serverenv import ServerEnv
from sqlor.dbpools import get_sor_context
from appPublic.worker import awaitify
SMS_TEMPLATE_TABLE = {
"summary": [{"name": "sms_template", "primary": "id"}],
"fields": [
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "name", "type": "str", "length": 32},
{"name": "template_type", "type": "str", "length": 32},
{"name": "code", "type": "str", "length": 32},
{"name": "content", "type": "str", "length": 200},
{"name": "description", "type": "str", "length": 100},
{"name": "provider", "type": "str", "length": 100},
{"name": "del_flg", "type": "str", "length": 1, "default": "0"},
{"name": "create_at", "type": "timestamp"}
]
}
VALIDATE_CODE_TABLE = {
"summary": [{"name": "validatecode", "primary": "id"}],
"fields": [
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "vcode", "type": "str", "length": 32},
{"name": "expire_time", "type": "timestamp", "nullable": "no"},
{"name": "del_flg", "type": "str", "length": 1, "default": "0"},
{"name": "create_at", "type": "timestamp", "nullable": "no"}
]
}
SMS_RECORD_TABLE = {
"summary": [{"name": "sms_record", "primary": "id"}],
"fields": [
{"name": "id", "type": "str", "length": 32, "nullable": "no"},
{"name": "customerid", "type": "str", "length": 32},
{"name": "send_type", "type": "str", "length": 32},
{"name": "mobile", "type": "str", "length": 15},
{"name": "email", "type": "str", "length": 32},
{"name": "message", "type": "str", "length": 510},
{"name": "send_time", "type": "str", "length": 32},
{"name": "send_status", "type": "str", "length": 1},
{"name": "task_status", "type": "str", "length": 2},
{"name": "remark", "type": "str", "length": 200},
{"name": "del_flg", "type": "str", "length": 1},
{"name": "create_at", "type": "timestamp"}
]
}
class SMSEngine:
doc = "https://cloud.baidu.com/doc/SMS/s/zjwvxry6e"
def __init__(self):
self.access_key = os.getenv('BAIDU_SMS_ACCESS_KEY')
self.access_key_secret = os.getenv('BAIDU_SMS_ACCESS_KEY_SECRET')
self.host = os.getenv('BAIDU_SMS_HOST')
self.signature_id = os.getenv('BAIDU_SMS_SIGNATURE_ID')
# 检查必需的环境变量是否都已设置,若缺失则抛出异常
required_vars = [self.access_key, self.access_key_secret, self.host, self.signature_id]
if not all(required_vars):
missing = [name for name, val in zip(
['BAIDU_SMS_ACCESS_KEY', 'BAIDU_SMS_ACCESS_KEY_SECRET', 'BAIDU_SMS_HOST', 'BAIDU_SMS_SIGNATURE_ID'],
required_vars) if val is None]
raise EnvironmentError(f"Missing required environment variables: {', '.join(missing)}")
self.sms_client = (awaitifyself.create_client())
self.sms_types = {}
def create_client(self):
baiDuSmsConfig = BceClientConfiguration(
credentials=BceCredentials(self.access_key, self.access_key_secret),
endpoint=self.host
)
return sms.SmsClient(baiDuSmsConfig)
async def send(self, stype, template_id, phone, params) -> dict:
try:
resp = self.sms_client.send_message(
signature_id=self.signature_id,
template_id=template_id,
mobile=phone,
content_var_dict=params
)
return await self.__validation(stype, template_id, params, phone, resp)
except ex.BceHttpClientError as e:
if isinstance(e.last_error, ex.BceServerError):
print('send request failed. Response %s, code: %s, request_id: %s'
% (e.last_error.status_code, e.last_error.code, e.last_error.request_id))
else:
print('send request failed. Unknown exception: %s' % e)
return {'status': False, 'msg': str(e)}
async def send_vcode(self, phone: str, stype: str, vcode) -> dict:
env = ServerEnv()
async with get_sor_context(env, 'kboss') as sor:
template_info = await sor.R('sms_template', {'name': stype, 'del_flg': '0'})
if template_info:
template_id = template_info[0]['code']
else:
log = {
'id': uuid(),
'send_type': stype,
'mobile': phone,
'message': str(vcode),
'send_time': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'send_status': '0',
'remark': f'未找到模板类型: {stype}'
}
await sor.C('sms_record', log)
return {'status': False, 'msg': '模板未配置请检查sms_template表'}
return await self.send(stype, template_id, phone, vcode)
async def __validation(self, stype, template_id, params, phone, resp) -> dict:
env = ServerEnv()
async with get_sor_context(env, 'kboss') as sor:
send_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log = {
'id': uuid(),
'send_type': stype,
'mobile': phone,
'message': str(params),
'send_time': send_time,
'send_status': '1'
}
if resp.code == '1000':
msg = f'{send_time} {phone} 百度短信发送成功code: {resp.code}'
log['send_status'] = '1'
await sor.C('sms_record', log)
return {'status': True, 'msg': msg}
else:
msg = f'{send_time} {phone} 百度短信发送失败code: {resp.code},参考文档: {self.doc}'
log['send_status'] = '0'
log['remark'] = msg
await sor.C('sms_record', log)
return {'status': False, 'msg': msg}
async def generate_sms_code(self, length=None, expire_minutes=None, phone=None):
length = int(length) if length is not None else 6
expire_minutes = int(expire_minutes) if expire_minutes is not None else 5
code = ''.join(random.choices(string.digits, k=length))
code_id = uuid()
expire_time = datetime.datetime.now() + datetime.timedelta(minutes=expire_minutes)
env = ServerEnv()
async with get_sor_context(env, 'kboss') as sor:
await sor.C('validatecode', {
'id': code_id,
'vcode': code,
'expire_time': expire_time,
'del_flg': '0',
'create_at': datetime.datetime.now()
})
return code_id, code
async def check_sms_code(self, code_id: str, vcode: str) -> bool:
env = ServerEnv()
async with get_sor_context(env, 'kboss') as sor:
code_info = await sor.R('validatecode', {'id': code_id, 'del_flg': '0'})
if not code_info:
return False
code_info = code_info[0]
if code_info['vcode'] != vcode:
return False
now = datetime.datetime.now()
# 处理 code_info['expire_time'] 为 datetime 对象
if isinstance(code_info['expire_time'], str):
code_info['expire_time'] = datetime.datetime.fromisoformat(code_info['expire_time'])
if now > code_info['expire_time']:
return False
await sor.U('validatecode', {'id': code_id, 'del_flg': '1'})
return True
async def send_sms(self, phone: str, stype: str) -> dict:
code_id, code = await self.generate_sms_code()
if code_id is None:
return {
'status': 'error',
'data': {
'message': '生成的手机号出错'
}
}
vcode = {'SMSvCode': code}
result = await self.send_vcode(phone, stype, vcode)
if result.get('status'):
return {
'status': 'ok',
'data': {
'codeid': code_id
}
}
else:
return {
'status': 'error',
'data': {
'message': result.get('msg', '发送失败')
}
}
_sms_engine = None
def get_sms_engine() -> SMSEngine:
global _sms_engine
if _sms_engine is None:
_sms_engine = SMSEngine()
return _sms_engine