mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-23 05:10:57 +00:00
refactor: 整合仪表盘功能到监控模块,清理冗余代码
- 移除原监控仪表盘独立模块,将相关功能合并到在线监控模块 - 重构租户配置字段名,统一使用logo_url和name替代tenant_logo/tenant_name - 优化搜索工具函数,移除重复导入 - 调整参数配置模型字段长度限制,移除config_value的max_length约束 - 清理冗余的常量定义和导入语句 - 修复批量状态设置接口的redis依赖注入 - 增强OAuth登录安全性,添加租户默认归属和state一次性消费 - 优化资源目录缓存逻辑,减少重复计算 - 新增API Token模块基础框架 - 完善用户token版本管理,支持主动失效JWT - 调整AI模型配置缓存过期时间 - 修复菜单类型字段索引,提升查询性能 - 简化前端刷新token调用逻辑 - 新增滑块验证完成接口和忘记密码验证码校验 - 调整系统配置默认值,添加操作日志保留天数和接口白名单配置 - 限制Mock支付回调仅在开发环境可用 - 重构websocket认证方式,支持更安全的subprotocol传参
This commit is contained in:
@@ -64,12 +64,6 @@ async def get_scheduler_console_controller() -> JSONResponse:
|
||||
return SuccessResponse(data=console_output, msg="获取控制台信息成功")
|
||||
|
||||
|
||||
@JobRouter.post("/scheduler/sync", summary="同步调度器任务到数据库", response_model=ResponseSchema[int], dependencies=[Security(AuthPermission(["module_task:cronjob:job:update"]))])
|
||||
async def sync_jobs_controller() -> JSONResponse:
|
||||
sync_count = SchedulerUtil.sync_jobs_to_db()
|
||||
return SuccessResponse(data=sync_count, msg=f"同步完成,共同步 {sync_count} 个任务")
|
||||
|
||||
|
||||
@JobRouter.post("/task/pause/{job_id}", summary="暂停任务", response_model=ResponseSchema[None], dependencies=[Security(AuthPermission(["module_task:cronjob:job:task"]))])
|
||||
async def pause_job_controller(
|
||||
job_id: Annotated[str, Path(description="调度器任务ID")],
|
||||
|
||||
@@ -3,6 +3,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.ap_scheduler import SchedulerUtil
|
||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.common_util import search_to_dict
|
||||
|
||||
from .crud import JobCRUD
|
||||
from .schema import JobCreateSchema, JobOutSchema, JobQueryParam, JobUpdateSchema
|
||||
@@ -28,7 +29,7 @@ class JobService:
|
||||
) -> list[JobOutSchema]:
|
||||
if order_by is None:
|
||||
order_by = [{"created_time": "desc"}]
|
||||
obj_list = await JobCRUD(self.auth, self.db).get_obj_list_crud(search=vars(search) if search else {}, order_by=order_by)
|
||||
obj_list = await JobCRUD(self.auth, self.db).get_obj_list_crud(search=search_to_dict(search, {}), order_by=order_by)
|
||||
return [JobOutSchema.model_validate(obj) for obj in obj_list]
|
||||
|
||||
async def get_job_log_page(
|
||||
@@ -44,7 +45,7 @@ class JobService:
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=ob,
|
||||
search=vars(search) if search else {},
|
||||
search=search_to_dict(search, {}),
|
||||
out_schema=JobOutSchema,
|
||||
)
|
||||
|
||||
@@ -83,7 +84,7 @@ class JobService:
|
||||
return JobOutSchema.model_validate(obj)
|
||||
|
||||
async def delete_job_log(self, ids: list[int]) -> None:
|
||||
if len(ids) < 1:
|
||||
if not ids:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
await JobCRUD(self.auth, self.db).delete_obj_crud(ids=ids)
|
||||
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
from apscheduler.jobstores.base import JobLookupError
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from apscheduler.job import Job
|
||||
from apscheduler.jobstores.base import ConflictingIdError, JobLookupError
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.date import DateTrigger
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.ap_scheduler import SchedulerUtil
|
||||
from app.core.ap_scheduler import (
|
||||
SchedulerUtil,
|
||||
scheduler,
|
||||
)
|
||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import logger
|
||||
from app.utils.common_util import search_to_dict
|
||||
from app.utils.cron_util import CronUtil
|
||||
|
||||
from .crud import NodeCRUD
|
||||
from .model import NodeModel
|
||||
from .schema import (
|
||||
NodeCreateSchema,
|
||||
NodeExecuteSchema,
|
||||
@@ -46,7 +59,7 @@ class NodeService:
|
||||
search: NodeQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> list[NodeOutSchema]:
|
||||
obj_list = await NodeCRUD(self.auth, self.db).get_obj_list_crud(search=vars(search) if search else {}, order_by=order_by)
|
||||
obj_list = await NodeCRUD(self.auth, self.db).get_obj_list_crud(search=search_to_dict(search, {}), order_by=order_by)
|
||||
return [NodeOutSchema.model_validate(obj) for obj in obj_list]
|
||||
|
||||
async def page(
|
||||
@@ -61,7 +74,7 @@ class NodeService:
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order_by or [{"id": "asc"}],
|
||||
search=vars(search) if search else {},
|
||||
search=search_to_dict(search, {}),
|
||||
out_schema=NodeOutSchema,
|
||||
)
|
||||
|
||||
@@ -86,7 +99,7 @@ class NodeService:
|
||||
return NodeOutSchema.model_validate(obj)
|
||||
|
||||
async def delete(self, ids: list[int]) -> None:
|
||||
if len(ids) < 1:
|
||||
if not ids:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
for mid in ids:
|
||||
exist_obj = await NodeCRUD(self.auth, self.db).get_obj_by_id_crud(id=mid)
|
||||
@@ -113,13 +126,13 @@ class NodeService:
|
||||
end_date = execute_data.end_date
|
||||
|
||||
if trigger == "now":
|
||||
SchedulerUtil.add_and_run_job_now(job_info=obj)
|
||||
add_and_run_job_now(job_info=obj)
|
||||
elif trigger == "cron":
|
||||
if not trigger_args:
|
||||
raise CustomException(msg="Cron执行需要提供Cron表达式")
|
||||
if not CronUtil.validate_cron_expression(trigger_args):
|
||||
raise CustomException(msg=f"Cron表达式不正确: {trigger_args}")
|
||||
SchedulerUtil.add_cron_job(
|
||||
add_cron_job(
|
||||
job_info=obj,
|
||||
trigger_args=trigger_args,
|
||||
start_date=start_date,
|
||||
@@ -128,7 +141,7 @@ class NodeService:
|
||||
elif trigger == "interval":
|
||||
if not trigger_args:
|
||||
raise CustomException(msg="间隔执行需要提供间隔参数")
|
||||
SchedulerUtil.add_interval_job(
|
||||
add_interval_job(
|
||||
job_info=obj,
|
||||
trigger_args=trigger_args,
|
||||
start_date=start_date,
|
||||
@@ -137,7 +150,7 @@ class NodeService:
|
||||
elif trigger == "date":
|
||||
if not trigger_args:
|
||||
raise CustomException(msg="指定时间执行需要提供执行时间")
|
||||
SchedulerUtil.add_date_job(job_info=obj, run_date=trigger_args)
|
||||
add_date_job(job_info=obj, run_date=trigger_args)
|
||||
else:
|
||||
raise CustomException(msg=f"不支持的触发方式: {trigger}")
|
||||
|
||||
@@ -151,3 +164,151 @@ class NodeService:
|
||||
ids=ids,
|
||||
status=status,
|
||||
)
|
||||
|
||||
|
||||
# ── NodeModel 封装的任务添加方法 ────────────────────────────
|
||||
|
||||
|
||||
def _add_job_with_trigger(job_info: NodeModel, trigger) -> Job:
|
||||
"""将 NodeModel 封装的任务添加到 APScheduler 调度器。"""
|
||||
code_block = job_info.func
|
||||
if not code_block or not code_block.strip():
|
||||
raise ValueError("任务代码块不能为空")
|
||||
|
||||
jobstore = job_info.jobstore or "sqlalchemy"
|
||||
executor = job_info.executor or "threadpool"
|
||||
|
||||
job_args = []
|
||||
if job_info.args:
|
||||
args_str = str(job_info.args).strip()
|
||||
if args_str:
|
||||
job_args = [arg.strip() for arg in args_str.split(",") if arg.strip()]
|
||||
|
||||
job_kwargs = {}
|
||||
if job_info.kwargs:
|
||||
kwargs_str = str(job_info.kwargs).strip()
|
||||
if kwargs_str:
|
||||
try:
|
||||
job_kwargs = json.loads(kwargs_str)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError(f"关键字参数JSON格式无效: {kwargs_str}")
|
||||
|
||||
SchedulerUtil.job_name_cache[str(job_info.id)] = job_info.name or ""
|
||||
|
||||
try:
|
||||
job = scheduler.add_job(
|
||||
func=SchedulerUtil._task_wrapper,
|
||||
trigger=trigger,
|
||||
args=[str(job_info.id), code_block, *job_args],
|
||||
kwargs=job_kwargs,
|
||||
id=str(job_info.id),
|
||||
name=job_info.name,
|
||||
coalesce=job_info.coalesce,
|
||||
max_instances=1,
|
||||
jobstore=jobstore,
|
||||
executor=executor,
|
||||
)
|
||||
logger.info(f"任务 {job_info.id} 添加到 {jobstore} 存储器成功")
|
||||
return job
|
||||
except ConflictingIdError:
|
||||
scheduler.remove_job(job_id=str(job_info.id), jobstore=jobstore)
|
||||
job = scheduler.add_job(
|
||||
func=SchedulerUtil._task_wrapper,
|
||||
trigger=trigger,
|
||||
args=[str(job_info.id), code_block, *job_args],
|
||||
kwargs=job_kwargs,
|
||||
id=str(job_info.id),
|
||||
name=job_info.name,
|
||||
coalesce=job_info.coalesce,
|
||||
max_instances=1,
|
||||
jobstore=jobstore,
|
||||
executor=executor,
|
||||
)
|
||||
logger.info(f"任务 {job_info.id} 已存在,已移除旧任务并重新添加")
|
||||
return job
|
||||
|
||||
|
||||
def add_and_run_job_now(job_info: NodeModel) -> Job:
|
||||
"""立即执行任务(加入调度器并尽快触发一次)。"""
|
||||
trigger = DateTrigger(run_date=datetime.now() + timedelta(seconds=0.1))
|
||||
return _add_job_with_trigger(job_info, trigger)
|
||||
|
||||
|
||||
def add_cron_job(
|
||||
job_info: NodeModel,
|
||||
trigger_args: str | None = None,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
) -> Job:
|
||||
"""创建 Cron 定时任务。"""
|
||||
cron_expr = trigger_args or job_info.trigger_args
|
||||
if not cron_expr:
|
||||
raise ValueError("Cron触发器缺少参数")
|
||||
|
||||
fields = cron_expr.strip().split()
|
||||
if len(fields) not in (6, 7):
|
||||
raise ValueError("无效的 Cron 表达式")
|
||||
if not CronUtil.validate_cron_expression(cron_expr):
|
||||
raise ValueError(f"Cron表达式不正确: {cron_expr}")
|
||||
|
||||
parsed_fields = [field if field != "?" else "*" for field in fields]
|
||||
if len(fields) == 6:
|
||||
parsed_fields.append("*")
|
||||
|
||||
second, minute, hour, day, month, day_of_week, year = tuple(parsed_fields)
|
||||
|
||||
if second == "*" and minute == "*" and hour == "*" and day == "*" and month == "*" and day_of_week in ("*", "?"):
|
||||
raise ValueError("Cron表达式不允许每秒执行,请至少指定秒数(如:0 * * * * ? * 表示每分钟执行)")
|
||||
|
||||
trigger = CronTrigger(
|
||||
second=second,
|
||||
minute=minute,
|
||||
hour=hour,
|
||||
day=day,
|
||||
month=month,
|
||||
day_of_week=day_of_week,
|
||||
year=year,
|
||||
start_date=start_date or job_info.start_date,
|
||||
end_date=end_date or job_info.end_date,
|
||||
timezone="Asia/Shanghai",
|
||||
)
|
||||
return _add_job_with_trigger(job_info, trigger)
|
||||
|
||||
|
||||
def add_interval_job(
|
||||
job_info: NodeModel,
|
||||
trigger_args: str | None = None,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
) -> Job:
|
||||
"""创建间隔执行任务。"""
|
||||
interval_args = trigger_args or job_info.trigger_args
|
||||
if not interval_args:
|
||||
raise ValueError("interval触发器缺少参数")
|
||||
|
||||
fields = interval_args.strip().split()
|
||||
if len(fields) != 5:
|
||||
raise ValueError("无效的 interval 表达式,格式: 秒 分 时 天 周")
|
||||
|
||||
second, minute, hour, day, week = tuple(int(field) if field != "*" else 0 for field in fields)
|
||||
trigger = IntervalTrigger(
|
||||
weeks=week,
|
||||
days=day,
|
||||
hours=hour,
|
||||
minutes=minute,
|
||||
seconds=second,
|
||||
start_date=start_date or job_info.start_date,
|
||||
end_date=end_date or job_info.end_date,
|
||||
timezone="Asia/Shanghai",
|
||||
)
|
||||
return _add_job_with_trigger(job_info, trigger)
|
||||
|
||||
|
||||
def add_date_job(job_info: NodeModel, run_date: str | None = None) -> Job:
|
||||
"""创建指定时刻执行一次的任务。"""
|
||||
date_str = run_date or job_info.trigger_args
|
||||
if not date_str:
|
||||
raise ValueError("date触发器缺少执行时间参数")
|
||||
|
||||
trigger = DateTrigger(run_date=date_str, timezone="Asia/Shanghai")
|
||||
return _add_job_with_trigger(job_info, trigger)
|
||||
|
||||
@@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.common_util import search_to_dict
|
||||
|
||||
from ..node_type.crud import WorkflowNodeTypeCRUD
|
||||
from .crud import WorkflowCRUD
|
||||
@@ -52,7 +53,7 @@ class WorkflowService:
|
||||
if order_by is None:
|
||||
order_by = [{"updated_time": "desc"}]
|
||||
obj_list = await WorkflowCRUD(self.auth, self.db).get_obj_list_crud(
|
||||
search=vars(search) if search else {},
|
||||
search=search_to_dict(search, {}),
|
||||
order_by=order_by,
|
||||
)
|
||||
return [self._out(o) for o in obj_list]
|
||||
@@ -70,7 +71,7 @@ class WorkflowService:
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order,
|
||||
search=vars(search) if search else {},
|
||||
search=search_to_dict(search, {}),
|
||||
out_schema=WorkflowOutSchema,
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -2,6 +2,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.common_util import search_to_dict
|
||||
|
||||
from .crud import WorkflowNodeTypeCRUD
|
||||
from .schema import (
|
||||
@@ -51,7 +52,7 @@ class WorkflowNodeTypeService:
|
||||
if order_by is None:
|
||||
order_by = [{"sort_order": "asc"}, {"id": "asc"}]
|
||||
obj_list = await WorkflowNodeTypeCRUD(self.auth, self.db).get_obj_list_crud(
|
||||
search=vars(search) if search else {},
|
||||
search=search_to_dict(search, {}),
|
||||
order_by=order_by,
|
||||
)
|
||||
return [self._out(o) for o in obj_list]
|
||||
@@ -69,7 +70,7 @@ class WorkflowNodeTypeService:
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order,
|
||||
search=vars(search) if search else {},
|
||||
search=search_to_dict(search, {}),
|
||||
out_schema=WorkflowNodeTypeOutSchema,
|
||||
)
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user