diff --git a/apps/locales/en_US/LC_MESSAGES/django.po b/apps/locales/en_US/LC_MESSAGES/django.po index c2049024620..230c1afd40e 100644 --- a/apps/locales/en_US/LC_MESSAGES/django.po +++ b/apps/locales/en_US/LC_MESSAGES/django.po @@ -9153,4 +9153,16 @@ msgid "Share link does not exist" msgstr "Share link does not exist" msgid "Chat has been deleted" -msgstr "Chat has been deleted" \ No newline at end of file +msgstr "Chat has been deleted" + +msgid "cron type requires cron_expression field" +msgstr "cron type requires cron_expression field" + +msgid "Invalid cron expression: %s" +msgstr "Invalid cron expression: %s" + +msgid "Batch Remove Documents from Tag" +msgstr "Batch Remove Documents from Tag" + +msgid "Document does not belong to current knowledge" +msgstr "Document does not belong to current knowledge" \ No newline at end of file diff --git a/apps/locales/zh_CN/LC_MESSAGES/django.po b/apps/locales/zh_CN/LC_MESSAGES/django.po index 44d63854524..c84def98172 100644 --- a/apps/locales/zh_CN/LC_MESSAGES/django.po +++ b/apps/locales/zh_CN/LC_MESSAGES/django.po @@ -9276,4 +9276,16 @@ msgid "Share link does not exist" msgstr "分享链接不存在" msgid "Chat has been deleted" -msgstr "聊天记录已被删除" \ No newline at end of file +msgstr "聊天记录已被删除" + +msgid "cron type requires cron_expression field" +msgstr "cron 类型需要 cron_expression 字段" + +msgid "Invalid cron expression: %s" +msgstr "Cron 表达式不合法:%s" + +msgid "Batch Remove Documents from Tag" +msgstr "批量删除标签下的文档" + +msgid "Document does not belong to current knowledge" +msgstr "文档不属于当前知识库" \ No newline at end of file diff --git a/apps/locales/zh_Hant/LC_MESSAGES/django.po b/apps/locales/zh_Hant/LC_MESSAGES/django.po index 05ad7b47f77..27f850329ff 100644 --- a/apps/locales/zh_Hant/LC_MESSAGES/django.po +++ b/apps/locales/zh_Hant/LC_MESSAGES/django.po @@ -9273,4 +9273,16 @@ msgid "Share link does not exist" msgstr "分享連結不存在" msgid "Chat has been deleted" -msgstr "聊天記錄已被刪除" \ No newline at end of file +msgstr "聊天記錄已被刪除" + +msgid "cron type requires cron_expression field" +msgstr "cron 類型需要 cron_expression 欄位" + +msgid "Invalid cron expression: %s" +msgstr "Cron 表達式不合法:%s" + +msgid "Batch Remove Documents from Tag" +msgstr "批量刪除標籤下的文件" + +msgid "Document does not belong to current knowledge" +msgstr "文件不屬於當前知識庫" \ No newline at end of file diff --git a/apps/trigger/handler/impl/trigger/scheduled_trigger.py b/apps/trigger/handler/impl/trigger/scheduled_trigger.py index 52b2b36ecb2..e055f0c14f9 100644 --- a/apps/trigger/handler/impl/trigger/scheduled_trigger.py +++ b/apps/trigger/handler/impl/trigger/scheduled_trigger.py @@ -1,5 +1,4 @@ # coding=utf-8 - from django.db.models import QuerySet from common.utils.logger import maxkb_logger @@ -132,6 +131,30 @@ def _deploy_monthly(trigger: dict, trigger_tasks: list[dict], setting: dict, tri replace_existing=True, ) +def _deploy_cron(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None: + from common.job import scheduler + from apscheduler.triggers.cron import CronTrigger + + cron_expression = setting.get('cron_expression') + if not cron_expression: + maxkb_logger.warning(f"empty cron_expression, trigger_id={trigger_id}") + return + + try: + cron_trigger = CronTrigger.from_crontab(cron_expression.strip()) + except ValueError: + maxkb_logger.warning(f"invalid cron_expression={cron_expression}, trigger_id={trigger_id}") + return + + for task in trigger_tasks: + job_id = f"trigger:{trigger_id}:task:{task['id']}:cron:{cron_expression.strip()}" + scheduler.add_job( + ScheduledTrigger.execute, + trigger=cron_trigger, + id=job_id, + kwargs={"trigger": trigger, "trigger_task": task}, + replace_existing=True, + ) def _deploy_interval(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None: from common.job import scheduler @@ -184,6 +207,7 @@ def deploy_scheduled_trigger(trigger: dict, trigger_tasks: list[dict], setting: "weekly": _deploy_weekly, "monthly": _deploy_monthly, "interval": _deploy_interval, + 'cron': _deploy_cron } fn = deployers.get(schedule_type) if not fn: diff --git a/apps/trigger/serializers/task_source_trigger.py b/apps/trigger/serializers/task_source_trigger.py index be4e893f8f0..b38614e9ced 100644 --- a/apps/trigger/serializers/task_source_trigger.py +++ b/apps/trigger/serializers/task_source_trigger.py @@ -6,26 +6,16 @@ @date:2026/1/22 16:18 @desc: """ -import os.path -import re from typing import Dict -import uuid_utils.compat as uuid -from django.core import validators -from django.db import models, transaction +from django.db import transaction from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import Application -from common.db.search import page_search, get_dynamics_model, native_page_search, native_search from common.exception.app_exception import AppApiException -from common.field.common import ObjectField -from common.utils.common import get_file_content -from knowledge.serializers.common import BatchSerializer -from maxkb.conf import PROJECT_DIR from tools.models import Tool -from tools.serializers.tool import ToolModelSerializer from trigger.models import TriggerTypeChoices, Trigger, TriggerTaskTypeChoices, TriggerTask from trigger.serializers.trigger import TriggerModelSerializer, TriggerSerializer, ApplicationTriggerTaskSerializer, \ ToolTriggerTaskSerializer, TriggerTaskModelSerializer diff --git a/apps/trigger/serializers/trigger.py b/apps/trigger/serializers/trigger.py index 992fcfc8e88..7f58e6336cb 100644 --- a/apps/trigger/serializers/trigger.py +++ b/apps/trigger/serializers/trigger.py @@ -160,7 +160,7 @@ def _validate_time_format(time_str): def _validate_scheduled_setting(self, setting): schedule_type = setting.get('schedule_type') - valid_types = ['daily', 'weekly', 'monthly', 'interval'] + valid_types = ['daily', 'weekly', 'monthly', 'interval','cron'] if schedule_type not in valid_types: raise serializers.ValidationError( {'trigger_setting': _('schedule_type must be one of %s') % ', '.join(valid_types) @@ -173,7 +173,8 @@ def _validate_scheduled_setting(self, setting): self._validate_monthly(setting) elif schedule_type == 'interval': self._validate_interval(setting) - + elif schedule_type == 'cron': + self._validate_cron(setting) def _validate_daily(self, setting): self._validate_required_field(setting, 'time', 'daily') self._validate_time_array(setting['time']) @@ -212,6 +213,21 @@ def _validate_interval(self, setting): raise serializers.ValidationError({ 'trigger_setting': _('interval_unit must be one of %s') % ', '.join(valid_units) }) + @staticmethod + def _validate_cron(setting): + from apscheduler.triggers.cron import CronTrigger + + cron_expression: str = setting.get('cron_expression') + if not cron_expression: + raise serializers.ValidationError({ + 'trigger_setting': _('cron type requires cron_expression field') + }) + try: + CronTrigger.from_crontab(cron_expression.strip()) + except ValueError: + raise serializers.ValidationError({ + 'trigger_setting': _('Invalid cron expression: %s') % cron_expression + }) @staticmethod def _validate_event_setting(setting): diff --git a/apps/trigger/views/trigger_task.py b/apps/trigger/views/trigger_task.py index d105127cdd8..27758fe8c48 100644 --- a/apps/trigger/views/trigger_task.py +++ b/apps/trigger/views/trigger_task.py @@ -17,8 +17,7 @@ from trigger.api.trigger_task import TriggerTaskRecordExecutionDetailsAPI, TriggerTaskRecordPageAPI, TriggerTaskAPI from trigger.serializers.trigger_task import TriggerTaskQuerySerializer, TriggerTaskRecordQuerySerializer, \ TriggerTaskRecordOperateSerializer -from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants, \ - Permission, Group, Operate +from common.constants.permission_constants import PermissionConstants, RoleConstants class TriggerTaskView(APIView):