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:
zhangtao
2026-07-13 01:14:20 +08:00
parent 6a5f8cf0dd
commit cf88ab8897
102 changed files with 4078 additions and 3777 deletions
@@ -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