Files
d3i-szct/apps/app_handler.py
T
2026-06-02 17:46:38 +08:00

184 lines
6.1 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.
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)