init: 补充全局异常处理、基础接口实现
parent
61eea6dc63
commit
51eb18cd9f
@ -0,0 +1,82 @@
|
|||||||
|
"""全局异常处理
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
__all__ = ['AppException']
|
||||||
|
|
||||||
|
from enum import IntEnum, Enum
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
class CustomEnum(Enum):
|
||||||
|
@classmethod
|
||||||
|
def valid(cls, value):
|
||||||
|
try:
|
||||||
|
cls(value)
|
||||||
|
return True
|
||||||
|
except BaseException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def values(cls):
|
||||||
|
return [member.value for member in cls.__members__.values()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def names(cls):
|
||||||
|
return [member.name for member in cls.__members__.values()]
|
||||||
|
|
||||||
|
|
||||||
|
class RetCode(IntEnum, CustomEnum):
|
||||||
|
"""
|
||||||
|
SUCCESS = 0 # 成功
|
||||||
|
NOT_EFFECTIVE = 10 # 未生效
|
||||||
|
EXCEPTION_ERROR = 100 # 异常错误
|
||||||
|
ARGUMENT_ERROR = 101 # 参数错误
|
||||||
|
DATA_ERROR = 102 # 数据错误
|
||||||
|
OPERATING_ERROR = 103 # 操作错误
|
||||||
|
CONNECTION_ERROR = 105 # 连接错误
|
||||||
|
RUNNING = 106 # 运行中
|
||||||
|
PERMISSION_ERROR = 108 # 权限错误
|
||||||
|
AUTHENTICATION_ERROR = 109 # 认证错误
|
||||||
|
UNAUTHORIZED = 401 # 未授权
|
||||||
|
SERVER_ERROR = 500 # 服务器错误
|
||||||
|
FORBIDDEN = 403 # 禁止访问
|
||||||
|
NOT_FOUND = 404 # 未找到
|
||||||
|
"""
|
||||||
|
SUCCESS = 0 # 成功
|
||||||
|
NOT_EFFECTIVE = 10 # 未生效
|
||||||
|
EXCEPTION_ERROR = 100 # 异常错误
|
||||||
|
ARGUMENT_ERROR = 101 # 参数错误
|
||||||
|
DATA_ERROR = 102 # 数据错误
|
||||||
|
OPERATING_ERROR = 103 # 操作错误
|
||||||
|
CONNECTION_ERROR = 105 # 连接错误
|
||||||
|
RUNNING = 106 # 运行中
|
||||||
|
PERMISSION_ERROR = 108 # 权限错误
|
||||||
|
AUTHENTICATION_ERROR = 109 # 认证错误
|
||||||
|
UNAUTHORIZED = 401 # 未授权
|
||||||
|
SERVER_ERROR = 500 # 服务器错误
|
||||||
|
FORBIDDEN = 403 # 禁止访问
|
||||||
|
NOT_FOUND = 404 # 未找到
|
||||||
|
|
||||||
|
class AppException(Exception):
|
||||||
|
"""应用异常基类
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, msg, *args, code: int = None, echo_exc: bool = False, **kwargs):
|
||||||
|
super().__init__()
|
||||||
|
self._code = RetCode.SERVER_ERROR.value if code is None else code
|
||||||
|
self._message = msg
|
||||||
|
self.echo_exc = echo_exc
|
||||||
|
self.args = args or []
|
||||||
|
self.kwargs = kwargs or {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def code(self) -> int:
|
||||||
|
return self._code
|
||||||
|
|
||||||
|
@property
|
||||||
|
def msg(self) -> str:
|
||||||
|
return self._message
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{}: {}'.format(self.code, self.msg)
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.exceptions import RequestValidationError
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
from utils.api_utils import server_error_response
|
||||||
|
from .base import AppException
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_exception(app: FastAPI):
|
||||||
|
"""配置全局异常处理
|
||||||
|
"""
|
||||||
|
|
||||||
|
@app.exception_handler(AppException)
|
||||||
|
async def app_exception_handler(request: Request, exc: AppException):
|
||||||
|
"""处理自定义异常
|
||||||
|
code: .
|
||||||
|
"""
|
||||||
|
if exc.echo_exc:
|
||||||
|
logging.error('app_exception_handler: url=[%s]', request.url.path)
|
||||||
|
logging.error(exc, exc_info=True)
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=200,
|
||||||
|
content={'code': exc.code, 'message': exc.msg, 'data': False})
|
||||||
|
|
||||||
|
@app.exception_handler(RequestValidationError)
|
||||||
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=422,
|
||||||
|
content={"detail": exc.errors(), "body": exc.body}
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.exception_handler(Exception)
|
||||||
|
async def global_exception_handler(request: Request, exc: Exception):
|
||||||
|
return server_error_response(exc)
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Union, Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from exceptions.base import AppException
|
||||||
|
from utils import get_uuid
|
||||||
|
|
||||||
|
|
||||||
|
class BaseController:
|
||||||
|
|
||||||
|
def __init__(self, service):
|
||||||
|
self.service = service
|
||||||
|
|
||||||
|
async def base_page(self, req: Union[dict, BaseModel], dto_class: Type[BaseModel] = None):
|
||||||
|
if not isinstance(req, dict):
|
||||||
|
req = req.model_dump()
|
||||||
|
result = await self.service.get_by_page(req)
|
||||||
|
datas = result.data
|
||||||
|
if datas and dto_class:
|
||||||
|
result.data = self.service.entity_conversion_dto(datas, dto_class)
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def base_list(self, req: Union[dict, BaseModel], dto_class: Type[BaseModel] = None):
|
||||||
|
if not isinstance(req, dict):
|
||||||
|
req = req.model_dump()
|
||||||
|
datas = await self.service.get_list(req)
|
||||||
|
if datas and dto_class:
|
||||||
|
datas = self.service.entity_conversion_dto(datas, dto_class)
|
||||||
|
return datas
|
||||||
|
|
||||||
|
async def get_all(self, dto_class: Type[BaseModel] = None):
|
||||||
|
result = await self.service.get_all()
|
||||||
|
if dto_class:
|
||||||
|
result = self.service.entity_conversion_dto(result, dto_class)
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def get_by_id(self, id: str, dto_class: Type[BaseModel] = None):
|
||||||
|
data = await self.service.get_by_id(id)
|
||||||
|
if not data:
|
||||||
|
raise AppException(f"不存在 id 为{id}的数据")
|
||||||
|
result = data.to_dict()
|
||||||
|
if dto_class:
|
||||||
|
result = self.service.entity_conversion_dto(result, dto_class)
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def add(self, req: Union[dict, BaseModel]):
|
||||||
|
if not isinstance(req, dict):
|
||||||
|
req = req.model_dump()
|
||||||
|
req["id"] = get_uuid()
|
||||||
|
try:
|
||||||
|
return await self.service.save(**req)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
raise AppException(f"添加失败, error: {str(e)}")
|
||||||
|
|
||||||
|
async def delete(self, id: str, db_query_data=None):
|
||||||
|
if db_query_data is None:
|
||||||
|
db_query_data = await self.service.get_by_id(id)
|
||||||
|
if not db_query_data:
|
||||||
|
raise AppException(f"数据不存在")
|
||||||
|
self.service.check_base_permission(db_query_data)
|
||||||
|
try:
|
||||||
|
return await self.service.delete_by_id(id)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
raise AppException(f"删除失败")
|
||||||
|
|
||||||
|
async def update(self, request: BaseModel, db_query_data=None):
|
||||||
|
params = request.model_dump()
|
||||||
|
req = {k: v for k, v in params.items() if v is not None}
|
||||||
|
data_id = req.get("id")
|
||||||
|
if db_query_data is None:
|
||||||
|
db_query_data = await self.service.get_by_id(data_id)
|
||||||
|
if not db_query_data:
|
||||||
|
raise AppException(f"数据不存在")
|
||||||
|
self.service.check_base_permission(db_query_data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self.service.update_by_id(data_id, req)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
raise AppException(f"更新失败, error: {str(e)}")
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
|
from server_info import ServerInfo
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
router = APIRouter(prefix='/monitor', tags=["缓存监控服务"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/server',summary='服务监控')
|
||||||
|
def monitor_server():
|
||||||
|
"""服务器信息监控"""
|
||||||
|
return {
|
||||||
|
'cpu': ServerInfo.get_cpu_info(),
|
||||||
|
'mem': ServerInfo.get_mem_info(),
|
||||||
|
'sys': ServerInfo.get_sys_info(),
|
||||||
|
'disk': ServerInfo.get_disk_info(),
|
||||||
|
'py': ServerInfo.get_py_info(),
|
||||||
|
}
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import List, Any
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
from config import get_settings
|
||||||
|
from utils.ip_utils import IpUtil
|
||||||
|
|
||||||
|
|
||||||
|
def get_attr(obj: Any, attr: str, default: Any = None) -> Any:
|
||||||
|
return getattr(obj, attr, default)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerInfo:
|
||||||
|
"""服务器相关信息"""
|
||||||
|
datetime_fmt = get_settings().datetime_fmt
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_size(data, suffix='B') -> str:
|
||||||
|
"""
|
||||||
|
按照正确的格式缩放字节
|
||||||
|
eg:
|
||||||
|
1253656 => '1.20MB'
|
||||||
|
1253656678 => '1.17GB'
|
||||||
|
"""
|
||||||
|
factor = 1024
|
||||||
|
for unit in ['', 'K', 'M', 'G', 'T', 'P']:
|
||||||
|
if data < factor:
|
||||||
|
return f'{data:.2f}{unit}{suffix}'
|
||||||
|
data /= factor
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fmt_timedelta(td: timedelta) -> str:
|
||||||
|
"""格式化显示timedelta
|
||||||
|
eg:
|
||||||
|
timedelta => xx天xx小时xx分钟
|
||||||
|
"""
|
||||||
|
rem = td.seconds
|
||||||
|
days, rem = rem // 86400, rem % 86400
|
||||||
|
hours, rem = rem // 3600, rem % 3600
|
||||||
|
minutes = rem // 60
|
||||||
|
res = f'{minutes}分钟'
|
||||||
|
if hours > 0:
|
||||||
|
res = f'{hours}小时{res}'
|
||||||
|
if days > 0:
|
||||||
|
res = f'{days}天{res}'
|
||||||
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_cpu_info() -> dict:
|
||||||
|
"""获取CPU信息"""
|
||||||
|
res = {'cpu_num': psutil.cpu_count(logical=True)}
|
||||||
|
cpu_times = psutil.cpu_times()
|
||||||
|
total = cpu_times.user + cpu_times.nice + cpu_times.system + cpu_times.idle \
|
||||||
|
+ get_attr(cpu_times, 'iowait', 0.0) + get_attr(cpu_times, 'irq', 0.0) \
|
||||||
|
+ get_attr(cpu_times, 'softirq', 0.0) + get_attr(cpu_times, 'steal', 0.0)
|
||||||
|
res['total'] = round(total, 2)
|
||||||
|
res['sys'] = round(cpu_times.system / total, 2)
|
||||||
|
res['used'] = round(cpu_times.user / total, 2)
|
||||||
|
res['wait'] = round(get_attr(cpu_times, 'iowait', 0.0) / total, 2)
|
||||||
|
res['free'] = round(cpu_times.idle / total, 2)
|
||||||
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_mem_info() -> dict:
|
||||||
|
"""获取内存信息"""
|
||||||
|
number = 1024 ** 3
|
||||||
|
return {
|
||||||
|
'total': round(psutil.virtual_memory().total / number, 2),
|
||||||
|
'used': round(psutil.virtual_memory().used / number, 2),
|
||||||
|
'free': round(psutil.virtual_memory().available / number, 2),
|
||||||
|
'usage': round(psutil.virtual_memory().percent, 2)}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_sys_info() -> dict:
|
||||||
|
"""获取服务器信息"""
|
||||||
|
return {
|
||||||
|
'computerName': IpUtil.get_host_name(),
|
||||||
|
'computerIp': IpUtil.get_host_ip(),
|
||||||
|
'userDir': os.path.dirname(os.path.abspath(os.path.join(__file__, '../..'))),
|
||||||
|
'osName': platform.system(),
|
||||||
|
'osArch': platform.machine()}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_disk_info() -> List[dict]:
|
||||||
|
"""获取磁盘信息"""
|
||||||
|
disk_info = []
|
||||||
|
for disk in psutil.disk_partitions():
|
||||||
|
usage = psutil.disk_usage(disk.mountpoint)
|
||||||
|
disk_info.append({
|
||||||
|
'dirName': disk.mountpoint,
|
||||||
|
'sysTypeName': disk.fstype,
|
||||||
|
'typeName': disk.device,
|
||||||
|
'total': ServerInfo.get_size(usage.total),
|
||||||
|
'free': ServerInfo.get_size(usage.free),
|
||||||
|
'used': ServerInfo.get_size(usage.used),
|
||||||
|
'usage': round(usage.percent, 2),
|
||||||
|
})
|
||||||
|
return disk_info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_py_info():
|
||||||
|
"""获取Python环境及服务信息"""
|
||||||
|
number = 1024 ** 2
|
||||||
|
cur_proc = psutil.Process(os.getpid())
|
||||||
|
mem_info = cur_proc.memory_info()
|
||||||
|
start_dt = datetime.fromtimestamp(cur_proc.create_time())
|
||||||
|
return {
|
||||||
|
'name': 'Python',
|
||||||
|
'version': platform.python_version(),
|
||||||
|
'home': sys.executable,
|
||||||
|
'inputArgs': '[{}]'.format(', '.join(sys.argv[1:])),
|
||||||
|
'total': round(mem_info.vms / number, 2),
|
||||||
|
'max': round(mem_info.vms / number, 2),
|
||||||
|
'free': round((mem_info.vms - mem_info.rss) / number, 2),
|
||||||
|
'usage': round(mem_info.rss / number, 2),
|
||||||
|
'runTime': ServerInfo.fmt_timedelta(datetime.now() - start_dt),
|
||||||
|
'startTime': start_dt.strftime(ServerInfo.datetime_fmt),
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IpUtil:
|
||||||
|
"""IP工具类"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_host_name() -> str:
|
||||||
|
"""获取本地IP地址"""
|
||||||
|
return socket.gethostname()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_host_ip() -> str:
|
||||||
|
"""获取本地主机名"""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
s.connect(('10.0.0.0', 0))
|
||||||
|
ip = s.getsockname()[0]
|
||||||
|
except Exception as _:
|
||||||
|
ip = '127.0.0.1'
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
return ip
|
||||||
Loading…
Reference in New Issue