upload project source code

This commit is contained in:
2026-04-30 18:49:43 +08:00
commit 9b394ba682
2277 changed files with 660945 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,330 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Body, Depends, Path
from fastapi.responses import JSONResponse, StreamingResponse
from app.common.response import StreamResponse, SuccessResponse
from app.common.request import PaginationService
from app.core.router_class import OperationLogRoute
from app.utils.common_util import bytes2file_response
from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema
from .tools.ap_scheduler import SchedulerUtil
from .service import JobService, JobLogService
from .schema import (
JobCreateSchema,
JobUpdateSchema,
JobQueryParam,
JobLogQueryParam
)
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(["module_application:job:query"]))
) -> JSONResponse:
"""
获取定时任务详情
参数:
- id (int): 定时任务ID
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含定时任务详情的JSON响应
"""
result_dict = await JobService.get_job_detail_service(id=id, auth=auth)
log.info(f"获取定时任务详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取定时任务详情成功")
@JobRouter.get("/list", summary="查询定时任务", description="查询定时任务")
async def get_obj_list_controller(
page: PaginationQueryParam = Depends(),
search: JobQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_application:job:query"]))
) -> JSONResponse:
"""
查询定时任务
参数:
- page (PaginationQueryParam): 分页查询参数模型
- search (JobQueryParam): 查询参数模型
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含分页后的定时任务列表的JSON响应
"""
result_dict_list = await JobService.get_job_list_service(auth=auth, search=search, order_by=page.order_by)
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
log.info(f"查询定时任务列表成功")
return SuccessResponse(data=result_dict, msg="查询定时任务列表成功")
@JobRouter.post("/create", summary="创建定时任务", description="创建定时任务")
async def create_obj_controller(
data: JobCreateSchema,
auth: AuthSchema = Depends(AuthPermission(["module_application:job:create"]))
) -> JSONResponse:
"""
创建定时任务
参数:
- data (JobCreateSchema): 创建参数模型
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含创建定时任务结果的JSON响应
"""
result_dict = await JobService.create_job_service(auth=auth, data=data)
log.info(f"创建定时任务成功: {result_dict}")
return SuccessResponse(data=result_dict, msg="创建定时任务成功")
@JobRouter.put("/update/{id}", summary="修改定时任务", description="修改定时任务")
async def update_obj_controller(
data: JobUpdateSchema,
id: int = Path(..., description="定时任务ID"),
auth: AuthSchema = Depends(AuthPermission(["module_application:job:update"]))
) -> JSONResponse:
"""
修改定时任务
参数:
- data (JobUpdateSchema): 更新参数模型
- id (int): 定时任务ID
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含修改定时任务结果的JSON响应
"""
result_dict = await JobService.update_job_service(auth=auth, id=id, data=data)
log.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(["module_application:job:delete"]))
) -> JSONResponse:
"""
删除定时任务
参数:
- ids (list[int]): ID列表
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含删除定时任务结果的JSON响应
"""
await JobService.delete_job_service(auth=auth, ids=ids)
log.info(f"删除定时任务成功: {ids}")
return SuccessResponse(msg="删除定时任务成功")
@JobRouter.post('/export', summary="导出定时任务", description="导出定时任务")
async def export_obj_list_controller(
search: JobQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_application:job:export"]))
) -> StreamingResponse:
"""
导出定时任务
参数:
- search (JobQueryParam): 查询参数模型
- auth (AuthSchema): 认证信息模型
返回:
- 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)
log.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(["module_application:job:delete"]))
) -> JSONResponse:
"""
清空定时任务日志
参数:
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含清空定时任务结果的JSON响应
"""
await JobService.clear_job_service(auth=auth)
log.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(["module_application:job:update"]))
) -> JSONResponse:
"""
暂停/恢复/重启定时任务
参数:
- id (int): 定时任务ID
- option (int): 操作类型 1: 暂停 2: 恢复 3: 重启
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含操作定时任务结果的JSON响应
"""
await JobService.option_job_service(auth=auth, id=id, option=option)
log.info(f"操作定时任务成功: {id}")
return SuccessResponse(msg="操作定时任务成功")
@JobRouter.get("/log", summary="获取定时任务日志", description="获取定时任务日志", dependencies=[Depends(AuthPermission(["module_application:job:query"]))])
async def get_job_log_controller():
"""
获取定时任务日志
返回:
- JSONResponse: 获取定时任务日志的JSON响应
"""
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_single_job_status(job_id=i.id)
}
for i in SchedulerUtil.get_all_jobs()
]
return SuccessResponse(msg="获取定时任务日志成功", data=data)
# 定时任务日志管理接口
@JobRouter.get("/log/detail/{id}", summary="获取定时任务日志详情", description="获取定时任务日志详情")
async def get_job_log_detail_controller(
id: int = Path(..., description="定时任务日志ID"),
auth: AuthSchema = Depends(AuthPermission(["module_application:job:query"]))
) -> JSONResponse:
"""
获取定时任务日志详情
参数:
- id (int): 定时任务日志ID
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 获取定时任务日志详情的JSON响应
"""
result_dict = await JobLogService.get_job_log_detail_service(id=id, auth=auth)
log.info(f"获取定时任务日志详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取定时任务日志详情成功")
@JobRouter.get("/log/list", summary="查询定时任务日志", description="查询定时任务日志")
async def get_job_log_list_controller(
page: PaginationQueryParam = Depends(),
search: JobLogQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_application:job:query"]))
) -> JSONResponse:
"""
查询定时任务日志
参数:
- page (PaginationQueryParam): 分页查询参数模型
- search (JobLogQueryParam): 查询参数模型
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 查询定时任务日志列表的JSON响应
"""
order_by = [{"created_time": "desc"}]
result_dict_list = await JobLogService.get_job_log_list_service(auth=auth, search=search, order_by=order_by)
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
log.info(f"查询定时任务日志列表成功")
return SuccessResponse(data=result_dict, msg="查询定时任务日志列表成功")
@JobRouter.delete("/log/delete", summary="删除定时任务日志", description="删除定时任务日志")
async def delete_job_log_controller(
ids: list[int] = Body(..., description="ID列表"),
auth: AuthSchema = Depends(AuthPermission(["module_application:job:delete"]))
) -> JSONResponse:
"""
删除定时任务日志
参数:
- ids (list[int]): ID列表
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含删除定时任务日志结果的JSON响应
"""
await JobLogService.delete_job_log_service(auth=auth, ids=ids)
log.info(f"删除定时任务日志成功: {ids}")
return SuccessResponse(msg="删除定时任务日志成功")
@JobRouter.delete("/log/clear", summary="清空定时任务日志", description="清空定时任务日志")
async def clear_job_log_controller(
auth: AuthSchema = Depends(AuthPermission(["module_application:job:delete"]))
) -> JSONResponse:
"""
清空定时任务日志
参数:
- auth (AuthSchema): 认证信息模型
返回:
- JSONResponse: 包含清空定时任务日志结果的JSON响应
"""
await JobLogService.clear_job_log_service(auth=auth)
log.info(f"清空定时任务日志成功")
return SuccessResponse(msg="清空定时任务日志成功")
@JobRouter.post('/log/export', summary="导出定时任务日志", description="导出定时任务日志")
async def export_job_log_list_controller(
search: JobLogQueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["module_application:job:export"]))
) -> StreamingResponse:
"""
导出定时任务日志
参数:
- search (JobLogQueryParam): 查询参数模型
- auth (AuthSchema): 认证信息模型
返回:
- StreamingResponse: 包含导出定时任务日志结果的流式响应
"""
result_dict_list = await JobLogService.get_job_log_list_service(search=search, auth=auth)
export_result = await JobLogService.export_job_log_service(data_list=result_dict_list)
log.info('导出定时任务日志成功')
return StreamResponse(
data=bytes2file_response(export_result),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={
'Content-Disposition': 'attachment; filename=job_log.xlsx'
}
)

View File

@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
from typing import Sequence, Any
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
参数:
- auth (AuthSchema): 认证信息模型
"""
self.auth = auth
super().__init__(model=JobModel, auth=auth)
async def get_obj_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> JobModel | None:
"""
获取定时任务详情
参数:
- id (int): 定时任务ID
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
- JobModel | None: 定时任务模型,如果不存在则为None
"""
return await self.get(id=id, preload=preload)
async def get_obj_list_crud(self, search: dict | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[JobModel]:
"""
获取定时任务列表
参数:
- search (dict | None): 查询参数字典
- order_by (list[dict[str, str]] | None): 排序参数列表
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[JobModel]: 定时任务模型序列
"""
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_obj_crud(self, data: JobCreateSchema) -> JobModel | None:
"""
创建定时任务
参数:
- data (JobCreateSchema): 创建定时任务模型
返回:
- JobModel | None: 创建的定时任务模型,如果创建失败则为None
"""
return await self.create(data=data)
async def update_obj_crud(self, id: int, data: JobUpdateSchema) -> JobModel | None:
"""
更新定时任务
参数:
- id (int): 定时任务ID
- data (JobUpdateSchema): 更新定时任务模型
返回:
- JobModel | None: 更新后的定时任务模型,如果更新失败则为None
"""
return await self.update(id=id, data=data)
async def delete_obj_crud(self, ids: list[int]) -> None:
"""
删除定时任务
参数:
- ids (list[int]): 定时任务ID列表
"""
return await self.delete(ids=ids)
async def set_obj_field_crud(self, ids: list[int], **kwargs) -> None:
"""
设置定时任务的可用状态
参数:
- ids (list[int]): 定时任务ID列表
- kwargs: 其他要设置的字段,例如 available=True 或 available=False
"""
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
参数:
- auth (AuthSchema): 认证信息模型
"""
self.auth = auth
super().__init__(model=JobLogModel, auth=auth)
async def get_obj_log_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> JobLogModel | None:
"""
获取定时任务日志详情
参数:
- id (int): 定时任务日志ID
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
- JobLogModel | None: 定时任务日志模型,如果不存在则为None
"""
return await self.get(id=id, preload=preload)
async def get_obj_log_list_crud(self, search: dict | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[JobLogModel]:
"""
获取定时任务日志列表
参数:
- search (dict | None): 查询参数字典
- order_by (list[dict[str, str]] | None): 排序参数列表
- preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[JobLogModel]: 定时任务日志模型序列
"""
return await self.list(search=search, order_by=order_by, preload=preload)
async def delete_obj_log_crud(self, ids: list[int]) -> None:
"""
删除定时任务日志
参数:
- ids (list[int]): 定时任务日志ID列表
"""
return await self.delete(ids=ids)
async def clear_obj_log_crud(self) -> None:
"""
清除定时任务日志
注意:
- 此操作会删除所有定时任务日志,请谨慎操作
"""
return await self.clear()

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import time
from datetime import datetime
from app.core.logger import log
def job(*args, **kwargs) -> None:
"""
定时任务执行同步函数示例
参数:
- args: 位置参数。
- kwargs: 关键字参数。
"""
try:
print(f"开始执行任务: {args}-{kwargs}")
time.sleep(3)
print(f'{datetime.now()}同步函数执行完成')
except Exception as e:
log.error(f"同步任务执行失败: {e}")
raise
async def async_job(*args, **kwargs) -> None:
"""
定时任务执行异步函数示例
参数:
- args: 位置参数。
- kwargs: 关键字参数。
"""
try:
print(f"开始执行任务: {args}-{kwargs}")
time.sleep(3)
print(f'{datetime.now()}异步函数执行完成')
except Exception as e:
log.error(f"异步任务执行失败: {e}")
raise

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
from sqlalchemy import Boolean, String, Integer, Text, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.base_model import ModelMixin, UserMixin
class JobModel(ModelMixin, UserMixin):
"""
定时任务调度表
- 0: 运行中
- 1: 暂停中
"""
__tablename__: str = 'app_job'
__table_args__: dict[str, str] = ({'comment': '定时任务调度表'})
__loader_options__: list[str] = ["job_logs", "created_by", "updated_by"]
name: Mapped[str | None] = mapped_column(String(64), nullable=True, default='', comment='任务名称')
jobstore: Mapped[str | None] = mapped_column(String(64), nullable=True, default='default', comment='存储器')
executor: Mapped[str | None] = mapped_column(String(64), nullable=True, default='default', comment='执行器:将运行此作业的执行程序的名称')
trigger: Mapped[str] = mapped_column(String(64), nullable=False, comment='触发器:控制此作业计划的 trigger 对象')
trigger_args: Mapped[str | None] = mapped_column(Text, nullable=True, comment='触发器参数')
func: Mapped[str] = mapped_column(Text, nullable=False, comment='任务函数')
args: Mapped[str | None] = mapped_column(Text, nullable=True, comment='位置参数')
kwargs: Mapped[str | None] = 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[str | None] = mapped_column(String(64), nullable=True, comment='开始时间')
end_date: Mapped[str | None] = mapped_column(String(64), nullable=True, comment='结束时间')
# 关联关系
job_logs: Mapped[list['JobLogModel'] | None] = relationship(
back_populates="job",
lazy="selectin"
)
class JobLogModel(ModelMixin):
"""
定时任务调度日志表
"""
__tablename__: str = 'app_job_log'
__table_args__: dict[str, str] = ({'comment': '定时任务调度日志表'})
__loader_options__: list[str] = ["job"]
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[str | None] = mapped_column(String(255), nullable=True, default='', comment='位置参数')
job_kwargs: Mapped[str | None] = mapped_column(String(255), nullable=True, default='', comment='关键字参数')
job_trigger: Mapped[str | None] = mapped_column(String(255), nullable=True, default='', comment='任务触发器')
job_message: Mapped[str | None] = mapped_column(String(500), nullable=True, default='', comment='日志信息')
exception_info: Mapped[str | None] = mapped_column(String(2000), nullable=True, default='', comment='异常信息')
# 任务关联
job_id: Mapped[int | None] = mapped_column(
ForeignKey('app_job.id', ondelete="CASCADE"),
nullable=True,
index=True,
comment='任务ID'
)
job: Mapped["JobModel | None"] = relationship(
back_populates="job_logs",
lazy="selectin"
)

View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from fastapi import Query
from app.core.base_schema import BaseSchema, UserBySchema
from app.core.validator import DateTimeStr, datetime_validator
class JobCreateSchema(BaseModel):
"""
定时任务调度表对应pydantic模型
"""
name: str = Field(..., max_length=64, description='任务名称')
func: str = Field(..., description='任务函数')
trigger: str = Field(..., description='触发器:控制此作业计划的 trigger 对象')
args: str | None = Field(default=None, description='位置参数')
kwargs: str | None = Field(default=None, description='关键字参数')
coalesce: bool | None = Field(..., description='是否合并运行:是否在多个运行时间到期时仅运行作业一次')
max_instances: int | None = Field(default=1, ge=1, description='最大实例数:允许的最大并发执行实例数')
jobstore: str | None = Field(..., max_length=64, description='任务存储')
executor: str | None = Field(..., max_length=64, description='任务执行器:将运行此作业的执行程序的名称')
trigger_args: str | None = Field(default=None, description='触发器参数')
start_date: str | None = Field(default=None, description='开始时间')
end_date: str | None = Field(default=None, description='结束时间')
description: str | None = Field(default=None, max_length=255, description='描述')
status: str = Field(default='0', description='任务状态:启动,停止')
@field_validator('trigger')
@classmethod
def _validate_trigger(cls, v: str) -> str:
allowed = {'cron', 'interval', 'date'}
v = v.strip()
if v not in allowed:
raise ValueError('触发器必须为 cron/interval/date')
return v
@model_validator(mode='after')
def _validate_dates(self):
"""跨字段校验:结束时间不得早于开始时间。"""
if self.start_date and self.end_date:
try:
start = datetime_validator(self.start_date)
end = datetime_validator(self.end_date)
except Exception:
raise ValueError('时间格式必须为 YYYY-MM-DD HH:MM:SS')
if end < start:
raise ValueError('结束时间不能早于开始时间')
return self
class JobUpdateSchema(JobCreateSchema):
"""定时任务更新模型"""
...
class JobOutSchema(JobCreateSchema, BaseSchema, UserBySchema):
"""定时任务响应模型"""
model_config = ConfigDict(from_attributes=True)
...
class JobLogCreateSchema(BaseModel):
"""
定时任务调度日志表对应pydantic模型
"""
model_config = ConfigDict(from_attributes=True)
job_name: str = Field(..., description='任务名称')
job_group: str | None = Field(default=None, description='任务组名')
job_executor: str | None = Field(default=None, description='任务执行器')
invoke_target: str | None = Field(default=None, description='调用目标字符串')
job_args: str | None = Field(default=None, description='位置参数')
job_kwargs: str | None = Field(default=None, description='关键字参数')
job_trigger: str | None = Field(default=None, description='任务触发器')
job_message: str | None = Field(default=None, description='日志信息')
exception_info: str | None = Field(default=None, description='异常信息')
status: str = Field(default='0', description='任务状态:正常,失败')
description: str | None = Field(default=None, max_length=255, description='描述')
created_time: DateTimeStr | None = Field(default=None, description='创建时间')
updated_time: DateTimeStr | None = Field(default=None, description='更新时间')
class JobLogUpdateSchema(JobLogCreateSchema):
"""定时任务调度日志表更新模型"""
...
id: int | None = Field(default=None, description='任务日志ID')
class JobLogOutSchema(JobLogUpdateSchema, BaseSchema, UserBySchema):
"""定时任务调度日志表响应模型"""
model_config = ConfigDict(from_attributes=True)
...
class JobQueryParam:
"""定时任务查询参数"""
def __init__(
self,
name: str | None = Query(None, description="任务名称"),
status: str | None = Query(None, description="状态: 启动,停止"),
created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
created_id: int | None = Query(None, description="创建人"),
updated_id: int | None = Query(None, description="更新人"),
) -> None:
# 模糊查询字段
self.name = ("like", f"%{name}%") if name else None
# 精确查询字段
self.created_id = created_id
self.updated_id = updated_id
self.status = status
# 时间范围查询
if created_time and len(created_time) == 2:
self.created_time = ("between", (created_time[0], created_time[1]))
if updated_time and len(updated_time) == 2:
self.updated_time = ("between", (updated_time[0], updated_time[1]))
class JobLogQueryParam:
"""定时任务查询参数"""
def __init__(
self,
job_id: int | None = Query(None, description="定时任务ID"),
job_name: str | None = Query(None, description="任务名称"),
status: str | None = Query(None, description="状态: 正常,失败"),
created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
) -> None:
# 定时任务ID查询
self.job_id = job_id
# 模糊查询字段
self.job_name = ("like", job_name)
# 精确查询字段
self.status = status
# 时间范围查询
if created_time and len(created_time) == 2:
self.created_time = ("between", (created_time[0], created_time[1]))
if updated_time and len(updated_time) == 2:
self.updated_time = ("between", (updated_time[0], updated_time[1]))

View File

@@ -0,0 +1,307 @@
# -*- coding: utf-8 -*-
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 .tools.ap_scheduler import SchedulerUtil
from .crud import JobCRUD, JobLogCRUD
from .schema import (
JobCreateSchema,
JobUpdateSchema,
JobOutSchema,
JobLogOutSchema,
JobQueryParam,
JobLogQueryParam
)
class JobService:
"""
定时任务管理模块服务层
"""
@classmethod
async def get_job_detail_service(cls, auth: AuthSchema, id: int) -> dict:
"""
获取定时任务详情
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 定时任务ID
返回:
- 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: JobQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict]:
"""
获取定时任务列表
参数:
- auth (AuthSchema): 认证信息模型
- search (JobQueryParam | None): 查询参数模型
- order_by (list[dict[str, str]] | None): 排序参数列表
返回:
- List[Dict]: 定时任务详情字典列表
"""
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:
"""
创建定时任务
参数:
- auth (AuthSchema): 认证信息模型
- data (JobCreateSchema): 定时任务创建模型
返回:
- Dict: 定时任务详情字典
"""
exist_obj = await JobCRUD(auth).get(name=data.name)
if exist_obj:
raise CustomException(msg='创建失败,该定时任务已存在')
obj = await JobCRUD(auth).create_obj_crud(data=data)
if not obj:
raise CustomException(msg='创建失败,该数据定时任务不存在')
SchedulerUtil().add_job(job_info=obj)
return JobOutSchema.model_validate(obj).model_dump()
@classmethod
async def update_job_service(cls, auth: AuthSchema, id:int, data: JobUpdateSchema) -> dict:
"""
更新定时任务
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 定时任务ID
- data (JobUpdateSchema): 定时任务更新模型
返回:
- dict: 定时任务详情字典
"""
exist_obj = await JobCRUD(auth).get_obj_by_id_crud(id=id)
if not exist_obj:
raise CustomException(msg='更新失败,该定时任务不存在')
if data.trigger == 'cron' and data.trigger_args and not CronUtil.validate_cron_expression(data.trigger_args):
raise CustomException(msg=f'新增定时任务{data.name}失败, Cron表达式不正确')
obj = await JobCRUD(auth).update_obj_crud(id=id, data=data)
if not obj:
raise CustomException(msg='更新失败,该数据定时任务不存在')
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:
"""
删除定时任务
参数:
- auth (AuthSchema): 认证信息模型
- ids (list[int]): 定时任务ID列表
"""
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='删除失败,该数据定时任务不存在')
obj = await JobLogCRUD(auth).get(job_id=id)
if obj:
raise CustomException(msg=f'删除失败,该定时任务存 {exist_obj.name} 在日志记录')
SchedulerUtil().remove_job(job_id=id)
await JobCRUD(auth).delete_obj_crud(ids=ids)
@classmethod
async def clear_job_service(cls, auth: AuthSchema) -> None:
"""
清空所有定时任务
参数:
- auth (AuthSchema): 认证信息模型
"""
SchedulerUtil().clear_jobs()
await JobLogCRUD(auth).clear_obj_log_crud()
await JobCRUD(auth).clear_obj_crud()
@classmethod
async def option_job_service(cls, auth: AuthSchema, id: int, option: int) -> None:
"""
操作定时任务
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 定时任务ID
- option (int): 操作类型, 1: 暂停 2: 恢复 3: 重启
"""
# 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().remove_job(job_id=id)
# 获取最新的任务配置
updated_job = await JobCRUD(auth).get_obj_by_id_crud(id=id)
if updated_job:
# 重新添加任务
SchedulerUtil.add_job(job_info=updated_job)
# 设置状态为运行中
await JobCRUD(auth).set_obj_field_crud(ids=[id], status=True)
@classmethod
async def export_job_service(cls, data_list: list[dict]) -> bytes:
"""
导出定时任务列表
参数:
- data_list (list[dict]): 定时任务列表
返回:
- bytes: Excel文件字节流
"""
mapping_dict = {
'id': '编号',
'name': '任务名称',
'func': '任务函数',
'trigger': '触发器',
'args': '位置参数',
'kwargs': '关键字参数',
'coalesce': '是否合并运行',
'max_instances': '最大实例数',
'jobstore': '任务存储',
'executor': '任务执行器',
'trigger_args': '触发器参数',
'status': '任务状态',
'message': '日志信息',
'description': '备注',
'created_time': '创建时间',
'updated_time': '更新时间',
'created_id': '创建者ID',
'updated_id': '更新者ID',
}
# 复制数据并转换状态
data = data_list.copy()
for item in data:
item['status'] = '已完成' if item['status'] == '0' else '运行中' if item['status'] == '1' else '暂停'
return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)
class JobLogService:
"""
定时任务日志管理模块服务层
"""
@classmethod
async def get_job_log_detail_service(cls, auth: AuthSchema, id: int) -> dict:
"""
获取定时任务日志详情
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 定时任务日志ID
返回:
- dict: 定时任务日志详情字典
"""
obj = await JobLogCRUD(auth).get_obj_log_by_id_crud(id=id)
return JobLogOutSchema.model_validate(obj).model_dump()
@classmethod
async def get_job_log_list_service(cls, auth: AuthSchema, search: JobLogQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]:
"""
获取定时任务日志列表
参数:
- auth (AuthSchema): 认证信息模型
- search (JobLogQueryParam | None): 查询参数模型, 包含分页信息和查询条件
- order_by (list[dict] | None): 排序参数列表, 每个元素为一个字典, 包含字段名和排序方向
返回:
- list[dict]: 定时任务日志详情字典列表
"""
obj_list = await JobLogCRUD(auth).get_obj_log_list_crud(search=search.__dict__, order_by=order_by)
return [JobLogOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def delete_job_log_service(cls, auth: AuthSchema, ids: list[int]) -> None:
"""
删除定时任务日志
参数:
- auth (AuthSchema): 认证信息模型
- ids (list[int]): 定时任务日志ID列表
"""
if len(ids) < 1:
raise CustomException(msg='删除失败,删除对象不能为空')
for id in ids:
exist_obj = await JobLogCRUD(auth).get_obj_log_by_id_crud(id=id)
if not exist_obj:
raise CustomException(msg=f'删除失败该定时任务日志ID为{id}的记录不存在')
await JobLogCRUD(auth).delete_obj_log_crud(ids=ids)
@classmethod
async def clear_job_log_service(cls, auth: AuthSchema) -> None:
"""
清空定时任务日志
参数:
- auth (AuthSchema): 认证信息模型
"""
# 获取所有日志ID并批量删除
all_logs = await JobLogCRUD(auth).get_obj_log_list_crud()
if all_logs:
ids = [log.id for log in all_logs]
await JobLogCRUD(auth).delete_obj_log_crud(ids=ids)
@classmethod
async def export_job_log_service(cls, data_list: list[dict]) -> bytes:
"""
导出定时任务日志列表
参数:
- data_list (List[Dict[str, Any]]): 定时任务日志列表
返回:
- bytes: Excel文件字节流
"""
mapping_dict = {
'id': '编号',
'job_name': '任务名称',
'job_group': '任务组名',
'job_executor': '任务执行器',
'invoke_target': '调用目标字符串',
'job_args': '位置参数',
'job_kwargs': '关键字参数',
'job_trigger': '任务触发器',
'job_message': '日志信息',
'exception_info': '异常信息',
'status': '执行状态',
'created_time': '创建时间',
'updated_time': '更新时间',
}
# 复制数据并转换状态
data = data_list.copy()
for item in data:
item['status'] = '成功' if item.get('status') == '0' else '失败'
return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,589 @@
# -*- coding: utf-8 -*-
import json
import importlib
from datetime import datetime
from typing import Any
from asyncio import iscoroutinefunction
from apscheduler.job import Job
from apscheduler.events import JobExecutionEvent, EVENT_ALL, JobEvent
from apscheduler.executors.asyncio import AsyncIOExecutor
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.executors.pool import ProcessPoolExecutor
from apscheduler.jobstores.memory import MemoryJobStore
from apscheduler.jobstores.redis import RedisJobStore
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.date import DateTrigger
from apscheduler.triggers.interval import IntervalTrigger
from concurrent.futures import ThreadPoolExecutor
from app.config.setting import settings
from app.core.database import engine, db_session, async_db_session
from app.core.exceptions import CustomException
from app.core.logger import log
from app.utils.cron_util import CronUtil
from app.api.v1.module_application.job.model import JobModel
job_stores = {
'default': MemoryJobStore(),
'sqlalchemy': SQLAlchemyJobStore(url=settings.DB_URI, engine=engine),
'redis': RedisJobStore(
host=settings.REDIS_HOST,
port=int(settings.REDIS_PORT),
username=settings.REDIS_USER,
password=settings.REDIS_PASSWORD,
db=int(settings.REDIS_DB_NAME),
),
}
# 配置执行器
executors = {
'default': AsyncIOExecutor(),
'processpool': ProcessPoolExecutor(max_workers=1) # 减少进程数量以减少资源消耗
}
# 配置默认参数
job_defaults = {
'coalesce': True, # 合并执行错过的任务
'max_instances': 1, # 最大实例数
}
# 配置调度器
scheduler = AsyncIOScheduler()
scheduler.configure(
jobstores=job_stores,
executors=executors,
job_defaults=job_defaults,
timezone='Asia/Shanghai'
)
class SchedulerUtil:
"""
定时任务相关方法
"""
@classmethod
def scheduler_event_listener(cls, event: JobEvent | JobExecutionEvent) -> None:
"""
监听任务执行事件。
参数:
- event (JobEvent | JobExecutionEvent): 任务事件对象。
返回:
- None
"""
# 延迟导入避免循环导入
from app.api.v1.module_application.job.model import JobLogModel
# 获取事件类型和任务ID
event_type = event.__class__.__name__
# 初始化任务状态
status = True
exception_info = ''
if isinstance(event, JobExecutionEvent) and event.exception:
exception_info = str(event.exception)
status = False
if hasattr(event, 'job_id'):
job_id = event.job_id
query_job = cls.get_job(job_id=job_id)
if query_job:
query_job_info = query_job.__getstate__()
# 获取任务名称
job_name = query_job_info.get('name')
# 获取任务组名
job_group = query_job._jobstore_alias
# # 获取任务执行器
job_executor = query_job_info.get('executor')
# 获取调用目标字符串
invoke_target = query_job_info.get('func')
# 获取调用函数位置参数
job_args = ','.join(map(str, query_job_info.get('args', [])))
# 获取调用函数关键字参数
job_kwargs = json.dumps(query_job_info.get('kwargs'))
# 获取任务触发器
job_trigger = str(query_job_info.get('trigger'))
# 构造日志消息
job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 状态: {status}, 任务组: {job_group}, 错误详情: {exception_info}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
# 创建ORM对象
job_log = JobLogModel(
job_name=job_name,
job_group=job_group,
job_executor=job_executor,
invoke_target=invoke_target,
job_args=job_args,
job_kwargs=job_kwargs,
job_trigger=job_trigger,
job_message=job_message,
status=status,
exception_info=exception_info,
created_time=datetime.now(),
updated_time=datetime.now(),
job_id=job_id,
)
# 使用线程池执行操作以避免阻塞调度器和数据库锁定问题
executor = ThreadPoolExecutor(max_workers=1)
executor.submit(cls._save_job_log_async_wrapper, job_log)
executor.shutdown(wait=False)
@classmethod
def _save_job_log_async_wrapper(cls, job_log) -> None:
"""
异步保存任务日志的包装器函数,在独立线程中运行
参数:
- job_log (JobLogModel): 任务日志对象
返回:
- None
"""
with db_session.begin() as session:
try:
session.add(job_log)
session.commit()
except Exception as e:
session.rollback()
log.error(f"保存任务日志失败: {str(e)}")
finally:
session.close()
@classmethod
async def init_system_scheduler(cls) -> None:
"""
应用启动时初始化定时任务。
返回:
- None
"""
# 延迟导入避免循环导入
from app.api.v1.module_application.job.crud import JobCRUD
from app.api.v1.module_system.auth.schema import AuthSchema
log.info('🔎 开始启动定时任务...')
# 启动调度器
scheduler.start()
# 添加事件监听器
scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL)
async with async_db_session() as session:
async with session.begin():
auth = AuthSchema(db=session)
job_list = await JobCRUD(auth).get_obj_list_crud()
# 只在一个实例上初始化任务
# 使用Redis锁确保只有一个实例执行任务初始化
import redis.asyncio as redis
redis_client = redis.Redis(
host=settings.REDIS_HOST,
port=int(settings.REDIS_PORT),
username=settings.REDIS_USER,
password=settings.REDIS_PASSWORD,
db=int(settings.REDIS_DB_NAME),
)
# 尝试获取锁过期时间10秒
lock_key = "scheduler_init_lock"
lock_acquired = await redis_client.set(lock_key, "1", ex=10, nx=True)
if lock_acquired:
try:
for item in job_list:
# 检查任务是否已经存在
existing_job = cls.get_job(job_id=item.id)
if existing_job:
cls.remove_job(job_id=item.id) # 删除旧任务
# 添加新任务
cls.add_job(item)
# 根据数据库中保存的状态来设置任务状态
if hasattr(item, 'status') and item.status == "1":
# 如果任务状态为暂停,则立即暂停刚添加的任务
cls.pause_job(job_id=item.id)
log.info('✅️ 系统初始定时任务加载成功')
finally:
# 释放锁
await redis_client.delete(lock_key)
else:
# 等待其他实例完成初始化
import asyncio
await asyncio.sleep(2)
log.info('✅️ 定时任务已由其他实例初始化完成')
@classmethod
async def close_system_scheduler(cls) -> None:
"""
关闭系统定时任务。
返回:
- None
"""
try:
# 移除所有任务
scheduler.remove_all_jobs()
# 等待所有任务完成后再关闭
scheduler.shutdown(wait=True)
log.info('✅️ 关闭定时任务成功')
except Exception as e:
log.error(f'关闭定时任务失败: {str(e)}')
@classmethod
def get_job(cls, job_id: str | int) -> Job | None:
"""
根据任务ID获取任务对象。
参数:
- job_id (str | int): 任务ID。
返回:
- Job | None: 任务对象,未找到则为 None。
"""
return scheduler.get_job(job_id=str(job_id))
@classmethod
def get_all_jobs(cls) -> list[Job]:
"""
获取全部调度任务列表。
返回:
- list[Job]: 任务列表。
"""
return scheduler.get_jobs()
@classmethod
async def _task_wrapper(cls, job_id, func, *args, **kwargs):
"""
任务执行包装器,添加分布式锁防止同一任务被多个实例同时执行。
参数:
- job_id: 任务ID
- func: 实际要执行的任务函数
- *args: 任务函数位置参数
- **kwargs: 任务函数关键字参数
返回:
- 任务函数的返回值
"""
import redis.asyncio as redis
import asyncio
from app.config.setting import settings
# 创建Redis客户端
redis_client = redis.Redis(
host=settings.REDIS_HOST,
port=int(settings.REDIS_PORT),
username=settings.REDIS_USER,
password=settings.REDIS_PASSWORD,
db=int(settings.REDIS_DB_NAME),
)
# 生成锁键
lock_key = f"job_lock:{job_id}"
# 设置锁的过期时间根据任务类型调整这里设置为30秒
lock_expire = 30
lock_acquired = False
try:
# 尝试获取锁
lock_acquired = await redis_client.set(lock_key, "1", ex=lock_expire, nx=True)
if lock_acquired:
log.info(f"任务 {job_id} 获取执行锁成功")
# 执行任务
if iscoroutinefunction(func):
return await func(*args, **kwargs)
else:
# 对于同步函数,使用线程池执行
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, func, *args, **kwargs)
else:
# 获取锁失败,记录日志
log.info(f"任务 {job_id} 获取执行锁失败,跳过本次执行")
return None
finally:
# 释放锁
if lock_acquired:
await redis_client.delete(lock_key)
log.info(f"任务 {job_id} 释放执行锁")
@classmethod
def add_job(cls, job_info: JobModel) -> Job:
"""
根据任务配置创建并添加调度任务。
参数:
- job_info (JobModel): 任务对象信息(包含触发器、函数、参数等)。
返回:
- Job: 新增的任务对象。
"""
# 动态导入模块
# 1. 解析调用目标
module_path, func_name = str(job_info.func).rsplit('.', 1)
module_path = "app.api.v1.module_application.job.function_task." + module_path
try:
module = importlib.import_module(module_path)
job_func = getattr(module, func_name)
# 2. 确定任务存储器优先使用redis确保分布式环境中任务同步
if job_info.jobstore is None:
job_info.jobstore = 'redis' # 改为默认使用redis存储
# 3. 确定执行器
job_executor = job_info.executor
if job_executor is None:
job_executor = 'default'
if job_info.trigger_args is None:
raise ValueError("触发器缺少参数")
# 异步函数必须使用默认执行器
if iscoroutinefunction(job_func):
job_executor = 'default'
# 4. 创建触发器
if job_info.trigger == 'date':
trigger = DateTrigger(run_date=job_info.trigger_args)
elif job_info.trigger == 'interval':
# 将传入的 interval 表达式拆分为不同的字段
fields = job_info.trigger_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])
# 秒、分、时、天、周(* * * * 1
trigger = IntervalTrigger(
weeks=week,
days=day,
hours=hour,
minutes=minute,
seconds=second,
start_date=job_info.start_date,
end_date=job_info.end_date,
timezone='Asia/Shanghai',
jitter=None
)
elif job_info.trigger == 'cron':
# 秒、分、时、天、月、星期几、年 ()
fields = job_info.trigger_args.strip().split()
if len(fields) not in (6, 7):
raise ValueError("无效的 Cron 表达式")
if not CronUtil.validate_cron_expression(job_info.trigger_args):
raise ValueError(f'定时任务{job_info.name}, Cron表达式不正确')
parsed_fields = [None if field in ('*', '?') else field for field in fields]
if len(fields) == 6:
parsed_fields.append(None)
second, minute, hour, day, month, day_of_week, year = tuple(parsed_fields)
trigger = CronTrigger(
second=second,
minute=minute,
hour=hour,
day=day,
month=month,
day_of_week=day_of_week,
year=year,
start_date=job_info.start_date,
end_date=job_info.end_date,
timezone='Asia/Shanghai'
)
else:
raise ValueError("无效的 trigger 触发器")
# 5. 添加任务(使用包装器函数)
job = scheduler.add_job(
func=cls._task_wrapper,
trigger=trigger,
args=[str(job_info.id), job_func] + (str(job_info.args).split(',') if job_info.args else []),
kwargs=json.loads(job_info.kwargs) if job_info.kwargs else {},
id=str(job_info.id),
name=job_info.name,
coalesce=job_info.coalesce,
max_instances=1, # 确保只有一个实例执行
jobstore=job_info.jobstore,
executor=job_executor,
)
log.info(f"任务 {job_info.id} 添加到 {job_info.jobstore} 存储器成功")
return job
except ModuleNotFoundError:
raise ValueError(f"未找到该模块:{module_path}")
except AttributeError:
raise ValueError(f"未找到该模块下的方法:{func_name}")
except Exception as e:
raise CustomException(msg=f"添加任务失败: {str(e)}")
@classmethod
def remove_job(cls, job_id: str | int) -> None:
"""
根据任务ID删除调度任务。
参数:
- job_id (str | int): 任务ID。
返回:
- None
"""
query_job = cls.get_job(job_id=str(job_id))
if query_job:
scheduler.remove_job(job_id=str(job_id))
@classmethod
def clear_jobs(cls) -> None:
"""
删除所有调度任务。
返回:
- None
"""
scheduler.remove_all_jobs()
@classmethod
def modify_job(cls, job_id: str | int) -> Job:
"""
更新指定任务的配置(运行中的任务下次执行生效)。
参数:
- job_id (str | int): 任务ID。
返回:
- Job: 更新后的任务对象。
异常:
- CustomException: 当任务不存在时抛出。
"""
query_job = cls.get_job(job_id=str(job_id))
if not query_job:
raise CustomException(msg=f"未找到该任务:{job_id}")
return scheduler.modify_job(job_id=str(job_id))
@classmethod
def pause_job(cls, job_id: str | int) -> None:
"""
暂停指定任务(仅运行中可暂停,已终止不可)。
参数:
- job_id (str | int): 任务ID。
返回:
- None
异常:
- ValueError: 当任务不存在时抛出。
"""
query_job = cls.get_job(job_id=str(job_id))
if not query_job:
raise ValueError(f"未找到该任务:{job_id}")
scheduler.pause_job(job_id=str(job_id))
@classmethod
def resume_job(cls, job_id: str | int) -> None:
"""
恢复指定任务(仅暂停中可恢复,已终止不可)。
参数:
- job_id (str | int): 任务ID。
返回:
- None
异常:
- ValueError: 当任务不存在时抛出。
"""
query_job = cls.get_job(job_id=str(job_id))
if not query_job:
raise ValueError(f"未找到该任务:{job_id}")
scheduler.resume_job(job_id=str(job_id))
@classmethod
def reschedule_job(cls, job_id: str | int, trigger=None, **trigger_args) -> Job | None:
"""
重启指定任务的触发器。
参数:
- job_id (str | int): 任务ID。
- trigger: 触发器类型
- **trigger_args: 触发器参数
返回:
- Job: 更新后的任务对象
异常:
- CustomException: 当任务不存在时抛出。
"""
query_job = cls.get_job(job_id=str(job_id))
if not query_job:
raise CustomException(msg=f"未找到该任务:{job_id}")
# 如果没有提供新的触发器,则使用现有触发器
if trigger is None:
# 获取当前任务的触发器配置
current_trigger = query_job.trigger
# 重新调度任务,使用当前的触发器
return scheduler.reschedule_job(job_id=str(job_id), trigger=current_trigger)
else:
# 使用新提供的触发器
return scheduler.reschedule_job(job_id=str(job_id), trigger=trigger, **trigger_args)
@classmethod
def get_single_job_status(cls, job_id: str | int) -> str:
"""
获取单个任务的当前状态。
参数:
- job_id (str | int): 任务ID
返回:
- str: 任务状态('running' | 'paused' | 'stopped' | 'unknown'
"""
job = cls.get_job(job_id=str(job_id))
if not job:
return 'unknown'
# 检查任务是否在暂停列表中
if job_id in scheduler._jobstores[job._jobstore_alias]._paused_jobs:
return 'paused'
# 检查调度器状态
if scheduler.state == 0: # STATE_STOPPED
return 'stopped'
return 'running'
@classmethod
def print_jobs(cls,jobstore: Any | None = None, out: Any | None = None):
"""
打印调度任务列表。
参数:
- jobstore (Any | None): 任务存储别名。
- out (Any | None): 输出目标。
返回:
- None
"""
scheduler.print_jobs(jobstore=jobstore, out=out)
@classmethod
def get_job_status(cls) -> str:
"""
获取调度器当前状态。
返回:
- str: 状态字符串('stopped' | 'running' | 'paused' | 'unknown')。
"""
#: constant indicating a scheduler's stopped state
STATE_STOPPED = 0
#: constant indicating a scheduler's running state (started and processing jobs)
STATE_RUNNING = 1
#: constant indicating a scheduler's paused state (started but not processing jobs)
STATE_PAUSED = 2
if scheduler.state == STATE_STOPPED:
return 'stopped'
elif scheduler.state == STATE_RUNNING:
return 'running'
elif scheduler.state == STATE_PAUSED:
return 'paused'
else:
return 'unknown'