4729698049
git-subtree-dir: paste-framework git-subtree-split: 34e8684c4bc3cebbe177509f42ab4ef5b5425a7a
250 lines
8.5 KiB
Python
Executable File
250 lines
8.5 KiB
Python
Executable File
import importlib
|
|
import json
|
|
import logging
|
|
from abc import ABC
|
|
from collections import namedtuple
|
|
from typing import Optional, Union, Any, Type
|
|
|
|
import tornado.web
|
|
|
|
from paste.core import config
|
|
from paste.db.basemodel import BaseModel
|
|
from paste.util.encoder import JsonDumpsEncoder
|
|
from paste.core.logging import echo_log
|
|
|
|
|
|
def init_user_class():
|
|
"""
|
|
从配置文件初始化用户类。默认采用 rbac.RbacUser。
|
|
"""
|
|
|
|
try:
|
|
# 若没有配置 RBAC 直接返回 None
|
|
_rbac_cfg = config.get_config('rbac.user_class', None)
|
|
except AssertionError:
|
|
return None
|
|
|
|
_cfg_user_class: str = config.get_config('rbac.user_class', None)
|
|
if _cfg_user_class is not None:
|
|
_parts = _cfg_user_class.split('.')
|
|
_module_name = '.'.join(_parts[:-1])
|
|
_user_module = importlib.import_module(_module_name)
|
|
_user_class = getattr(_user_module, _parts[-1])
|
|
return _user_class
|
|
|
|
from paste.rbac.rbac_user import RbacUser
|
|
return RbacUser
|
|
|
|
|
|
class RequestHandler(tornado.web.RequestHandler, ABC):
|
|
"""
|
|
请求控制父类。
|
|
"""
|
|
|
|
route_pattern: Optional[str] = None
|
|
"""
|
|
URL 路径模式。由装饰器 web.decorators.route 赋值,在 base.Application.load_handler_module 自动加载时调用,作为访问
|
|
路径,设置到 Application 中。
|
|
"""
|
|
|
|
user_class: Type[BaseModel] = init_user_class()
|
|
"""
|
|
用户数据处理类。装饰器 web.decorators.auth_token 执行令牌验证时调用该类,用于创建用户对象,并保存在 current_user 属性中。
|
|
注意:这里仅初始化类,而不创建对象。该类允许用户继承扩展,然后自行配置。主要用于执行有关用户的数据操作。
|
|
"""
|
|
|
|
@classmethod
|
|
def log(cls, msg: Union[str, Exception], level: int = logging.INFO, is_log_exc: bool = False):
|
|
"""
|
|
输出日志文本。
|
|
|
|
:param msg: 消息内容,当是 Exception 对象时,从 args 中取出第一项作为消息
|
|
:param level: 消息等级
|
|
:param is_log_exc: 是否输出异常信息到日志文件
|
|
"""
|
|
echo_log(msg=msg, level=level, is_log_exc=is_log_exc)
|
|
|
|
@classmethod
|
|
def dict_to_namedtuple(cls, name, data):
|
|
"""
|
|
递归转换字典和列表中的字典为 namedtuple 对象。
|
|
|
|
参数:
|
|
name: 用于创建 namedtuple 的名称
|
|
data: 要转换的数据,可以是 dict、list 或基本类型
|
|
|
|
返回:
|
|
转换后的 namedtuple 对象或列表
|
|
"""
|
|
if isinstance(data, dict):
|
|
# 处理字典类型
|
|
NT = namedtuple(name, data.keys())
|
|
return NT(**{
|
|
k: cls.dict_to_namedtuple(k, v)
|
|
for k, v in data.items()
|
|
})
|
|
elif isinstance(data, list):
|
|
# 处理列表类型:递归转换列表中的每个元素
|
|
return [
|
|
cls.dict_to_namedtuple(f"{name}_item", item)
|
|
if isinstance(item, (dict, list)) else item
|
|
for item in data
|
|
]
|
|
else:
|
|
# 基本类型直接返回
|
|
return data
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.rule_kwargs = {}
|
|
"""
|
|
规则参数,用于在控制器和规则之间做数据交换
|
|
"""
|
|
|
|
self.token_payload: dict[str: Any] = {}
|
|
"""
|
|
令牌配载数据字典。装饰器 web.decorators.auth_token 执行令牌验证时解码并赋值。 在 HandlerRequest 子类中
|
|
只要配置 auth_token 装饰即可使用该配载数据。
|
|
|
|
其结构为::
|
|
|
|
{
|
|
'iss': private_iss,
|
|
'iat': datetime.datetime.utcnow(),
|
|
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=7),
|
|
'params': {
|
|
'id': user_id,
|
|
'username': username
|
|
}
|
|
}
|
|
"""
|
|
|
|
async def after_auth_token(self, token_payload: dict):
|
|
"""
|
|
在验证 Token 后调用的函数,子类可覆盖。
|
|
|
|
:param token_payload: Token 数据项
|
|
"""
|
|
pass
|
|
|
|
def token_params(self) -> dict:
|
|
"""
|
|
取出 Token 中的参数字典。
|
|
|
|
:return: 参数字典
|
|
"""
|
|
return self.token_payload.get('params', {})
|
|
|
|
def token_param(self, key):
|
|
"""
|
|
取出 Token 参数字典中的参数。
|
|
|
|
:param key: 参数名称
|
|
"""
|
|
return self.token_params().get(key, None)
|
|
|
|
def set_default_headers(self):
|
|
"""
|
|
设置默认的请求头。
|
|
"""
|
|
request_headers = dict(self.request.headers)
|
|
allow_headers = [
|
|
'Accept', 'Content-Type', 'Origin', 'Access-Token', 'ClientId', 'Timestamp', 'Verify-Hash', 'Security-Key'
|
|
]
|
|
allow_methods = [
|
|
'OPTIONS', 'GET', 'POST'
|
|
]
|
|
allow_origins = [
|
|
request_headers.get('Origin', '*')
|
|
]
|
|
content_type = [
|
|
request_headers.get('Content-type', 'application/json')
|
|
]
|
|
response_header_cfg = {
|
|
'Access-Control-Allow-Headers': ','.join(set(allow_headers)),
|
|
'Access-Control-Allow-Methods': ','.join(set(allow_methods)),
|
|
'Access-Control-Allow-Origin': ','.join(set(allow_origins)),
|
|
'Access-Control-Allow-Credentials': 'true',
|
|
'Content-type': ','.join(set(content_type)),
|
|
}
|
|
for _k, _v in response_header_cfg.items():
|
|
self.set_header(_k, _v)
|
|
|
|
def get_current_user(self) -> Any:
|
|
if not hasattr(self, '_current_user'):
|
|
if self.user_class is not None:
|
|
# 设置了用户类,但是未创建对象的,这里默认创建空用户对象
|
|
setattr(self, '_current_user', self.user_class())
|
|
else:
|
|
setattr(self, '_current_user', None)
|
|
return self._current_user
|
|
|
|
def options(self):
|
|
"""
|
|
处理跨域请求中的 OPTIONS 预检。
|
|
"""
|
|
self.set_status(status_code=200)
|
|
self.finish()
|
|
|
|
def request_arguments(self):
|
|
"""
|
|
取得所有请求参数。若 self.request.arguments 中有参数,则优先读取。
|
|
若无参数,则从 self.request.body 读取,且该参数必须为 JSON 结构。
|
|
|
|
:return: 请求参数字典
|
|
"""
|
|
_args: dict[str: Any] = dict()
|
|
if len(self.request.arguments) > 0:
|
|
# 按 Form 提交时,从 Form 参数中读取命令,命令参数从 request.arguments 读取
|
|
for _n, _v in self.request.arguments.items():
|
|
if isinstance(_v, list):
|
|
# 对数组进行分解
|
|
if len(_v) == 1:
|
|
_args[_n] = _v[0].decode("utf-8")
|
|
else:
|
|
_args[_n] = [__v.decode("utf-8") for __v in _v]
|
|
else:
|
|
_args[_n] = f"{_v}"
|
|
else:
|
|
# 非 Form 提交时,从 Body 解析命令,命令参数从 body.params 读取
|
|
_body = self.request.body if self.request.body else '{}'
|
|
_args = json.loads(_body)
|
|
return _args
|
|
|
|
def response_ok(self, **kwargs):
|
|
"""
|
|
成功响应内容。
|
|
|
|
:param kwargs: 参数
|
|
"""
|
|
self.set_status(status_code=200)
|
|
chunk = {'code': 200, 'status': 'OK'}
|
|
chunk.update(kwargs)
|
|
self.write(json.dumps(chunk, cls=JsonDumpsEncoder, ensure_ascii=False))
|
|
self.set_header('Content-Type', 'application/json')
|
|
|
|
def response_error(self, e: Exception, status_code: int = 200, api_status_code: int = None, **kwargs):
|
|
"""
|
|
错误响应内容。
|
|
|
|
:param e: 异常对象
|
|
:param status_code: HTTP/HTTPS 响应状态码
|
|
:param api_status_code: API 状态码,若不提供则使用 status_code 参数
|
|
"""
|
|
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['message'] = 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')
|