From 9a4d563c547e002f59e20f54f8c26cd1acf0b69d Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 29 Jul 2026 18:43:09 +0800 Subject: [PATCH 01/20] feat(schedule): add quartz trigger types --- .../api/dto/ScheduleParam.java | 6 + .../service/impl/SchedulerServiceImpl.java | 46 +++- .../dolphinscheduler/api/vo/ScheduleVO.java | 8 + .../common/enums/MisfirePolicy.java | 37 ++++ .../common/enums/ScheduleTriggerType.java | 39 ++++ .../common/utils/IntervalSchedule.java | 71 ++++++ .../common/utils/IntervalScheduleTest.java | 41 ++++ .../dolphinscheduler/dao/entity/Schedule.java | 6 + .../dao/mapper/ScheduleMapper.xml | 4 +- .../resources/sql/dolphinscheduler_h2.sql | 2 + .../resources/sql/dolphinscheduler_mysql.sql | 2 + .../sql/dolphinscheduler_postgresql.sql | 2 + .../mysql/dolphinscheduler_ddl_post.sql | 3 + .../postgresql/dolphinscheduler_ddl_post.sql | 3 + .../quartz/QuartzCornTriggerBuilder.java | 9 +- .../quartz/QuartzMisfirePolicyApplier.java | 66 ++++++ .../scheduler/quartz/QuartzScheduler.java | 20 +- .../quartz/QuartzSimpleTriggerBuilder.java | 90 ++++++++ .../QuartzSimpleTriggerBuilderTest.java | 48 ++++ .../src/locales/en_US/project.ts | 14 ++ .../src/locales/zh_CN/project.ts | 14 ++ .../definition/components/timing-modal.tsx | 209 +++++++++++++++--- .../definition/components/use-form.ts | 2 + .../definition/components/use-modal.ts | 4 + .../workflow/definition/timing/use-table.ts | 24 ++ 25 files changed, 729 insertions(+), 41 deletions(-) create mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java create mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java create mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java create mode 100644 dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java create mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql create mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 88fd4ea9664b..d8c2b7e83156 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -17,6 +17,9 @@ package org.apache.dolphinscheduler.api.dto; +import org.apache.dolphinscheduler.common.enums.MisfirePolicy; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; + import java.util.Date; import lombok.Data; @@ -31,6 +34,8 @@ public class ScheduleParam { private Date endTime; private String crontab; private String timezoneId; + private ScheduleTriggerType triggerType = ScheduleTriggerType.CRON; + private MisfirePolicy misfirePolicy = MisfirePolicy.IGNORE_MISFIRES; public ScheduleParam() { } @@ -40,6 +45,7 @@ public ScheduleParam(Date startTime, Date endTime, String timezoneId, String cro this.endTime = endTime; this.timezoneId = timezoneId; this.crontab = crontab; + this.triggerType = ScheduleTriggerType.CRON; } @Override diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 12df89e9fccf..e00923377b02 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -32,11 +32,14 @@ import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.MisfirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.common.utils.IntervalSchedule; import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.Schedule; @@ -52,6 +55,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -167,11 +171,16 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setStartTime(scheduleParam.getStartTime()); scheduleObj.setEndTime(scheduleParam.getEndTime()); - if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) { + if (!isValidScheduleExpression(scheduleParam)) { log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab()); throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); + scheduleObj.setMisfirePolicy(scheduleParam.getMisfirePolicy() == null + ? MisfirePolicy.IGNORE_MISFIRES + : scheduleParam.getMisfirePolicy()); + scheduleObj.setTriggerType( + scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -387,6 +396,20 @@ public List previewSchedule(User loginUser, String schedule) { ZonedDateTime startTime = ZonedDateTime.ofInstant(scheduleParam.getStartTime().toInstant(), zoneId); ZonedDateTime endTime = ZonedDateTime.ofInstant(scheduleParam.getEndTime().toInstant(), zoneId); startTime = now.isAfter(startTime) ? now : startTime; + if (scheduleParam.getTriggerType() == ScheduleTriggerType.INTERVAL) { + IntervalSchedule intervalSchedule = IntervalSchedule.parse(scheduleParam.getCrontab()); + List fireTimes = new ArrayList<>(); + int executionLimit = intervalSchedule.getRepeatCount() < 0 + ? Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT + : Math.min(intervalSchedule.getRepeatCount() + 1, Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT); + for (int i = 0; i < executionLimit && !startTime.isAfter(endTime); i++) { + fireTimes.add(startTime); + startTime = startTime.plus(Duration.ofMillis(intervalSchedule.getIntervalMilliseconds())); + } + return fireTimes.stream() + .map(t -> DateUtils.dateToString(t, zoneId)) + .collect(Collectors.toList()); + } try { cron = CronUtils.parse2Cron(scheduleParam.getCrontab()); @@ -401,6 +424,20 @@ public List previewSchedule(User loginUser, String schedule) { .collect(Collectors.toList()); } + private boolean isValidScheduleExpression(ScheduleParam scheduleParam) { + ScheduleTriggerType triggerType = scheduleParam.getTriggerType() == null + ? ScheduleTriggerType.CRON + : scheduleParam.getTriggerType(); + if (triggerType == ScheduleTriggerType.CRON) { + return CronUtils.isValidExpression(scheduleParam.getCrontab()); + } + try { + IntervalSchedule.parse(scheduleParam.getCrontab()); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } /** * update workflow definition schedule * @@ -552,11 +589,16 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe schedule.setStartTime(scheduleParam.getStartTime()); schedule.setEndTime(scheduleParam.getEndTime()); - if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) { + if (!isValidScheduleExpression(scheduleParam)) { log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab()); throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); + schedule.setMisfirePolicy(scheduleParam.getMisfirePolicy() == null + ? MisfirePolicy.IGNORE_MISFIRES + : scheduleParam.getMisfirePolicy()); + schedule.setTriggerType( + scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java index fc4b1d966859..c2509399e1a8 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java @@ -18,8 +18,10 @@ package org.apache.dolphinscheduler.api.vo; import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.MisfirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; import org.apache.dolphinscheduler.dao.entity.Schedule; @@ -54,6 +56,10 @@ public class ScheduleVO { private String crontab; + private ScheduleTriggerType triggerType; + + private MisfirePolicy misfirePolicy; + private FailureStrategy failureStrategy; private WarningType warningType; @@ -83,6 +89,8 @@ public class ScheduleVO { public ScheduleVO(Schedule schedule) { this.setId(schedule.getId()); this.setCrontab(schedule.getCrontab()); + this.setTriggerType(schedule.getTriggerType()); + this.setMisfirePolicy(schedule.getMisfirePolicy()); this.setProjectName(schedule.getProjectName()); this.setUserName(schedule.getUserName()); this.setWorkerGroup(schedule.getWorkerGroup()); diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java new file mode 100644 index 000000000000..0acf53d866d9 --- /dev/null +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.enums; + +import lombok.Getter; + +import com.baomidou.mybatisplus.annotation.EnumValue; + +@Getter +public enum MisfirePolicy { + + DO_NOTHING(0), + FIRE_AND_PROCEED(1), + IGNORE_MISFIRES(2); + + @EnumValue + private final int code; + + MisfirePolicy(int code) { + this.code = code; + } +} diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java new file mode 100644 index 000000000000..5700a00667ca --- /dev/null +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.enums; + +import lombok.Getter; + +import com.baomidou.mybatisplus.annotation.EnumValue; + +@Getter +public enum ScheduleTriggerType { + + CRON(0, "Cron expression"), + INTERVAL(1, "Fixed interval"); + + @EnumValue + private final int code; + + private final String description; + + ScheduleTriggerType(int code, String description) { + this.code = code; + this.description = description; + } +} diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java new file mode 100644 index 000000000000..8a889fd83f6d --- /dev/null +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.utils; + +import lombok.Value; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Fixed interval schedule encoded as JSON. + */ +@Value +public class IntervalSchedule { + + long intervalMilliseconds; + + int repeatCount; + + public static IntervalSchedule parse(String expression) { + final ObjectNode values; + try { + values = JSONUtils.parseObject(expression); + } catch (Exception e) { + throw new IllegalArgumentException("Interval schedule expression must be a JSON object", e); + } + if (values == null || !values.isObject()) { + throw new IllegalArgumentException("Interval schedule expression must not be null"); + } + + int hours = valueOf(values, "hour"); + int minutes = valueOf(values, "minute"); + int seconds = valueOf(values, "second"); + int repeat = valueOf(values, "repeat"); + if (hours < 0 || minutes < 0 || seconds < 0 || repeat < -1) { + throw new IllegalArgumentException("Interval duration values must not be negative"); + } + + long intervalMilliseconds = Math.addExact( + Math.addExact(Math.multiplyExact(hours, 3_600_000L), Math.multiplyExact(minutes, 60_000L)), + Math.multiplyExact(seconds, 1_000L)); + if (intervalMilliseconds == 0) { + throw new IllegalArgumentException("Interval duration must be positive"); + } + return new IntervalSchedule(intervalMilliseconds, repeat); + } + + private static int valueOf(ObjectNode values, String key) { + if (!values.has(key)) { + return 0; + } + if (!values.get(key).isInt()) { + throw new IllegalArgumentException("Interval schedule field must be an integer: " + key); + } + return values.get(key).intValue(); + } +} diff --git a/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java b/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java new file mode 100644 index 000000000000..4935f77a69e2 --- /dev/null +++ b/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class IntervalScheduleTest { + + @Test + void parseIntervalSchedule() { + IntervalSchedule intervalSchedule = + IntervalSchedule.parse("{\"hour\":1,\"minute\":2,\"second\":3,\"repeat\":4}"); + + assertEquals(3_723_000L, intervalSchedule.getIntervalMilliseconds()); + assertEquals(4, intervalSchedule.getRepeatCount()); + } + + @Test + void rejectInvalidIntervalSchedule() { + assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":0}")); + assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":-1}")); + } +} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java index a55c8d1ad529..708a83a357f4 100644 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java +++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java @@ -18,8 +18,10 @@ package org.apache.dolphinscheduler.dao.entity; import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.MisfirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.WarningType; import java.util.Date; @@ -67,6 +69,10 @@ public class Schedule { private String crontab; + private MisfirePolicy misfirePolicy; + + private ScheduleTriggerType triggerType; + private FailureStrategy failureStrategy; private WarningType warningType; diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml index 99c1a59ef6eb..6fa5f3e452c3 100644 --- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml +++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml @@ -19,12 +19,12 @@ - id, workflow_definition_code, start_time, end_time, timezone_id, crontab, failure_strategy, user_id, release_state, + id, workflow_definition_code, start_time, end_time, timezone_id, crontab, misfire_policy, trigger_type, failure_strategy, user_id, release_state, warning_type, warning_group_id, workflow_instance_priority, worker_group, tenant_code, environment_code, create_time, update_time ${alias}.id, ${alias}.workflow_definition_code, ${alias}.start_time, ${alias}.end_time, ${alias}.timezone_id, - ${alias}.crontab, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, + ${alias}.crontab, ${alias}.misfire_policy, ${alias}.trigger_type, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, ${alias}.warning_group_id, ${alias}.workflow_instance_priority, ${alias}.worker_group, ${alias}.tenant_code, ${alias}.environment_code, ${alias}.create_time, ${alias}.update_time diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql index 1725b5c2df36..223d77bc6536 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql @@ -858,6 +858,8 @@ CREATE TABLE t_ds_schedules end_time datetime NOT NULL, timezone_id varchar(40) DEFAULT NULL, crontab varchar(255) NOT NULL, + misfire_policy tinyint NOT NULL DEFAULT 2, + trigger_type tinyint NOT NULL DEFAULT 0, failure_strategy tinyint(4) NOT NULL, user_id int(11) NOT NULL, release_state tinyint(4) NOT NULL, diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql index f6dfa61fc122..c93126b53918 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql @@ -859,6 +859,8 @@ CREATE TABLE `t_ds_schedules` ( `end_time` datetime NOT NULL COMMENT 'end time', `timezone_id` varchar(40) DEFAULT NULL COMMENT 'schedule timezone id', `crontab` varchar(255) NOT NULL COMMENT 'crontab description', + `misfire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'misfire policy: 0 do nothing, 1 fire and proceed, 2 ignore misfires', + `trigger_type` tinyint NOT NULL DEFAULT '0' COMMENT 'schedule trigger type: 0 cron, 1 interval', `failure_strategy` tinyint(4) NOT NULL COMMENT 'failure strategy. 0:end,1:continue', `user_id` int(11) NOT NULL COMMENT 'user id', `release_state` tinyint(4) NOT NULL COMMENT 'release state. 0:offline,1:online ', diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql index 698b35768d94..ad3a5133bd85 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql @@ -785,6 +785,8 @@ CREATE TABLE t_ds_schedules ( end_time timestamp NOT NULL , timezone_id varchar(40) default NULL , crontab varchar(255) NOT NULL , + misfire_policy smallint NOT NULL DEFAULT 2, + trigger_type smallint NOT NULL DEFAULT 0, failure_strategy int NOT NULL , user_id int NOT NULL , release_state int NOT NULL , diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql new file mode 100644 index 000000000000..2c86d94d6e7e --- /dev/null +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql @@ -0,0 +1,3 @@ +ALTER TABLE `t_ds_schedules` + ADD COLUMN `misfire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'misfire policy: 0 do nothing, 1 fire and proceed, 2 ignore misfires' AFTER `crontab`, + ADD COLUMN `trigger_type` tinyint NOT NULL DEFAULT '0' COMMENT 'schedule trigger type: 0 cron, 1 interval' AFTER `misfire_policy`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql new file mode 100644 index 000000000000..f0e00c8ef5f2 --- /dev/null +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql @@ -0,0 +1,3 @@ +ALTER TABLE t_ds_schedules + ADD COLUMN misfire_policy smallint NOT NULL DEFAULT 2, + ADD COLUMN trigger_type smallint NOT NULL DEFAULT 0; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index b7177e74db84..2fe1e684a31b 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -81,14 +81,15 @@ public CronTrigger build() { JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); + QuartzMisfirePolicyApplier.apply(scheduleBuilder, schedule.getMisfirePolicy()); + return TriggerBuilder.newTrigger() .withIdentity(triggerKey) .startAt(startDate) .endAt(endDate) - .withSchedule( - CronScheduleBuilder.cronSchedule(schedule.getCrontab()) - .withMisfireHandlingInstructionIgnoreMisfires() - .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId()))) + .withSchedule(scheduleBuilder) .build(); } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java new file mode 100644 index 000000000000..e1699f3ac088 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.apache.dolphinscheduler.common.enums.MisfirePolicy; + +import org.quartz.CronScheduleBuilder; +import org.quartz.SimpleScheduleBuilder; + +final class QuartzMisfirePolicyApplier { + + private QuartzMisfirePolicyApplier() { + throw new IllegalStateException("Utility class"); + } + + static void apply(SimpleScheduleBuilder scheduleBuilder, MisfirePolicy misfirePolicy) { + switch (effectivePolicy(misfirePolicy)) { + case DO_NOTHING: + scheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); + return; + case FIRE_AND_PROCEED: + scheduleBuilder.withMisfireHandlingInstructionFireNow(); + return; + case IGNORE_MISFIRES: + scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); + return; + default: + throw new IllegalStateException("Unsupported misfire policy: " + misfirePolicy); + } + } + + static void apply(CronScheduleBuilder scheduleBuilder, MisfirePolicy misfirePolicy) { + switch (effectivePolicy(misfirePolicy)) { + case DO_NOTHING: + scheduleBuilder.withMisfireHandlingInstructionDoNothing(); + return; + case FIRE_AND_PROCEED: + scheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); + return; + case IGNORE_MISFIRES: + scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); + return; + default: + throw new IllegalStateException("Unsupported misfire policy: " + misfirePolicy); + } + } + + private static MisfirePolicy effectivePolicy(MisfirePolicy misfirePolicy) { + return misfirePolicy == null ? MisfirePolicy.IGNORE_MISFIRES : misfirePolicy; + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java index 70d5bf8fabc0..ff3d6b31303d 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java @@ -17,6 +17,7 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.dao.entity.Schedule; import org.apache.dolphinscheduler.scheduler.api.SchedulerApi; import org.apache.dolphinscheduler.scheduler.api.SchedulerException; @@ -24,10 +25,10 @@ import lombok.extern.slf4j.Slf4j; -import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; +import org.quartz.Trigger; import com.google.common.collect.Sets; @@ -52,16 +53,21 @@ public void start() throws SchedulerException { @Override public void insertOrUpdateScheduleTask(int projectId, Schedule schedule) throws SchedulerException { try { - CronTrigger cornTrigger = QuartzCornTriggerBuilder.newBuilder() - .withProjectId(projectId) - .withSchedule(schedule) - .build(); + Trigger trigger = schedule.getTriggerType() == ScheduleTriggerType.INTERVAL + ? QuartzSimpleTriggerBuilder.newBuilder() + .withProjectId(projectId) + .withSchedule(schedule) + .build() + : QuartzCornTriggerBuilder.newBuilder() + .withProjectId(projectId) + .withSchedule(schedule) + .build(); JobDetail jobDetail = QuartzJobDetailBuilder.newBuilder() .withProjectId(projectId) .withSchedule(schedule.getId()) .build(); - scheduler.scheduleJob(jobDetail, Sets.newHashSet(cornTrigger), true); - log.info("Success scheduleJob: {} with trigger: {} at quartz", jobDetail, cornTrigger); + scheduler.scheduleJob(jobDetail, Sets.newHashSet(trigger), true); + log.info("Success scheduleJob: {} with trigger: {} at quartz", jobDetail, trigger); } catch (Exception e) { log.error("Failed to add scheduler task, projectId: {}, scheduler: {}", projectId, schedule, e); throw new SchedulerException(QuartzSchedulerExceptionEnum.QUARTZ_UPSERT_JOB_ERROR, e); diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java new file mode 100644 index 000000000000..ebce6fb082ee --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.common.utils.IntervalSchedule; +import org.apache.dolphinscheduler.dao.entity.Schedule; + +import java.util.Date; + +import org.quartz.JobKey; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.SimpleTrigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; + +/** + * Builds a Quartz {@link SimpleTrigger} from a fixed-interval schedule expression. + */ +public class QuartzSimpleTriggerBuilder { + + private Integer projectId; + + private Schedule schedule; + + public static QuartzSimpleTriggerBuilder newBuilder() { + return new QuartzSimpleTriggerBuilder(); + } + + public QuartzSimpleTriggerBuilder withProjectId(Integer projectId) { + this.projectId = projectId; + return this; + } + + public QuartzSimpleTriggerBuilder withSchedule(Schedule schedule) { + this.schedule = schedule; + return this; + } + + public SimpleTrigger build() { + if (projectId == null) { + throw new IllegalArgumentException("projectId cannot be null"); + } + if (schedule == null) { + throw new IllegalArgumentException("schedule cannot be null"); + } + + IntervalSchedule intervalSchedule = IntervalSchedule.parse(schedule.getCrontab()); + JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); + TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); + Date startTime = DateUtils.transformTimezoneDate(schedule.getStartTime(), schedule.getTimezoneId()); + Date endTime = DateUtils.transformTimezoneDate(schedule.getEndTime(), schedule.getTimezoneId()); + Date now = new Date(); + if (startTime.before(now)) { + startTime = now; + } + + SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() + .withIntervalInMilliseconds(intervalSchedule.getIntervalMilliseconds()); + if (intervalSchedule.getRepeatCount() < 0) { + scheduleBuilder.repeatForever(); + } else { + scheduleBuilder.withRepeatCount(intervalSchedule.getRepeatCount()); + } + + QuartzMisfirePolicyApplier.apply(scheduleBuilder, schedule.getMisfirePolicy()); + + return TriggerBuilder.newTrigger() + .withIdentity(triggerKey) + .startAt(startTime) + .endAt(endTime) + .withSchedule(scheduleBuilder) + .build(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java new file mode 100644 index 000000000000..53f69b3e0d15 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.dolphinscheduler.dao.entity.Schedule; + +import java.util.Date; + +import org.junit.jupiter.api.Test; +import org.quartz.SimpleTrigger; + +class QuartzSimpleTriggerBuilderTest { + + @Test + void buildIntervalTrigger() { + Schedule schedule = new Schedule(); + schedule.setId(2); + schedule.setCrontab("{\"minute\":5,\"repeat\":3}"); + schedule.setTimezoneId("UTC"); + schedule.setStartTime(new Date(System.currentTimeMillis() + 60_000)); + schedule.setEndTime(new Date(System.currentTimeMillis() + 3_600_000)); + + SimpleTrigger trigger = QuartzSimpleTriggerBuilder.newBuilder() + .withProjectId(1) + .withSchedule(schedule) + .build(); + + assertEquals(300_000L, trigger.getRepeatInterval()); + assertEquals(3, trigger.getRepeatCount()); + } +} diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index 40e553d4dd1d..3f64b21c71ec 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -149,6 +149,20 @@ export default { start_time: 'Start Time', end_time: 'End Time', crontab: 'Crontab', + trigger_type: 'Trigger Type', + cron_trigger: 'Cron', + interval_trigger: 'Interval', + interval: 'Interval', + hours: 'Hours', + minutes: 'Minutes', + seconds: 'Seconds', + repeat: 'Repeat', + unlimited_repeat_tip: '-1 means unlimited repeats', + interval_must_be_positive: 'Interval must be positive', + misfire_policy: 'Misfire Policy', + do_nothing: 'Skip missed executions', + fire_and_proceed: 'Fire once immediately', + ignore_misfires: 'Ignore misfires', delete_confirm: 'Delete?', delete_confirm_with_name: 'Delete "{name}"?', delete_irreversible: diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index abaa84a34b1a..e054a1c2bfe4 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,6 +148,20 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', + trigger_type: '触发类型', + cron_trigger: 'Cron 表达式', + interval_trigger: '固定间隔', + interval: '间隔配置', + hours: '小时', + minutes: '分钟', + seconds: '秒', + repeat: '重复次数', + unlimited_repeat_tip: '-1 表示不限次数', + interval_must_be_positive: '固定间隔必须大于 0', + misfire_policy: '错过触发策略', + do_nothing: '不执行补偿,等待下一次调度', + fire_and_proceed: '立即执行错过的任务,之后按正常节奏继续调度', + ignore_misfires: '补齐所有错过的任务,之后按正常节奏继续调度', delete_confirm: '确定删除吗?', delete_confirm_with_name: '确定删除“{name}”吗?', delete_irreversible: '此操作不可撤销。工作流及其关联数据将被永久删除。', diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index c30230581a65..2f3717d8f59f 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -36,6 +36,7 @@ import { NButton, NIcon, NInput, + NInputNumber, NSpace, NRadio, NRadioGroup, @@ -103,6 +104,11 @@ export default defineComponent({ ) const projectPreferences = ref({} as any) + const intervalHours = ref(1) + const intervalMinutes = ref(0) + const intervalSeconds = ref(0) + const intervalRepeat = ref(-1) + const cronExpression = ref(timingState.timingForm.crontab) const initProjectPreferences = (projectCode: number) => { queryProjectPreferenceByProjectCode(projectCode).then((result: any) => { @@ -185,6 +191,15 @@ export default defineComponent({ } const handlePreview = () => { + if ( + timingState.timingForm.triggerType === 'INTERVAL' && + intervalHours.value === 0 && + intervalMinutes.value === 0 && + intervalSeconds.value === 0 + ) { + window.$message.error(t('project.workflow.interval_must_be_positive')) + return + } getPreviewSchedule() } @@ -273,6 +288,57 @@ export default defineComponent({ const trim = getCurrentInstance()?.appContext.config.globalProperties.trim + const updateIntervalExpression = () => { + timingState.timingForm.crontab = JSON.stringify({ + hour: intervalHours.value, + minute: intervalMinutes.value, + second: intervalSeconds.value, + repeat: intervalRepeat.value + }) + } + + const restoreIntervalExpression = (expression: string): boolean => { + try { + const interval = JSON.parse(expression) + intervalHours.value = interval.hour || 0 + intervalMinutes.value = interval.minute || 0 + intervalSeconds.value = interval.second || 0 + intervalRepeat.value = interval.repeat ?? -1 + return true + } catch { + return false + } + } + + watch( + () => timingState.timingForm.triggerType, + (triggerType, previousTriggerType) => { + if (previousTriggerType === 'CRON') { + cronExpression.value = timingState.timingForm.crontab + } + + if (triggerType === 'CRON') { + timingState.timingForm.crontab = cronExpression.value + return + } + + if ( + previousTriggerType !== 'INTERVAL' && + !restoreIntervalExpression(timingState.timingForm.crontab) + ) { + updateIntervalExpression() + } + } + ) + + watch( + [intervalHours, intervalMinutes, intervalSeconds, intervalRepeat], + () => { + if (timingState.timingForm.triggerType === 'INTERVAL') { + updateIntervalExpression() + } + } + ) onMounted(() => { getWorkerGroups() getTenantList() @@ -293,7 +359,16 @@ export default defineComponent({ new Date(props.row.startTime), new Date(props.row.endTime) ] + const triggerType = props.row.triggerType || 'CRON' + timingState.timingForm.triggerType = triggerType timingState.timingForm.crontab = props.row.crontab + timingState.timingForm.misfirePolicy = + props.row.misfirePolicy || 'IGNORE_MISFIRES' + if (triggerType === 'CRON') { + cronExpression.value = props.row.crontab + } else if (!restoreIntervalExpression(props.row.crontab)) { + updateIntervalExpression() + } timingState.timingForm.timezoneId = props.row.timezoneId timingState.timingForm.failureStrategy = props.row.failureStrategy timingState.timingForm.warningType = props.row.warningType @@ -318,6 +393,10 @@ export default defineComponent({ renderLabel, updateWorkerGroup, handlePreview, + intervalHours, + intervalMinutes, + intervalSeconds, + intervalRepeat, ...toRefs(variables), ...toRefs(timingState), ...toRefs(props), @@ -352,33 +431,89 @@ export default defineComponent({ v-model:value={this.timingForm.startEndTime} /> - - - - {{ - trigger: () => ( - - ), - default: () => ( - - ) - }} - - - {t('project.workflow.execute_time')} - - + + + {this.timingForm.triggerType === 'CRON' ? ( + + + + {{ + trigger: () => ( + + ), + default: () => ( + + ) + }} + + + {t('project.workflow.execute_time')} + + + + ) : ( + +
+ + + {t('project.workflow.hours')} + + {t('project.workflow.minutes')} + + {t('project.workflow.seconds')} + + + {t('project.workflow.repeat')} + + {t('project.workflow.unlimited_repeat_tip')} + + {t('project.workflow.execute_time')} + + +
+
+ )} + + + { new Date(year + 100, month, day) ], crontab: '0 0 * * * ? *', + triggerType: 'CRON', + misfirePolicy: 'IGNORE_MISFIRES', timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, failureStrategy: 'CONTINUE', warningType: 'NONE', diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts index 4baac0f1d1dc..31a5b5988ca7 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts @@ -180,6 +180,8 @@ export function useModal( startTime: start, endTime: end, crontab: state.timingForm.crontab, + triggerType: state.timingForm.triggerType, + misfirePolicy: state.timingForm.misfirePolicy, timezoneId: state.timingForm.timezoneId }), failureStrategy: state.timingForm.failureStrategy, @@ -263,6 +265,8 @@ export function useModal( startTime: start, endTime: end, crontab: state.timingForm.crontab, + triggerType: state.timingForm.triggerType, + misfirePolicy: state.timingForm.misfirePolicy, timezoneId: state.timingForm.timezoneId }) previewSchedule({ schedule }, projectCode).then((res: any) => { diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts index 4d0196df70c4..75b0603f478d 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts @@ -212,6 +212,30 @@ export function useTable() { key: 'crontab', width: 140 }, + { + title: t('project.workflow.trigger_type'), + key: 'triggerType', + width: 120, + render: (row: any) => + row.triggerType === 'INTERVAL' + ? t('project.workflow.interval_trigger') + : t('project.workflow.cron_trigger') + }, + { + title: t('project.workflow.misfire_policy'), + key: 'misfirePolicy', + width: 180, + render: (row: any) => { + const labels: Record = { + DO_NOTHING: t('project.workflow.do_nothing'), + FIRE_AND_PROCEED: t('project.workflow.fire_and_proceed'), + IGNORE_MISFIRES: t('project.workflow.ignore_misfires') + } + return ( + labels[row.misfirePolicy] || t('project.workflow.ignore_misfires') + ) + } + }, { title: t('project.workflow.failure_strategy'), key: 'failureStrategy', From 89cd1ed7fe590845b26bbd48146648a6607fe887 Mon Sep 17 00:00:00 2001 From: wenjie liang <97488304+liang-wenjie@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:55:25 +0800 Subject: [PATCH 02/20] feat(schedule): add missed fire policy --- .../api/dto/ScheduleParam.java | 3 + .../service/impl/SchedulerServiceImpl.java | 7 ++ .../dolphinscheduler/api/vo/ScheduleVO.java | 4 ++ .../enums/ScheduleMissedFirePolicy.java | 37 +++++++++++ .../dolphinscheduler/dao/entity/Schedule.java | 3 + .../dao/mapper/ScheduleMapper.xml | 4 +- .../resources/sql/dolphinscheduler_h2.sql | 1 + .../resources/sql/dolphinscheduler_mysql.sql | 1 + .../sql/dolphinscheduler_postgresql.sql | 1 + .../mysql/dolphinscheduler_ddl_post.sql | 2 + .../postgresql/dolphinscheduler_ddl_post.sql | 2 + .../quartz/QuartzCornTriggerBuilder.java | 9 +-- ...QuartzScheduleMissedFirePolicyApplier.java | 49 ++++++++++++++ ...tzScheduleMissedFirePolicyApplierTest.java | 66 +++++++++++++++++++ .../src/locales/en_US/project.ts | 4 ++ .../src/locales/zh_CN/project.ts | 4 ++ .../definition/components/timing-modal.tsx | 24 +++++++ .../definition/components/use-form.ts | 1 + 18 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java create mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql create mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 88fd4ea9664b..4247cc3eedea 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -17,6 +17,8 @@ package org.apache.dolphinscheduler.api.dto; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + import java.util.Date; import lombok.Data; @@ -31,6 +33,7 @@ public class ScheduleParam { private Date endTime; private String crontab; private String timezoneId; + private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ONCE_NOW; public ScheduleParam() { } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 12df89e9fccf..3968ff994bce 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -32,6 +32,7 @@ import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.UserType; @@ -172,6 +173,7 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); + scheduleObj.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -401,6 +403,10 @@ public List previewSchedule(User loginUser, String schedule) { .collect(Collectors.toList()); } + private ScheduleMissedFirePolicy defaultMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; + } + /** * update workflow definition schedule * @@ -557,6 +563,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); + schedule.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java index fc4b1d966859..29475610aeb7 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java @@ -18,6 +18,7 @@ package org.apache.dolphinscheduler.api.vo; import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.WarningType; @@ -54,6 +55,8 @@ public class ScheduleVO { private String crontab; + private ScheduleMissedFirePolicy missedFirePolicy; + private FailureStrategy failureStrategy; private WarningType warningType; @@ -83,6 +86,7 @@ public class ScheduleVO { public ScheduleVO(Schedule schedule) { this.setId(schedule.getId()); this.setCrontab(schedule.getCrontab()); + this.setMissedFirePolicy(schedule.getMissedFirePolicy()); this.setProjectName(schedule.getProjectName()); this.setUserName(schedule.getUserName()); this.setWorkerGroup(schedule.getWorkerGroup()); diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java new file mode 100644 index 000000000000..7959d636191e --- /dev/null +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.enums; + +import lombok.Getter; + +import com.baomidou.mybatisplus.annotation.EnumValue; + +@Getter +public enum ScheduleMissedFirePolicy { + + SKIP_MISSED(0), + FIRE_ONCE_NOW(1), + FIRE_ALL_MISSED(2); + + @EnumValue + private final int code; + + ScheduleMissedFirePolicy(int code) { + this.code = code; + } +} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java index a55c8d1ad529..965a5c919477 100644 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java +++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java @@ -20,6 +20,7 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.WarningType; import java.util.Date; @@ -67,6 +68,8 @@ public class Schedule { private String crontab; + private ScheduleMissedFirePolicy missedFirePolicy; + private FailureStrategy failureStrategy; private WarningType warningType; diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml index 99c1a59ef6eb..78a401c9d1b2 100644 --- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml +++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml @@ -19,12 +19,12 @@ - id, workflow_definition_code, start_time, end_time, timezone_id, crontab, failure_strategy, user_id, release_state, + id, workflow_definition_code, start_time, end_time, timezone_id, crontab, missed_fire_policy, failure_strategy, user_id, release_state, warning_type, warning_group_id, workflow_instance_priority, worker_group, tenant_code, environment_code, create_time, update_time ${alias}.id, ${alias}.workflow_definition_code, ${alias}.start_time, ${alias}.end_time, ${alias}.timezone_id, - ${alias}.crontab, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, + ${alias}.crontab, ${alias}.missed_fire_policy, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, ${alias}.warning_group_id, ${alias}.workflow_instance_priority, ${alias}.worker_group, ${alias}.tenant_code, ${alias}.environment_code, ${alias}.create_time, ${alias}.update_time diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql index 1725b5c2df36..a16f213ef4d7 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql @@ -858,6 +858,7 @@ CREATE TABLE t_ds_schedules end_time datetime NOT NULL, timezone_id varchar(40) DEFAULT NULL, crontab varchar(255) NOT NULL, + missed_fire_policy tinyint NOT NULL DEFAULT 1, failure_strategy tinyint(4) NOT NULL, user_id int(11) NOT NULL, release_state tinyint(4) NOT NULL, diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql index f6dfa61fc122..ac1d3abe531f 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql @@ -859,6 +859,7 @@ CREATE TABLE `t_ds_schedules` ( `end_time` datetime NOT NULL COMMENT 'end time', `timezone_id` varchar(40) DEFAULT NULL COMMENT 'schedule timezone id', `crontab` varchar(255) NOT NULL COMMENT 'crontab description', + `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed', `failure_strategy` tinyint(4) NOT NULL COMMENT 'failure strategy. 0:end,1:continue', `user_id` int(11) NOT NULL COMMENT 'user id', `release_state` tinyint(4) NOT NULL COMMENT 'release state. 0:offline,1:online ', diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql index 698b35768d94..91843ffa413e 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql @@ -785,6 +785,7 @@ CREATE TABLE t_ds_schedules ( end_time timestamp NOT NULL , timezone_id varchar(40) default NULL , crontab varchar(255) NOT NULL , + missed_fire_policy smallint NOT NULL DEFAULT 1, failure_strategy int NOT NULL , user_id int NOT NULL , release_state int NOT NULL , diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql new file mode 100644 index 000000000000..cd76951444a5 --- /dev/null +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql @@ -0,0 +1,2 @@ +ALTER TABLE `t_ds_schedules` + ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql new file mode 100644 index 000000000000..2f84808b68cf --- /dev/null +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql @@ -0,0 +1,2 @@ +ALTER TABLE t_ds_schedules + ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 1; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index b7177e74db84..6028c9223906 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -81,14 +81,15 @@ public CronTrigger build() { JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); + QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, schedule.getMissedFirePolicy()); + return TriggerBuilder.newTrigger() .withIdentity(triggerKey) .startAt(startDate) .endAt(endDate) - .withSchedule( - CronScheduleBuilder.cronSchedule(schedule.getCrontab()) - .withMisfireHandlingInstructionIgnoreMisfires() - .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId()))) + .withSchedule(scheduleBuilder) .build(); } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java new file mode 100644 index 000000000000..4f6e0d8d55fd --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + +import org.quartz.CronScheduleBuilder; + +final class QuartzScheduleMissedFirePolicyApplier { + + private QuartzScheduleMissedFirePolicyApplier() { + throw new IllegalStateException("Utility class"); + } + + static void apply(CronScheduleBuilder scheduleBuilder, ScheduleMissedFirePolicy missedFirePolicy) { + switch (effectivePolicy(missedFirePolicy)) { + case SKIP_MISSED: + scheduleBuilder.withMisfireHandlingInstructionDoNothing(); + return; + case FIRE_ONCE_NOW: + scheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); + return; + case FIRE_ALL_MISSED: + scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); + return; + default: + throw new IllegalStateException("Unsupported schedule missed fire policy: " + missedFirePolicy); + } + } + + private static ScheduleMissedFirePolicy effectivePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java new file mode 100644 index 000000000000..9b962602cd3b --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + +import org.junit.jupiter.api.Test; +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Trigger; + +class QuartzScheduleMissedFirePolicyApplierTest { + + private static final String CRON_EXPRESSION = "0 0 * * * ?"; + + @Test + void shouldSkipMissedExecutions() { + assertEquals( + CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING, + buildMisfireInstruction(ScheduleMissedFirePolicy.SKIP_MISSED)); + } + + @Test + void shouldFireOnceNow() { + assertEquals( + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, + buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ONCE_NOW)); + } + + @Test + void shouldFireAllMissedExecutions() { + assertEquals( + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, + buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ALL_MISSED)); + } + + @Test + void shouldFireOnceNowByDefault() { + assertEquals( + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, + buildMisfireInstruction(null)); + } + + private int buildMisfireInstruction(ScheduleMissedFirePolicy policy) { + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(CRON_EXPRESSION); + QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, policy); + return scheduleBuilder.build().getMisfireInstruction(); + } +} diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index 40e553d4dd1d..534c11915b84 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -149,6 +149,10 @@ export default { start_time: 'Start Time', end_time: 'End Time', crontab: 'Crontab', + missed_fire_policy: 'Missed Fire Policy', + skip_missed: 'Skip missed executions', + fire_once_now: 'Fire once immediately', + fire_all_missed: 'Fire all missed executions', delete_confirm: 'Delete?', delete_confirm_with_name: 'Delete "{name}"?', delete_irreversible: diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index abaa84a34b1a..1f99db99c032 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,6 +148,10 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', + missed_fire_policy: '错过触发策略', + skip_missed: '跳过错过的执行,等待下一次调度', + fire_once_now: '立即补触发一次,之后按正常节奏继续调度', + fire_all_missed: '补触发所有错过的执行,之后按正常节奏继续调度', delete_confirm: '确定删除吗?', delete_confirm_with_name: '确定删除“{name}”吗?', delete_irreversible: '此操作不可撤销。工作流及其关联数据将被永久删除。', diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index c30230581a65..fc8bb583bec1 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -295,6 +295,8 @@ export default defineComponent({ ] timingState.timingForm.crontab = props.row.crontab timingState.timingForm.timezoneId = props.row.timezoneId + timingState.timingForm.missedFirePolicy = + props.row.missedFirePolicy || 'FIRE_ONCE_NOW' timingState.timingForm.failureStrategy = props.row.failureStrategy timingState.timingForm.warningType = props.row.warningType timingState.timingForm.workflowInstancePriority = @@ -410,6 +412,28 @@ export default defineComponent({ ) : null} + + + { ], crontab: '0 0 * * * ? *', timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, + missedFirePolicy: 'FIRE_ONCE_NOW', failureStrategy: 'CONTINUE', warningType: 'NONE', workflowInstancePriority: 'MEDIUM', From c73327eb99d92dab4e0ad5b4a6c4e58ba306eb0f Mon Sep 17 00:00:00 2001 From: wenjie liang <97488304+liang-wenjie@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:21:38 +0800 Subject: [PATCH 03/20] refactor(schedule): apply review suggestions --- ...r.java => CronScheduleBuilderFactory.java} | 28 +++---- ...reAllMissedCronScheduleBuilderFactory.java | 29 +++++++ ...FireOnceNowCronScheduleBuilderFactory.java | 29 +++++++ .../quartz/QuartzCornTriggerBuilder.java | 4 +- .../SkipMissedCronScheduleBuilderFactory.java | 29 +++++++ .../CronScheduleBuilderFactoryTest.java | 75 +++++++++++++++++++ ...tzScheduleMissedFirePolicyApplierTest.java | 66 ---------------- .../src/locales/zh_CN/project.ts | 2 +- 8 files changed, 176 insertions(+), 86 deletions(-) rename dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/{QuartzScheduleMissedFirePolicyApplier.java => CronScheduleBuilderFactory.java} (53%) create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java delete mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java similarity index 53% rename from dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java rename to dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java index 4f6e0d8d55fd..214146968067 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java @@ -21,29 +21,23 @@ import org.quartz.CronScheduleBuilder; -final class QuartzScheduleMissedFirePolicyApplier { +interface CronScheduleBuilderFactory { - private QuartzScheduleMissedFirePolicyApplier() { - throw new IllegalStateException("Utility class"); - } + CronScheduleBuilder createCronScheduleBuilder(String cronExpression); - static void apply(CronScheduleBuilder scheduleBuilder, ScheduleMissedFirePolicy missedFirePolicy) { - switch (effectivePolicy(missedFirePolicy)) { + static CronScheduleBuilderFactory getFactory(ScheduleMissedFirePolicy missedFirePolicy) { + ScheduleMissedFirePolicy effectivePolicy = missedFirePolicy == null + ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW + : missedFirePolicy; + switch (effectivePolicy) { case SKIP_MISSED: - scheduleBuilder.withMisfireHandlingInstructionDoNothing(); - return; + return new SkipMissedCronScheduleBuilderFactory(); case FIRE_ONCE_NOW: - scheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); - return; + return new FireOnceNowCronScheduleBuilderFactory(); case FIRE_ALL_MISSED: - scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); - return; + return new FireAllMissedCronScheduleBuilderFactory(); default: - throw new IllegalStateException("Unsupported schedule missed fire policy: " + missedFirePolicy); + throw new IllegalArgumentException("Unsupported schedule missed fire policy: " + missedFirePolicy); } } - - private static ScheduleMissedFirePolicy effectivePolicy(ScheduleMissedFirePolicy missedFirePolicy) { - return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; - } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java new file mode 100644 index 000000000000..3bc89ff25c94 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.quartz.CronScheduleBuilder; + +final class FireAllMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { + + @Override + public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { + return CronScheduleBuilder.cronSchedule(cronExpression) + .withMisfireHandlingInstructionIgnoreMisfires(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java new file mode 100644 index 000000000000..ea354cbd3e77 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.quartz.CronScheduleBuilder; + +final class FireOnceNowCronScheduleBuilderFactory implements CronScheduleBuilderFactory { + + @Override + public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { + return CronScheduleBuilder.cronSchedule(cronExpression) + .withMisfireHandlingInstructionFireAndProceed(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index 6028c9223906..e28d6c77ede2 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -81,9 +81,9 @@ public CronTrigger build() { JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + CronScheduleBuilder scheduleBuilder = CronScheduleBuilderFactory.getFactory(schedule.getMissedFirePolicy()) + .createCronScheduleBuilder(schedule.getCrontab()) .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); - QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, schedule.getMissedFirePolicy()); return TriggerBuilder.newTrigger() .withIdentity(triggerKey) diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java new file mode 100644 index 000000000000..7a7f87bbc613 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.quartz.CronScheduleBuilder; + +final class SkipMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { + + @Override + public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { + return CronScheduleBuilder.cronSchedule(cronExpression) + .withMisfireHandlingInstructionDoNothing(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java new file mode 100644 index 000000000000..2f9823218ee8 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + +import org.junit.jupiter.api.Test; +import org.quartz.CronTrigger; +import org.quartz.Trigger; + +class CronScheduleBuilderFactoryTest { + + private static final String CRON_EXPRESSION = "0 0 * * * ?"; + + @Test + void shouldCreateSkipMissedCronScheduleBuilder() { + assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy.SKIP_MISSED, + SkipMissedCronScheduleBuilderFactory.class, + CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING); + } + + @Test + void shouldCreateFireOnceNowCronScheduleBuilder() { + assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy.FIRE_ONCE_NOW, + FireOnceNowCronScheduleBuilderFactory.class, + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); + } + + @Test + void shouldCreateFireAllMissedCronScheduleBuilder() { + assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy.FIRE_ALL_MISSED, + FireAllMissedCronScheduleBuilderFactory.class, + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); + } + + @Test + void shouldCreateFireOnceNowCronScheduleBuilderByDefault() { + assertFactoryAndMisfireInstruction( + null, + FireOnceNowCronScheduleBuilderFactory.class, + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); + } + + private void assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy policy, + Class expectedFactoryClass, + int expectedMisfireInstruction) { + CronScheduleBuilderFactory factory = CronScheduleBuilderFactory.getFactory(policy); + assertInstanceOf(expectedFactoryClass, factory); + assertEquals( + expectedMisfireInstruction, + factory.createCronScheduleBuilder(CRON_EXPRESSION).build().getMisfireInstruction()); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java deleted file mode 100644 index 9b962602cd3b..000000000000 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.scheduler.quartz; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; - -import org.junit.jupiter.api.Test; -import org.quartz.CronScheduleBuilder; -import org.quartz.CronTrigger; -import org.quartz.Trigger; - -class QuartzScheduleMissedFirePolicyApplierTest { - - private static final String CRON_EXPRESSION = "0 0 * * * ?"; - - @Test - void shouldSkipMissedExecutions() { - assertEquals( - CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING, - buildMisfireInstruction(ScheduleMissedFirePolicy.SKIP_MISSED)); - } - - @Test - void shouldFireOnceNow() { - assertEquals( - CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, - buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ONCE_NOW)); - } - - @Test - void shouldFireAllMissedExecutions() { - assertEquals( - Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, - buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ALL_MISSED)); - } - - @Test - void shouldFireOnceNowByDefault() { - assertEquals( - CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, - buildMisfireInstruction(null)); - } - - private int buildMisfireInstruction(ScheduleMissedFirePolicy policy) { - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(CRON_EXPRESSION); - QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, policy); - return scheduleBuilder.build().getMisfireInstruction(); - } -} diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index 1f99db99c032..429fd4990bf7 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,7 +148,7 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', - missed_fire_policy: '错过触发策略', + missed_fire_policy: '定时错过策略', skip_missed: '跳过错过的执行,等待下一次调度', fire_once_now: '立即补触发一次,之后按正常节奏继续调度', fire_all_missed: '补触发所有错过的执行,之后按正常节奏继续调度', From 2868f0960da1da692b76c5482bc95f8003085a37 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 5 Aug 2026 11:19:50 +0800 Subject: [PATCH 04/20] fix(schedule): preserve existing misfire behavior --- .../api/dto/ScheduleParam.java | 2 +- .../service/impl/SchedulerServiceImpl.java | 4 ++-- .../dolphinscheduler/api/vo/ScheduleVO.java | 2 +- .../resources/sql/dolphinscheduler_h2.sql | 2 +- .../resources/sql/dolphinscheduler_mysql.sql | 2 +- .../sql/dolphinscheduler_postgresql.sql | 2 +- .../mysql/dolphinscheduler_ddl_post.sql | 19 ++++++++++++++++++- .../postgresql/dolphinscheduler_ddl_post.sql | 19 ++++++++++++++++++- .../quartz/CronScheduleBuilderFactory.java | 2 +- .../CronScheduleBuilderFactoryTest.java | 6 +++--- .../definition/components/timing-modal.tsx | 2 +- .../definition/components/use-form.ts | 2 +- 12 files changed, 49 insertions(+), 15 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 4247cc3eedea..6369f56e4881 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -33,7 +33,7 @@ public class ScheduleParam { private Date endTime; private String crontab; private String timezoneId; - private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ONCE_NOW; + private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ALL_MISSED; public ScheduleParam() { } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 3968ff994bce..a167f0f5772a 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -32,9 +32,9 @@ import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -404,7 +404,7 @@ public List previewSchedule(User loginUser, String schedule) { } private ScheduleMissedFirePolicy defaultMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { - return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; + return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED : missedFirePolicy; } /** diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java index 29475610aeb7..8ddf9c3ade64 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java @@ -18,9 +18,9 @@ package org.apache.dolphinscheduler.api.vo; import org.apache.dolphinscheduler.common.enums.FailureStrategy; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; import org.apache.dolphinscheduler.dao.entity.Schedule; diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql index a16f213ef4d7..d69728be6ef3 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql @@ -858,7 +858,7 @@ CREATE TABLE t_ds_schedules end_time datetime NOT NULL, timezone_id varchar(40) DEFAULT NULL, crontab varchar(255) NOT NULL, - missed_fire_policy tinyint NOT NULL DEFAULT 1, + missed_fire_policy tinyint NOT NULL DEFAULT 2, failure_strategy tinyint(4) NOT NULL, user_id int(11) NOT NULL, release_state tinyint(4) NOT NULL, diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql index ac1d3abe531f..361cdc3ff7fd 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql @@ -859,7 +859,7 @@ CREATE TABLE `t_ds_schedules` ( `end_time` datetime NOT NULL COMMENT 'end time', `timezone_id` varchar(40) DEFAULT NULL COMMENT 'schedule timezone id', `crontab` varchar(255) NOT NULL COMMENT 'crontab description', - `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed', + `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed', `failure_strategy` tinyint(4) NOT NULL COMMENT 'failure strategy. 0:end,1:continue', `user_id` int(11) NOT NULL COMMENT 'user id', `release_state` tinyint(4) NOT NULL COMMENT 'release state. 0:offline,1:online ', diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql index 91843ffa413e..387716298493 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql @@ -785,7 +785,7 @@ CREATE TABLE t_ds_schedules ( end_time timestamp NOT NULL , timezone_id varchar(40) default NULL , crontab varchar(255) NOT NULL , - missed_fire_policy smallint NOT NULL DEFAULT 1, + missed_fire_policy smallint NOT NULL DEFAULT 2, failure_strategy int NOT NULL , user_id int NOT NULL , release_state int NOT NULL , diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql index cd76951444a5..fbf620b4c825 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql @@ -1,2 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + ALTER TABLE `t_ds_schedules` - ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; + ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql index 2f84808b68cf..0aaff6c8eea4 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql @@ -1,2 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + ALTER TABLE t_ds_schedules - ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 1; + ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 2; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java index 214146968067..1784aeae044e 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java @@ -27,7 +27,7 @@ interface CronScheduleBuilderFactory { static CronScheduleBuilderFactory getFactory(ScheduleMissedFirePolicy missedFirePolicy) { ScheduleMissedFirePolicy effectivePolicy = missedFirePolicy == null - ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW + ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED : missedFirePolicy; switch (effectivePolicy) { case SKIP_MISSED: diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java index 2f9823218ee8..9d0b135eb327 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java @@ -55,11 +55,11 @@ void shouldCreateFireAllMissedCronScheduleBuilder() { } @Test - void shouldCreateFireOnceNowCronScheduleBuilderByDefault() { + void shouldCreateFireAllMissedCronScheduleBuilderByDefault() { assertFactoryAndMisfireInstruction( null, - FireOnceNowCronScheduleBuilderFactory.class, - CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); + FireAllMissedCronScheduleBuilderFactory.class, + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); } private void assertFactoryAndMisfireInstruction( diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index fc8bb583bec1..5d15f06962a2 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -296,7 +296,7 @@ export default defineComponent({ timingState.timingForm.crontab = props.row.crontab timingState.timingForm.timezoneId = props.row.timezoneId timingState.timingForm.missedFirePolicy = - props.row.missedFirePolicy || 'FIRE_ONCE_NOW' + props.row.missedFirePolicy || 'FIRE_ALL_MISSED' timingState.timingForm.failureStrategy = props.row.failureStrategy timingState.timingForm.warningType = props.row.warningType timingState.timingForm.workflowInstancePriority = diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts index e9a6f9517ebc..9329c251737a 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts @@ -136,7 +136,7 @@ export const useForm = () => { ], crontab: '0 0 * * * ? *', timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, - missedFirePolicy: 'FIRE_ONCE_NOW', + missedFirePolicy: 'FIRE_ALL_MISSED', failureStrategy: 'CONTINUE', warningType: 'NONE', workflowInstancePriority: 'MEDIUM', From 060743fd25e93f16c0d409f7b2a6470deb3f08af Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Thu, 6 Aug 2026 19:29:45 +0800 Subject: [PATCH 05/20] fix: address missed fire policy review feedback --- docs/docs/en/guide/upgrade/incompatible.md | 4 ++++ docs/docs/zh/guide/upgrade/incompatible.md | 4 ++++ .../service/impl/SchedulerServiceImpl.java | 9 ++------- .../mysql/dolphinscheduler_ddl_post.sql | 19 ------------------- .../postgresql/dolphinscheduler_ddl_post.sql | 19 ------------------- .../mysql/dolphinscheduler_ddl.sql | 2 ++ .../postgresql/dolphinscheduler_ddl.sql | 2 ++ .../quartz/CronScheduleBuilderFactory.java | 3 ++- ...reAllMissedCronScheduleBuilderFactory.java | 10 +++++++--- ...FireOnceNowCronScheduleBuilderFactory.java | 10 +++++++--- .../quartz/QuartzCornTriggerBuilder.java | 3 +-- .../SkipMissedCronScheduleBuilderFactory.java | 10 +++++++--- .../CronScheduleBuilderFactoryTest.java | 13 ++++++++++--- 13 files changed, 48 insertions(+), 60 deletions(-) delete mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql delete mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql diff --git a/docs/docs/en/guide/upgrade/incompatible.md b/docs/docs/en/guide/upgrade/incompatible.md index 0f60fcb9986a..026d9b88bbcb 100644 --- a/docs/docs/en/guide/upgrade/incompatible.md +++ b/docs/docs/en/guide/upgrade/incompatible.md @@ -44,3 +44,7 @@ This document records the incompatible updates between each version. You need to * Remove import and export of workflow definition. ([#17940])(https://github.com/apache/dolphinscheduler/issues/17940) +## 3.5.0 + +* Add the `missed_fire_policy` column to `t_ds_schedules`. Existing schedules default to `FIRE_ALL_MISSED` to preserve the previous Quartz `IgnoreMisfires` behavior. ([#18464](https://github.com/apache/dolphinscheduler/pull/18464)) + diff --git a/docs/docs/zh/guide/upgrade/incompatible.md b/docs/docs/zh/guide/upgrade/incompatible.md index 2f0c4c044ff8..301d4e66d30b 100644 --- a/docs/docs/zh/guide/upgrade/incompatible.md +++ b/docs/docs/zh/guide/upgrade/incompatible.md @@ -48,3 +48,7 @@ * 移除导入导出工作流([#17940])(https://github.com/apache/dolphinscheduler/issues/17940) +## 3.5.0 + +* 为 `t_ds_schedules` 表新增 `missed_fire_policy` 字段。现有定时默认使用 `FIRE_ALL_MISSED`,以保持原有 Quartz `IgnoreMisfires` 行为。([#18464](https://github.com/apache/dolphinscheduler/pull/18464)) + diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index a167f0f5772a..23bb799a9249 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -34,7 +34,6 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -173,7 +172,7 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); - scheduleObj.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); + scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -403,10 +402,6 @@ public List previewSchedule(User loginUser, String schedule) { .collect(Collectors.toList()); } - private ScheduleMissedFirePolicy defaultMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { - return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED : missedFirePolicy; - } - /** * update workflow definition schedule * @@ -563,7 +558,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); - schedule.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); + schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql deleted file mode 100644 index fbf620b4c825..000000000000 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -ALTER TABLE `t_ds_schedules` - ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql deleted file mode 100644 index 0aaff6c8eea4..000000000000 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -ALTER TABLE t_ds_schedules - ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 2; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql index 3f7d3175979f..afeb9917b86b 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql @@ -17,4 +17,6 @@ ALTER TABLE `t_ds_task_instance` ADD INDEX idx_project_submit_time (project_code ASC, submit_time DESC); ALTER TABLE `t_ds_workflow_instance` ADD INDEX idx_project_start_time (project_code ASC, start_time DESC); +ALTER TABLE `t_ds_schedules` + ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql index 61a5ae809a13..1709fe57e737 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql @@ -17,3 +17,5 @@ CREATE INDEX idx_project_submit_time ON t_ds_task_instance (project_code ASC, submit_time DESC); CREATE INDEX idx_project_start_time ON t_ds_workflow_instance (project_code ASC, start_time DESC); +ALTER TABLE t_ds_schedules + ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 2; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java index 1784aeae044e..4702995a973a 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java @@ -18,12 +18,13 @@ package org.apache.dolphinscheduler.scheduler.quartz; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.dao.entity.Schedule; import org.quartz.CronScheduleBuilder; interface CronScheduleBuilderFactory { - CronScheduleBuilder createCronScheduleBuilder(String cronExpression); + CronScheduleBuilder createCronScheduleBuilder(Schedule schedule); static CronScheduleBuilderFactory getFactory(ScheduleMissedFirePolicy missedFirePolicy) { ScheduleMissedFirePolicy effectivePolicy = missedFirePolicy == null diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java index 3bc89ff25c94..b3022d1cb569 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java @@ -17,13 +17,17 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.dao.entity.Schedule; + import org.quartz.CronScheduleBuilder; final class FireAllMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { @Override - public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { - return CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionIgnoreMisfires(); + public CronScheduleBuilder createCronScheduleBuilder(Schedule schedule) { + return CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .withMisfireHandlingInstructionIgnoreMisfires() + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java index ea354cbd3e77..9990f429fd63 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java @@ -17,13 +17,17 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.dao.entity.Schedule; + import org.quartz.CronScheduleBuilder; final class FireOnceNowCronScheduleBuilderFactory implements CronScheduleBuilderFactory { @Override - public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { - return CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionFireAndProceed(); + public CronScheduleBuilder createCronScheduleBuilder(Schedule schedule) { + return CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .withMisfireHandlingInstructionFireAndProceed() + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index e28d6c77ede2..270f06eb970a 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -82,8 +82,7 @@ public CronTrigger build() { TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); CronScheduleBuilder scheduleBuilder = CronScheduleBuilderFactory.getFactory(schedule.getMissedFirePolicy()) - .createCronScheduleBuilder(schedule.getCrontab()) - .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); + .createCronScheduleBuilder(schedule); return TriggerBuilder.newTrigger() .withIdentity(triggerKey) diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java index 7a7f87bbc613..2d948085acca 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java @@ -17,13 +17,17 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.dao.entity.Schedule; + import org.quartz.CronScheduleBuilder; final class SkipMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { @Override - public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { - return CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionDoNothing(); + public CronScheduleBuilder createCronScheduleBuilder(Schedule schedule) { + return CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .withMisfireHandlingInstructionDoNothing() + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java index 9d0b135eb327..f006472188b0 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.dao.entity.Schedule; import org.junit.jupiter.api.Test; import org.quartz.CronTrigger; @@ -30,6 +31,8 @@ class CronScheduleBuilderFactoryTest { private static final String CRON_EXPRESSION = "0 0 * * * ?"; + private static final String TIMEZONE_ID = "Asia/Shanghai"; + @Test void shouldCreateSkipMissedCronScheduleBuilder() { assertFactoryAndMisfireInstruction( @@ -66,10 +69,14 @@ private void assertFactoryAndMisfireInstruction( ScheduleMissedFirePolicy policy, Class expectedFactoryClass, int expectedMisfireInstruction) { + Schedule schedule = new Schedule(); + schedule.setCrontab(CRON_EXPRESSION); + schedule.setTimezoneId(TIMEZONE_ID); CronScheduleBuilderFactory factory = CronScheduleBuilderFactory.getFactory(policy); assertInstanceOf(expectedFactoryClass, factory); - assertEquals( - expectedMisfireInstruction, - factory.createCronScheduleBuilder(CRON_EXPRESSION).build().getMisfireInstruction()); + Trigger trigger = factory.createCronScheduleBuilder(schedule).build(); + CronTrigger cronTrigger = assertInstanceOf(CronTrigger.class, trigger); + assertEquals(expectedMisfireInstruction, cronTrigger.getMisfireInstruction()); + assertEquals(TIMEZONE_ID, cronTrigger.getTimeZone().getID()); } } From dcf1d0d7d65b4634c4c69e85986b58f2d5ab707f Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Fri, 7 Aug 2026 14:53:01 +0800 Subject: [PATCH 06/20] fix(schedule): serialize missedFirePolicy and preserve existing policy on update Address review feedback (SbloodyS): - Frontend: include missedFirePolicy in the schedule create/update payload so the selected policy is actually persisted to the backend. - Backend: distinguish an omitted JSON field from an explicit value. A new missedFirePolicySet marker tracks field presence, because Jackson cannot tell omission from an explicit null. - create: omitted or explicit null falls back to FIRE_ALL_MISSED - update: when the client omits the field (e.g. an older client), the existing stored policy is preserved instead of being overwritten - Add unit tests covering create/update semantics and JSON presence detection. Co-Authored-By: WorkBuddy --- .../api/dto/ScheduleParam.java | 14 ++ .../service/impl/SchedulerServiceImpl.java | 10 +- .../api/service/SchedulerServiceTest.java | 120 ++++++++++++++++++ .../definition/components/use-modal.ts | 3 +- 4 files changed, 144 insertions(+), 3 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 6369f56e4881..7d70c77b9c3a 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -23,6 +23,8 @@ import lombok.Data; +import com.fasterxml.jackson.annotation.JsonIgnore; + /** * schedule parameters */ @@ -35,6 +37,9 @@ public class ScheduleParam { private String timezoneId; private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ALL_MISSED; + @JsonIgnore + private boolean missedFirePolicySet; + public ScheduleParam() { } @@ -45,6 +50,15 @@ public ScheduleParam(Date startTime, Date endTime, String timezoneId, String cro this.crontab = crontab; } + public void setMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + this.missedFirePolicy = missedFirePolicy; + this.missedFirePolicySet = true; + } + + public boolean isMissedFirePolicySet() { + return missedFirePolicySet; + } + @Override public String toString() { return "ScheduleParam{" diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 23bb799a9249..219e80147434 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -34,6 +34,7 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -172,7 +173,10 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); - scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); + ScheduleMissedFirePolicy missedFirePolicy = scheduleParam.getMissedFirePolicy(); + scheduleObj.setMissedFirePolicy(missedFirePolicy == null + ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED + : missedFirePolicy); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -558,7 +562,9 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); - schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); + if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { + schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); + } schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java index 20ea9e3ea4a1..c3572a4b1368 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java @@ -17,10 +17,17 @@ package org.apache.dolphinscheduler.api.service; +import org.apache.dolphinscheduler.api.dto.ScheduleParam; import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.impl.SchedulerServiceImpl; +import org.apache.dolphinscheduler.api.validator.TenantExistValidator; +import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.common.enums.WarningType; +import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.Schedule; import org.apache.dolphinscheduler.dao.entity.User; @@ -28,6 +35,7 @@ import org.apache.dolphinscheduler.dao.repository.ProjectDao; import org.apache.dolphinscheduler.dao.repository.ScheduleDao; import org.apache.dolphinscheduler.dao.repository.WorkflowDefinitionDao; +import org.apache.dolphinscheduler.scheduler.api.SchedulerApi; import java.util.Optional; @@ -35,6 +43,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -61,6 +72,15 @@ public class SchedulerServiceTest extends BaseServiceTestTool { @Mock private ProjectService projectService; + @Mock + private ExecutorService executorService; + + @Mock + private TenantExistValidator tenantExistValidator; + + @Mock + private SchedulerApi schedulerApi; + protected static User user; protected Exception exception; private static final String userName = "userName"; @@ -80,6 +100,106 @@ public void setUp() { user.setId(userId); } + @Test + public void testScheduleParamMissedFirePolicyPresence() { + String scheduleWithoutPolicy = "{\"startTime\":\"2019-12-16 00:00:00\"," + + "\"endTime\":\"2019-12-17 00:00:00\",\"crontab\":\"0 0 6 * * ? *\"}"; + String scheduleWithPolicy = "{\"startTime\":\"2019-12-16 00:00:00\"," + + "\"endTime\":\"2019-12-17 00:00:00\",\"crontab\":\"0 0 6 * * ? *\"," + + "\"missedFirePolicy\":\"SKIP_MISSED\"}"; + + ScheduleParam withoutPolicy = JSONUtils.parseObject(scheduleWithoutPolicy, ScheduleParam.class); + ScheduleParam withPolicy = JSONUtils.parseObject(scheduleWithPolicy, ScheduleParam.class); + + Assertions.assertEquals(ScheduleMissedFirePolicy.FIRE_ALL_MISSED, withoutPolicy.getMissedFirePolicy()); + Assertions.assertFalse(withoutPolicy.isMissedFirePolicySet()); + Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, withPolicy.getMissedFirePolicy()); + Assertions.assertTrue(withPolicy.isMissedFirePolicySet()); + } + + @ParameterizedTest + @EnumSource(ScheduleMissedFirePolicy.class) + public void testInsertScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + Project project = this.getProject(); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Schedule insertedSchedule = new Schedule(); + insertedSchedule.setId(scheduleId); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(project); + Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + Mockito.when(scheduleDao.queryById(Mockito.anyInt())).thenReturn(insertedSchedule); + + Schedule result = schedulerService.insertSchedule( + user, projectCode, processDefinitionCode, scheduleExpression(missedFirePolicy), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + ArgumentCaptor scheduleCaptor = ArgumentCaptor.forClass(Schedule.class); + Mockito.verify(scheduleDao).insert(scheduleCaptor.capture()); + Assertions.assertEquals(missedFirePolicy, scheduleCaptor.getValue().getMissedFirePolicy()); + Assertions.assertSame(insertedSchedule, result); + } + + @Test + public void testInsertScheduleDefaultsMissedFirePolicy() { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(this.getProcessDefinition())); + Mockito.when(scheduleDao.queryById(Mockito.anyInt())).thenReturn(new Schedule()); + + schedulerService.insertSchedule( + user, projectCode, processDefinitionCode, scheduleExpression(null), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + ArgumentCaptor scheduleCaptor = ArgumentCaptor.forClass(Schedule.class); + Mockito.verify(scheduleDao).insert(scheduleCaptor.capture()); + Assertions.assertEquals(ScheduleMissedFirePolicy.FIRE_ALL_MISSED, + scheduleCaptor.getValue().getMissedFirePolicy()); + } + + @ParameterizedTest + @EnumSource(ScheduleMissedFirePolicy.class) + public void testUpdateScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + + schedulerService.updateSchedule( + user, projectCode, scheduleId, scheduleExpression(missedFirePolicy), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + Assertions.assertEquals(missedFirePolicy, schedule.getMissedFirePolicy()); + } + + @Test + public void testUpdateSchedulePreservesMissedFirePolicyWhenOmitted() { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + schedule.setMissedFirePolicy(ScheduleMissedFirePolicy.SKIP_MISSED); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + + schedulerService.updateSchedule( + user, projectCode, scheduleId, scheduleExpression(null), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, schedule.getMissedFirePolicy()); + } + + private String scheduleExpression(ScheduleMissedFirePolicy missedFirePolicy) { + String policy = missedFirePolicy == null ? "" : ",\"missedFirePolicy\":\"" + missedFirePolicy.name() + "\""; + return "{\"startTime\":\"2019-12-16 00:00:00\",\"endTime\":\"2019-12-17 00:00:00\"," + + "\"crontab\":\"0 0 6 * * ? *\",\"timezoneId\":\"Asia/Shanghai\"" + policy + "}"; + } + @Test public void testDeleteSchedules() { Schedule schedule = this.getSchedule(); diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts index 4baac0f1d1dc..203ae5bc447e 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts @@ -180,7 +180,8 @@ export function useModal( startTime: start, endTime: end, crontab: state.timingForm.crontab, - timezoneId: state.timingForm.timezoneId + timezoneId: state.timingForm.timezoneId, + missedFirePolicy: state.timingForm.missedFirePolicy }), failureStrategy: state.timingForm.failureStrategy, warningType: state.timingForm.warningType, From c804e50cf51a43a0753dd2f79766c4717de49c52 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Fri, 7 Aug 2026 16:36:49 +0800 Subject: [PATCH 07/20] test(api): fix schedule insert mock --- .../dolphinscheduler/api/service/SchedulerServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java index c3572a4b1368..d6da88e731aa 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java @@ -128,7 +128,7 @@ public void testInsertScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy miss Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) .thenReturn(Optional.of(workflowDefinition)); - Mockito.when(scheduleDao.queryById(Mockito.anyInt())).thenReturn(insertedSchedule); + Mockito.when(scheduleDao.queryById(Mockito.any())).thenReturn(insertedSchedule); Schedule result = schedulerService.insertSchedule( user, projectCode, processDefinitionCode, scheduleExpression(missedFirePolicy), WarningType.NONE, 0, From 1831d55e5d6be9b00d1712de464e7da3411ead49 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Mon, 10 Aug 2026 14:16:41 +0800 Subject: [PATCH 08/20] fix(api): reject invalid missed fire policy --- .../service/impl/SchedulerServiceImpl.java | 15 +++-- .../api/service/SchedulerServiceTest.java | 67 +++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 219e80147434..7b0ae201c947 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -34,7 +34,6 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -173,10 +172,8 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); - ScheduleMissedFirePolicy missedFirePolicy = scheduleParam.getMissedFirePolicy(); - scheduleObj.setMissedFirePolicy(missedFirePolicy == null - ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED - : missedFirePolicy); + validateMissedFirePolicy(scheduleParam); + scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -562,6 +559,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); + validateMissedFirePolicy(scheduleParam); if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); } @@ -593,4 +591,11 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe return schedule; } + private void validateMissedFirePolicy(ScheduleParam scheduleParam) { + if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() == null) { + log.warn("Schedule missed fire policy is invalid."); + throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, "missedFirePolicy"); + } + } + } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java index d6da88e731aa..3c9a9dedc696 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java @@ -158,6 +158,16 @@ user, projectCode, processDefinitionCode, scheduleExpression(null), WarningType. scheduleCaptor.getValue().getMissedFirePolicy()); } + @Test + public void testInsertScheduleRejectsExplicitNullMissedFirePolicy() { + assertInsertScheduleRejectsInvalidMissedFirePolicy("null"); + } + + @Test + public void testInsertScheduleRejectsUnknownMissedFirePolicy() { + assertInsertScheduleRejectsInvalidMissedFirePolicy("\"FIRE_ONCE_NWO\""); + } + @ParameterizedTest @EnumSource(ScheduleMissedFirePolicy.class) public void testUpdateScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { @@ -194,8 +204,65 @@ user, projectCode, scheduleId, scheduleExpression(null), WarningType.NONE, 0, Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, schedule.getMissedFirePolicy()); } + @Test + public void testUpdateScheduleRejectsExplicitNullMissedFirePolicy() { + assertUpdateScheduleRejectsInvalidMissedFirePolicy("null"); + } + + @Test + public void testUpdateScheduleRejectsUnknownMissedFirePolicy() { + assertUpdateScheduleRejectsInvalidMissedFirePolicy("\"FIRE_ONCE_NWO\""); + } + + private void assertInsertScheduleRejectsInvalidMissedFirePolicy(String missedFirePolicyValue) { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(this.getProcessDefinition())); + + exception = Assertions.assertThrows(ServiceException.class, + () -> schedulerService.insertSchedule( + user, projectCode, processDefinitionCode, + scheduleExpressionWithPolicyValue(missedFirePolicyValue), + WarningType.NONE, 0, FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", + environmentCode)); + + Assertions.assertEquals(Status.REQUEST_PARAMS_NOT_VALID_ERROR.getCode(), + ((ServiceException) exception).getCode()); + Mockito.verify(scheduleDao, Mockito.never()).insert(Mockito.any()); + } + + private void assertUpdateScheduleRejectsInvalidMissedFirePolicy(String missedFirePolicyValue) { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + schedule.setMissedFirePolicy(ScheduleMissedFirePolicy.SKIP_MISSED); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(this.getProcessDefinition())); + + exception = Assertions.assertThrows(ServiceException.class, + () -> schedulerService.updateSchedule( + user, projectCode, scheduleId, scheduleExpressionWithPolicyValue(missedFirePolicyValue), + WarningType.NONE, 0, FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", + environmentCode)); + + Assertions.assertEquals(Status.REQUEST_PARAMS_NOT_VALID_ERROR.getCode(), + ((ServiceException) exception).getCode()); + Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, schedule.getMissedFirePolicy()); + Mockito.verify(scheduleDao, Mockito.never()).updateById(Mockito.any()); + } + private String scheduleExpression(ScheduleMissedFirePolicy missedFirePolicy) { String policy = missedFirePolicy == null ? "" : ",\"missedFirePolicy\":\"" + missedFirePolicy.name() + "\""; + return scheduleExpressionWithPolicy(policy); + } + + private String scheduleExpressionWithPolicyValue(String missedFirePolicyValue) { + return scheduleExpressionWithPolicy(",\"missedFirePolicy\":" + missedFirePolicyValue); + } + + private String scheduleExpressionWithPolicy(String policy) { return "{\"startTime\":\"2019-12-16 00:00:00\",\"endTime\":\"2019-12-17 00:00:00\"," + "\"crontab\":\"0 0 6 * * ? *\",\"timezoneId\":\"Asia/Shanghai\"" + policy + "}"; } From b362b9d9b1a4ede4945a921894d0661c34165665 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 11:58:16 +0800 Subject: [PATCH 09/20] Revert "merge: add quartz trigger types" This reverts commit 0245abdfc0258d5c2a10141fbaf652ff5ef64ec5, reversing changes made to 375234665081ccf9e56819580957842e0e3e84e9. --- .../api/dto/ScheduleParam.java | 6 - .../service/impl/SchedulerServiceImpl.java | 46 +--- .../dolphinscheduler/api/vo/ScheduleVO.java | 8 - .../common/enums/MisfirePolicy.java | 37 ---- .../common/enums/ScheduleTriggerType.java | 39 ---- .../common/utils/IntervalSchedule.java | 71 ------ .../common/utils/IntervalScheduleTest.java | 41 ---- .../dolphinscheduler/dao/entity/Schedule.java | 6 - .../dao/mapper/ScheduleMapper.xml | 4 +- .../resources/sql/dolphinscheduler_h2.sql | 2 - .../resources/sql/dolphinscheduler_mysql.sql | 2 - .../sql/dolphinscheduler_postgresql.sql | 2 - .../mysql/dolphinscheduler_ddl_post.sql | 3 - .../postgresql/dolphinscheduler_ddl_post.sql | 3 - .../quartz/QuartzCornTriggerBuilder.java | 9 +- .../quartz/QuartzMisfirePolicyApplier.java | 66 ------ .../scheduler/quartz/QuartzScheduler.java | 20 +- .../quartz/QuartzSimpleTriggerBuilder.java | 90 -------- .../QuartzSimpleTriggerBuilderTest.java | 48 ---- .../src/locales/en_US/project.ts | 14 -- .../src/locales/zh_CN/project.ts | 14 -- .../definition/components/timing-modal.tsx | 209 +++--------------- .../definition/components/use-form.ts | 2 - .../definition/components/use-modal.ts | 4 - .../workflow/definition/timing/use-table.ts | 24 -- 25 files changed, 41 insertions(+), 729 deletions(-) delete mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java delete mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java delete mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java delete mode 100644 dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java delete mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql delete mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql delete mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java delete mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java delete mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index d8c2b7e83156..88fd4ea9664b 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -17,9 +17,6 @@ package org.apache.dolphinscheduler.api.dto; -import org.apache.dolphinscheduler.common.enums.MisfirePolicy; -import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; - import java.util.Date; import lombok.Data; @@ -34,8 +31,6 @@ public class ScheduleParam { private Date endTime; private String crontab; private String timezoneId; - private ScheduleTriggerType triggerType = ScheduleTriggerType.CRON; - private MisfirePolicy misfirePolicy = MisfirePolicy.IGNORE_MISFIRES; public ScheduleParam() { } @@ -45,7 +40,6 @@ public ScheduleParam(Date startTime, Date endTime, String timezoneId, String cro this.endTime = endTime; this.timezoneId = timezoneId; this.crontab = crontab; - this.triggerType = ScheduleTriggerType.CRON; } @Override diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index e00923377b02..12df89e9fccf 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -32,14 +32,11 @@ import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; -import org.apache.dolphinscheduler.common.enums.MisfirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; -import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; -import org.apache.dolphinscheduler.common.utils.IntervalSchedule; import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.Schedule; @@ -55,7 +52,6 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -171,16 +167,11 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setStartTime(scheduleParam.getStartTime()); scheduleObj.setEndTime(scheduleParam.getEndTime()); - if (!isValidScheduleExpression(scheduleParam)) { + if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) { log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab()); throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); - scheduleObj.setMisfirePolicy(scheduleParam.getMisfirePolicy() == null - ? MisfirePolicy.IGNORE_MISFIRES - : scheduleParam.getMisfirePolicy()); - scheduleObj.setTriggerType( - scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -396,20 +387,6 @@ public List previewSchedule(User loginUser, String schedule) { ZonedDateTime startTime = ZonedDateTime.ofInstant(scheduleParam.getStartTime().toInstant(), zoneId); ZonedDateTime endTime = ZonedDateTime.ofInstant(scheduleParam.getEndTime().toInstant(), zoneId); startTime = now.isAfter(startTime) ? now : startTime; - if (scheduleParam.getTriggerType() == ScheduleTriggerType.INTERVAL) { - IntervalSchedule intervalSchedule = IntervalSchedule.parse(scheduleParam.getCrontab()); - List fireTimes = new ArrayList<>(); - int executionLimit = intervalSchedule.getRepeatCount() < 0 - ? Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT - : Math.min(intervalSchedule.getRepeatCount() + 1, Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT); - for (int i = 0; i < executionLimit && !startTime.isAfter(endTime); i++) { - fireTimes.add(startTime); - startTime = startTime.plus(Duration.ofMillis(intervalSchedule.getIntervalMilliseconds())); - } - return fireTimes.stream() - .map(t -> DateUtils.dateToString(t, zoneId)) - .collect(Collectors.toList()); - } try { cron = CronUtils.parse2Cron(scheduleParam.getCrontab()); @@ -424,20 +401,6 @@ public List previewSchedule(User loginUser, String schedule) { .collect(Collectors.toList()); } - private boolean isValidScheduleExpression(ScheduleParam scheduleParam) { - ScheduleTriggerType triggerType = scheduleParam.getTriggerType() == null - ? ScheduleTriggerType.CRON - : scheduleParam.getTriggerType(); - if (triggerType == ScheduleTriggerType.CRON) { - return CronUtils.isValidExpression(scheduleParam.getCrontab()); - } - try { - IntervalSchedule.parse(scheduleParam.getCrontab()); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } /** * update workflow definition schedule * @@ -589,16 +552,11 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe schedule.setStartTime(scheduleParam.getStartTime()); schedule.setEndTime(scheduleParam.getEndTime()); - if (!isValidScheduleExpression(scheduleParam)) { + if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) { log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab()); throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); - schedule.setMisfirePolicy(scheduleParam.getMisfirePolicy() == null - ? MisfirePolicy.IGNORE_MISFIRES - : scheduleParam.getMisfirePolicy()); - schedule.setTriggerType( - scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java index c2509399e1a8..fc4b1d966859 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java @@ -18,10 +18,8 @@ package org.apache.dolphinscheduler.api.vo; import org.apache.dolphinscheduler.common.enums.FailureStrategy; -import org.apache.dolphinscheduler.common.enums.MisfirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; -import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; import org.apache.dolphinscheduler.dao.entity.Schedule; @@ -56,10 +54,6 @@ public class ScheduleVO { private String crontab; - private ScheduleTriggerType triggerType; - - private MisfirePolicy misfirePolicy; - private FailureStrategy failureStrategy; private WarningType warningType; @@ -89,8 +83,6 @@ public class ScheduleVO { public ScheduleVO(Schedule schedule) { this.setId(schedule.getId()); this.setCrontab(schedule.getCrontab()); - this.setTriggerType(schedule.getTriggerType()); - this.setMisfirePolicy(schedule.getMisfirePolicy()); this.setProjectName(schedule.getProjectName()); this.setUserName(schedule.getUserName()); this.setWorkerGroup(schedule.getWorkerGroup()); diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java deleted file mode 100644 index 0acf53d866d9..000000000000 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/MisfirePolicy.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.common.enums; - -import lombok.Getter; - -import com.baomidou.mybatisplus.annotation.EnumValue; - -@Getter -public enum MisfirePolicy { - - DO_NOTHING(0), - FIRE_AND_PROCEED(1), - IGNORE_MISFIRES(2); - - @EnumValue - private final int code; - - MisfirePolicy(int code) { - this.code = code; - } -} diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java deleted file mode 100644 index 5700a00667ca..000000000000 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.common.enums; - -import lombok.Getter; - -import com.baomidou.mybatisplus.annotation.EnumValue; - -@Getter -public enum ScheduleTriggerType { - - CRON(0, "Cron expression"), - INTERVAL(1, "Fixed interval"); - - @EnumValue - private final int code; - - private final String description; - - ScheduleTriggerType(int code, String description) { - this.code = code; - this.description = description; - } -} diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java deleted file mode 100644 index 8a889fd83f6d..000000000000 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.common.utils; - -import lombok.Value; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * Fixed interval schedule encoded as JSON. - */ -@Value -public class IntervalSchedule { - - long intervalMilliseconds; - - int repeatCount; - - public static IntervalSchedule parse(String expression) { - final ObjectNode values; - try { - values = JSONUtils.parseObject(expression); - } catch (Exception e) { - throw new IllegalArgumentException("Interval schedule expression must be a JSON object", e); - } - if (values == null || !values.isObject()) { - throw new IllegalArgumentException("Interval schedule expression must not be null"); - } - - int hours = valueOf(values, "hour"); - int minutes = valueOf(values, "minute"); - int seconds = valueOf(values, "second"); - int repeat = valueOf(values, "repeat"); - if (hours < 0 || minutes < 0 || seconds < 0 || repeat < -1) { - throw new IllegalArgumentException("Interval duration values must not be negative"); - } - - long intervalMilliseconds = Math.addExact( - Math.addExact(Math.multiplyExact(hours, 3_600_000L), Math.multiplyExact(minutes, 60_000L)), - Math.multiplyExact(seconds, 1_000L)); - if (intervalMilliseconds == 0) { - throw new IllegalArgumentException("Interval duration must be positive"); - } - return new IntervalSchedule(intervalMilliseconds, repeat); - } - - private static int valueOf(ObjectNode values, String key) { - if (!values.has(key)) { - return 0; - } - if (!values.get(key).isInt()) { - throw new IllegalArgumentException("Interval schedule field must be an integer: " + key); - } - return values.get(key).intValue(); - } -} diff --git a/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java b/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java deleted file mode 100644 index 4935f77a69e2..000000000000 --- a/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.common.utils; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; - -class IntervalScheduleTest { - - @Test - void parseIntervalSchedule() { - IntervalSchedule intervalSchedule = - IntervalSchedule.parse("{\"hour\":1,\"minute\":2,\"second\":3,\"repeat\":4}"); - - assertEquals(3_723_000L, intervalSchedule.getIntervalMilliseconds()); - assertEquals(4, intervalSchedule.getRepeatCount()); - } - - @Test - void rejectInvalidIntervalSchedule() { - assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":0}")); - assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":-1}")); - } -} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java index 708a83a357f4..a55c8d1ad529 100644 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java +++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java @@ -18,10 +18,8 @@ package org.apache.dolphinscheduler.dao.entity; import org.apache.dolphinscheduler.common.enums.FailureStrategy; -import org.apache.dolphinscheduler.common.enums.MisfirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; -import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.WarningType; import java.util.Date; @@ -69,10 +67,6 @@ public class Schedule { private String crontab; - private MisfirePolicy misfirePolicy; - - private ScheduleTriggerType triggerType; - private FailureStrategy failureStrategy; private WarningType warningType; diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml index 6fa5f3e452c3..99c1a59ef6eb 100644 --- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml +++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml @@ -19,12 +19,12 @@ - id, workflow_definition_code, start_time, end_time, timezone_id, crontab, misfire_policy, trigger_type, failure_strategy, user_id, release_state, + id, workflow_definition_code, start_time, end_time, timezone_id, crontab, failure_strategy, user_id, release_state, warning_type, warning_group_id, workflow_instance_priority, worker_group, tenant_code, environment_code, create_time, update_time ${alias}.id, ${alias}.workflow_definition_code, ${alias}.start_time, ${alias}.end_time, ${alias}.timezone_id, - ${alias}.crontab, ${alias}.misfire_policy, ${alias}.trigger_type, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, + ${alias}.crontab, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, ${alias}.warning_group_id, ${alias}.workflow_instance_priority, ${alias}.worker_group, ${alias}.tenant_code, ${alias}.environment_code, ${alias}.create_time, ${alias}.update_time diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql index 223d77bc6536..1725b5c2df36 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql @@ -858,8 +858,6 @@ CREATE TABLE t_ds_schedules end_time datetime NOT NULL, timezone_id varchar(40) DEFAULT NULL, crontab varchar(255) NOT NULL, - misfire_policy tinyint NOT NULL DEFAULT 2, - trigger_type tinyint NOT NULL DEFAULT 0, failure_strategy tinyint(4) NOT NULL, user_id int(11) NOT NULL, release_state tinyint(4) NOT NULL, diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql index c93126b53918..f6dfa61fc122 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql @@ -859,8 +859,6 @@ CREATE TABLE `t_ds_schedules` ( `end_time` datetime NOT NULL COMMENT 'end time', `timezone_id` varchar(40) DEFAULT NULL COMMENT 'schedule timezone id', `crontab` varchar(255) NOT NULL COMMENT 'crontab description', - `misfire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'misfire policy: 0 do nothing, 1 fire and proceed, 2 ignore misfires', - `trigger_type` tinyint NOT NULL DEFAULT '0' COMMENT 'schedule trigger type: 0 cron, 1 interval', `failure_strategy` tinyint(4) NOT NULL COMMENT 'failure strategy. 0:end,1:continue', `user_id` int(11) NOT NULL COMMENT 'user id', `release_state` tinyint(4) NOT NULL COMMENT 'release state. 0:offline,1:online ', diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql index ad3a5133bd85..698b35768d94 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql @@ -785,8 +785,6 @@ CREATE TABLE t_ds_schedules ( end_time timestamp NOT NULL , timezone_id varchar(40) default NULL , crontab varchar(255) NOT NULL , - misfire_policy smallint NOT NULL DEFAULT 2, - trigger_type smallint NOT NULL DEFAULT 0, failure_strategy int NOT NULL , user_id int NOT NULL , release_state int NOT NULL , diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql deleted file mode 100644 index 2c86d94d6e7e..000000000000 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE `t_ds_schedules` - ADD COLUMN `misfire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'misfire policy: 0 do nothing, 1 fire and proceed, 2 ignore misfires' AFTER `crontab`, - ADD COLUMN `trigger_type` tinyint NOT NULL DEFAULT '0' COMMENT 'schedule trigger type: 0 cron, 1 interval' AFTER `misfire_policy`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql deleted file mode 100644 index f0e00c8ef5f2..000000000000 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE t_ds_schedules - ADD COLUMN misfire_policy smallint NOT NULL DEFAULT 2, - ADD COLUMN trigger_type smallint NOT NULL DEFAULT 0; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index 2fe1e684a31b..b7177e74db84 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -81,15 +81,14 @@ public CronTrigger build() { JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedule.getCrontab()) - .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); - QuartzMisfirePolicyApplier.apply(scheduleBuilder, schedule.getMisfirePolicy()); - return TriggerBuilder.newTrigger() .withIdentity(triggerKey) .startAt(startDate) .endAt(endDate) - .withSchedule(scheduleBuilder) + .withSchedule( + CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .withMisfireHandlingInstructionIgnoreMisfires() + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId()))) .build(); } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java deleted file mode 100644 index e1699f3ac088..000000000000 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzMisfirePolicyApplier.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.scheduler.quartz; - -import org.apache.dolphinscheduler.common.enums.MisfirePolicy; - -import org.quartz.CronScheduleBuilder; -import org.quartz.SimpleScheduleBuilder; - -final class QuartzMisfirePolicyApplier { - - private QuartzMisfirePolicyApplier() { - throw new IllegalStateException("Utility class"); - } - - static void apply(SimpleScheduleBuilder scheduleBuilder, MisfirePolicy misfirePolicy) { - switch (effectivePolicy(misfirePolicy)) { - case DO_NOTHING: - scheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); - return; - case FIRE_AND_PROCEED: - scheduleBuilder.withMisfireHandlingInstructionFireNow(); - return; - case IGNORE_MISFIRES: - scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); - return; - default: - throw new IllegalStateException("Unsupported misfire policy: " + misfirePolicy); - } - } - - static void apply(CronScheduleBuilder scheduleBuilder, MisfirePolicy misfirePolicy) { - switch (effectivePolicy(misfirePolicy)) { - case DO_NOTHING: - scheduleBuilder.withMisfireHandlingInstructionDoNothing(); - return; - case FIRE_AND_PROCEED: - scheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); - return; - case IGNORE_MISFIRES: - scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); - return; - default: - throw new IllegalStateException("Unsupported misfire policy: " + misfirePolicy); - } - } - - private static MisfirePolicy effectivePolicy(MisfirePolicy misfirePolicy) { - return misfirePolicy == null ? MisfirePolicy.IGNORE_MISFIRES : misfirePolicy; - } -} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java index ff3d6b31303d..70d5bf8fabc0 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java @@ -17,7 +17,6 @@ package org.apache.dolphinscheduler.scheduler.quartz; -import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.dao.entity.Schedule; import org.apache.dolphinscheduler.scheduler.api.SchedulerApi; import org.apache.dolphinscheduler.scheduler.api.SchedulerException; @@ -25,10 +24,10 @@ import lombok.extern.slf4j.Slf4j; +import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; -import org.quartz.Trigger; import com.google.common.collect.Sets; @@ -53,21 +52,16 @@ public void start() throws SchedulerException { @Override public void insertOrUpdateScheduleTask(int projectId, Schedule schedule) throws SchedulerException { try { - Trigger trigger = schedule.getTriggerType() == ScheduleTriggerType.INTERVAL - ? QuartzSimpleTriggerBuilder.newBuilder() - .withProjectId(projectId) - .withSchedule(schedule) - .build() - : QuartzCornTriggerBuilder.newBuilder() - .withProjectId(projectId) - .withSchedule(schedule) - .build(); + CronTrigger cornTrigger = QuartzCornTriggerBuilder.newBuilder() + .withProjectId(projectId) + .withSchedule(schedule) + .build(); JobDetail jobDetail = QuartzJobDetailBuilder.newBuilder() .withProjectId(projectId) .withSchedule(schedule.getId()) .build(); - scheduler.scheduleJob(jobDetail, Sets.newHashSet(trigger), true); - log.info("Success scheduleJob: {} with trigger: {} at quartz", jobDetail, trigger); + scheduler.scheduleJob(jobDetail, Sets.newHashSet(cornTrigger), true); + log.info("Success scheduleJob: {} with trigger: {} at quartz", jobDetail, cornTrigger); } catch (Exception e) { log.error("Failed to add scheduler task, projectId: {}, scheduler: {}", projectId, schedule, e); throw new SchedulerException(QuartzSchedulerExceptionEnum.QUARTZ_UPSERT_JOB_ERROR, e); diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java deleted file mode 100644 index ebce6fb082ee..000000000000 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.scheduler.quartz; - -import org.apache.dolphinscheduler.common.utils.DateUtils; -import org.apache.dolphinscheduler.common.utils.IntervalSchedule; -import org.apache.dolphinscheduler.dao.entity.Schedule; - -import java.util.Date; - -import org.quartz.JobKey; -import org.quartz.SimpleScheduleBuilder; -import org.quartz.SimpleTrigger; -import org.quartz.TriggerBuilder; -import org.quartz.TriggerKey; - -/** - * Builds a Quartz {@link SimpleTrigger} from a fixed-interval schedule expression. - */ -public class QuartzSimpleTriggerBuilder { - - private Integer projectId; - - private Schedule schedule; - - public static QuartzSimpleTriggerBuilder newBuilder() { - return new QuartzSimpleTriggerBuilder(); - } - - public QuartzSimpleTriggerBuilder withProjectId(Integer projectId) { - this.projectId = projectId; - return this; - } - - public QuartzSimpleTriggerBuilder withSchedule(Schedule schedule) { - this.schedule = schedule; - return this; - } - - public SimpleTrigger build() { - if (projectId == null) { - throw new IllegalArgumentException("projectId cannot be null"); - } - if (schedule == null) { - throw new IllegalArgumentException("schedule cannot be null"); - } - - IntervalSchedule intervalSchedule = IntervalSchedule.parse(schedule.getCrontab()); - JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); - TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); - Date startTime = DateUtils.transformTimezoneDate(schedule.getStartTime(), schedule.getTimezoneId()); - Date endTime = DateUtils.transformTimezoneDate(schedule.getEndTime(), schedule.getTimezoneId()); - Date now = new Date(); - if (startTime.before(now)) { - startTime = now; - } - - SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() - .withIntervalInMilliseconds(intervalSchedule.getIntervalMilliseconds()); - if (intervalSchedule.getRepeatCount() < 0) { - scheduleBuilder.repeatForever(); - } else { - scheduleBuilder.withRepeatCount(intervalSchedule.getRepeatCount()); - } - - QuartzMisfirePolicyApplier.apply(scheduleBuilder, schedule.getMisfirePolicy()); - - return TriggerBuilder.newTrigger() - .withIdentity(triggerKey) - .startAt(startTime) - .endAt(endTime) - .withSchedule(scheduleBuilder) - .build(); - } -} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java deleted file mode 100644 index 53f69b3e0d15..000000000000 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.scheduler.quartz; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.apache.dolphinscheduler.dao.entity.Schedule; - -import java.util.Date; - -import org.junit.jupiter.api.Test; -import org.quartz.SimpleTrigger; - -class QuartzSimpleTriggerBuilderTest { - - @Test - void buildIntervalTrigger() { - Schedule schedule = new Schedule(); - schedule.setId(2); - schedule.setCrontab("{\"minute\":5,\"repeat\":3}"); - schedule.setTimezoneId("UTC"); - schedule.setStartTime(new Date(System.currentTimeMillis() + 60_000)); - schedule.setEndTime(new Date(System.currentTimeMillis() + 3_600_000)); - - SimpleTrigger trigger = QuartzSimpleTriggerBuilder.newBuilder() - .withProjectId(1) - .withSchedule(schedule) - .build(); - - assertEquals(300_000L, trigger.getRepeatInterval()); - assertEquals(3, trigger.getRepeatCount()); - } -} diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index 3f64b21c71ec..40e553d4dd1d 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -149,20 +149,6 @@ export default { start_time: 'Start Time', end_time: 'End Time', crontab: 'Crontab', - trigger_type: 'Trigger Type', - cron_trigger: 'Cron', - interval_trigger: 'Interval', - interval: 'Interval', - hours: 'Hours', - minutes: 'Minutes', - seconds: 'Seconds', - repeat: 'Repeat', - unlimited_repeat_tip: '-1 means unlimited repeats', - interval_must_be_positive: 'Interval must be positive', - misfire_policy: 'Misfire Policy', - do_nothing: 'Skip missed executions', - fire_and_proceed: 'Fire once immediately', - ignore_misfires: 'Ignore misfires', delete_confirm: 'Delete?', delete_confirm_with_name: 'Delete "{name}"?', delete_irreversible: diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index e054a1c2bfe4..abaa84a34b1a 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,20 +148,6 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', - trigger_type: '触发类型', - cron_trigger: 'Cron 表达式', - interval_trigger: '固定间隔', - interval: '间隔配置', - hours: '小时', - minutes: '分钟', - seconds: '秒', - repeat: '重复次数', - unlimited_repeat_tip: '-1 表示不限次数', - interval_must_be_positive: '固定间隔必须大于 0', - misfire_policy: '错过触发策略', - do_nothing: '不执行补偿,等待下一次调度', - fire_and_proceed: '立即执行错过的任务,之后按正常节奏继续调度', - ignore_misfires: '补齐所有错过的任务,之后按正常节奏继续调度', delete_confirm: '确定删除吗?', delete_confirm_with_name: '确定删除“{name}”吗?', delete_irreversible: '此操作不可撤销。工作流及其关联数据将被永久删除。', diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index 2f3717d8f59f..c30230581a65 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -36,7 +36,6 @@ import { NButton, NIcon, NInput, - NInputNumber, NSpace, NRadio, NRadioGroup, @@ -104,11 +103,6 @@ export default defineComponent({ ) const projectPreferences = ref({} as any) - const intervalHours = ref(1) - const intervalMinutes = ref(0) - const intervalSeconds = ref(0) - const intervalRepeat = ref(-1) - const cronExpression = ref(timingState.timingForm.crontab) const initProjectPreferences = (projectCode: number) => { queryProjectPreferenceByProjectCode(projectCode).then((result: any) => { @@ -191,15 +185,6 @@ export default defineComponent({ } const handlePreview = () => { - if ( - timingState.timingForm.triggerType === 'INTERVAL' && - intervalHours.value === 0 && - intervalMinutes.value === 0 && - intervalSeconds.value === 0 - ) { - window.$message.error(t('project.workflow.interval_must_be_positive')) - return - } getPreviewSchedule() } @@ -288,57 +273,6 @@ export default defineComponent({ const trim = getCurrentInstance()?.appContext.config.globalProperties.trim - const updateIntervalExpression = () => { - timingState.timingForm.crontab = JSON.stringify({ - hour: intervalHours.value, - minute: intervalMinutes.value, - second: intervalSeconds.value, - repeat: intervalRepeat.value - }) - } - - const restoreIntervalExpression = (expression: string): boolean => { - try { - const interval = JSON.parse(expression) - intervalHours.value = interval.hour || 0 - intervalMinutes.value = interval.minute || 0 - intervalSeconds.value = interval.second || 0 - intervalRepeat.value = interval.repeat ?? -1 - return true - } catch { - return false - } - } - - watch( - () => timingState.timingForm.triggerType, - (triggerType, previousTriggerType) => { - if (previousTriggerType === 'CRON') { - cronExpression.value = timingState.timingForm.crontab - } - - if (triggerType === 'CRON') { - timingState.timingForm.crontab = cronExpression.value - return - } - - if ( - previousTriggerType !== 'INTERVAL' && - !restoreIntervalExpression(timingState.timingForm.crontab) - ) { - updateIntervalExpression() - } - } - ) - - watch( - [intervalHours, intervalMinutes, intervalSeconds, intervalRepeat], - () => { - if (timingState.timingForm.triggerType === 'INTERVAL') { - updateIntervalExpression() - } - } - ) onMounted(() => { getWorkerGroups() getTenantList() @@ -359,16 +293,7 @@ export default defineComponent({ new Date(props.row.startTime), new Date(props.row.endTime) ] - const triggerType = props.row.triggerType || 'CRON' - timingState.timingForm.triggerType = triggerType timingState.timingForm.crontab = props.row.crontab - timingState.timingForm.misfirePolicy = - props.row.misfirePolicy || 'IGNORE_MISFIRES' - if (triggerType === 'CRON') { - cronExpression.value = props.row.crontab - } else if (!restoreIntervalExpression(props.row.crontab)) { - updateIntervalExpression() - } timingState.timingForm.timezoneId = props.row.timezoneId timingState.timingForm.failureStrategy = props.row.failureStrategy timingState.timingForm.warningType = props.row.warningType @@ -393,10 +318,6 @@ export default defineComponent({ renderLabel, updateWorkerGroup, handlePreview, - intervalHours, - intervalMinutes, - intervalSeconds, - intervalRepeat, ...toRefs(variables), ...toRefs(timingState), ...toRefs(props), @@ -431,89 +352,33 @@ export default defineComponent({ v-model:value={this.timingForm.startEndTime} /> - - + + + + {{ + trigger: () => ( + + ), + default: () => ( + + ) + }} + + + {t('project.workflow.execute_time')} + + - {this.timingForm.triggerType === 'CRON' ? ( - - - - {{ - trigger: () => ( - - ), - default: () => ( - - ) - }} - - - {t('project.workflow.execute_time')} - - - - ) : ( - -
- - - {t('project.workflow.hours')} - - {t('project.workflow.minutes')} - - {t('project.workflow.seconds')} - - - {t('project.workflow.repeat')} - - {t('project.workflow.unlimited_repeat_tip')} - - {t('project.workflow.execute_time')} - - -
-
- )} - - - { new Date(year + 100, month, day) ], crontab: '0 0 * * * ? *', - triggerType: 'CRON', - misfirePolicy: 'IGNORE_MISFIRES', timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, failureStrategy: 'CONTINUE', warningType: 'NONE', diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts index 31a5b5988ca7..4baac0f1d1dc 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts @@ -180,8 +180,6 @@ export function useModal( startTime: start, endTime: end, crontab: state.timingForm.crontab, - triggerType: state.timingForm.triggerType, - misfirePolicy: state.timingForm.misfirePolicy, timezoneId: state.timingForm.timezoneId }), failureStrategy: state.timingForm.failureStrategy, @@ -265,8 +263,6 @@ export function useModal( startTime: start, endTime: end, crontab: state.timingForm.crontab, - triggerType: state.timingForm.triggerType, - misfirePolicy: state.timingForm.misfirePolicy, timezoneId: state.timingForm.timezoneId }) previewSchedule({ schedule }, projectCode).then((res: any) => { diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts index 75b0603f478d..4d0196df70c4 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/timing/use-table.ts @@ -212,30 +212,6 @@ export function useTable() { key: 'crontab', width: 140 }, - { - title: t('project.workflow.trigger_type'), - key: 'triggerType', - width: 120, - render: (row: any) => - row.triggerType === 'INTERVAL' - ? t('project.workflow.interval_trigger') - : t('project.workflow.cron_trigger') - }, - { - title: t('project.workflow.misfire_policy'), - key: 'misfirePolicy', - width: 180, - render: (row: any) => { - const labels: Record = { - DO_NOTHING: t('project.workflow.do_nothing'), - FIRE_AND_PROCEED: t('project.workflow.fire_and_proceed'), - IGNORE_MISFIRES: t('project.workflow.ignore_misfires') - } - return ( - labels[row.misfirePolicy] || t('project.workflow.ignore_misfires') - ) - } - }, { title: t('project.workflow.failure_strategy'), key: 'failureStrategy', From 03b330bcd26fecf796a6433b8b82b8f3fc5ef1b7 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 14:26:36 +0800 Subject: [PATCH 10/20] feat(schedule): add interval trigger type --- .../api/dto/ScheduleParam.java | 2 + .../service/impl/SchedulerServiceImpl.java | 37 +++- .../dolphinscheduler/api/vo/ScheduleVO.java | 4 + .../common/enums/ScheduleTriggerType.java | 39 ++++ .../common/utils/IntervalSchedule.java | 71 +++++++ .../common/utils/IntervalScheduleTest.java | 41 ++++ .../dolphinscheduler/dao/entity/Schedule.java | 3 + .../dao/mapper/ScheduleMapper.xml | 4 +- .../resources/sql/dolphinscheduler_h2.sql | 1 + .../resources/sql/dolphinscheduler_mysql.sql | 1 + .../sql/dolphinscheduler_postgresql.sql | 1 + .../mysql/dolphinscheduler_ddl.sql | 1 + .../postgresql/dolphinscheduler_ddl.sql | 1 + .../scheduler/quartz/QuartzScheduler.java | 14 +- .../quartz/QuartzSimpleTriggerBuilder.java | 98 +++++++++ .../QuartzSimpleTriggerBuilderTest.java | 48 +++++ .../definition/components/timing-modal.tsx | 187 +++++++++++++++--- .../definition/components/use-form.ts | 5 + .../definition/components/use-modal.ts | 5 +- 19 files changed, 524 insertions(+), 39 deletions(-) create mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java create mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java create mode 100644 dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 7d70c77b9c3a..69a8fbfb07a9 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -18,6 +18,7 @@ package org.apache.dolphinscheduler.api.dto; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import java.util.Date; @@ -36,6 +37,7 @@ public class ScheduleParam { private String crontab; private String timezoneId; private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ALL_MISSED; + private ScheduleTriggerType triggerType = ScheduleTriggerType.CRON; @JsonIgnore private boolean missedFirePolicySet; diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 7b0ae201c947..c6913e3fd282 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -34,9 +34,11 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.common.utils.IntervalSchedule; import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.Schedule; @@ -52,6 +54,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -167,13 +170,14 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setStartTime(scheduleParam.getStartTime()); scheduleObj.setEndTime(scheduleParam.getEndTime()); - if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) { + if (!isValidScheduleExpression(scheduleParam)) { log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab()); throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); validateMissedFirePolicy(scheduleParam); scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); + scheduleObj.setTriggerType(`r`n scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -389,6 +393,20 @@ public List previewSchedule(User loginUser, String schedule) { ZonedDateTime startTime = ZonedDateTime.ofInstant(scheduleParam.getStartTime().toInstant(), zoneId); ZonedDateTime endTime = ZonedDateTime.ofInstant(scheduleParam.getEndTime().toInstant(), zoneId); startTime = now.isAfter(startTime) ? now : startTime; + if (scheduleParam.getTriggerType() == ScheduleTriggerType.INTERVAL) { + IntervalSchedule intervalSchedule = IntervalSchedule.parse(scheduleParam.getCrontab()); + List fireTimes = new ArrayList<>(); + int executionLimit = intervalSchedule.getRepeatCount() < 0 + ? Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT + : Math.min(intervalSchedule.getRepeatCount() + 1, Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT); + for (int i = 0; i < executionLimit && !startTime.isAfter(endTime); i++) { + fireTimes.add(startTime); + startTime = startTime.plus(Duration.ofMillis(intervalSchedule.getIntervalMilliseconds())); + } + return fireTimes.stream() + .map(t -> DateUtils.dateToString(t, zoneId)) + .collect(Collectors.toList()); + } try { cron = CronUtils.parse2Cron(scheduleParam.getCrontab()); @@ -403,6 +421,20 @@ public List previewSchedule(User loginUser, String schedule) { .collect(Collectors.toList()); } + private boolean isValidScheduleExpression(ScheduleParam scheduleParam) { + ScheduleTriggerType triggerType = scheduleParam.getTriggerType() == null + ? ScheduleTriggerType.CRON + : scheduleParam.getTriggerType(); + if (triggerType == ScheduleTriggerType.CRON) { + return CronUtils.isValidExpression(scheduleParam.getCrontab()); + } + try { + IntervalSchedule.parse(scheduleParam.getCrontab()); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } /** * update workflow definition schedule * @@ -554,7 +586,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe schedule.setStartTime(scheduleParam.getStartTime()); schedule.setEndTime(scheduleParam.getEndTime()); - if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) { + if (!isValidScheduleExpression(scheduleParam)) { log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab()); throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } @@ -563,6 +595,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); } + schedule.setTriggerType(`r`n scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java index 8ddf9c3ade64..065af206430e 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java @@ -21,6 +21,7 @@ import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; import org.apache.dolphinscheduler.dao.entity.Schedule; @@ -57,6 +58,8 @@ public class ScheduleVO { private ScheduleMissedFirePolicy missedFirePolicy; + private ScheduleTriggerType triggerType; + private FailureStrategy failureStrategy; private WarningType warningType; @@ -87,6 +90,7 @@ public ScheduleVO(Schedule schedule) { this.setId(schedule.getId()); this.setCrontab(schedule.getCrontab()); this.setMissedFirePolicy(schedule.getMissedFirePolicy()); + this.setTriggerType(schedule.getTriggerType()); this.setProjectName(schedule.getProjectName()); this.setUserName(schedule.getUserName()); this.setWorkerGroup(schedule.getWorkerGroup()); diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java new file mode 100644 index 000000000000..5700a00667ca --- /dev/null +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleTriggerType.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.enums; + +import lombok.Getter; + +import com.baomidou.mybatisplus.annotation.EnumValue; + +@Getter +public enum ScheduleTriggerType { + + CRON(0, "Cron expression"), + INTERVAL(1, "Fixed interval"); + + @EnumValue + private final int code; + + private final String description; + + ScheduleTriggerType(int code, String description) { + this.code = code; + this.description = description; + } +} diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java new file mode 100644 index 000000000000..8a889fd83f6d --- /dev/null +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.utils; + +import lombok.Value; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Fixed interval schedule encoded as JSON. + */ +@Value +public class IntervalSchedule { + + long intervalMilliseconds; + + int repeatCount; + + public static IntervalSchedule parse(String expression) { + final ObjectNode values; + try { + values = JSONUtils.parseObject(expression); + } catch (Exception e) { + throw new IllegalArgumentException("Interval schedule expression must be a JSON object", e); + } + if (values == null || !values.isObject()) { + throw new IllegalArgumentException("Interval schedule expression must not be null"); + } + + int hours = valueOf(values, "hour"); + int minutes = valueOf(values, "minute"); + int seconds = valueOf(values, "second"); + int repeat = valueOf(values, "repeat"); + if (hours < 0 || minutes < 0 || seconds < 0 || repeat < -1) { + throw new IllegalArgumentException("Interval duration values must not be negative"); + } + + long intervalMilliseconds = Math.addExact( + Math.addExact(Math.multiplyExact(hours, 3_600_000L), Math.multiplyExact(minutes, 60_000L)), + Math.multiplyExact(seconds, 1_000L)); + if (intervalMilliseconds == 0) { + throw new IllegalArgumentException("Interval duration must be positive"); + } + return new IntervalSchedule(intervalMilliseconds, repeat); + } + + private static int valueOf(ObjectNode values, String key) { + if (!values.has(key)) { + return 0; + } + if (!values.get(key).isInt()) { + throw new IllegalArgumentException("Interval schedule field must be an integer: " + key); + } + return values.get(key).intValue(); + } +} diff --git a/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java b/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java new file mode 100644 index 000000000000..4935f77a69e2 --- /dev/null +++ b/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class IntervalScheduleTest { + + @Test + void parseIntervalSchedule() { + IntervalSchedule intervalSchedule = + IntervalSchedule.parse("{\"hour\":1,\"minute\":2,\"second\":3,\"repeat\":4}"); + + assertEquals(3_723_000L, intervalSchedule.getIntervalMilliseconds()); + assertEquals(4, intervalSchedule.getRepeatCount()); + } + + @Test + void rejectInvalidIntervalSchedule() { + assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":0}")); + assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":-1}")); + } +} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java index 965a5c919477..9158357fb8f7 100644 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java +++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java @@ -21,6 +21,7 @@ import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.WarningType; import java.util.Date; @@ -70,6 +71,8 @@ public class Schedule { private ScheduleMissedFirePolicy missedFirePolicy; + private ScheduleTriggerType triggerType; + private FailureStrategy failureStrategy; private WarningType warningType; diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml index 78a401c9d1b2..e6620cd3be41 100644 --- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml +++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml @@ -19,12 +19,12 @@ - id, workflow_definition_code, start_time, end_time, timezone_id, crontab, missed_fire_policy, failure_strategy, user_id, release_state, + id, workflow_definition_code, start_time, end_time, timezone_id, crontab, missed_fire_policy, trigger_type, failure_strategy, user_id, release_state, warning_type, warning_group_id, workflow_instance_priority, worker_group, tenant_code, environment_code, create_time, update_time ${alias}.id, ${alias}.workflow_definition_code, ${alias}.start_time, ${alias}.end_time, ${alias}.timezone_id, - ${alias}.crontab, ${alias}.missed_fire_policy, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, + ${alias}.crontab, ${alias}.missed_fire_policy, ${alias}.trigger_type, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, ${alias}.warning_group_id, ${alias}.workflow_instance_priority, ${alias}.worker_group, ${alias}.tenant_code, ${alias}.environment_code, ${alias}.create_time, ${alias}.update_time diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql index d69728be6ef3..a4dfa14ac00d 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql @@ -859,6 +859,7 @@ CREATE TABLE t_ds_schedules timezone_id varchar(40) DEFAULT NULL, crontab varchar(255) NOT NULL, missed_fire_policy tinyint NOT NULL DEFAULT 2, + trigger_type tinyint NOT NULL DEFAULT 0, failure_strategy tinyint(4) NOT NULL, user_id int(11) NOT NULL, release_state tinyint(4) NOT NULL, diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql index 361cdc3ff7fd..594835ce8b3f 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql @@ -860,6 +860,7 @@ CREATE TABLE `t_ds_schedules` ( `timezone_id` varchar(40) DEFAULT NULL COMMENT 'schedule timezone id', `crontab` varchar(255) NOT NULL COMMENT 'crontab description', `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed', + `trigger_type` tinyint NOT NULL DEFAULT '0' COMMENT 'schedule trigger type: 0 cron, 1 interval', `failure_strategy` tinyint(4) NOT NULL COMMENT 'failure strategy. 0:end,1:continue', `user_id` int(11) NOT NULL COMMENT 'user id', `release_state` tinyint(4) NOT NULL COMMENT 'release state. 0:offline,1:online ', diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql index 387716298493..09ad523b5fd5 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql @@ -786,6 +786,7 @@ CREATE TABLE t_ds_schedules ( timezone_id varchar(40) default NULL , crontab varchar(255) NOT NULL , missed_fire_policy smallint NOT NULL DEFAULT 2, + trigger_type smallint NOT NULL DEFAULT 0, failure_strategy int NOT NULL , user_id int NOT NULL , release_state int NOT NULL , diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql index afeb9917b86b..500c1e27f9b1 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql @@ -20,3 +20,4 @@ ALTER TABLE `t_ds_workflow_instance` ADD INDEX idx_project_start_time (project_c ALTER TABLE `t_ds_schedules` ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; +ALTER TABLE `t_ds_schedules` ADD COLUMN `trigger_type` tinyint NOT NULL DEFAULT '0' COMMENT 'schedule trigger type: 0 cron, 1 interval' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql index 1709fe57e737..4e0bb57112a9 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql @@ -19,3 +19,4 @@ CREATE INDEX idx_project_submit_time ON t_ds_task_instance (project_code ASC, su CREATE INDEX idx_project_start_time ON t_ds_workflow_instance (project_code ASC, start_time DESC); ALTER TABLE t_ds_schedules ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 2; +ALTER TABLE t_ds_schedules ADD COLUMN trigger_type smallint NOT NULL DEFAULT 0; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java index 70d5bf8fabc0..55e158d66b0e 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduler.java @@ -17,6 +17,7 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.dao.entity.Schedule; import org.apache.dolphinscheduler.scheduler.api.SchedulerApi; import org.apache.dolphinscheduler.scheduler.api.SchedulerException; @@ -24,10 +25,10 @@ import lombok.extern.slf4j.Slf4j; -import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; +import org.quartz.Trigger; import com.google.common.collect.Sets; @@ -52,16 +53,15 @@ public void start() throws SchedulerException { @Override public void insertOrUpdateScheduleTask(int projectId, Schedule schedule) throws SchedulerException { try { - CronTrigger cornTrigger = QuartzCornTriggerBuilder.newBuilder() - .withProjectId(projectId) - .withSchedule(schedule) - .build(); + Trigger trigger = schedule.getTriggerType() == ScheduleTriggerType.INTERVAL + ? QuartzSimpleTriggerBuilder.newBuilder().withProjectId(projectId).withSchedule(schedule).build() + : QuartzCornTriggerBuilder.newBuilder().withProjectId(projectId).withSchedule(schedule).build(); JobDetail jobDetail = QuartzJobDetailBuilder.newBuilder() .withProjectId(projectId) .withSchedule(schedule.getId()) .build(); - scheduler.scheduleJob(jobDetail, Sets.newHashSet(cornTrigger), true); - log.info("Success scheduleJob: {} with trigger: {} at quartz", jobDetail, cornTrigger); + scheduler.scheduleJob(jobDetail, Sets.newHashSet(trigger), true); + log.info("Success scheduleJob: {} with trigger: {} at quartz", jobDetail, trigger); } catch (Exception e) { log.error("Failed to add scheduler task, projectId: {}, scheduler: {}", projectId, schedule, e); throw new SchedulerException(QuartzSchedulerExceptionEnum.QUARTZ_UPSERT_JOB_ERROR, e); diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java new file mode 100644 index 000000000000..368757ddd84a --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilder.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.common.utils.IntervalSchedule; +import org.apache.dolphinscheduler.dao.entity.Schedule; + +import java.util.Date; + +import org.quartz.JobKey; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.SimpleTrigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; + +/** + * Builds a Quartz {@link SimpleTrigger} from a fixed-interval schedule expression. + */ +public class QuartzSimpleTriggerBuilder { + + private Integer projectId; + + private Schedule schedule; + + public static QuartzSimpleTriggerBuilder newBuilder() { + return new QuartzSimpleTriggerBuilder(); + } + + public QuartzSimpleTriggerBuilder withProjectId(Integer projectId) { + this.projectId = projectId; + return this; + } + + public QuartzSimpleTriggerBuilder withSchedule(Schedule schedule) { + this.schedule = schedule; + return this; + } + + public SimpleTrigger build() { + if (projectId == null) { + throw new IllegalArgumentException("projectId cannot be null"); + } + if (schedule == null) { + throw new IllegalArgumentException("schedule cannot be null"); + } + + IntervalSchedule intervalSchedule = IntervalSchedule.parse(schedule.getCrontab()); + JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); + TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); + Date startTime = DateUtils.transformTimezoneDate(schedule.getStartTime(), schedule.getTimezoneId()); + Date endTime = DateUtils.transformTimezoneDate(schedule.getEndTime(), schedule.getTimezoneId()); + Date now = new Date(); + if (startTime.before(now)) { + startTime = now; + } + + SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() + .withIntervalInMilliseconds(intervalSchedule.getIntervalMilliseconds()); + if (intervalSchedule.getRepeatCount() < 0) { + scheduleBuilder.repeatForever(); + } else { + scheduleBuilder.withRepeatCount(intervalSchedule.getRepeatCount()); + } + + ScheduleMissedFirePolicy missedFirePolicy = schedule.getMissedFirePolicy(); + if (missedFirePolicy == ScheduleMissedFirePolicy.SKIP_MISSED) { + scheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); + } else if (missedFirePolicy == ScheduleMissedFirePolicy.FIRE_ONCE_NOW) { + scheduleBuilder.withMisfireHandlingInstructionFireNow(); + } else { + scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); + } + + return TriggerBuilder.newTrigger() + .withIdentity(triggerKey) + .startAt(startTime) + .endAt(endTime) + .withSchedule(scheduleBuilder) + .build(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java new file mode 100644 index 000000000000..53f69b3e0d15 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.dolphinscheduler.dao.entity.Schedule; + +import java.util.Date; + +import org.junit.jupiter.api.Test; +import org.quartz.SimpleTrigger; + +class QuartzSimpleTriggerBuilderTest { + + @Test + void buildIntervalTrigger() { + Schedule schedule = new Schedule(); + schedule.setId(2); + schedule.setCrontab("{\"minute\":5,\"repeat\":3}"); + schedule.setTimezoneId("UTC"); + schedule.setStartTime(new Date(System.currentTimeMillis() + 60_000)); + schedule.setEndTime(new Date(System.currentTimeMillis() + 3_600_000)); + + SimpleTrigger trigger = QuartzSimpleTriggerBuilder.newBuilder() + .withProjectId(1) + .withSchedule(schedule) + .build(); + + assertEquals(300_000L, trigger.getRepeatInterval()); + assertEquals(3, trigger.getRepeatCount()); + } +} diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index 5d15f06962a2..6fd3ff1f43e7 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -36,6 +36,7 @@ import { NButton, NIcon, NInput, + NInputNumber, NSpace, NRadio, NRadioGroup, @@ -103,6 +104,11 @@ export default defineComponent({ ) const projectPreferences = ref({} as any) + const intervalHours = ref(1) + const intervalMinutes = ref(0) + const intervalSeconds = ref(0) + const intervalRepeat = ref(-1) + const cronExpression = ref(timingState.timingForm.crontab) const initProjectPreferences = (projectCode: number) => { queryProjectPreferenceByProjectCode(projectCode).then((result: any) => { @@ -185,6 +191,15 @@ export default defineComponent({ } const handlePreview = () => { + if ( + timingState.timingForm.triggerType === 'INTERVAL' && + intervalHours.value === 0 && + intervalMinutes.value === 0 && + intervalSeconds.value === 0 + ) { + window.$message.error(t('project.workflow.interval_must_be_positive')) + return + } getPreviewSchedule() } @@ -273,6 +288,57 @@ export default defineComponent({ const trim = getCurrentInstance()?.appContext.config.globalProperties.trim + const updateIntervalExpression = () => { + timingState.timingForm.crontab = JSON.stringify({ + hour: intervalHours.value, + minute: intervalMinutes.value, + second: intervalSeconds.value, + repeat: intervalRepeat.value + }) + } + + const restoreIntervalExpression = (expression: string): boolean => { + try { + const interval = JSON.parse(expression) + intervalHours.value = interval.hour || 0 + intervalMinutes.value = interval.minute || 0 + intervalSeconds.value = interval.second || 0 + intervalRepeat.value = interval.repeat ?? -1 + return true + } catch { + return false + } + } + + watch( + () => timingState.timingForm.triggerType, + (triggerType, previousTriggerType) => { + if (previousTriggerType === 'CRON') { + cronExpression.value = timingState.timingForm.crontab + } + + if (triggerType === 'CRON') { + timingState.timingForm.crontab = cronExpression.value + return + } + + if ( + previousTriggerType !== 'INTERVAL' && + !restoreIntervalExpression(timingState.timingForm.crontab) + ) { + updateIntervalExpression() + } + } + ) + + watch( + [intervalHours, intervalMinutes, intervalSeconds, intervalRepeat], + () => { + if (timingState.timingForm.triggerType === 'INTERVAL') { + updateIntervalExpression() + } + } + ) onMounted(() => { getWorkerGroups() getTenantList() @@ -293,10 +359,17 @@ export default defineComponent({ new Date(props.row.startTime), new Date(props.row.endTime) ] + const triggerType = props.row.triggerType || 'CRON' + timingState.timingForm.triggerType = triggerType timingState.timingForm.crontab = props.row.crontab - timingState.timingForm.timezoneId = props.row.timezoneId timingState.timingForm.missedFirePolicy = props.row.missedFirePolicy || 'FIRE_ALL_MISSED' + if (triggerType === 'CRON') { + cronExpression.value = props.row.crontab + } else if (!restoreIntervalExpression(props.row.crontab)) { + updateIntervalExpression() + } + timingState.timingForm.timezoneId = props.row.timezoneId timingState.timingForm.failureStrategy = props.row.failureStrategy timingState.timingForm.warningType = props.row.warningType timingState.timingForm.workflowInstancePriority = @@ -320,6 +393,10 @@ export default defineComponent({ renderLabel, updateWorkerGroup, handlePreview, + intervalHours, + intervalMinutes, + intervalSeconds, + intervalRepeat, ...toRefs(variables), ...toRefs(timingState), ...toRefs(props), @@ -354,33 +431,89 @@ export default defineComponent({ v-model:value={this.timingForm.startEndTime} /> - - - - {{ - trigger: () => ( - - ), - default: () => ( - - ) - }} - - - {t('project.workflow.execute_time')} - - + + + {this.timingForm.triggerType === 'CRON' ? ( + + + + {{ + trigger: () => ( + + ), + default: () => ( + + ) + }} + + + {t('project.workflow.execute_time')} + + + + ) : ( + +
+ + + {t('project.workflow.hours')} + + {t('project.workflow.minutes')} + + {t('project.workflow.seconds')} + + + {t('project.workflow.repeat')} + + {t('project.workflow.unlimited_repeat_tip')} + + {t('project.workflow.execute_time')} + + +
+
+ )} { new Date(year + 100, month, day) ], crontab: '0 0 * * * ? *', + triggerType: 'CRON', + intervalHour: 0, + intervalMinute: 0, + intervalSecond: 0, + intervalRepeat: -1, timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, missedFirePolicy: 'FIRE_ALL_MISSED', failureStrategy: 'CONTINUE', diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts index 203ae5bc447e..c94fffc3b713 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts @@ -176,6 +176,7 @@ export function useModal( ) const data = { + triggerType: state.timingForm.triggerType, schedule: JSON.stringify({ startTime: start, endTime: end, @@ -264,7 +265,9 @@ export function useModal( startTime: start, endTime: end, crontab: state.timingForm.crontab, - timezoneId: state.timingForm.timezoneId + timezoneId: state.timingForm.timezoneId, + missedFirePolicy: state.timingForm.missedFirePolicy, + triggerType: state.timingForm.triggerType }) previewSchedule({ schedule }, projectCode).then((res: any) => { variables.schedulePreviewList = res From 3c840904f4c2a6c50aadb4f1bf22758e616f6d85 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 14:33:37 +0800 Subject: [PATCH 11/20] fix(schedule): remove invalid source characters --- .../api/service/impl/SchedulerServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index c6913e3fd282..b6f0cbd0aa84 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -177,7 +177,7 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setCrontab(scheduleParam.getCrontab()); validateMissedFirePolicy(scheduleParam); scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); - scheduleObj.setTriggerType(`r`n scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); + scheduleObj.setTriggerType(scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -595,7 +595,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); } - schedule.setTriggerType(`r`n scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); + schedule.setTriggerType(scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } From c30e22061ff0b86f1ebf9f59bdabda281bfe50d6 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 15:01:23 +0800 Subject: [PATCH 12/20] fix(ui): add interval schedule translations --- dolphinscheduler-ui/src/locales/en_US/project.ts | 10 ++++++++++ dolphinscheduler-ui/src/locales/zh_CN/project.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index 534c11915b84..2e56d47e991e 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -149,6 +149,16 @@ export default { start_time: 'Start Time', end_time: 'End Time', crontab: 'Crontab', + trigger_type: 'Trigger Type', + cron_trigger: 'Cron Expression', + interval_trigger: 'Interval', + interval: 'Interval', + hours: 'Hours', + minutes: 'Minutes', + seconds: 'Seconds', + repeat: 'Execution Count', + unlimited_repeat_tip: '-1 means unlimited executions', + interval_must_be_positive: 'The interval must be greater than 0', missed_fire_policy: 'Missed Fire Policy', skip_missed: 'Skip missed executions', fire_once_now: 'Fire once immediately', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index 429fd4990bf7..f4a2b31c076d 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,6 +148,16 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', + trigger_type: '????', + cron_trigger: 'Cron ???', + interval_trigger: '????', + interval: '????', + hours: '??', + minutes: '??', + seconds: '?', + repeat: '????', + unlimited_repeat_tip: '-1 ??????', + interval_must_be_positive: '???????? 0', missed_fire_policy: '定时错过策略', skip_missed: '跳过错过的执行,等待下一次调度', fire_once_now: '立即补触发一次,之后按正常节奏继续调度', From e7a5948a6255edaa8c46719c7cfd5842ab3d162b Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 15:10:51 +0800 Subject: [PATCH 13/20] style(ui): improve interval schedule layout --- .../definition/components/timing-modal.tsx | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index 6fd3ff1f43e7..9679a97a60dc 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -476,41 +476,29 @@ export default defineComponent({ ) : ( -
- - - {t('project.workflow.hours')} - - {t('project.workflow.minutes')} - - {t('project.workflow.seconds')} - - - {t('project.workflow.repeat')} - - {t('project.workflow.unlimited_repeat_tip')} +
+
+ + + + + + + + + +
+
+ + + {t('project.workflow.execute_time')} - +
+
+ {t('project.workflow.unlimited_repeat_tip')} +
)} From 1094eabb132ec021f7e24bcdaa29c38935ce76ab Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 15:25:24 +0800 Subject: [PATCH 14/20] fix(ui): restore chinese schedule translations --- .../src/locales/zh_CN/project.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index f4a2b31c076d..816d015ba12f 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,16 +148,16 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', - trigger_type: '????', - cron_trigger: 'Cron ???', - interval_trigger: '????', - interval: '????', - hours: '??', - minutes: '??', - seconds: '?', - repeat: '????', - unlimited_repeat_tip: '-1 ??????', - interval_must_be_positive: '???????? 0', + trigger_type: '触发类型', + cron_trigger: 'Cron 表达式', + interval_trigger: '时间间隔', + interval: '时间间隔', + hours: '小时', + minutes: '分钟', + seconds: '秒', + repeat: '执行次数', + unlimited_repeat_tip: '-1 表示无限执行', + interval_must_be_positive: '时间间隔必须大于 0', missed_fire_policy: '定时错过策略', skip_missed: '跳过错过的执行,等待下一次调度', fire_once_now: '立即补触发一次,之后按正常节奏继续调度', From f492927bcb7590138831388e91d710f31121faf7 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 15:30:37 +0800 Subject: [PATCH 15/20] style(ui): match repeat input width --- .../projects/workflow/definition/components/timing-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index 9679a97a60dc..1ccec3c18dfc 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -488,7 +488,7 @@ export default defineComponent({
-
+
From 5b1c6a2befec5ee677e63a50840d686115a43f8c Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 12 Aug 2026 15:32:55 +0800 Subject: [PATCH 16/20] fix(ui): correct interval layout jsx --- .../projects/workflow/definition/components/timing-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index 1ccec3c18dfc..c6791827ddce 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -488,7 +488,7 @@ export default defineComponent({
-
+
From ef3655eb49a894ad8e54040a90fb878b06799a65 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Mon, 17 Aug 2026 11:26:13 +0800 Subject: [PATCH 17/20] fix(schedule): persist interval trigger type --- .../api/service/impl/SchedulerServiceImpl.java | 6 ++++-- .../projects/workflow/definition/components/use-modal.ts | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index b6f0cbd0aa84..7442bcf0f107 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -177,7 +177,8 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setCrontab(scheduleParam.getCrontab()); validateMissedFirePolicy(scheduleParam); scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); - scheduleObj.setTriggerType(scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); + scheduleObj.setTriggerType( + scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -595,7 +596,8 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); } - schedule.setTriggerType(scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); + schedule.setTriggerType( + scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts index c94fffc3b713..81e70b3808c5 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts @@ -178,6 +178,7 @@ export function useModal( const data = { triggerType: state.timingForm.triggerType, schedule: JSON.stringify({ + triggerType: state.timingForm.triggerType, startTime: start, endTime: end, crontab: state.timingForm.crontab, From 972aa48a817358941b3b63d166a12bd5181c4894 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Mon, 17 Aug 2026 14:02:02 +0800 Subject: [PATCH 18/20] test(schedule): cover interval trigger API flow --- .../dolphinscheduler/api/test/cases/SchedulerAPITest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java index 2919b0a66f08..40d396fe9514 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java @@ -111,7 +111,7 @@ public void testCreateSchedule() { workflowDefinitionPage.releaseWorkflowDefinition(loginUser, projectCode, workflowDefinitionCode, ReleaseState.ONLINE); final String schedule = - "{\"startTime\":\"2019-08-08 00:00:00\",\"endTime\":\"2100-08-08 00:00:00\",\"timezoneId\":\"America/Phoenix\",\"crontab\":\"0 0 3/6 * * ? *\"}"; + "{\"startTime\":\"2019-08-08 00:00:00\",\"endTime\":\"2100-08-08 00:00:00\",\"timezoneId\":\"America/Phoenix\",\"triggerType\":\"INTERVAL\",\"crontab\":\"{\\\"hour\\\":0,\\\"minute\\\":0,\\\"second\\\":10,\\\"repeat\\\":2}\"}"; HttpResponse createScheduleResponse = schedulerPage.createSchedule(loginUser, projectCode, workflowDefinitionCode, schedule); Assertions.assertTrue(createScheduleResponse.getBody().getSuccess()); @@ -124,6 +124,7 @@ public void testQueryScheduleList() { HttpResponse queryScheduleListResponse = schedulerPage.queryScheduleList(loginUser, projectCode); Assertions.assertTrue(queryScheduleListResponse.getBody().getSuccess()); Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("2019-08-08")); + Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("triggerType=INTERVAL")); scheduleId = (int) ((LinkedHashMap) ((List) queryScheduleListResponse.getBody() .getData()).get(0)).get("id"); } @@ -156,7 +157,7 @@ public void testOfflineSchedule() { @Order(5) public void testUpdateSchedule() { final String schedule = - "{\"startTime\":\"1996-08-08 00:00:00\",\"endTime\":\"2200-08-08 00:00:00\",\"timezoneId\":\"America/Phoenix\",\"crontab\":\"0 0 3/6 * * ? *\"}"; + "{\"startTime\":\"1996-08-08 00:00:00\",\"endTime\":\"2200-08-08 00:00:00\",\"timezoneId\":\"America/Phoenix\",\"triggerType\":\"INTERVAL\",\"crontab\":\"{\\\"hour\\\":0,\\\"minute\\\":0,\\\"second\\\":20,\\\"repeat\\\":1}\"}"; HttpResponse updateScheduleResponse = schedulerPage.updateSchedule(loginUser, projectCode, scheduleId, schedule); Assertions.assertTrue(updateScheduleResponse.getBody().getSuccess()); @@ -164,6 +165,7 @@ public void testUpdateSchedule() { HttpResponse queryScheduleListResponse = schedulerPage.queryScheduleList(loginUser, projectCode); Assertions.assertTrue(queryScheduleListResponse.getBody().getSuccess()); Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("1996-08-08")); + Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("triggerType=INTERVAL")); } @Test From f48a528139565d103d2b64e76af5c2e3f3996632 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Tue, 18 Aug 2026 15:06:11 +0800 Subject: [PATCH 19/20] fix: harden interval schedule trigger handling --- .../api/test/cases/SchedulerAPITest.java | 6 +- .../api/dto/ScheduleParam.java | 12 +++ .../service/impl/SchedulerServiceImpl.java | 29 +++++- .../api/service/SchedulerServiceTest.java | 96 +++++++++++++++++++ .../common/utils/IntervalSchedule.java | 16 +++- .../common/utils/IntervalScheduleTest.java | 12 ++- .../QuartzSimpleTriggerBuilderTest.java | 32 +++++++ 7 files changed, 190 insertions(+), 13 deletions(-) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java index 40d396fe9514..e7250ac9f459 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java @@ -124,7 +124,8 @@ public void testQueryScheduleList() { HttpResponse queryScheduleListResponse = schedulerPage.queryScheduleList(loginUser, projectCode); Assertions.assertTrue(queryScheduleListResponse.getBody().getSuccess()); Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("2019-08-08")); - Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("triggerType=INTERVAL")); + Assertions.assertTrue( + queryScheduleListResponse.getBody().getData().toString().contains("triggerType=INTERVAL")); scheduleId = (int) ((LinkedHashMap) ((List) queryScheduleListResponse.getBody() .getData()).get(0)).get("id"); } @@ -165,7 +166,8 @@ public void testUpdateSchedule() { HttpResponse queryScheduleListResponse = schedulerPage.queryScheduleList(loginUser, projectCode); Assertions.assertTrue(queryScheduleListResponse.getBody().getSuccess()); Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("1996-08-08")); - Assertions.assertTrue(queryScheduleListResponse.getBody().getData().toString().contains("triggerType=INTERVAL")); + Assertions.assertTrue( + queryScheduleListResponse.getBody().getData().toString().contains("triggerType=INTERVAL")); } @Test diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 69a8fbfb07a9..a5154f2ee5b8 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -42,6 +42,9 @@ public class ScheduleParam { @JsonIgnore private boolean missedFirePolicySet; + @JsonIgnore + private boolean triggerTypeSet; + public ScheduleParam() { } @@ -61,6 +64,15 @@ public boolean isMissedFirePolicySet() { return missedFirePolicySet; } + public void setTriggerType(ScheduleTriggerType triggerType) { + this.triggerType = triggerType; + this.triggerTypeSet = true; + } + + public boolean isTriggerTypeSet() { + return triggerTypeSet; + } + @Override public String toString() { return "ScheduleParam{" diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 7442bcf0f107..69a5eacdb2fc 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -176,6 +176,7 @@ public Schedule insertSchedule(User loginUser, } scheduleObj.setCrontab(scheduleParam.getCrontab()); validateMissedFirePolicy(scheduleParam); + validateTriggerType(scheduleParam); scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); scheduleObj.setTriggerType( scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); @@ -426,6 +427,10 @@ private boolean isValidScheduleExpression(ScheduleParam scheduleParam) { ScheduleTriggerType triggerType = scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType(); + return isValidScheduleExpression(scheduleParam, triggerType); + } + + private boolean isValidScheduleExpression(ScheduleParam scheduleParam, ScheduleTriggerType triggerType) { if (triggerType == ScheduleTriggerType.CRON) { return CronUtils.isValidExpression(scheduleParam.getCrontab()); } @@ -587,17 +592,26 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe schedule.setStartTime(scheduleParam.getStartTime()); schedule.setEndTime(scheduleParam.getEndTime()); - if (!isValidScheduleExpression(scheduleParam)) { - log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab()); + ScheduleTriggerType effectiveTriggerType = schedule.getTriggerType() == null + ? ScheduleTriggerType.CRON + : schedule.getTriggerType(); + if (scheduleParam.isTriggerTypeSet() && scheduleParam.getTriggerType() != null) { + effectiveTriggerType = scheduleParam.getTriggerType(); + } + if (!isValidScheduleExpression(scheduleParam, effectiveTriggerType)) { + log.error("Schedule expression validation failure, triggerType:{}, expression:{}.", + effectiveTriggerType, scheduleParam.getCrontab()); throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); validateMissedFirePolicy(scheduleParam); + validateTriggerType(scheduleParam); if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); } - schedule.setTriggerType( - scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType()); + if (scheduleParam.isTriggerTypeSet() && scheduleParam.getTriggerType() != null) { + schedule.setTriggerType(scheduleParam.getTriggerType()); + } schedule.setTimezoneId(scheduleParam.getTimezoneId()); } @@ -633,4 +647,11 @@ private void validateMissedFirePolicy(ScheduleParam scheduleParam) { } } + private void validateTriggerType(ScheduleParam scheduleParam) { + if (scheduleParam.isTriggerTypeSet() && scheduleParam.getTriggerType() == null) { + log.warn("Schedule trigger type is invalid."); + throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, "triggerType"); + } + } + } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java index 3c9a9dedc696..7f077b8264c0 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java @@ -26,6 +26,7 @@ import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.dao.entity.Project; @@ -115,6 +116,17 @@ public void testScheduleParamMissedFirePolicyPresence() { Assertions.assertFalse(withoutPolicy.isMissedFirePolicySet()); Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, withPolicy.getMissedFirePolicy()); Assertions.assertTrue(withPolicy.isMissedFirePolicySet()); + + ScheduleParam withoutTriggerType = JSONUtils.parseObject(scheduleWithoutPolicy, ScheduleParam.class); + ScheduleParam withTriggerType = JSONUtils.parseObject( + scheduleWithoutPolicy.replace("}", ",\"triggerType\":\"INTERVAL\"}"), ScheduleParam.class); + ScheduleParam withNullTriggerType = JSONUtils.parseObject( + scheduleWithoutPolicy.replace("}", ",\"triggerType\":null}"), ScheduleParam.class); + Assertions.assertFalse(withoutTriggerType.isTriggerTypeSet()); + Assertions.assertTrue(withTriggerType.isTriggerTypeSet()); + Assertions.assertEquals(ScheduleTriggerType.INTERVAL, withTriggerType.getTriggerType()); + Assertions.assertTrue(withNullTriggerType.isTriggerTypeSet()); + Assertions.assertNull(withNullTriggerType.getTriggerType()); } @ParameterizedTest @@ -158,6 +170,23 @@ user, projectCode, processDefinitionCode, scheduleExpression(null), WarningType. scheduleCaptor.getValue().getMissedFirePolicy()); } + @Test + public void testInsertScheduleDefaultsTriggerType() { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(this.getProcessDefinition())); + Mockito.when(scheduleDao.queryById(Mockito.anyInt())).thenReturn(new Schedule()); + + schedulerService.insertSchedule( + user, projectCode, processDefinitionCode, scheduleExpression(null), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + ArgumentCaptor scheduleCaptor = ArgumentCaptor.forClass(Schedule.class); + Mockito.verify(scheduleDao).insert(scheduleCaptor.capture()); + Assertions.assertEquals(ScheduleTriggerType.CRON, scheduleCaptor.getValue().getTriggerType()); + } + @Test public void testInsertScheduleRejectsExplicitNullMissedFirePolicy() { assertInsertScheduleRejectsInvalidMissedFirePolicy("null"); @@ -204,6 +233,73 @@ user, projectCode, scheduleId, scheduleExpression(null), WarningType.NONE, 0, Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, schedule.getMissedFirePolicy()); } + @Test + public void testUpdateSchedulePreservesTriggerTypeWhenOmitted() { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + schedule.setTriggerType(ScheduleTriggerType.INTERVAL); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + + schedulerService.updateSchedule( + user, projectCode, scheduleId, + "{\"startTime\":\"2019-12-16 00:00:00\",\"endTime\":\"2019-12-17 00:00:00\"," + + "\"crontab\":\"{\\\"second\\\":10,\\\"repeat\\\":-1}\"," + + "\"timezoneId\":\"Asia/Shanghai\"}", + WarningType.NONE, 0, FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", + environmentCode); + + Assertions.assertEquals(ScheduleTriggerType.INTERVAL, schedule.getTriggerType()); + Assertions.assertEquals("{\"second\":10,\"repeat\":-1}", schedule.getCrontab()); + } + + @Test + public void testUpdateScheduleChangesTriggerTypeWhenProvided() { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + schedule.setTriggerType(ScheduleTriggerType.INTERVAL); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + + schedulerService.updateSchedule( + user, projectCode, scheduleId, + "{\"startTime\":\"2019-12-16 00:00:00\",\"endTime\":\"2019-12-17 00:00:00\"," + + "\"crontab\":\"0 0 6 * * ? *\",\"timezoneId\":\"Asia/Shanghai\"," + + "\"triggerType\":\"CRON\"}", + WarningType.NONE, 0, FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", + environmentCode); + + Assertions.assertEquals(ScheduleTriggerType.CRON, schedule.getTriggerType()); + } + + @Test + public void testUpdateScheduleRejectsExplicitNullTriggerType() { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + schedule.setTriggerType(ScheduleTriggerType.INTERVAL); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + + Assertions.assertThrows(ServiceException.class, () -> schedulerService.updateSchedule( + user, projectCode, scheduleId, + "{\"startTime\":\"2019-12-16 00:00:00\",\"endTime\":\"2019-12-17 00:00:00\"," + + "\"crontab\":\"{\\\"second\\\":10,\\\"repeat\\\":-1}\"," + + "\"timezoneId\":\"Asia/Shanghai\",\"triggerType\":null}", + WarningType.NONE, 0, FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", + environmentCode)); + Assertions.assertEquals(ScheduleTriggerType.INTERVAL, schedule.getTriggerType()); + Mockito.verify(scheduleDao, Mockito.never()).updateById(Mockito.any()); + } + @Test public void testUpdateScheduleRejectsExplicitNullMissedFirePolicy() { assertUpdateScheduleRejectsInvalidMissedFirePolicy("null"); diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java index 8a889fd83f6d..3e7df610a298 100644 --- a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/utils/IntervalSchedule.java @@ -42,13 +42,16 @@ public static IntervalSchedule parse(String expression) { throw new IllegalArgumentException("Interval schedule expression must not be null"); } - int hours = valueOf(values, "hour"); - int minutes = valueOf(values, "minute"); - int seconds = valueOf(values, "second"); - int repeat = valueOf(values, "repeat"); + int hours = valueOf(values, "hour", false); + int minutes = valueOf(values, "minute", false); + int seconds = valueOf(values, "second", false); + int repeat = valueOf(values, "repeat", true); if (hours < 0 || minutes < 0 || seconds < 0 || repeat < -1) { throw new IllegalArgumentException("Interval duration values must not be negative"); } + if (minutes > 59 || seconds > 59) { + throw new IllegalArgumentException("Interval minutes and seconds must be between 0 and 59"); + } long intervalMilliseconds = Math.addExact( Math.addExact(Math.multiplyExact(hours, 3_600_000L), Math.multiplyExact(minutes, 60_000L)), @@ -59,8 +62,11 @@ public static IntervalSchedule parse(String expression) { return new IntervalSchedule(intervalMilliseconds, repeat); } - private static int valueOf(ObjectNode values, String key) { + private static int valueOf(ObjectNode values, String key, boolean required) { if (!values.has(key)) { + if (required) { + throw new IllegalArgumentException("Interval schedule field is required: " + key); + } return 0; } if (!values.get(key).isInt()) { diff --git a/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java b/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java index 4935f77a69e2..1d7e2b98b2c6 100644 --- a/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java +++ b/dolphinscheduler-common/src/test/java/org/apache/dolphinscheduler/common/utils/IntervalScheduleTest.java @@ -35,7 +35,15 @@ void parseIntervalSchedule() { @Test void rejectInvalidIntervalSchedule() { - assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":0}")); - assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":-1}")); + assertThrows(IllegalArgumentException.class, + () -> IntervalSchedule.parse("{\"second\":0}")); + assertThrows(IllegalArgumentException.class, + () -> IntervalSchedule.parse("{\"second\":-1,\"repeat\":-1}")); + assertThrows(IllegalArgumentException.class, + () -> IntervalSchedule.parse("{\"minute\":60,\"repeat\":1}")); + assertThrows(IllegalArgumentException.class, + () -> IntervalSchedule.parse("{\"second\":60,\"repeat\":1}")); + assertThrows(IllegalArgumentException.class, + () -> IntervalSchedule.parse("{\"second\":1}")); } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java index 53f69b3e0d15..f917eb73f919 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzSimpleTriggerBuilderTest.java @@ -19,12 +19,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.dao.entity.Schedule; import java.util.Date; import org.junit.jupiter.api.Test; import org.quartz.SimpleTrigger; +import org.quartz.Trigger; class QuartzSimpleTriggerBuilderTest { @@ -44,5 +46,35 @@ void buildIntervalTrigger() { assertEquals(300_000L, trigger.getRepeatInterval()); assertEquals(3, trigger.getRepeatCount()); + assertEquals(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, trigger.getMisfireInstruction()); + } + + @Test + void buildUnlimitedIntervalTriggerWithMisfirePolicies() { + for (ScheduleMissedFirePolicy policy : ScheduleMissedFirePolicy.values()) { + Schedule schedule = new Schedule(); + schedule.setId(2); + schedule.setCrontab("{\"second\":10,\"repeat\":-1}"); + schedule.setTimezoneId("UTC"); + schedule.setStartTime(new Date(System.currentTimeMillis() + 60_000)); + schedule.setEndTime(new Date(System.currentTimeMillis() + 3_600_000)); + schedule.setMissedFirePolicy(policy); + + SimpleTrigger trigger = QuartzSimpleTriggerBuilder.newBuilder() + .withProjectId(1) + .withSchedule(schedule) + .build(); + + assertEquals(SimpleTrigger.REPEAT_INDEFINITELY, trigger.getRepeatCount()); + int expectedInstruction; + if (policy == ScheduleMissedFirePolicy.SKIP_MISSED) { + expectedInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT; + } else if (policy == ScheduleMissedFirePolicy.FIRE_ONCE_NOW) { + expectedInstruction = SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW; + } else { + expectedInstruction = Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY; + } + assertEquals(expectedInstruction, trigger.getMisfireInstruction()); + } } } From 6dda72b37d4f12979b9c0296b9ba735eed3f1b9f Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Fri, 21 Aug 2026 15:01:26 +0800 Subject: [PATCH 20/20] fix(ui): add spacing for unlimited repeat tip --- .../projects/workflow/definition/components/timing-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index c6791827ddce..277884a212a7 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -496,7 +496,7 @@ export default defineComponent({ {t('project.workflow.execute_time')}
-
+
{t('project.workflow.unlimited_repeat_tip')}