- shared/base_repository.py 新增 batch_delete_r(返回 R,等价原 base_model 语义) - 20 个模块删除函数从 Model.batch_delete_r 改为 x_repo.batch_delete_r - shared/base_model.py 移除 classmethod(get_by_id/get_by_ids/get_all_active/exists/batch_delete_r),只留字段 + to_dict - 清理 base_model 多余 imports - 实测 dept 删除、批量删除及多模块读接口正常 Co-Authored-By: Claude <noreply@anthropic.com>
368 lines
11 KiB
Python
368 lines
11 KiB
Python
import logging
|
||
from datetime import datetime, timezone
|
||
|
||
from shared.enums import JOB_STATUS_LIST, JOB_EXECUTE_POLICY_LIST
|
||
from shared.constants import PAGE_SIZE
|
||
from modules.job.schemas import JobForm, JobStatusForm
|
||
from modules.job.models import Job
|
||
from modules.job.repository import job_repo, joblog_repo
|
||
from modules.job.scheduler import (
|
||
add_job_to_scheduler,
|
||
remove_job_from_scheduler,
|
||
pause_job_in_scheduler,
|
||
get_scheduler,
|
||
run_job_once as scheduler_run_job_once,
|
||
reload_scheduler,
|
||
)
|
||
from core.database import get_db_session
|
||
from core import R
|
||
from core.security import get_user_id
|
||
from utils.request import like_contains, parse_id_list
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
# 查询定时任务分页数据
|
||
|
||
def get_job_page(request):
|
||
# 页码
|
||
page = int(request.query_params.get("pageNo", 1))
|
||
# 每页数
|
||
limit = int(request.query_params.get("pageSize", PAGE_SIZE))
|
||
# 实例化查询对象
|
||
query = job_repo.filter(Job.is_delete == 0)
|
||
# 任务名称
|
||
job_name = request.query_params.get('jobName')
|
||
if job_name:
|
||
query = query.filter(Job.job_name.like(like_contains(job_name), escape='\\'))
|
||
# 任务状态
|
||
status = request.query_params.get('status')
|
||
if status:
|
||
query = query.filter(Job.status == status)
|
||
# 任务分组
|
||
job_group = request.query_params.get('jobGroup')
|
||
if job_group:
|
||
query = query.filter(Job.job_group == job_group)
|
||
# 排序
|
||
query = query.order_by(Job.id.desc())
|
||
# 记录总数
|
||
count = query.count()
|
||
# 分页查询
|
||
job_list = query.limit(limit).offset((page - 1) * limit).all()
|
||
# 若当前页超出末页(数据被并发删除),自动回退到第一页
|
||
if page > 1 and not job_list:
|
||
page = 1
|
||
count = query.count()
|
||
job_list = query.limit(limit).offset(0).all()
|
||
# 实例化结果
|
||
scheduler = get_scheduler()
|
||
result = []
|
||
if len(job_list) > 0:
|
||
for item in job_list:
|
||
data = item.to_dict()
|
||
data['status_name'] = JOB_STATUS_LIST.get(item.status)
|
||
data['execute_policy_name'] = JOB_EXECUTE_POLICY_LIST.get(item.execute_policy)
|
||
data['next_run_time'] = _get_next_run_time(scheduler, item.id)
|
||
result.append(data)
|
||
return R.ok(data=result, count=count)
|
||
|
||
|
||
def _get_next_run_time(scheduler, job_id):
|
||
"""从调度器获取任务下次执行时间"""
|
||
try:
|
||
scheduler_job = scheduler.get_job(f"job_{job_id}")
|
||
if scheduler_job and scheduler_job.next_run_time:
|
||
return scheduler_job.next_run_time.strftime('%Y-%m-%d %H:%M:%S')
|
||
except Exception:
|
||
pass
|
||
return None
|
||
|
||
|
||
# 根据ID查询任务详情
|
||
|
||
def get_job_detail(job_id):
|
||
job = job_repo.get_by_id(job_id)
|
||
if not job:
|
||
return None
|
||
data = job.to_dict()
|
||
data['status_name'] = JOB_STATUS_LIST.get(job.status)
|
||
data['execute_policy_name'] = JOB_EXECUTE_POLICY_LIST.get(job.execute_policy)
|
||
return data
|
||
|
||
|
||
# 添加定时任务
|
||
|
||
def add_job(request, data: JobForm):
|
||
# 表单数据赋值给对象并插入
|
||
job = job_repo.create(**data.model_dump(exclude={'id'}), create_user=get_user_id(request))
|
||
|
||
# 状态为运行中且有Cron表达式,添加到调度器
|
||
if job.status == 1 and job.cron_expression:
|
||
add_job_to_scheduler(job.to_dict())
|
||
|
||
return R.ok(msg="添加成功")
|
||
|
||
|
||
# 更新定时任务
|
||
|
||
def update_job(request, data: JobForm):
|
||
# 记录ID判空
|
||
if not data.id or data.id <= 0:
|
||
return R.failed("记录ID不能为空")
|
||
|
||
# 根据ID查询记录
|
||
job = job_repo.get_by_id(data.id)
|
||
if not job:
|
||
return R.failed("任务不存在")
|
||
|
||
old_status = job.status
|
||
old_cron = job.cron_expression
|
||
|
||
# 构造更新数据(排除 id)
|
||
updData = data.model_dump(exclude={'id'})
|
||
updData['update_user'] = get_user_id(request)
|
||
updData['update_time'] = datetime.now(tz=timezone.utc)
|
||
result = job_repo.update(data.id, updData)
|
||
if not result:
|
||
return R.failed("更新失败")
|
||
|
||
# 处理调度器更新
|
||
_handle_scheduler_update(data.model_dump(), old_status, old_cron)
|
||
|
||
return R.ok(msg="更新成功")
|
||
|
||
|
||
def _handle_scheduler_update(job_data, old_status, old_cron):
|
||
"""处理调度器更新(job_data 为完整任务数据,含 execute_policy/params)"""
|
||
job_id = job_data.get('id')
|
||
new_status = job_data.get('status')
|
||
new_cron = job_data.get('cron_expression')
|
||
if old_status != 1 and new_status == 1 and new_cron:
|
||
add_job_to_scheduler(job_data)
|
||
elif old_status == 1 and new_status != 1:
|
||
remove_job_from_scheduler(job_id)
|
||
elif new_status == 1 and old_cron != new_cron:
|
||
if new_cron:
|
||
add_job_to_scheduler(job_data)
|
||
else:
|
||
remove_job_from_scheduler(job_id)
|
||
|
||
|
||
# 删除定时任务
|
||
|
||
def delete_job(job_id):
|
||
# 从调度器移除
|
||
id_list = parse_id_list(job_id)
|
||
for jid in id_list:
|
||
remove_job_from_scheduler(jid)
|
||
return job_repo.batch_delete_r(job_id)
|
||
|
||
|
||
# 设置任务状态
|
||
|
||
def update_job_status(request, data: JobStatusForm):
|
||
# 根据ID查询记录
|
||
job = job_repo.get_by_id(data.id)
|
||
if not job:
|
||
return R.failed("任务不存在")
|
||
|
||
old_status = job.status
|
||
|
||
# 更新记录
|
||
result = job_repo.update(data.id, {
|
||
"status": data.status,
|
||
"update_user": get_user_id(request),
|
||
"update_time": datetime.now(tz=timezone.utc)
|
||
})
|
||
if not result:
|
||
return R.failed("设置失败")
|
||
|
||
# 处理调度器
|
||
if data.status == 1 and job.cron_expression:
|
||
job_dict = job.to_dict()
|
||
job_dict['status'] = data.status
|
||
add_job_to_scheduler(job_dict)
|
||
elif data.status == 2:
|
||
pause_job_in_scheduler(data.id)
|
||
elif old_status == 1 and data.status != 1:
|
||
remove_job_from_scheduler(data.id)
|
||
|
||
return R.ok(msg="设置成功")
|
||
|
||
|
||
# 暂停任务
|
||
|
||
def pause_job_by_id(job_id):
|
||
db = get_db_session()
|
||
job = job_repo.get_by_id(job_id)
|
||
if not job:
|
||
return R.failed("任务不存在")
|
||
|
||
if job.status == 2:
|
||
return R.ok(msg="任务已处于暂停状态")
|
||
|
||
job.status = 2
|
||
job.update_user = job.create_user
|
||
job.update_time = datetime.now(tz=timezone.utc)
|
||
db.commit()
|
||
pause_job_in_scheduler(job_id)
|
||
|
||
return R.ok(msg="暂停成功")
|
||
|
||
|
||
# 恢复任务
|
||
|
||
def resume_job_by_id(job_id):
|
||
db = get_db_session()
|
||
job = job_repo.get_by_id(job_id)
|
||
if not job:
|
||
return R.failed("任务不存在")
|
||
|
||
if job.status == 1:
|
||
return R.ok(msg="任务已处于运行中状态")
|
||
|
||
if not job.cron_expression:
|
||
return R.failed("Cron表达式为空,无法恢复")
|
||
|
||
job.status = 1
|
||
job.update_user = job.create_user
|
||
job.update_time = datetime.now(tz=timezone.utc)
|
||
db.commit()
|
||
add_job_to_scheduler(job.to_dict())
|
||
|
||
return R.ok(msg="恢复成功")
|
||
|
||
|
||
# 立即执行一次
|
||
|
||
def run_job_once_api(job_id):
|
||
result = scheduler_run_job_once(job_id)
|
||
if result.get('success'):
|
||
return R.ok(data=result, msg="执行成功")
|
||
else:
|
||
return R.failed(msg=result.get('error', '执行失败'))
|
||
|
||
|
||
# 重新加载调度器
|
||
|
||
def reload_all_jobs():
|
||
count = reload_scheduler()
|
||
return R.ok(data=count, msg=f"已重新加载 {count} 个任务")
|
||
|
||
|
||
import logging
|
||
|
||
from shared.enums import JOB_LOG_STATUS_LIST
|
||
from shared.constants import PAGE_SIZE
|
||
from modules.job.models import Job
|
||
from modules.job.models import JobLog
|
||
from core.database import SessionLocal
|
||
from core import R
|
||
from utils.request import like_contains, parse_id_list
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
# ============================================================
|
||
# 日志写入(供调度器调用,可在请求上下文外执行)
|
||
# ============================================================
|
||
|
||
def record_job_log(job_id, start_time, end_time, status, exception_info=None, job_message=None):
|
||
"""
|
||
记录任务执行日志
|
||
|
||
使用独立会话写入,调度器线程等无请求上下文场景也可调用。
|
||
"""
|
||
session = SessionLocal()
|
||
try:
|
||
job = session.query(Job).filter(Job.id == job_id, Job.is_delete == 0).first()
|
||
consume_time = 0
|
||
if end_time and start_time:
|
||
consume_time = int((end_time - start_time).total_seconds() * 1000)
|
||
log = JobLog(
|
||
job_id=job_id,
|
||
job_name=job.job_name if job else '',
|
||
job_group=job.job_group if job else None,
|
||
job_trigger=job.job_trigger if job else None,
|
||
cron_expression=job.cron_expression if job else None,
|
||
job_message=job_message,
|
||
status=status,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
consume_time=consume_time,
|
||
exception_info=exception_info,
|
||
)
|
||
session.add(log)
|
||
session.commit()
|
||
return log
|
||
except Exception as e:
|
||
session.rollback()
|
||
logger.error(f"记录任务执行日志异常: {str(e)}")
|
||
return None
|
||
finally:
|
||
session.close()
|
||
|
||
|
||
# ============================================================
|
||
# 日志查询/删除
|
||
# ============================================================
|
||
|
||
def get_job_log_page(request):
|
||
# 页码
|
||
page = int(request.query_params.get("pageNo", 1))
|
||
# 每页数
|
||
limit = int(request.query_params.get("pageSize", PAGE_SIZE))
|
||
# 实例化查询对象
|
||
query = joblog_repo.filter(JobLog.del_flag == 0)
|
||
# 任务名称
|
||
job_name = request.query_params.get('jobName')
|
||
if job_name:
|
||
query = query.filter(JobLog.job_name.like(like_contains(job_name), escape='\\'))
|
||
# 任务分组
|
||
job_group = request.query_params.get('jobGroup')
|
||
if job_group:
|
||
query = query.filter(JobLog.job_group == job_group)
|
||
# 执行状态
|
||
status = request.query_params.get('status')
|
||
if status:
|
||
query = query.filter(JobLog.status == status)
|
||
# 排序
|
||
query = query.order_by(JobLog.id.desc())
|
||
# 记录总数
|
||
count = query.count()
|
||
# 分页查询
|
||
log_list = query.limit(limit).offset((page - 1) * limit).all()
|
||
# 若当前页超出末页(数据被并发删除),自动回退到第一页
|
||
if page > 1 and not log_list:
|
||
page = 1
|
||
count = query.count()
|
||
log_list = query.limit(limit).offset(0).all()
|
||
# 实例化结果
|
||
result = []
|
||
for item in log_list:
|
||
data = item.to_dict()
|
||
data['status_name'] = JOB_LOG_STATUS_LIST.get(item.status)
|
||
result.append(data)
|
||
return R.ok(data=result, count=count)
|
||
|
||
|
||
# 根据ID查询日志详情
|
||
def get_job_log_detail(job_log_id):
|
||
log = joblog_repo.get_by_id(job_log_id)
|
||
if not log:
|
||
return None
|
||
data = log.to_dict()
|
||
data['status_name'] = JOB_LOG_STATUS_LIST.get(log.status)
|
||
return data
|
||
|
||
|
||
# 删除任务日志(软删除)
|
||
def delete_job_log(ids_str):
|
||
id_list = parse_id_list(ids_str)
|
||
if not id_list:
|
||
return R.failed("记录ID不存在")
|
||
result = joblog_repo.filter(JobLog.id.in_(id_list), JobLog.del_flag == 0).update(
|
||
{"del_flag": 1}, synchronize_session=False
|
||
)
|
||
return R.ok(msg="本次共删除{0}条数据".format(result))
|