Browse Source

perf: 优化定时任务模块service层及异常处理

master
insistence 7 months ago
parent
commit
c39375b776
  1. 27
      ruoyi-fastapi-backend/config/constant.py
  2. 171
      ruoyi-fastapi-backend/config/get_scheduler.py
  3. 104
      ruoyi-fastapi-backend/module_admin/controller/job_controller.py
  4. 11
      ruoyi-fastapi-backend/module_admin/dao/job_dao.py
  5. 96
      ruoyi-fastapi-backend/module_admin/service/job_service.py
  6. 55
      ruoyi-fastapi-backend/utils/string_util.py
  7. 2
      ruoyi-fastapi-frontend/src/views/monitor/job/index.vue

27
ruoyi-fastapi-backend/config/constant.py

@ -5,6 +5,9 @@ class CommonConstant:
WWW: www主域 WWW: www主域
HTTP: http请求 HTTP: http请求
HTTPS: https请求 HTTPS: https请求
LOOKUP_RMI: RMI远程方法调用
LOOKUP_LDAP: LDAP远程方法调用
LOOKUP_LDAPS: LDAPS远程方法调用
YES: 是否为系统默认 YES: 是否为系统默认
NO: 是否为系统默认 NO: 是否为系统默认
DEPT_NORMAL: 部门正常状态 DEPT_NORMAL: 部门正常状态
@ -16,6 +19,9 @@ class CommonConstant:
WWW = 'www.' WWW = 'www.'
HTTP = 'http://' HTTP = 'http://'
HTTPS = 'https://' HTTPS = 'https://'
LOOKUP_RMI = 'rmi:'
LOOKUP_LDAP = 'ldap:'
LOOKUP_LDAPS = 'ldaps:'
YES = 'Y' YES = 'Y'
NO = 'N' NO = 'N'
DEPT_NORMAL = '0' DEPT_NORMAL = '0'
@ -66,6 +72,27 @@ class HttpStatusConstant:
WARN = 601 WARN = 601
class JobConstant:
"""
定时任务常量
JOB_ERROR_LIST: 定时任务禁止调用模块列表
JOB_WHITE_LIST: 定时任务允许调用模块列表
"""
JOB_ERROR_LIST = [
'app',
'config',
'exceptions',
'middlewares',
'module_admin',
'server',
'sub_applications',
'utils',
]
JOB_WHITE_LIST = ['module_task']
class MenuConstant: class MenuConstant:
""" """
菜单常量 菜单常量

171
ruoyi-fastapi-backend/config/get_scheduler.py

