mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 04:46:26 +00:00
refactor(api): 重构API路由及模块结构,清理废弃模型代码
- 修改alembic配置以使用异步数据库URI和更新Base类引用 - 新增数据库Schema优化脚本,统一字段长度,添加索引,规范外键策略 - 重构API路由管理,按模块类型分组并统一前缀 - 删除示例、监控及系统各子模块的模型定义,减少冗余代码 - 将mcp_server相关代码迁移到module_ai模块下,规范模块目录结构 - 迁移example控制器至module_application.application模块,并重命名相关服务和参数名 - 优化示例控制器中的依赖和响应结构,统一命名规范
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
from redis.asyncio.client import Redis
|
||||
|
||||
from app.common.response import SuccessResponse
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.core.dependencies import AuthPermission, redis_getter
|
||||
from app.core.logger import logger
|
||||
from .service import CacheService
|
||||
|
||||
|
||||
CacheRouter = APIRouter(route_class=OperationLogRoute, prefix="/cache", tags=["缓存监控"])
|
||||
|
||||
|
||||
@CacheRouter.get(
|
||||
'/info',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:cache:query']))],
|
||||
summary="获取缓存监控信息",
|
||||
description="获取缓存监控信息"
|
||||
)
|
||||
async def get_monitor_cache_info_controller(
|
||||
redis: Redis = Depends(redis_getter)
|
||||
) -> JSONResponse:
|
||||
"""获取缓存监控统计信息"""
|
||||
result = await CacheService.get_cache_monitor_statistical_info_service(redis=redis)
|
||||
logger.info('获取缓存监控信息成功')
|
||||
return SuccessResponse(data=result, msg='获取缓存监控信息成功')
|
||||
|
||||
|
||||
@CacheRouter.get(
|
||||
'/get/names',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:cache:query']))],
|
||||
summary="获取缓存名称列表",
|
||||
description="获取缓存名称列表"
|
||||
)
|
||||
async def get_monitor_cache_name_controller() -> JSONResponse:
|
||||
"""获取缓存名称列表"""
|
||||
result = await CacheService.get_cache_monitor_cache_name_service()
|
||||
logger.info('获取缓存名称列表成功')
|
||||
return SuccessResponse(data=result, msg='获取缓存名称列表成功')
|
||||
|
||||
|
||||
@CacheRouter.get(
|
||||
'/get/keys/{cache_name}',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:cache:query']))],
|
||||
summary="获取缓存键名列表",
|
||||
description="获取缓存键名列表"
|
||||
)
|
||||
async def get_monitor_cache_key_controller(
|
||||
cache_name: str,
|
||||
redis: Redis = Depends(redis_getter)
|
||||
) -> JSONResponse:
|
||||
"""获取指定缓存名称下的键名列表"""
|
||||
result = await CacheService.get_cache_monitor_cache_key_service(redis=redis, cache_name=cache_name)
|
||||
logger.info(f'获取缓存{cache_name}的键名列表成功')
|
||||
return SuccessResponse(data=result, msg=f'获取缓存{cache_name}的键名列表成功')
|
||||
|
||||
|
||||
@CacheRouter.get(
|
||||
'/get/value/{cache_name}/{cache_key}',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:cache:query']))],
|
||||
summary="获取缓存值",
|
||||
description="获取缓存值"
|
||||
)
|
||||
async def get_monitor_cache_value_controller(
|
||||
cache_name: str,
|
||||
cache_key: str,
|
||||
redis: Redis = Depends(redis_getter)
|
||||
)-> JSONResponse:
|
||||
"""获取指定缓存键的值"""
|
||||
result = await CacheService.get_cache_monitor_cache_value_service(redis=redis, cache_name=cache_name, cache_key=cache_key)
|
||||
logger.info(f'获取缓存{cache_name}:{cache_key}的值成功')
|
||||
return SuccessResponse(data=result, msg=f'获取缓存{cache_name}:{cache_key}的值成功')
|
||||
|
||||
|
||||
@CacheRouter.delete(
|
||||
'/delete/name/{cache_name}',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:cache:delete']))],
|
||||
summary="清除指定缓存名称的所有缓存",
|
||||
description="清除指定缓存名称的所有缓存"
|
||||
)
|
||||
async def clear_monitor_cache_name_controller(
|
||||
cache_name: str,
|
||||
redis: Redis = Depends(redis_getter)
|
||||
) -> JSONResponse:
|
||||
"""清除指定缓存名称下的所有缓存"""
|
||||
result = await CacheService.clear_cache_monitor_cache_name_service(redis=redis, cache_name=cache_name)
|
||||
if not result:
|
||||
raise CustomException(message='清除缓存失败', data=result)
|
||||
logger.info(f'清除缓存{cache_name}成功')
|
||||
return SuccessResponse(msg=f'{cache_name}对应键值清除成功', data=result)
|
||||
|
||||
|
||||
@CacheRouter.delete(
|
||||
'/delete/key/{cache_key}',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:cache:delete']))],
|
||||
summary="清除指定缓存键",
|
||||
description="清除指定缓存键"
|
||||
)
|
||||
async def clear_monitor_cache_key_controller(
|
||||
cache_key: str,
|
||||
redis: Redis = Depends(redis_getter)
|
||||
) -> JSONResponse:
|
||||
"""清除指定缓存键"""
|
||||
result = await CacheService.clear_cache_monitor_cache_key_service(redis=redis, cache_key=cache_key)
|
||||
if not result:
|
||||
raise CustomException(message='清除缓存失败', data=result)
|
||||
logger.info(f'清除缓存键{cache_key}成功')
|
||||
return SuccessResponse(msg=f'{cache_key}清除成功', data=result)
|
||||
|
||||
|
||||
@CacheRouter.delete(
|
||||
'/delete/all',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:cache:delete']))],
|
||||
summary="清除所有缓存",
|
||||
description="清除所有缓存"
|
||||
)
|
||||
async def clear_monitor_cache_all_controller(
|
||||
redis: Redis = Depends(redis_getter)
|
||||
) -> JSONResponse:
|
||||
"""清除所有缓存"""
|
||||
result = await CacheService.clear_cache_monitor_all_service(redis=redis)
|
||||
if not result:
|
||||
raise CustomException(message='清除缓存失败', data=result)
|
||||
logger.info('清除所有缓存成功')
|
||||
return SuccessResponse(msg='所有缓存清除成功', data=result)
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class CacheMonitorSchema(BaseModel):
|
||||
"""缓存监控信息模型"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
command_stats: List[Dict] = Field(default_factory=list, description='Redis命令统计信息')
|
||||
db_size: int = Field(default=0, description='Redis数据库中的Key总数')
|
||||
info: Dict[str, Any] = Field(default_factory=dict, description='Redis服务器信息')
|
||||
|
||||
|
||||
class CacheInfoSchema(BaseModel):
|
||||
"""缓存对象信息模型"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
cache_key: str = Field(..., description='缓存键名')
|
||||
cache_name: str = Field(..., description='缓存名称')
|
||||
cache_value: Any = Field(default=None, description='缓存值')
|
||||
remark: Optional[str] = Field(default=None, description='备注说明')
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from redis.asyncio.client import Redis
|
||||
|
||||
from app.common.enums import RedisInitKeyConfig
|
||||
from app.core.redis_crud import RedisCURD
|
||||
from .schema import CacheMonitorSchema, CacheInfoSchema
|
||||
|
||||
|
||||
class CacheService:
|
||||
"""
|
||||
缓存监控模块服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_cache_monitor_statistical_info_service(cls, redis: Redis)->dict:
|
||||
"""
|
||||
获取缓存监控信息service
|
||||
|
||||
:param redis: Redis对象
|
||||
:return: 缓存监控信息
|
||||
"""
|
||||
info = await RedisCURD(redis).info()
|
||||
db_size = await RedisCURD(redis).db_size()
|
||||
command_stats_dict = await RedisCURD(redis).commandstats()
|
||||
|
||||
command_stats = [
|
||||
dict(name=key.split('_')[1], value=str(value.get('calls'))) for key, value in command_stats_dict.items()
|
||||
]
|
||||
result = CacheMonitorSchema(command_stats=command_stats, db_size=db_size, info=info)
|
||||
|
||||
return result.model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_cache_monitor_cache_name_service(cls)->list:
|
||||
"""
|
||||
获取缓存名称列表信息service
|
||||
|
||||
:return: 缓存名称列表信息
|
||||
"""
|
||||
name_list = []
|
||||
for key_config in RedisInitKeyConfig:
|
||||
name_list.append(
|
||||
CacheInfoSchema(
|
||||
cache_key='',
|
||||
cache_name=key_config.key,
|
||||
cache_value='',
|
||||
remark=key_config.remark,
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
return name_list
|
||||
|
||||
@classmethod
|
||||
async def get_cache_monitor_cache_key_service(cls, redis: Redis, cache_name: str)->list:
|
||||
"""
|
||||
获取缓存键名列表信息service
|
||||
|
||||
:param redis: Redis对象
|
||||
:param cache_name: 缓存名称
|
||||
:return: 缓存键名列表信息
|
||||
"""
|
||||
cache_keys = await RedisCURD(redis).get_keys(f'{cache_name}*')
|
||||
cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f'{cache_name}:')]
|
||||
|
||||
return cache_key_list
|
||||
|
||||
@classmethod
|
||||
async def get_cache_monitor_cache_value_service(cls, redis: Redis, cache_name: str, cache_key: str)->dict:
|
||||
"""
|
||||
获取缓存内容信息service
|
||||
|
||||
:param redis: Redis对象
|
||||
:param cache_name: 缓存名称
|
||||
:param cache_key: 缓存键名
|
||||
:return: 缓存内容信息
|
||||
"""
|
||||
cache_value = await RedisCURD(redis).get(f'{cache_name}:{cache_key}')
|
||||
|
||||
return CacheInfoSchema(cache_key=cache_key, cache_name=cache_name, cache_value=cache_value, remark='').model_dump()
|
||||
|
||||
@classmethod
|
||||
async def clear_cache_monitor_cache_name_service(cls, redis: Redis, cache_name: str)->bool:
|
||||
"""
|
||||
清除缓存名称对应所有键值service
|
||||
|
||||
:param redis: Redis对象
|
||||
:param cache_name: 缓存名称
|
||||
:return: 操作缓存响应信息
|
||||
"""
|
||||
cache_keys = await RedisCURD(redis).get_keys(f'{cache_name}*')
|
||||
if cache_keys:
|
||||
await RedisCURD(redis).delete(*cache_keys)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def clear_cache_monitor_cache_key_service(cls, redis: Redis, cache_key: str)->bool:
|
||||
"""
|
||||
清除缓存名称对应所有键值service
|
||||
|
||||
:param redis: Redis对象
|
||||
:param cache_key: 缓存键名
|
||||
:return: 操作缓存响应信息
|
||||
"""
|
||||
cache_keys = await RedisCURD(redis).get_keys(f'*{cache_key}')
|
||||
if cache_keys:
|
||||
await RedisCURD(redis).delete(*cache_keys)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def clear_cache_monitor_all_service(cls, redis: Redis)->bool:
|
||||
"""
|
||||
清除所有缓存service
|
||||
|
||||
:param redis: Redis对象
|
||||
:return: 操作缓存响应信息
|
||||
"""
|
||||
cache_keys = await RedisCURD(redis).get_keys()
|
||||
if cache_keys:
|
||||
await RedisCURD(redis).delete(*cache_keys)
|
||||
|
||||
return True
|
||||
|
||||
# 避免清除所有的缓存,而采用上面的方式,只清除本系统内指定的所有缓存
|
||||
# return await RedisCURD(redis).clear()
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from app.common.response import StreamResponse, SuccessResponse
|
||||
from app.common.request import PaginationService
|
||||
from app.utils.common_util import bytes2file_response
|
||||
from app.core.base_params import PaginationQueryParams
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.core.logger import logger
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from .param import JobQueryParams
|
||||
from .service import JobService
|
||||
from .schema import (
|
||||
JobCreateSchema,
|
||||
JobUpdateSchema
|
||||
)
|
||||
from app.core.ap_scheduler import SchedulerUtil
|
||||
|
||||
|
||||
JobRouter = APIRouter(route_class=OperationLogRoute, prefix="/job", tags=["定时任务"])
|
||||
|
||||
@JobRouter.get("/detail/{id}", summary="获取定时任务详情", description="获取定时任务详情")
|
||||
async def get_obj_detail_controller(
|
||||
id: int = Path(..., description="定时任务ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await JobService.get_job_detail_service(id=id, auth=auth)
|
||||
logger.info(f"获取定时任务详情成功 {id}")
|
||||
return SuccessResponse(data=result_dict, msg="获取定时任务详情成功")
|
||||
|
||||
@JobRouter.get("/list", summary="查询定时任务", description="查询定时任务")
|
||||
async def get_obj_list_controller(
|
||||
page: PaginationQueryParams = Depends(),
|
||||
search: JobQueryParams = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict_list = await JobService.get_job_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||
logger.info(f"查询定时任务列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询定时任务列表成功")
|
||||
|
||||
@JobRouter.post("/create", summary="创建定时任务", description="创建定时任务")
|
||||
async def create_obj_controller(
|
||||
data: JobCreateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:create"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await JobService.create_job_service(auth=auth, data=data)
|
||||
logger.info(f"创建定时任务成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="创建定时任务成功")
|
||||
|
||||
@JobRouter.put("/update", summary="修改定时任务", description="修改定时任务")
|
||||
async def update_obj_controller(
|
||||
data: JobUpdateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:update"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await JobService.update_job_service(auth=auth, data=data)
|
||||
logger.info(f"修改定时任务成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="修改定时任务成功")
|
||||
|
||||
@JobRouter.delete("/delete", summary="删除定时任务", description="删除定时任务")
|
||||
async def delete_obj_controller(
|
||||
ids: list[int] = Body(..., description="ID列表"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:delete"]))
|
||||
) -> JSONResponse:
|
||||
await JobService.delete_job_service(auth=auth, ids=ids)
|
||||
logger.info(f"删除定时任务成功: {id}")
|
||||
return SuccessResponse(msg="删除定时任务成功")
|
||||
|
||||
@JobRouter.post('/export', summary="导出定时任务", description="导出定时任务")
|
||||
async def export_obj_list_controller(
|
||||
search: JobQueryParams = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:export"]))
|
||||
) -> StreamingResponse:
|
||||
# 获取全量数据
|
||||
result_dict_list = await JobService.get_job_list_service(search=search, auth=auth)
|
||||
export_result = await JobService.export_job_service(data_list=result_dict_list)
|
||||
logger.info('导出定时任务成功')
|
||||
|
||||
return StreamResponse(
|
||||
data=bytes2file_response(export_result),
|
||||
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
headers = {
|
||||
'Content-Disposition': 'attachment; filename=job.xlsx'
|
||||
}
|
||||
)
|
||||
|
||||
@JobRouter.delete("/clear", summary="清空定时任务日志", description="清空定时任务日志")
|
||||
async def clear_obj_log_controller(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:delete"]))
|
||||
) -> JSONResponse:
|
||||
await JobService.clear_job_service(auth=auth)
|
||||
logger.info(f"清空定时任务成功")
|
||||
return SuccessResponse(msg="清空定时任务成功")
|
||||
|
||||
@JobRouter.put("/option", summary="暂停/恢复/重启定时任务", description="暂停/恢复/重启定时任务")
|
||||
async def option_obj_controller(
|
||||
id: int = Body(..., description="定时任务ID"),
|
||||
option: int = Body(..., description="操作类型 1: 暂停 2: 恢复 3: 重启"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:update"]))
|
||||
) -> JSONResponse:
|
||||
await JobService.option_job_service(auth=auth, id=id, option=option)
|
||||
logger.info(f"操作定时任务成功: {id}")
|
||||
return SuccessResponse(msg="操作定时任务成功")
|
||||
|
||||
@JobRouter.get("/log", summary="获取定时任务日志", description="获取定时任务日志", dependencies=[Depends(AuthPermission(permissions=["monitor:job:query"]))])
|
||||
async def get_job_log_controller():
|
||||
data = [
|
||||
{
|
||||
"id": i.id,
|
||||
"name": i.name,
|
||||
"trigger": i.trigger.__class__.__name__,
|
||||
"executor": i.executor,
|
||||
"func": i.func,
|
||||
"func_ref": i.func_ref,
|
||||
"args": i.args,
|
||||
"kwargs": i.kwargs,
|
||||
"misfire_grace_time": i.misfire_grace_time,
|
||||
"coalesce": i.coalesce,
|
||||
"max_instances": i.max_instances,
|
||||
"next_run_time": i.next_run_time,
|
||||
"state": SchedulerUtil.get_job_status()
|
||||
}
|
||||
for i in SchedulerUtil.get_all_jobs()
|
||||
]
|
||||
|
||||
return SuccessResponse(msg="获取定时任务日志成功", data=data)
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
|
||||
from app.core.base_crud import CRUDBase
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from .model import JobModel, JobLogModel
|
||||
from .schema import JobCreateSchema,JobUpdateSchema,JobLogCreateSchema,JobLogUpdateSchema
|
||||
|
||||
|
||||
|
||||
class JobCRUD(CRUDBase[JobModel, JobCreateSchema, JobUpdateSchema]):
|
||||
"""定时任务数据层"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""初始化定时任务CRUD"""
|
||||
self.auth = auth
|
||||
super().__init__(model=JobModel, auth=auth)
|
||||
|
||||
async def get_obj_by_id_crud(self, id: int) -> Optional[JobModel]:
|
||||
"""获取定时任务详情"""
|
||||
return await self.get(id=id)
|
||||
|
||||
async def get_obj_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[JobModel]:
|
||||
"""获取定时任务列表"""
|
||||
return await self.list(search=search, order_by=order_by)
|
||||
|
||||
async def create_obj_crud(self, data: JobCreateSchema) -> Optional[JobModel]:
|
||||
"""创定时任务"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def update_obj_crud(self, id: int, data: JobUpdateSchema) -> Optional[JobModel]:
|
||||
"""更新定时任务"""
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_obj_crud(self, ids: List[int]) -> None:
|
||||
"""删除定时任务"""
|
||||
return await self.delete(ids=ids)
|
||||
|
||||
async def set_obj_field_crud(self, ids: List[int], **kwargs) -> None:
|
||||
"""设置定时任务的可用状态"""
|
||||
return await self.set(ids=ids, **kwargs)
|
||||
|
||||
async def clear_obj_crud(self) -> None:
|
||||
"""清除定时任务日志"""
|
||||
return await self.clear()
|
||||
|
||||
|
||||
class JobLogCRUD(CRUDBase[JobLogModel, JobLogCreateSchema, JobLogUpdateSchema]):
|
||||
"""定时任务日志数据层"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""初始化定时任务日志CRUD"""
|
||||
self.auth = auth
|
||||
super().__init__(model=JobLogModel, auth=auth)
|
||||
|
||||
async def get_obj_log_by_id_crud(self, id: int) -> Optional[JobLogModel]:
|
||||
"""获取定时任务日志详情"""
|
||||
return await self.get(id=id)
|
||||
|
||||
async def get_obj_log_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[JobLogModel]:
|
||||
"""获取定时任务日志列表"""
|
||||
return await self.list(search=search, order_by=order_by)
|
||||
|
||||
async def create_obj_log_crud(self, data: JobLogCreateSchema) -> Optional[JobLogModel]:
|
||||
"""创建定时任务日志"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def delete_obj_log_crud(self, ids: List[int]) -> None:
|
||||
"""删除定时任务日志"""
|
||||
return await self.delete(ids=ids)
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Optional
|
||||
from sqlalchemy import Boolean, String, Integer, Text, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.core.base_model import CreatorMixin, MappedBase
|
||||
|
||||
|
||||
class JobModel(CreatorMixin):
|
||||
"""
|
||||
定时任务调度表
|
||||
"""
|
||||
__tablename__ = 'monitor_job'
|
||||
__table_args__ = ({'comment': '定时任务调度表'})
|
||||
|
||||
name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, default='', comment='任务名称')
|
||||
jobstore: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, default='default', comment='存储器')
|
||||
executor: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, default='default', comment='执行器:将运行此作业的执行程序的名称')
|
||||
trigger: Mapped[str] = mapped_column(String(64), nullable=False, comment='触发器:控制此作业计划的 trigger 对象')
|
||||
trigger_args: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment='触发器参数')
|
||||
func: Mapped[str] = mapped_column(Text, nullable=False, comment='任务函数')
|
||||
args: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment='位置参数')
|
||||
kwargs: Mapped[Optional[str]] = mapped_column(Text, nullable=True, comment='关键字参数')
|
||||
coalesce: Mapped[bool] = mapped_column(Boolean, nullable=True, default=False, comment='是否合并运行:是否在多个运行时间到期时仅运行作业一次')
|
||||
max_instances: Mapped[int] = mapped_column(Integer, nullable=True, default=1, comment='最大实例数:允许的最大并发执行实例数 工作')
|
||||
start_date: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, comment='开始时间')
|
||||
end_date: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, comment='结束时间')
|
||||
|
||||
job_logs: Mapped[Optional[list['JobLogModel']]] = relationship(back_populates="job", lazy="select")
|
||||
|
||||
|
||||
class JobLogModel(MappedBase):
|
||||
"""
|
||||
定时任务调度日志表
|
||||
"""
|
||||
__tablename__ = 'monitor_job_log'
|
||||
__table_args__ = ({'comment': '定时任务调度日志表'})
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
|
||||
job_name: Mapped[str] = mapped_column(String(64),nullable=False,comment='任务名称')
|
||||
job_group: Mapped[str] = mapped_column(String(64),nullable=False,comment='任务组名')
|
||||
job_executor: Mapped[str] = mapped_column(String(64),nullable=False,comment='任务执行器')
|
||||
invoke_target: Mapped[str] = mapped_column(String(500),nullable=False,comment='调用目标字符串')
|
||||
job_args: Mapped[Optional[str]] = mapped_column(String(255),nullable=True,default='',comment='位置参数')
|
||||
job_kwargs: Mapped[Optional[str]] = mapped_column(String(255),nullable=True,default='',comment='关键字参数')
|
||||
job_trigger: Mapped[Optional[str]] = mapped_column(String(255),nullable=True,default='',comment='任务触发器')
|
||||
job_message: Mapped[Optional[str]] = mapped_column(String(500),nullable=True,default='',comment='日志信息')
|
||||
exception_info: Mapped[Optional[str]] = mapped_column(String(2000),nullable=True,default='',comment='异常信息')
|
||||
job_id: Mapped[Optional[int]] = mapped_column(ForeignKey('monitor_job.id'), comment='任务ID')
|
||||
|
||||
# 任务关联关系
|
||||
job: Mapped[Optional["JobModel"]] = relationship(back_populates="job_logs", lazy="select")
|
||||
@@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import Query
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
|
||||
class JobQueryParams:
|
||||
"""定时任务查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str] = Query(None, description="任务名称"),
|
||||
status: Optional[bool] = Query(None, description="状态: 启动,停止"),
|
||||
creator: Optional[int] = Query(None, description="创建人"),
|
||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
# 模糊查询字段
|
||||
self.name = ("like", f"%{name}%") if name else None
|
||||
|
||||
# 精确查询字段
|
||||
self.creator_id = creator
|
||||
self.status = status
|
||||
|
||||
# 时间范围查询
|
||||
if start_time and end_time:
|
||||
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
||||
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
||||
self.created_at = ("between", (start_datetime, end_datetime))
|
||||
|
||||
|
||||
class JobLogQueryParams:
|
||||
"""定时任务查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
status: Optional[bool] = Query(None, description="状态: 正常,失败"),
|
||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||
) -> None:
|
||||
super().__init__()
|
||||
# 精确查询字段
|
||||
self.status = status
|
||||
|
||||
# 时间范围查询
|
||||
if start_time and end_time:
|
||||
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
||||
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
||||
self.created_at = ("between", (start_datetime, end_datetime))
|
||||
@@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from typing import Optional
|
||||
from app.core.base_schema import BaseSchema
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
|
||||
class JobCreateSchema(BaseModel):
|
||||
"""
|
||||
定时任务调度表对应pydantic模型
|
||||
"""
|
||||
name: Optional[str] = Field(..., max_length=64, description='任务名称')
|
||||
func: str = Field(..., description='任务函数')
|
||||
trigger: str = Field(..., description='触发器:控制此作业计划的 trigger 对象')
|
||||
args: Optional[str] = Field(default=None, description='位置参数')
|
||||
kwargs: Optional[str] = Field(default=None, description='关键字参数')
|
||||
coalesce: Optional[bool] = Field(..., description='是否合并运行:是否在多个运行时间到期时仅运行作业一次')
|
||||
max_instances: Optional[int] = Field(default=1, ge=1, description='最大实例数:允许的最大并发执行实例数')
|
||||
jobstore: Optional[str] = Field(..., max_length=64, description='任务存储')
|
||||
executor: Optional[str] = Field(..., max_length=64, description='任务执行器:将运行此作业的执行程序的名称')
|
||||
trigger_args: Optional[str] = Field(default=None, description='触发器参数')
|
||||
start_date: Optional[str] = Field(default=None, description='开始时间')
|
||||
end_date: Optional[str] = Field(default=None, description='结束时间')
|
||||
description: Optional[str] = Field(default=None, description='备注说明')
|
||||
status: Optional[bool] = Field(default=False, description='任务状态:启动,停止')
|
||||
message: Optional[str] = Field(default=None, max_length=500, description='日志信息')
|
||||
|
||||
|
||||
class JobUpdateSchema(JobCreateSchema):
|
||||
"""定时任务更新模型"""
|
||||
id: int = Field(..., gt=0, description="ID")
|
||||
|
||||
|
||||
class JobOutSchema(JobCreateSchema, BaseSchema):
|
||||
"""定时任务响应模型"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
...
|
||||
|
||||
|
||||
class JobLogCreateSchema(BaseModel):
|
||||
"""
|
||||
定时任务调度日志表对应pydantic模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
job_name: Optional[str] = Field(default=None, description='任务名称')
|
||||
job_group: Optional[str] = Field(default=None, description='任务组名')
|
||||
job_executor: Optional[str] = Field(default=None, description='任务执行器')
|
||||
invoke_target: Optional[str] = Field(default=None, description='调用目标字符串')
|
||||
job_args: Optional[str] = Field(default=None, description='位置参数')
|
||||
job_kwargs: Optional[str] = Field(default=None, description='关键字参数')
|
||||
job_trigger: Optional[str] = Field(default=None, description='任务触发器')
|
||||
job_message: Optional[str] = Field(default=None, description='日志信息')
|
||||
status: Optional[bool] = Field(default=None, description='任务状态:正常,失败')
|
||||
exception_info: Optional[str] = Field(default=None, description='异常信息')
|
||||
create_time: Optional[DateTimeStr] = Field(default=None, description='创建时间')
|
||||
|
||||
|
||||
class JobLogUpdateSchema(JobLogCreateSchema):
|
||||
"""定时任务调度日志表更新模型"""
|
||||
...
|
||||
job_log_id: Optional[int] = Field(default=None, description='任务日志ID')
|
||||
|
||||
|
||||
class JobLogOutSchema(JobLogCreateSchema):
|
||||
"""定时任务调度日志表响应模型"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
...
|
||||
@@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Any, List, Dict
|
||||
|
||||
from app.core.ap_scheduler import SchedulerUtil
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.cron_util import CronUtil
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from .schema import JobCreateSchema, JobUpdateSchema, JobOutSchema
|
||||
from .param import JobQueryParams
|
||||
from .crud import JobCRUD
|
||||
|
||||
|
||||
class JobService:
|
||||
"""
|
||||
定时任务管理模块服务层
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def get_job_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
||||
obj = await JobCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
return JobOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_job_list_service(cls, auth: AuthSchema, search: JobQueryParams = None, order_by: List[Dict[str, str]] = None) -> List[Dict]:
|
||||
if order_by:
|
||||
order_by = eval(order_by)
|
||||
obj_list = await JobCRUD(auth).get_obj_list_crud(search=search.__dict__, order_by=order_by)
|
||||
return [JobOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_job_service(cls, auth: AuthSchema, data: JobCreateSchema) -> Dict:
|
||||
exist_obj = await JobCRUD(auth).get(name=data.name)
|
||||
if exist_obj:
|
||||
raise CustomException(msg='创建失败,该定时任务已存在')
|
||||
if data.trigger == 'cron' and not CronUtil.validate_cron_expression(data.trigger_args):
|
||||
raise CustomException(msg=f'新增定时任务{data.name}失败, Cron表达式不正确')
|
||||
|
||||
obj = await JobCRUD(auth).create_obj_crud(data=data)
|
||||
SchedulerUtil().add_job(job_info=obj)
|
||||
return JobOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_job_service(cls, auth: AuthSchema, data: JobUpdateSchema) -> Dict:
|
||||
exist_obj = await JobCRUD(auth).get_obj_by_id_crud(id=data.id)
|
||||
if not exist_obj:
|
||||
raise CustomException(msg='更新失败,该定时任务不存在')
|
||||
if data.trigger == 'cron' and not CronUtil.validate_cron_expression(data.trigger_args):
|
||||
raise CustomException(msg=f'新增定时任务{data.name}失败, Cron表达式不正确')
|
||||
obj = await JobCRUD(auth).update_obj_crud(id=data.id, data=data)
|
||||
SchedulerUtil().modify_job(job_id=obj.id)
|
||||
return JobOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_job_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
exist_obj = await JobCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
if not exist_obj:
|
||||
raise CustomException(msg='删除失败,该数据定时任务不存在')
|
||||
SchedulerUtil.remove_job(job_id=id)
|
||||
await JobCRUD(auth).delete_obj_crud(ids=ids)
|
||||
|
||||
|
||||
@classmethod
|
||||
async def clear_job_service(cls, auth: AuthSchema) -> None:
|
||||
SchedulerUtil().clear_jobs()
|
||||
await JobCRUD(auth).clear_obj_crud()
|
||||
|
||||
@classmethod
|
||||
async def option_job_service(cls, auth: AuthSchema, id: int, option: int) -> None:
|
||||
# 1: 暂停 2: 恢复 3: 重启
|
||||
obj = await JobCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='操作失败,该数据定时任务不存在')
|
||||
if option == 1:
|
||||
SchedulerUtil().pause_job(job_id=id)
|
||||
await JobCRUD(auth).set_obj_field_crud(ids=[id], status=False)
|
||||
elif option == 2:
|
||||
SchedulerUtil().resume_job(job_id=id)
|
||||
await JobCRUD(auth).set_obj_field_crud(ids=[id], status=True)
|
||||
# elif option == 3:
|
||||
# SchedulerUtil().reschedule_job(job_id=id)
|
||||
# await JobCRUD(auth).set_obj_field_crud(ids=[id], status=False)
|
||||
|
||||
@classmethod
|
||||
async def export_job_service(cls, data_list: List[Dict[str, Any]]) -> bytes:
|
||||
"""导出公告列表"""
|
||||
mapping_dict = {
|
||||
'id': '编号',
|
||||
'name': '任务名称',
|
||||
'func': '任务函数',
|
||||
'trigger': '触发器',
|
||||
'args': '位置参数',
|
||||
'kwargs': '关键字参数',
|
||||
'coalesce': '是否合并运行',
|
||||
'max_instances': '最大实例数',
|
||||
'jobstore': '任务存储',
|
||||
'executor': '任务执行器',
|
||||
'trigger_args': '触发器参数',
|
||||
'status': '任务状态',
|
||||
'message': '日志信息',
|
||||
'description': '备注',
|
||||
'created_at': '创建时间',
|
||||
'updated_at': '更新时间',
|
||||
'creator_id': '创建者ID',
|
||||
'creator': '创建者',
|
||||
}
|
||||
|
||||
# 复制数据并转换状态
|
||||
data = data_list.copy()
|
||||
for item in data:
|
||||
item['status'] = '已完成' if item['status'] == 0 else '运行中' if item['status'] == 1 else '暂停'
|
||||
item['creator'] = item.get('creator', {}).get('name', '未知') if isinstance(item.get('creator'), dict) else '未知'
|
||||
|
||||
return ExcelUtil.export_list2excel(list_data=data_list, mapping_dict=mapping_dict)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter, Body, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
from redis.asyncio.client import Redis
|
||||
|
||||
from app.common.request import PaginationService
|
||||
from app.common.response import SuccessResponse,ErrorResponse
|
||||
from app.core.dependencies import AuthPermission, redis_getter
|
||||
from app.core.base_params import PaginationQueryParams
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.core.logger import logger
|
||||
from .param import OnlineQueryParams
|
||||
from .service import OnlineService
|
||||
|
||||
|
||||
OnlineRouter = APIRouter(route_class=OperationLogRoute, prefix="/online", tags=["在线用户"])
|
||||
|
||||
|
||||
@OnlineRouter.get(
|
||||
'/list',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:online:query']))],
|
||||
summary="获取在线用户列表",
|
||||
description="获取在线用户列表"
|
||||
)
|
||||
async def get_online_list_controller(
|
||||
redis: Redis = Depends(redis_getter),
|
||||
paging_query: PaginationQueryParams = Depends(),
|
||||
search: OnlineQueryParams = Depends()
|
||||
)->JSONResponse:
|
||||
# 获取全量数据
|
||||
result_dict_list = await OnlineService.get_online_list_service(redis=redis, search=search)
|
||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= paging_query.page_no, page_size = paging_query.page_size)
|
||||
logger.info('获取成功')
|
||||
|
||||
return SuccessResponse(data=result_dict,msg='获取成功')
|
||||
|
||||
|
||||
@OnlineRouter.delete(
|
||||
'/delete',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:online:delete']))],
|
||||
summary="强制下线",
|
||||
description="强制下线"
|
||||
)
|
||||
async def delete_online_controller(
|
||||
session_id: str = Body(..., description="会话编号"),
|
||||
redis: Redis = Depends(redis_getter),
|
||||
)->JSONResponse:
|
||||
is_ok = await OnlineService.delete_online_service(redis=redis, session_id=session_id)
|
||||
if is_ok:
|
||||
logger.info("强制下线成功")
|
||||
return SuccessResponse(msg="强制下线成功")
|
||||
else:
|
||||
logger.info("强制下线失败")
|
||||
return ErrorResponse(msg="强制下线失败")
|
||||
|
||||
@OnlineRouter.delete(
|
||||
'/clear',
|
||||
dependencies=[Depends(AuthPermission(permissions=['monitor:online:delete']))],
|
||||
summary="清除所有在线用户",
|
||||
description="清除所有在线用户"
|
||||
)
|
||||
async def clear_online_controller(
|
||||
redis: Redis = Depends(redis_getter),
|
||||
)->JSONResponse:
|
||||
is_ok = await OnlineService.clear_online_service(redis=redis)
|
||||
if is_ok:
|
||||
logger.info("清除所有在线用户成功")
|
||||
return SuccessResponse(msg="清除所有在线用户成功")
|
||||
else:
|
||||
logger.info("清除所有在线用户失败")
|
||||
return ErrorResponse(msg="清除所有在线用户失败")
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import Query
|
||||
|
||||
|
||||
class OnlineQueryParams:
|
||||
"""在线用户查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str] = Query(None, description="登录名称"),
|
||||
ipaddr: Optional[str] = Query(None, description="登陆IP地址"),
|
||||
login_location: Optional[str] = Query(None, description="登录所属地"),
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
# 模糊查询字段
|
||||
self.name = ("like", f"%{name}%") if name else None
|
||||
self.login_location = ("like", f"%{login_location}%") if login_location else None
|
||||
self.ipaddr = ("like", f"%{ipaddr}%") if ipaddr else None
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from typing import Optional
|
||||
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
|
||||
class OnlineOutSchema(BaseModel):
|
||||
"""
|
||||
在线用户对应pydantic模型
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
name: str = Field(..., description='用户名称')
|
||||
session_id: str = Field(..., description='会话编号')
|
||||
user_id: int = Field(..., description='用户ID')
|
||||
user_name: str = Field(..., description='用户名')
|
||||
ipaddr: Optional[str] = Field(default=None, description='登陆IP地址')
|
||||
login_location: Optional[str] = Field(default=None, description='登录所属地')
|
||||
os: Optional[str] = Field(default=None, description='操作系统')
|
||||
browser: Optional[str] = Field(default=None, description='浏览器')
|
||||
login_time: Optional[DateTimeStr] = Field(default=None, description='登录时间')
|
||||
login_type: Optional[str] = Field(default=None, description='登录类型 PC端 | 移动端')
|
||||
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
from typing import Dict, List, Optional
|
||||
from redis.asyncio.client import Redis
|
||||
|
||||
from app.common.enums import RedisInitKeyConfig
|
||||
from app.core.redis_crud import RedisCURD
|
||||
from app.core.security import decode_access_token
|
||||
from app.core.logger import logger
|
||||
from .param import OnlineQueryParams
|
||||
from .schema import OnlineOutSchema
|
||||
|
||||
class OnlineService:
|
||||
"""在线用户管理模块服务层"""
|
||||
|
||||
@classmethod
|
||||
async def get_online_list_service(cls, redis: Redis, search: Optional[OnlineQueryParams] = None) -> List[Dict]:
|
||||
"""
|
||||
获取在线用户列表信息(支持分页和搜索)
|
||||
"""
|
||||
|
||||
keys = await RedisCURD(redis).get_keys(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*")
|
||||
tokens = await RedisCURD(redis).mget(*keys)
|
||||
|
||||
online_users = []
|
||||
for token in tokens:
|
||||
if not token:
|
||||
continue
|
||||
try:
|
||||
payload = decode_access_token(token=token)
|
||||
session_info = json.loads(payload.sub)
|
||||
if cls._match_search_conditions(session_info, search):
|
||||
online_users.append(session_info)
|
||||
except Exception as e:
|
||||
logger.error(f"解析在线用户数据失败: {e}")
|
||||
continue
|
||||
# 按照 login_time 倒序排序
|
||||
online_users.sort(key=lambda x: x.get('login_time', ''), reverse=True)
|
||||
|
||||
return online_users
|
||||
|
||||
|
||||
@classmethod
|
||||
async def delete_online_service(cls, redis: Redis, session_id: str) -> bool:
|
||||
"""强制下线在线用户"""
|
||||
# 删除 token
|
||||
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}")
|
||||
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:{session_id}")
|
||||
|
||||
|
||||
logger.info(f"强制下线用户会话: {session_id}")
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def clear_online_service(cls, redis: Redis) -> bool:
|
||||
"""强制下线在线用户"""
|
||||
# 删除 token
|
||||
await RedisCURD(redis).clear(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*")
|
||||
await RedisCURD(redis).clear(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:*")
|
||||
|
||||
logger.info(f"清除所有在线用户会话成功")
|
||||
return True
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _match_search_conditions(online_info: Dict, search: Optional[OnlineQueryParams]) -> bool:
|
||||
"""检查是否匹配搜索条件"""
|
||||
if not search:
|
||||
return True
|
||||
|
||||
if search.name and search.name[1]:
|
||||
keyword = search.name[1].strip('%')
|
||||
if keyword.lower() not in online_info.get("name", "").lower():
|
||||
return False
|
||||
|
||||
if search.ipaddr and search.ipaddr[1]:
|
||||
keyword = search.ipaddr[1].strip('%')
|
||||
if keyword not in online_info.get("ipaddr", ""):
|
||||
return False
|
||||
|
||||
if search.login_location and search.login_location[1]:
|
||||
keyword = search.login_location[1].strip('%')
|
||||
if keyword.lower() not in online_info.get("login_location", "").lower():
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.common.response import SuccessResponse
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.core.logger import logger
|
||||
from .service import ServerService
|
||||
|
||||
|
||||
ServerRouter = APIRouter(route_class=OperationLogRoute, prefix="/server", tags=["服务器监控"])
|
||||
|
||||
@ServerRouter.get(
|
||||
'/info',
|
||||
summary="查询服务器监控信息",
|
||||
description="查询服务器监控信息",
|
||||
dependencies=[Depends(AuthPermission(permissions=["monitor:server:query"]))]
|
||||
)
|
||||
async def get_monitor_server_info_controller() -> JSONResponse:
|
||||
# 获取全量数据
|
||||
result_dict = await ServerService.get_server_monitor_info_service()
|
||||
logger.info(f'获取服务器监控信息成功: {result_dict}')
|
||||
|
||||
return SuccessResponse(data=result_dict, msg='获取服务器监控信息成功')
|
||||
@@ -0,0 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from typing import List
|
||||
|
||||
|
||||
class CpuInfoSchema(BaseModel):
|
||||
"""CPU信息模型"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
cpu_num: int = Field(description="CPU核心数")
|
||||
used: float = Field(ge=0, le=100, description="CPU用户使用率(%)")
|
||||
sys: float = Field(ge=0, le=100, description="CPU系统使用率(%)")
|
||||
free: float = Field(ge=0, le=100, description="CPU空闲率(%)")
|
||||
|
||||
|
||||
class MemoryInfoSchema(BaseModel):
|
||||
"""内存信息模型"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
total: str = Field(description="内存总量")
|
||||
used: str = Field(description="已用内存")
|
||||
free: str = Field(description="剩余内存")
|
||||
usage: float = Field(ge=0, le=100, description="使用率(%)")
|
||||
|
||||
|
||||
class SysInfoSchema(BaseModel):
|
||||
"""系统信息模型"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
computer_ip: str = Field(description="服务器IP")
|
||||
computer_name: str = Field(description="服务器名称")
|
||||
os_arch: str = Field(description="系统架构")
|
||||
os_name: str = Field(description="操作系统")
|
||||
user_dir: str = Field(description="项目路径")
|
||||
|
||||
|
||||
class PyInfoSchema(BaseModel):
|
||||
"""Python运行信息模型"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
name: str = Field(description="Python名称")
|
||||
version: str = Field(description="Python版本")
|
||||
start_time: str = Field(description="启动时间")
|
||||
run_time: str = Field(description="运行时长")
|
||||
home: str = Field(description="安装路径")
|
||||
memory_used: str = Field(description="内存占用")
|
||||
memory_usage: float = Field(ge=0, le=100, description="内存使用率(%)")
|
||||
memory_total: str = Field(description="总内存")
|
||||
memory_free: str = Field(description="剩余内存")
|
||||
|
||||
|
||||
class DiskInfoSchema(BaseModel):
|
||||
"""磁盘信息模型"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
dir_name: str = Field(description="磁盘路径")
|
||||
sys_type_name: str = Field(description="文件系统类型")
|
||||
type_name: str = Field(description="磁盘类型")
|
||||
total: str = Field(description="总容量")
|
||||
used: str = Field(description="已用容量")
|
||||
free: str = Field(description="可用容量")
|
||||
usage: float = Field(ge=0, le=100, description="使用率(%)")
|
||||
|
||||
|
||||
class ServerMonitorSchema(BaseModel):
|
||||
"""服务器监控信息模型"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
cpu: CpuInfoSchema = Field(description="CPU信息")
|
||||
mem: MemoryInfoSchema = Field(description="内存信息")
|
||||
py: PyInfoSchema = Field(description="Python运行信息")
|
||||
sys: SysInfoSchema = Field(description="系统信息")
|
||||
disks: List[DiskInfoSchema] = Field(default_factory=list, description="磁盘信息")
|
||||
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import platform
|
||||
import psutil
|
||||
import socket
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
|
||||
from app.utils.common_util import bytes2human
|
||||
from .schema import (
|
||||
CpuInfoSchema,
|
||||
MemoryInfoSchema,
|
||||
PyInfoSchema,
|
||||
ServerMonitorSchema,
|
||||
DiskInfoSchema,
|
||||
SysInfoSchema
|
||||
)
|
||||
|
||||
|
||||
class ServerService:
|
||||
"""服务监控模块服务层"""
|
||||
|
||||
@classmethod
|
||||
async def get_server_monitor_info_service(cls) -> Dict:
|
||||
"""获取服务器监控信息"""
|
||||
return ServerMonitorSchema(
|
||||
cpu=cls._get_cpu_info().model_dump(),
|
||||
mem=cls._get_memory_info().model_dump(),
|
||||
sys=cls._get_system_info().model_dump(),
|
||||
py=cls._get_python_info().model_dump(),
|
||||
disks=cls._get_disk_info()
|
||||
).model_dump()
|
||||
|
||||
@classmethod
|
||||
def _get_cpu_info(cls) -> CpuInfoSchema:
|
||||
"""获取CPU信息"""
|
||||
cpu_times = psutil.cpu_times_percent()
|
||||
return CpuInfoSchema(
|
||||
cpu_num=psutil.cpu_count(logical=True),
|
||||
used=cpu_times.user,
|
||||
sys=cpu_times.system,
|
||||
free=cpu_times.idle
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_memory_info(cls) -> MemoryInfoSchema:
|
||||
"""获取内存信息"""
|
||||
memory = psutil.virtual_memory()
|
||||
return MemoryInfoSchema(
|
||||
total=bytes2human(memory.total),
|
||||
used=bytes2human(memory.used),
|
||||
free=bytes2human(memory.free),
|
||||
usage=memory.percent
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_system_info(cls) -> SysInfoSchema:
|
||||
"""获取系统信息"""
|
||||
hostname = socket.gethostname()
|
||||
return SysInfoSchema(
|
||||
computer_ip=socket.gethostbyname(hostname),
|
||||
computer_name=platform.node(),
|
||||
os_arch=platform.machine(),
|
||||
os_name=platform.platform(),
|
||||
user_dir=str(Path.cwd())
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_python_info(cls) -> PyInfoSchema:
|
||||
"""获取Python解释器信息"""
|
||||
current_process = psutil.Process()
|
||||
memory = psutil.virtual_memory()
|
||||
process_memory = current_process.memory_info()
|
||||
|
||||
start_time = current_process.create_time()
|
||||
run_time = ServerService._calculate_run_time(start_time)
|
||||
|
||||
return PyInfoSchema(
|
||||
name=current_process.name(),
|
||||
version=platform.python_version(),
|
||||
start_time=time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time)),
|
||||
run_time=run_time,
|
||||
home=str(Path(current_process.exe())),
|
||||
memory_total=bytes2human(memory.available),
|
||||
memory_used=bytes2human(process_memory.rss),
|
||||
memory_free=bytes2human(memory.available - process_memory.rss),
|
||||
memory_usage=round((process_memory.rss / memory.available) * 100, 2)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_disk_info(cls) -> List[Dict]:
|
||||
"""获取磁盘信息"""
|
||||
disk_info = []
|
||||
for partition in psutil.disk_partitions():
|
||||
try:
|
||||
# 使用mountpoint而不是device来获取磁盘使用情况
|
||||
usage = psutil.disk_usage(partition.mountpoint)
|
||||
mount_point = str(Path(partition.mountpoint))
|
||||
disk_info.append(
|
||||
DiskInfoSchema(
|
||||
dir_name=mount_point, # 使用mountpoint替代device
|
||||
sys_type_name=partition.fstype,
|
||||
type_name=f'本地固定磁盘({mount_point})',
|
||||
total=bytes2human(usage.total),
|
||||
used=bytes2human(usage.used),
|
||||
free=bytes2human(usage.free),
|
||||
usage=usage.percent # 直接使用数字而不是字符串
|
||||
).model_dump()
|
||||
)
|
||||
except (PermissionError, FileNotFoundError):
|
||||
# 明确指定可能的异常
|
||||
continue
|
||||
return disk_info
|
||||
|
||||
@classmethod
|
||||
def _calculate_run_time(cls,start_time: float) -> str:
|
||||
"""计算运行时间"""
|
||||
difference = time.time() - start_time
|
||||
days = int(difference // (24 * 60 * 60))
|
||||
hours = int((difference % (24 * 60 * 60)) // (60 * 60))
|
||||
minutes = int((difference % (60 * 60)) // 60)
|
||||
return f'{days}天{hours}小时{minutes}分钟'
|
||||
Reference in New Issue
Block a user