From 068503d275fb92a4c5d2b5683083dc4292444e03 Mon Sep 17 00:00:00 2001 From: insistence <3055204202@qq.com> Date: Mon, 15 Jul 2024 08:49:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9ECronUtil=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-fastapi-backend/config/get_scheduler.py | 156 ---------------- .../module_admin/service/job_service.py | 13 +- ruoyi-fastapi-backend/utils/cron_util.py | 172 ++++++++++++++++++ 3 files changed, 179 insertions(+), 162 deletions(-) create mode 100644 ruoyi-fastapi-backend/utils/cron_util.py diff --git a/ruoyi-fastapi-backend/config/get_scheduler.py b/ruoyi-fastapi-backend/config/get_scheduler.py index 9b6ed2a..5c5809c 100644 --- a/ruoyi-fastapi-backend/config/get_scheduler.py +++ b/ruoyi-fastapi-backend/config/get_scheduler.py @@ -1,5 +1,4 @@ import json -import re from apscheduler.events import EVENT_ALL from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor from apscheduler.schedulers.background import BackgroundScheduler @@ -13,7 +12,6 @@ from sqlalchemy.orm import sessionmaker from typing import Union from config.database import AsyncSessionLocal, quote_plus from config.env import DataBaseConfig, RedisConfig -from exceptions.exception import ServiceException from module_admin.dao.job_dao import JobDao from module_admin.entity.vo.job_vo import JobLogModel, JobModel from module_admin.service.job_log_service import JobLogService @@ -213,160 +211,6 @@ class SchedulerUtil: """ 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 def scheduler_event_listener(cls, event): # 获取事件类型和任务ID diff --git a/ruoyi-fastapi-backend/module_admin/service/job_service.py b/ruoyi-fastapi-backend/module_admin/service/job_service.py index 76c2dfa..edd26e0 100644 --- a/ruoyi-fastapi-backend/module_admin/service/job_service.py +++ b/ruoyi-fastapi-backend/module_admin/service/job_service.py @@ -9,6 +9,7 @@ from module_admin.entity.vo.common_vo import CrudResponseModel from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel from module_admin.service.dict_service import DictDataService from utils.common_util import CamelCaseUtil, export_list2excel +from utils.cron_util import CronUtil from utils.string_util import StringUtil @@ -57,10 +58,8 @@ class JobService: :param page_object: 新增定时任务对象 :return: 新增定时任务校验结果 """ - if not SchedulerUtil.validate_cron_expression(page_object.cron_expression): + if not CronUtil.validate_cron_expression(page_object.cron_expression): raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,Cron表达式不正确') - 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( @@ -75,6 +74,8 @@ class JobService: 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}失败,目标字符串不在白名单内') + elif not await cls.check_job_unique_services(query_db, page_object): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,定时任务已存在') else: try: add_job = await JobDao.add_job_dao(query_db, page_object) @@ -104,10 +105,8 @@ class JobService: job_info = await cls.job_detail_services(query_db, page_object.job_id) if job_info: if page_object.type != 'status': - if not SchedulerUtil.validate_cron_expression(page_object.cron_expression): + if not CronUtil.validate_cron_expression(page_object.cron_expression): raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,Cron表达式不正确') - 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( @@ -126,6 +125,8 @@ class JobService: 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}失败,目标字符串不在白名单内') + elif not await cls.check_job_unique_services(query_db, page_object): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在') try: await JobDao.edit_job_dao(query_db, edit_job) query_job = SchedulerUtil.get_scheduler_job(job_id=edit_job.get('job_id')) diff --git a/ruoyi-fastapi-backend/utils/cron_util.py b/ruoyi-fastapi-backend/utils/cron_util.py new file mode 100644 index 0000000..09050e5 --- /dev/null +++ b/ruoyi-fastapi-backend/utils/cron_util.py @@ -0,0 +1,172 @@ +import re +from datetime import datetime + + +class CronUtil: + """ + Cron表达式工具类 + """ + + @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) + future_years = [current_year + i for i in range(9)] + 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) + or ( + (len(year) == 4 or ',' in year) + and all(int(item) in future_years and current_year <= int(item) <= 2099 for item in year.split(',')) + ) + ): + 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