@ -1,4 +1,5 @@
import json import json
import re
from apscheduler.events import EVENT_ALL from apscheduler.events import EVENT_ALL
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
@ -9,10 +10,12 @@ from apscheduler.triggers.cron import CronTrigger
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlalchemy.engine import create_engine from sqlalchemy.engine import create_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from typing import Union
from config.database import AsyncSessionLocal, quote_plus from config.database import AsyncSessionLocal, quote_plus
from config.env import DataBaseConfig, RedisConfig from config.env import DataBaseConfig, RedisConfig
from exceptions.exception import ServiceException
from module_admin.dao.job_dao import JobDao from module_admin.dao.job_dao import JobDao
from module_admin.entity.vo.job_vo import JobLogModel from module_admin.entity.vo.job_vo import JobLogModel, JobModel
from module_admin.service.job_log_service import JobLogService from module_admin.service.job_log_service import JobLogService
from utils.log_util import logger from utils.log_util import logger
import module_task # noqa: F401 import module_task # noqa: F401
@ -21,7 +24,7 @@ import module_task # noqa: F401
# 重写Cron定时 # 重写Cron定时
class MyCronTrigger(CronTrigger): class MyCronTrigger(CronTrigger):
@classmethod @classmethod
def from_crontab(cls, expr, timezone=None): def from_crontab(cls, expr: str, timezone=None):
values = expr.split() values = expr.split()
if len(values) != 6 and len(values) != 7: if len(values) != 6 and len(values) != 7:
raise ValueError('Wrong number of fields; got {}, expected 6 or 7'.format(len(values))) raise ValueError('Wrong number of fields; got {}, expected 6 or 7'.format(len(values)))
@ -62,7 +65,7 @@ class MyCronTrigger(CronTrigger):
) )
@classmethod @classmethod
def __find_recent_workday(cls, day): def __find_recent_workday(cls, day: int):
now = datetime.now() now = datetime.now()
date = datetime(now.year, now.month, day) date = datetime(now.year, now.month, day)
if date.weekday() < 5: if date.weekday() < 5:
@ -144,7 +147,7 @@ class SchedulerUtil:
logger.info('关闭定时任务成功') logger.info('关闭定时任务成功')
@classmethod @classmethod
def get_scheduler_job(cls, job_id): def get_scheduler_job(cls, job_id: Union[str, int]):
""" """
根据任务id获取任务对象 根据任务id获取任务对象
@ -156,7 +159,7 @@ class SchedulerUtil:
return query_job return query_job
@classmethod @classmethod
def add_scheduler_job(cls, job_info): def add_scheduler_job(cls, job_info: JobModel):
""" """
根据输入的任务对象信息添加任务 根据输入的任务对象信息添加任务
@ -178,7 +181,7 @@ class SchedulerUtil:
) )
@classmethod @classmethod
def execute_scheduler_job_once(cls, job_info): def execute_scheduler_job_once(cls, job_info: JobModel):
""" """
根据输入的任务对象执行一次任务 根据输入的任务对象执行一次任务
@ -201,7 +204,7 @@ class SchedulerUtil:
) )
@classmethod @classmethod
def remove_scheduler_job(cls, job_id): def remove_scheduler_job(cls, job_id: Union[str, int]):
""" """
根据任务id移除任务 根据任务id移除任务
@ -210,6 +213,160 @@ class SchedulerUtil:
""" """
scheduler.remove_job(job_id=str(job_id)) scheduler.remove_job(job_id=str(job_id))
@classmethod
def __valid_range(cls, search_str: str, start_range: int, end_range: int):
match = re.match(r'^(\d+)-(\d+)$', search_str)
if match:
start, end = int(match.group(1)), int(match.group(2))
return start_range <= start < end <= end_range
return False
@classmethod
def __valid_sum(
cls, search_str: str, start_range_a: int, start_range_b: int, end_range_a: int, end_range_b: int, sum_range: int
):
match = re.match(r'^(\d+)/(\d+)$', search_str)
if match:
start, end = int(match.group(1)), int(match.group(2))
return (
start_range_a <= start <= start_range_b
and end_range_a <= end <= end_range_b
and start + end <= sum_range
)
return False
@classmethod
def __validate_second_or_minute(cls, second_or_minute: str):
"""
校验秒或分钟值是否正确
:param second_or_minute: 秒或分钟值
:return: 校验结果
"""
if (
second_or_minute == '*'
or ('-' in second_or_minute and cls.__valid_range(second_or_minute, 0, 59))
or ('/' in second_or_minute and cls.__valid_sum(second_or_minute, 0, 58, 1, 59))
or re.match(r'^(?:[0-5]?\d|59)(?:,[0-5]?\d|59)*$', second_or_minute)
):
return True
return False
@classmethod
def __validate_hour(cls, hour: str):
"""
校验小时值是否正确
:param hour: 小时值
:return: 校验结果
"""
if (
hour == '*'
or ('-' in hour and cls.__valid_range(hour, 0, 23))
or ('/' in hour and cls.__valid_sum(hour, 0, 22, 1, 23, 23))
or re.match(r'^(?:0|[1-9]|1\d|2[0-3])(?:,(?:0|[1-9]|1\d|2[0-3]))*$', hour)
):
return True
return False
@classmethod
def __validate_day(cls, day: str):
"""
校验日值是否正确
:param day: 日值
:return: 校验结果
"""
if (
day in ['*', '?', 'L']
or ('-' in day and cls.__valid_range(day, 1, 31))
or ('/' in day and cls.__valid_sum(day, 1, 30, 1, 30, 31))
or ('W' in day and re.match(r'^(?:[1-9]|1\d|2\d|3[01])W$', day))
or re.match(r'^(?:0|[1-9]|1\d|2[0-9]|3[0-1])(?:,(?:0|[1-9]|1\d|2[0-9]|3[0-1]))*$', day)
):
return True
return False
@classmethod
def __validate_month(cls, month: str):
"""
校验月值是否正确
:param month: 月值
:return: 校验结果
"""
if (
month == '*'
or ('-' in month and cls.__valid_range(month, 1, 12))
or ('/' in month and cls.__valid_sum(month, 1, 11, 1, 11, 12))
or re.match(r'^(?:0|[1-9]|1[0-2])(?:,(?:0|[1-9]|1[0-2]))*$', month)
):
return True
return False
@classmethod
def __validate_week(cls, week: str):
"""
校验周值是否正确
:param week: 周值
:return: 校验结果
"""
if (
week in ['*', '?']
or ('-' in week and cls.__valid_range(week, 1, 7))
or re.match(r'^[1-7]#[1-4]$', week)
or re.match(r'^[1-7]L$', week)
or re.match(r'^[1-7](?:(,[1-7]))*$', week)
):
return True
return False
@classmethod
def __validate_year(cls, year: str):
"""
校验年值是否正确
:param year: 年值
:return: 校验结果
"""
current_year = int(datetime.now().year)
if (
year == '*'
or ('-' in year and cls.__valid_range(year, current_year, 2099))
or ('/' in year and cls.__valid_sum(year, current_year, 2098, 1, 2099 - current_year, 2099))
or re.match(r'^[1-7]#[1-4]$', year)
or re.match(r'^[1-7]L$', year)
):
return True
return False
@classmethod
def validate_cron_expression(cls, cron_expression: str):
"""
校验Cron表达式是否正确
:param cron_expression: Cron表达式
:return: 校验结果
"""
values = cron_expression.split()
if len(values) != 6 and len(values) != 7:
return False
second_validation = cls.__validate_second_or_minute(values[0])
minute_validation = cls.__validate_second_or_minute(values[1])
hour_validation = cls.__validate_hour(values[2])
day_validation = cls.__validate_day(values[3])
month_validation = cls.__validate_month(values[4])
week_validation = cls.__validate_week(values[5])
validation = (
second_validation
and minute_validation
and hour_validation
and day_validation
and month_validation
and week_validation
)
if len(values) == 6:
return validation
if len(values) == 7:
year_validation = cls.__validate_year(values[6])
return validation and year_validation
@classmethod @classmethod
def scheduler_event_listener(cls, event): def scheduler_event_listener(cls, event):
# 获取事件类型和任务ID # 获取事件类型和任务ID

