184 lines
6.1 KiB
Python
184 lines
6.1 KiB
Python
import datetime
|
||
import json
|
||
import os
|
||
from abc import ABC
|
||
from typing import Optional, Callable, Awaitable
|
||
|
||
from paste.rbac.rbac_user import RbacUser
|
||
from paste.util.encoder import JsonDumpsEncoder
|
||
from paste.web.handler import RequestHandler
|
||
from paste.web.param_aware_loader import ParamAwareLoader
|
||
|
||
|
||
class AppHandler(RequestHandler, ABC):
|
||
"""
|
||
控制器基类。
|
||
"""
|
||
|
||
commands: dict[str, Callable] = {}
|
||
"""
|
||
API 接口命令字典,其结构为命令名称指向对应的方法。
|
||
|
||
其结构如下::
|
||
|
||
{
|
||
command_name: method
|
||
}
|
||
"""
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
self.user: Optional[RbacUser] = None
|
||
"""
|
||
当前登录用户对象。
|
||
"""
|
||
|
||
self.start_at = datetime.datetime.now()
|
||
"""
|
||
实例初始化时间。
|
||
"""
|
||
|
||
self.command = ""
|
||
"""
|
||
命令。
|
||
"""
|
||
|
||
self.request_params = {}
|
||
"""
|
||
命令参数。
|
||
"""
|
||
|
||
async def after_auth_token(self, token_payload: dict):
|
||
"""
|
||
初始化登录用户信息。
|
||
"""
|
||
from paste.security import token
|
||
if token_payload != token.PRIVATE_ISS:
|
||
from jwt import InvalidTokenError
|
||
raise InvalidTokenError()
|
||
|
||
async def run_command(self):
|
||
"""
|
||
根据请求参数运行命令方法,返回命令执行结果。
|
||
"""
|
||
self.get_request_params()
|
||
assert self.command in self.commands, '请提供正确的命令参数.'
|
||
|
||
# 读取命令方法对象,并执行
|
||
_cmd_func = self.commands[self.command]
|
||
_result = _cmd_func(self, **self.request_params)
|
||
# 处理异步方法执行
|
||
if isinstance(_result, Awaitable):
|
||
_result = await _result
|
||
return _result
|
||
|
||
async def gen_html(self, template_file: str, **kwargs):
|
||
"""
|
||
生成 HTML 内容。
|
||
|
||
:param template_file: 模板文件
|
||
:param kwargs: 参数数据字典
|
||
:return: 返回生成的 HTML 文件内容
|
||
"""
|
||
# 将参数字典转换为 namedtuple,名称固定
|
||
template_data_obj = self.dict_to_namedtuple('TemplateData', {**kwargs})
|
||
# 手动构建完整命名空间,加入自定义参数,传给生成器
|
||
namespace = self.get_template_namespace()
|
||
namespace.update({'td': template_data_obj})
|
||
|
||
# 获取模板文件
|
||
template_file = f"{self.application.settings.get('template_path')}/{template_file}"
|
||
# 用参数感知模板加载器,加载模板文件,并传入 namespace 以便在加载完成后,执行数据准备
|
||
loader = ParamAwareLoader(os.path.dirname(template_file), namespace=namespace)
|
||
# 从文件中加载模板,同步完成数据准备
|
||
template = await loader.load_with_prepare(os.path.basename(template_file))
|
||
|
||
# 渲染模板,传入需要的数据
|
||
output = template.generate(**namespace)
|
||
return output
|
||
|
||
def response_ok(self, **kwargs):
|
||
self.log_request_end()
|
||
super().response_ok(**kwargs)
|
||
|
||
def response_error(self, e: Exception, status_code: int = 200, api_status_code: int = None, **kwargs):
|
||
self.log_request_end()
|
||
if api_status_code is None:
|
||
api_status_code = status_code
|
||
|
||
self.set_status(status_code=status_code)
|
||
chunk = {'code': api_status_code, 'status': 'error'}
|
||
chunk.update(kwargs)
|
||
if len(e.args) > 0 and isinstance(e.args[0], str):
|
||
chunk['msg'] = e.args[0]
|
||
if len(e.args) > 1:
|
||
if isinstance(e.args[1], dict):
|
||
chunk.update(e.args[1])
|
||
elif isinstance(e.args[1], list):
|
||
chunk['errors'] = e.args[1]
|
||
self.write(json.dumps(chunk, cls=JsonDumpsEncoder, ensure_ascii=False))
|
||
self.set_header('Content-Type', 'application/json')
|
||
|
||
def get_request_params(self):
|
||
"""
|
||
读取命令名称及请求参数。注意,参数命名应当避开 cmd 和 params。
|
||
|
||
该方法自动合并参数,并输出请求开始日志。
|
||
|
||
支持通过 Form 或 Json 两种方式提交请求并读取相应参数。
|
||
|
||
如使用 Form 方式,则应当在 form-data 中包含名为 cmd 的输入项,其值为对应的命令,其他输入项为命令参数。
|
||
注意:不会自动读取上传的文件数据,可通过::
|
||
|
||
self.request.files
|
||
|
||
方法读取上传的文件。
|
||
|
||
如使用 Json 方式,则应当遵循以下结构::
|
||
|
||
{
|
||
cmd: command_name,
|
||
params:
|
||
{
|
||
key: value
|
||
}
|
||
}
|
||
|
||
:return: 命令,命令对应的参数
|
||
"""
|
||
_arguments = self.request_arguments()
|
||
_cmd = _arguments.get('cmd', None)
|
||
_params = _arguments.get('params', None)
|
||
if _params is None:
|
||
_arguments.pop('cmd', None)
|
||
_params = _arguments
|
||
|
||
self.command = _cmd
|
||
self.request_params = _params
|
||
# 合成规则参数到参数字典,规则参数可在相应规则中修改
|
||
self.request_params.update(self.rule_kwargs)
|
||
# 取得命令和参数之后,记录请求开始日志
|
||
self.log_request_start()
|
||
|
||
return self.command, self.request_params
|
||
|
||
def log_request_end(self):
|
||
end_at = datetime.datetime.now()
|
||
total_delta = (end_at - self.start_at).total_seconds()
|
||
_spend = f"耗时:{total_delta:f} 秒."
|
||
|
||
_user_name = self.user.username if self.user else 'Unknown'
|
||
_log = f"O 用户:{_user_name} 完成 {self.request.uri}"
|
||
_log = f"{_log} 接口命令 {self.command},{_spend}" if self.command else f"{_log} 请求,{_spend}"
|
||
self.log(_log)
|
||
|
||
def log_request_start(self):
|
||
"""
|
||
收到请求时记录的日志
|
||
"""
|
||
_user_name = self.user.username if self.user else 'Unknown'
|
||
_log = f"I 用户:{_user_name} 请求 {self.request.uri}"
|
||
_log = f"{_log} 接口命令 {self.command}." if self.command else f"{_log}."
|
||
self.log(_log)
|