# -*- 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