104
ruoyi-fastapi-backend/module_admin/controller/job_controller.py

@ -35,14 +35,11 @@ async def get_system_job_list(
job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_query), job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db), query_db: AsyncSession = Depends(get_db),
): ):
try:
# 获取分页数据 # 获取分页数据
notice_page_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=True) notice_page_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=True)
logger.info('获取成功') logger.info('获取成功')
return ResponseUtil.success(model_content=notice_page_query_result) return ResponseUtil.success(model_content=notice_page_query_result)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.post('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:add'))]) @jobController.post('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:add'))])
@ -54,21 +51,14 @@ async def add_system_job(
query_db: AsyncSession = Depends(get_db), query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user), current_user: CurrentUserModel = Depends(LoginService.get_current_user),
): ):
try:
add_job.create_by = current_user.user.user_name add_job.create_by = current_user.user.user_name
add_job.create_time = datetime.now() add_job.create_time = datetime.now()
add_job.update_by = current_user.user.user_name add_job.update_by = current_user.user.user_name
add_job.update_time = datetime.now() add_job.update_time = datetime.now()
add_job_result = await JobService.add_job_services(query_db, add_job) add_job_result = await JobService.add_job_services(query_db, add_job)
if add_job_result.is_success:
logger.info(add_job_result.message) logger.info(add_job_result.message)
return ResponseUtil.success(msg=add_job_result.message) return ResponseUtil.success(msg=add_job_result.message)
else:
logger.warning(add_job_result.message)
return ResponseUtil.failure(msg=add_job_result.message)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.put('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))]) @jobController.put('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))])
@ -80,89 +70,62 @@ async def edit_system_job(
query_db: AsyncSession = Depends(get_db), query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user), current_user: CurrentUserModel = Depends(LoginService.get_current_user),
): ):
try:
edit_job.update_by = current_user.user.user_name edit_job.update_by = current_user.user.user_name
edit_job.update_time = datetime.now() edit_job.update_time = datetime.now()
edit_job_result = await JobService.edit_job_services(query_db, edit_job) edit_job_result = await JobService.edit_job_services(query_db, edit_job)
if edit_job_result.is_success:
logger.info(edit_job_result.message) logger.info(edit_job_result.message)
return ResponseUtil.success(msg=edit_job_result.message) return ResponseUtil.success(msg=edit_job_result.message)
else:
logger.warning(edit_job_result.message)
return ResponseUtil.failure(msg=edit_job_result.message)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.put('/job/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) @jobController.put('/job/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))])
@log_decorator(title='定时任务管理', business_type=BusinessType.UPDATE) @log_decorator(title='定时任务管理', business_type=BusinessType.UPDATE)
async def change_system_job_status( async def change_system_job_status(
request: Request, request: Request,
edit_job: EditJobModel, change_job: EditJobModel,
query_db: AsyncSession = Depends(get_db), query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user), current_user: CurrentUserModel = Depends(LoginService.get_current_user),
): ):
try: edit_job = EditJobModel(
edit_job.update_by = current_user.user.user_name jobId=change_job.job_id,
edit_job.update_time = datetime.now() status=change_job.status,
edit_job.type = 'status' updateBy=current_user.user.user_name,
updateTime=datetime.now(),
type='status',
)
edit_job_result = await JobService.edit_job_services(query_db, edit_job) edit_job_result = await JobService.edit_job_services(query_db, edit_job)
if edit_job_result.is_success:
logger.info(edit_job_result.message) logger.info(edit_job_result.message)
return ResponseUtil.success(msg=edit_job_result.message) return ResponseUtil.success(msg=edit_job_result.message)
else:
logger.warning(edit_job_result.message)
return ResponseUtil.failure(msg=edit_job_result.message)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.put('/job/run', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) @jobController.put('/job/run', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))])
@log_decorator(title='定时任务管理', business_type=BusinessType.UPDATE) @log_decorator(title='定时任务管理', business_type=BusinessType.UPDATE)
async def execute_system_job(request: Request, execute_job: JobModel, query_db: AsyncSession = Depends(get_db)): async def execute_system_job(request: Request, execute_job: JobModel, query_db: AsyncSession = Depends(get_db)):
try:
execute_job_result = await JobService.execute_job_once_services(query_db, execute_job) execute_job_result = await JobService.execute_job_once_services(query_db, execute_job)
if execute_job_result.is_success:
logger.info(execute_job_result.message) logger.info(execute_job_result.message)
return ResponseUtil.success(msg=execute_job_result.message) return ResponseUtil.success(msg=execute_job_result.message)
else:
logger.warning(execute_job_result.message)
return ResponseUtil.failure(msg=execute_job_result.message)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.delete('/job/{job_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) @jobController.delete('/job/{job_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))])
@log_decorator(title='定时任务管理', business_type=BusinessType.DELETE) @log_decorator(title='定时任务管理', business_type=BusinessType.DELETE)
async def delete_system_job(request: Request, job_ids: str, query_db: AsyncSession = Depends(get_db)): async def delete_system_job(request: Request, job_ids: str, query_db: AsyncSession = Depends(get_db)):
try:
delete_job = DeleteJobModel(jobIds=job_ids) delete_job = DeleteJobModel(jobIds=job_ids)
delete_job_result = await JobService.delete_job_services(query_db, delete_job) delete_job_result = await JobService.delete_job_services(query_db, delete_job)
if delete_job_result.is_success:
logger.info(delete_job_result.message) logger.info(delete_job_result.message)
return ResponseUtil.success(msg=delete_job_result.message) return ResponseUtil.success(msg=delete_job_result.message)
else:
logger.warning(delete_job_result.message)
return ResponseUtil.failure(msg=delete_job_result.message)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.get( @jobController.get(
'/job/{job_id}', response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))] '/job/{job_id}', response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))]
) )
async def query_detail_system_job(request: Request, job_id: int, query_db: AsyncSession = Depends(get_db)): async def query_detail_system_job(request: Request, job_id: int, query_db: AsyncSession = Depends(get_db)):
try:
job_detail_result = await JobService.job_detail_services(query_db, job_id) job_detail_result = await JobService.job_detail_services(query_db, job_id)
logger.info(f'获取job_id为{job_id}的信息成功') logger.info(f'获取job_id为{job_id}的信息成功')
return ResponseUtil.success(data=job_detail_result) return ResponseUtil.success(data=job_detail_result)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.post('/job/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) @jobController.post('/job/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))])
@ -172,15 +135,12 @@ async def export_system_job_list(
job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_form), job_page_query: JobPageQueryModel = Depends(JobPageQueryModel.as_form),
query_db: AsyncSession = Depends(get_db), query_db: AsyncSession = Depends(get_db),
): ):
try:
# 获取全量数据 # 获取全量数据
job_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=False) job_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=False)
job_export_result = await JobService.export_job_list_services(request, job_query_result) job_export_result = await JobService.export_job_list_services(request, job_query_result)
logger.info('导出成功') logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(job_export_result)) return ResponseUtil.streaming(data=bytes2file_response(job_export_result))
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.get( @jobController.get(
@ -191,49 +151,32 @@ async def get_system_job_log_list(
job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_query), job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db), query_db: AsyncSession = Depends(get_db),
): ):
try:
# 获取分页数据 # 获取分页数据
job_log_page_query_result = await JobLogService.get_job_log_list_services( job_log_page_query_result = await JobLogService.get_job_log_list_services(
query_db, job_log_page_query, is_page=True query_db, job_log_page_query, is_page=True
) )
logger.info('获取成功') logger.info('获取成功')
return ResponseUtil.success(model_content=job_log_page_query_result) return ResponseUtil.success(model_content=job_log_page_query_result)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.delete('/jobLog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) @jobController.delete('/jobLog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))])
@log_decorator(title='定时任务日志管理', business_type=BusinessType.CLEAN) @log_decorator(title='定时任务日志管理', business_type=BusinessType.CLEAN)
async def clear_system_job_log(request: Request, query_db: AsyncSession = Depends(get_db)): async def clear_system_job_log(request: Request, query_db: AsyncSession = Depends(get_db)):
try:
clear_job_log_result = await JobLogService.clear_job_log_services(query_db) clear_job_log_result = await JobLogService.clear_job_log_services(query_db)
if clear_job_log_result.is_success:
logger.info(clear_job_log_result.message) logger.info(clear_job_log_result.message)
return ResponseUtil.success(msg=clear_job_log_result.message) return ResponseUtil.success(msg=clear_job_log_result.message)
else:
logger.warning(clear_job_log_result.message)
return ResponseUtil.failure(msg=clear_job_log_result.message)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.delete('/jobLog/{job_log_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) @jobController.delete('/jobLog/{job_log_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))])
@log_decorator(title='定时任务日志管理', business_type=BusinessType.DELETE) @log_decorator(title='定时任务日志管理', business_type=BusinessType.DELETE)
async def delete_system_job_log(request: Request, job_log_ids: str, query_db: AsyncSession = Depends(get_db)): async def delete_system_job_log(request: Request, job_log_ids: str, query_db: AsyncSession = Depends(get_db)):
try:
delete_job_log = DeleteJobLogModel(jobLogIds=job_log_ids) delete_job_log = DeleteJobLogModel(jobLogIds=job_log_ids)
delete_job_log_result = await JobLogService.delete_job_log_services(query_db, delete_job_log) delete_job_log_result = await JobLogService.delete_job_log_services(query_db, delete_job_log)
if delete_job_log_result.is_success:
logger.info(delete_job_log_result.message) logger.info(delete_job_log_result.message)
return ResponseUtil.success(msg=delete_job_log_result.message) return ResponseUtil.success(msg=delete_job_log_result.message)
else:
logger.warning(delete_job_log_result.message)
return ResponseUtil.failure(msg=delete_job_log_result.message)
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))
@jobController.post('/jobLog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) @jobController.post('/jobLog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))])
@ -243,14 +186,9 @@ async def export_system_job_log_list(
job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_form), job_log_page_query: JobLogPageQueryModel = Depends(JobLogPageQueryModel.as_form),
query_db: AsyncSession = Depends(get_db), query_db: AsyncSession = Depends(get_db),
): ):
try:
# 获取全量数据 # 获取全量数据
job_log_query_result = await JobLogService.get_job_log_list_services( job_log_query_result = await JobLogService.get_job_log_list_services(query_db, job_log_page_query, is_page=False)
query_db, job_log_page_query, is_page=False
)
job_log_export_result = await JobLogService.export_job_log_list_services(request, job_log_query_result) job_log_export_result = await JobLogService.export_job_log_list_services(request, job_log_query_result)
logger.info('导出成功') logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(job_log_export_result)) return ResponseUtil.streaming(data=bytes2file_response(job_log_export_result))
except Exception as e:
logger.exception(e)
return ResponseUtil.error(msg=str(e))

