235 lines
7.5 KiB
Python
235 lines
7.5 KiB
Python
# -*- 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
|