Squashed 'paste-framework/' content from commit 34e8684
git-subtree-dir: paste-framework git-subtree-split: 34e8684c4bc3cebbe177509f42ab4ef5b5425a7a
This commit is contained in:
Executable
+249
@@ -0,0 +1,249 @@
|
||||
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')
|
||||
Reference in New Issue
Block a user