11
ruoyi-fastapi-backend/module_admin/dao/job_dao.py

@ -36,10 +36,13 @@ class JobDao:
( (
await db.execute( await db.execute(
select(SysJob).where( select(SysJob).where(
SysJob.job_name == job.job_name if job.job_name else True, SysJob.job_name == job.job_name,
SysJob.job_group == job.job_group if job.job_group else True, SysJob.job_group == job.job_group,
SysJob.invoke_target == job.invoke_target if job.invoke_target else True, SysJob.job_executor == job.job_executor,
SysJob.cron_expression == job.cron_expression if job.cron_expression else True, SysJob.invoke_target == job.invoke_target,
SysJob.job_args == job.job_args,
SysJob.job_kwargs == job.job_kwargs,
SysJob.cron_expression == job.cron_expression,
) )
) )
) )

96
ruoyi-fastapi-backend/module_admin/service/job_service.py

@ -1,12 +1,15 @@
from fastapi import Request from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from typing import List from typing import List
from config.constant import CommonConstant, JobConstant
from config.get_scheduler import SchedulerUtil from config.get_scheduler import SchedulerUtil
from exceptions.exception import ServiceException
from module_admin.dao.job_dao import JobDao from module_admin.dao.job_dao import JobDao
from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel
from module_admin.service.dict_service import DictDataService from module_admin.service.dict_service import DictDataService
from utils.common_util import CamelCaseUtil, export_list2excel from utils.common_util import CamelCaseUtil, export_list2excel
from utils.string_util import StringUtil
class JobService: class JobService:
@ -30,6 +33,21 @@ class JobService:
return job_list_result return job_list_result
@classmethod
async def check_job_unique_services(cls, query_db: AsyncSession, page_object: JobModel):
"""
校验定时任务是否存在service
:param query_db: orm对象
:param page_object: 定时任务对象
:return: 校验结果
"""
job_id = -1 if page_object.job_id is None else page_object.job_id
job = await JobDao.get_job_detail_by_info(query_db, page_object)
if job and job.job_id != job_id:
return CommonConstant.NOT_UNIQUE
return CommonConstant.UNIQUE
@classmethod @classmethod
async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel): async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel):
""" """
@ -39,13 +57,28 @@ class JobService:
:param page_object: 新增定时任务对象 :param page_object: 新增定时任务对象
:return: 新增定时任务校验结果 :return: 新增定时任务校验结果
""" """
job = await JobDao.get_job_detail_by_info(query_db, page_object) if not SchedulerUtil.validate_cron_expression(page_object.cron_expression):
if job: raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,Cron表达式不正确')
result = dict(is_success=False, message='定时任务已存在') elif not await cls.check_job_unique_services(query_db, page_object):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,定时任务已存在')
elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许rmi调用')
elif StringUtil.contains_any_ignore_case(
page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS]
):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用')
elif StringUtil.contains_any_ignore_case(
page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS]
):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用')
elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串存在违规')
elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST):
raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不在白名单内')
else: else:
try: try:
await JobDao.add_job_dao(query_db, page_object) add_job = await JobDao.add_job_dao(query_db, page_object)
job_info = await JobDao.get_job_detail_by_info(query_db, page_object) job_info = await cls.job_detail_services(query_db, add_job.job_id)
if job_info.status == '0': if job_info.status == '0':
SchedulerUtil.add_scheduler_job(job_info=job_info) SchedulerUtil.add_scheduler_job(job_info=job_info)
await query_db.commit() await query_db.commit()
@ -68,18 +101,31 @@ class JobService:
edit_job = page_object.model_dump(exclude_unset=True) edit_job = page_object.model_dump(exclude_unset=True)
if page_object.type == 'status': if page_object.type == 'status':
del edit_job['type'] del edit_job['type']
job_info = await cls.job_detail_services(query_db, edit_job.get('job_id')) job_info = await cls.job_detail_services(query_db, page_object.job_id)
if job_info: if job_info:
if page_object.type != 'status' and ( if page_object.type != 'status':
job_info.job_name != page_object.job_name if not SchedulerUtil.validate_cron_expression(page_object.cron_expression):
or job_info.job_group != page_object.job_group raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,Cron表达式不正确')
or job_info.invoke_target != page_object.invoke_target elif not await cls.check_job_unique_services(query_db, page_object):
or job_info.cron_expression != page_object.cron_expression raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在')
elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许rmi调用')
elif StringUtil.contains_any_ignore_case(
page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS]
): ):
job = await JobDao.get_job_detail_by_info(query_db, page_object) raise ServiceException(
if job: message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用'
result = dict(is_success=False, message='定时任务已存在') )
return CrudResponseModel(**result) elif StringUtil.contains_any_ignore_case(
page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS]
):
raise ServiceException(
message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用'
)
elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串存在违规')
elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST):
raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不在白名单内')
try: try:
await JobDao.edit_job_dao(query_db, edit_job) await JobDao.edit_job_dao(query_db, edit_job)
query_job = SchedulerUtil.get_scheduler_job(job_id=edit_job.get('job_id')) query_job = SchedulerUtil.get_scheduler_job(job_id=edit_job.get('job_id'))
@ -89,14 +135,12 @@ class JobService:
job_info = await cls.job_detail_services(query_db, edit_job.get('job_id')) job_info = await cls.job_detail_services(query_db, edit_job.get('job_id'))
SchedulerUtil.add_scheduler_job(job_info=job_info) SchedulerUtil.add_scheduler_job(job_info=job_info)
await query_db.commit() await query_db.commit()
result = dict(is_success=True, message='更新成功') return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e: except Exception as e:
await query_db.rollback() await query_db.rollback()
raise e raise e
else: else:
result = dict(is_success=False, message='定时任务不存在') raise ServiceException(message='定时任务不存在')
return CrudResponseModel(**result)
@classmethod @classmethod
async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel): async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel):
@ -113,11 +157,9 @@ class JobService:
job_info = await cls.job_detail_services(query_db, page_object.job_id) job_info = await cls.job_detail_services(query_db, page_object.job_id)
if job_info: if job_info:
SchedulerUtil.execute_scheduler_job_once(job_info=job_info) SchedulerUtil.execute_scheduler_job_once(job_info=job_info)
result = dict(is_success=True, message='执行成功') return CrudResponseModel(is_success=True, message='执行成功')
else: else:
result = dict(is_success=False, message='定时任务不存在') raise ServiceException(message='定时任务不存在')
return CrudResponseModel(**result)
@classmethod @classmethod
async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel): async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel):
@ -137,13 +179,12 @@ class JobService:
if query_job: if query_job:
SchedulerUtil.remove_scheduler_job(job_id=job_id) SchedulerUtil.remove_scheduler_job(job_id=job_id)
await query_db.commit() await query_db.commit()
result = dict(is_success=True, message='删除成功') return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e: except Exception as e:
await query_db.rollback() await query_db.rollback()
raise e raise e
else: else:
result = dict(is_success=False, message='传入定时任务id为空') raise ServiceException(message='传入定时任务id为空')
return CrudResponseModel(**result)
@classmethod @classmethod
async def job_detail_services(cls, query_db: AsyncSession, job_id: int): async def job_detail_services(cls, query_db: AsyncSession, job_id: int):
@ -155,7 +196,10 @@ class JobService:
:return: 定时任务id对应的信息 :return: 定时任务id对应的信息
""" """
job = await JobDao.get_job_detail_by_id(query_db, job_id=job_id) job = await JobDao.get_job_detail_by_id(query_db, job_id=job_id)
if job:
result = JobModel(**CamelCaseUtil.transform_result(job)) result = JobModel(**CamelCaseUtil.transform_result(job))
else:
result = JobModel(**dict())
return result return result

55
ruoyi-fastapi-backend/utils/string_util.py

@ -1,3 +1,4 @@
from typing import List
from config.constant import CommonConstant from config.constant import CommonConstant
@ -44,3 +45,57 @@ class StringUtil:
:return: 是否为http(s)://开头 :return: 是否为http(s)://开头
""" """
return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS)
@classmethod
def contains_ignore_case(cls, search_str: str, compare_str: str):
"""
查找指定字符串是否包含指定字符串同时串忽略大小写
:param search_str: 查找的字符串
:param compare_str: 比对的字符串
:return: 查找结果
"""
if compare_str and search_str:
return compare_str.lower() in search_str.lower()
return False
@classmethod
def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]):
"""
查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
:param search_str: 查找的字符串
:param compare_str_list: 比对的字符串列表
:return: 查找结果
"""
if search_str and compare_str_list:
for compare_str in compare_str_list:
return cls.contains_ignore_case(search_str, compare_str)
return False
@classmethod
def startswith_case(cls, search_str: str, compare_str: str):
"""
查找指定字符串是否以指定字符串开头
:param search_str: 查找的字符串
:param compare_str: 比对的字符串
:return: 查找结果
"""
if compare_str and search_str:
return search_str.startswith(compare_str)
return False
@classmethod
def startswith_any_case(cls, search_str: str, compare_str_list: List[str]):
"""
查找指定字符串是否以指定字符串列表中的任意一个字符串开头
:param search_str: 查找的字符串
:param compare_str_list: 比对的字符串列表
:return: 查找结果
"""
if search_str and compare_str_list:
for compare_str in compare_str_list:
return cls.startswith_case(search_str, compare_str)
return False

2
ruoyi-fastapi-frontend/src/views/monitor/job/index.vue

@ -385,7 +385,7 @@ function reset() {
cronExpression: undefined, cronExpression: undefined,
misfirePolicy: "1", misfirePolicy: "1",
concurrent: "1", concurrent: "1",
status: "0" status: "1"
}; };
proxy.resetForm("jobRef"); proxy.resetForm("jobRef");
} }

Loading…
Cancel
Save