SpringBoot集成Quartz集群模式

		<!-- quartz定时任务 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency>

单机版本:

SpringBoot集成Quartz动态定时任务_jobgroupname_小百菜的博客-CSDN博客

集群遇到的问题:

需要注意:集群模式下,最优方案是调度器独立为一个项目,然后再去调度执行器(集群),成熟的解决方案比如xxl-job。

 1、同一个任务在不同节点同时执行。
 2、前端请求,会随机选择一个后台节点,不可控。

解决思路:

1、启动任务和关闭任务时,将任务ID插入一个记录表,并返回版本号(自增主键)。
2、修改任务时,需要先将任务关闭,并插入到记录表。
由于启动任务和关闭任务,是从同一个表的自增主键拿的版本号,所以一定就有先后顺序,可以根据这个先后顺序,判断是否执行任务。

当执行任务时,当前任务版本为最新版本,才可以继续往下执行,否则关闭当前节点的任务。

关键代码:

1、任务调度器

package com.study.job;import com.study.bean.Task;
import com.study.dao.DemoDao;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 定时任务* @author lhs* @date 2021年4月30日 下午4:58:24*/
@Component
public class CronScheduleJob {private Logger logger = LoggerFactory.getLogger(CronScheduleJob.class);@Autowiredprivate SchedulerFactoryBean schedulerFactoryBean;@Autowiredprivate DemoDao demoDao;@PostConstruct // 构造函数之后执行public void init() {//Spring容器加载之后,启动以前的定时任务logger.info("启动以前的定时任务");List<Task> list = demoDao.getAllTask();for (Task task : list) {String cron = task.getCron();if (cron != null) {int id = task.getTaskId();//插入任务记录,返回一个版本号int version = demoDao.addTaskRecord(id);//启动任务startTask(id, version, cron);}}logger.info("定时任务启动完成!");}/*** 添加一个定时任务* @param jobName          任务名* @param jobGroupName     任务组名* @param triggerName      触发器名* @param triggerGroupName 触发器组名* @param cron             时间设置,参考quartz说明文档* @param params           任务参数* @author lhs* @date 2021年4月30日 下午4:58:24*/public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, String cron, Map<String, Object> params) throws Exception {// 任务名,任务组,任务执行类JobDetail job = JobBuilder.newJob(ScheduledJob.class).withIdentity(jobName, jobGroupName).build();// 触发器,触发器名,触发器组TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName);// 触发器时间设定CronTrigger trigger = triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();//还可以指定开始执行时间,还可以指定间隔时间,比如cron表达式不能写出每隔50秒执行一次,可以用这种方式。//Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-05-06 17:10:00");//triggerBuilder.startAt(date);// 任务参数job.getJobDataMap().putAll(params);Scheduler scheduler = schedulerFactoryBean.getScheduler();// 调度容器设置Job和Triggerscheduler.scheduleJob(job, trigger);}/*** 执行一次* @param jobName          任务名* @param jobGroupName     任务组名* @param triggerName      触发器名* @param triggerGroupName 触发器组名* @param params           任务参数* @author lhs* @date 2021年4月30日 下午4:58:24*/public void onceJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Map<String, Object> params) throws Exception {// 任务名,任务组,任务执行类JobDetail job = JobBuilder.newJob(ScheduledJob.class).withIdentity(jobName, jobGroupName).build();// 触发器,触发器名,触发器组TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName);// 触发器间隔时间和重复时间设定,10这个参数代表指定一个以秒为单位的重复间隔,0这个参数代表指定触发器将重复的次数,总执行次数=重复次数+1SimpleTrigger trigger = triggerBuilder.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).withRepeatCount(0)).build();//还可以指定开始执行时间,还可以指定间隔时间,比如cron表达式不能写出每隔50秒执行一次,可以用这种方式。//Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-05-06 17:10:00");//SimpleTrigger trigger = triggerBuilder.startAt(date).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).withRepeatCount(2)).build();// 任务参数job.getJobDataMap().putAll(params);Scheduler scheduler = schedulerFactoryBean.getScheduler();// 调度容器设置Job和Triggerscheduler.scheduleJob(job, trigger);}/*** 重复执行* @param jobName          任务名* @param jobGroupName     任务组名* @param triggerName      触发器名* @param triggerGroupName 触发器组名* @param count            重复次数* @param params           任务参数* @author lhs* @date 2021年4月30日 下午4:58:24*/public void repeatJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Integer count, Map<String, Object> params) throws Exception {// 任务名,任务组,任务执行类JobDetail job = JobBuilder.newJob(ScheduledJob.class).withIdentity(jobName, jobGroupName).build();// 触发器,触发器名,触发器组TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName);// 触发器间隔时间和重复时间设定,10这个参数代表指定一个以秒为单位的重复间隔,0这个参数代表指定触发器将重复的次数,总执行次数=重复次数+1// SimpleTrigger trigger = triggerBuilder.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).withRepeatCount(0)).build();// 间隔时间:withIntervalInSeconds,执行次数:withRepeatCountSimpleTrigger trigger = triggerBuilder.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).withRepeatCount(count - 1)).build();//还可以指定开始执行时间,还可以指定间隔时间,比如cron表达式不能写出每隔50秒执行一次,可以用这种方式。//Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-05-06 17:10:00");//SimpleTrigger trigger = triggerBuilder.startAt(date).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).withRepeatCount(2)).build();// 任务参数job.getJobDataMap().putAll(params);Scheduler scheduler = schedulerFactoryBean.getScheduler();// 调度容器设置Job和Triggerscheduler.scheduleJob(job, trigger);}/*** 修改一个任务的触发时间* @param triggerName      触发器名* @param triggerGroupName 触发器组名* @param cron             时间设置,参考quartz说明文档* @author lhs* @date 2021年4月30日 下午4:58:24*/public void updateJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, String cron, Map<String, Object> params) throws Exception {TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);Scheduler scheduler = schedulerFactoryBean.getScheduler();CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);if (trigger == null) {addJob(jobName, jobGroupName, triggerName, triggerGroupName, cron, params);return;}String oldCron = trigger.getCronExpression();if (!oldCron.equalsIgnoreCase(cron)) {// 触发器,触发器名,触发器组TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName);// 触发器时间设定trigger = triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();// 修改一个任务的触发时间scheduler.rescheduleJob(triggerKey, trigger);}}/*** 移除一个任务* @param jobName          任务名* @param jobGroupName     任务组名* @param triggerName      触发器名* @param triggerGroupName 触发器组名* @author lhs* @date 2021年4月30日 下午4:58:24*/public void deleteJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) throws Exception {TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);Scheduler scheduler = schedulerFactoryBean.getScheduler();// 停止触发器scheduler.pauseTrigger(triggerKey);// 移除触发器scheduler.unscheduleJob(triggerKey);// 删除任务scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));}/*** 暂停job* @param jobName      任务名称* @param jobGroupName 任务所在组名称* @author lhs* @date 2021年4月30日 下午4:58:24*/public void pauseJob(String jobName, String jobGroupName) throws Exception {Scheduler scheduler = schedulerFactoryBean.getScheduler();scheduler.pauseJob(JobKey.jobKey(jobName, jobGroupName));}/*** 恢复job* @param jobName      任务名称* @param jobGroupName 任务所在组名称* @author lhs* @date 2021年4月30日 下午4:58:24*/public void resumeJob(String jobName, String jobGroupName) throws Exception {Scheduler scheduler = schedulerFactoryBean.getScheduler();scheduler.resumeJob(JobKey.jobKey(jobName, jobGroupName));}/*** 启动所有定时任务* @author lhs* @date 2021年4月30日 下午4:58:24*/public void startAllJobs() throws Exception {Scheduler scheduler = schedulerFactoryBean.getScheduler();scheduler.start();}/*** 关闭所有定时任务* @author lhs* @date 2021年4月30日 下午4:58:24*/public void shutdownAllJobs() throws Exception {Scheduler scheduler = schedulerFactoryBean.getScheduler();if (!scheduler.isShutdown()) {scheduler.shutdown();}}/*** 获取任务是否存在** <pre>* STATE_BLOCKED 4 阻塞* STATE_COMPLETE 2 完成* STATE_ERROR 3 错误* STATE_NONE -1 不存在* STATE_NORMAL 0 正常* STATE_PAUSED 1 暂停* </pre>* @param triggerName      触发器名* @param triggerGroupName 触发器组名* @author lhs* @date 2021年4月30日 下午4:58:24*/public Boolean isExists(String triggerName, String triggerGroupName) throws Exception {Scheduler scheduler = schedulerFactoryBean.getScheduler();return scheduler.getTriggerState(TriggerKey.triggerKey(triggerName, triggerGroupName)) == Trigger.TriggerState.NONE;}/*** 启动任务* @param id      任务ID* @param version 任务版本号* @param cron    CRON表达式*/public boolean startTask(int id, int version, String cron) {try {//新增定时任务String jobName = "job" + id;String jobGroupName = "group";String triggerName = "trigger" + id;String triggerGroupName = "group";Map<String, Object> params = new HashMap<>();params.put("id", id);params.put("version", version);addJob(jobName, jobGroupName, triggerName, triggerGroupName, cron, params);return true;} catch (Exception e) {logger.error(e.getMessage(), e);}return false;}/*** 停止任务* @param id 任务ID*/public boolean stopTask(int id) {try {//删除定时任务String jobName = "job" + id;String jobGroupName = "group";String triggerName = "trigger" + id;String triggerGroupName = "group";deleteJob(jobName, jobGroupName, triggerName, triggerGroupName);return true;} catch (Exception e) {logger.error(e.getMessage(), e);}return false;}
}

2、任务执行器

package com.study.job;import com.study.service.TaskService;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 定时任务实现类* 注意:该类是非单例的,每次被调用都会生成一个实例来执行。* @author lhs* @date 2021年4月30日 下午4:58:24*/
public class ScheduledJob implements Job {private static Logger logger = LoggerFactory.getLogger(ScheduledJob.class);private static DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");//由于该类实例不是单例,要全局共享使用一个只能使用static来修饰public static ExecutorService threadPool = Executors.newFixedThreadPool(16);//线程池,最大线程数为16@Autowiredprivate TaskService taskService;@Overridepublic void execute(JobExecutionContext jobExecutionContext) {// 传入的参数JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();int taskId = mergedJobDataMap.getInt("id");int version = mergedJobDataMap.getInt("version");long time = jobExecutionContext.getScheduledFireTime().getTime();logger.info("执行任务:{},版本号:{},任务计划时间:{}", taskId, version, Instant.ofEpochMilli(time).atZone(ZoneOffset.ofHours(8)).format(fmt));threadPool.execute(new Runnable() {@Overridepublic void run() {taskService.execTask(taskId, version, time);}});}
}

3、dao层

package com.study.dao;import com.study.bean.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** 示例* @author lhs* @date 2023/7/3 11:08*/
@Repository
public class DemoDao {private Logger logger = LoggerFactory.getLogger(DemoDao.class);@Autowiredprivate JdbcTemplate jdbcTemplate;/*** 查询任务*/public List<Task> getTaskList() {String sql = "select task_id,task_name,cron,status,content from task order by task_id desc";return this.jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Task.class));}/*** 查询任务*/public Task getTaskById(int id) {String sql = "select task_id,task_name,cron,status,content from task where task_id=? ";return this.jdbcTemplate.queryForObject(sql, new Integer[]{id}, new BeanPropertyRowMapper<>(Task.class));}/*** 查询任务* 状态:0未启动,1运行中。*/public List<Task> getAllTask() {String sql = "select task_id,task_name,cron,status,content from task where status=1 ";return this.jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Task.class));}/*** 新增任务*/public int addTask(Task Task) {String sql = "insert into task(task_name,cron,status,content) values (?,?,0,?)";return this.jdbcTemplate.update(sql, new PreparedStatementSetter() {@Overridepublic void setValues(PreparedStatement ps) throws SQLException {ps.setString(1, Task.getTaskName());ps.setString(2, Task.getCron());ps.setString(3, Task.getContent());}});}/*** 修改任务*/public int updateTask(Task Task) {String sql = "update task set task_name=?,cron=?,content=?,status=0 where task_id=? ";return this.jdbcTemplate.update(sql, new PreparedStatementSetter() {@Overridepublic void setValues(PreparedStatement ps) throws SQLException {ps.setString(1, Task.getTaskName());ps.setString(2, Task.getCron());ps.setString(3, Task.getContent());ps.setInt(4, Task.getTaskId());}});}/*** 修改任务状态*/public int updateTaskStatus(int status, int id) {String sql = "update task set status=? where task_id=? ";return this.jdbcTemplate.update(sql, new Integer[]{status, id});}/*** 删除任务*/public int deleteTask(int id) {String sql = "delete from task where task_id=? ";return this.jdbcTemplate.update(sql, new Integer[]{id});}/*** 插入任务记录*/public int addTaskRecord(int taskId) {String sql = "insert into task_record(task_id) values (?)";KeyHolder keyHolder = new GeneratedKeyHolder();this.jdbcTemplate.update(new PreparedStatementCreator() {@Overridepublic PreparedStatement createPreparedStatement(Connection connection) throws SQLException {PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);ps.setInt(1, taskId);return ps;}}, keyHolder);return keyHolder.getKey().intValue();}/*** 查询任务最新版本号*/public int getMaxVersion(int taskId) {String sql = "select ifnull(max(id),0) from task_record where task_id=? ";return jdbcTemplate.queryForObject(sql, new Integer[]{taskId}, Integer.class);}
}

4、业务代码

package com.study.service;import com.study.bean.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** 定时任务业务处理* @Author: lhs* @Date: 2022/6/25 18:18*/
@Service
public class TaskService {private final Logger logger = LoggerFactory.getLogger(TaskService.class);@Autowiredprivate DemoService demoService;/*** 执行任务* @param taskId  任务ID* @param version 任务版本号* @param time    计划执行时间* @author lhs* @date 2022/6/25 21:17*/public void execTask(int taskId, int version, long time) {// String lock = taskId + "_" + time;// 防止同一个任务在多个节同时执行,还可以将lock插入一个唯一索引字段,插入失败表示这个任务已经在其他节点执行。//当前任务版本为最新版本,才可以继续往下执行int maxVersion = demoService.getMaxVersion(taskId);if (version < maxVersion) {//非最新版本,当前版本任务已停止,仅停止当前节点的任务,不能插入任务记录demoService.onlyStopTask(taskId);logger.warn("当前节点任务非最新版本{},停止任务:{},版本号:{}", maxVersion, taskId, version);return;}logger.info("开始执行业务,当前任务:{},版本号:{}", taskId, version);Task task = demoService.getTaskById(taskId);// 取到业务数据String content = task.getContent();// 开始处理业务// ....}}

4、涉及表结构

CREATE TABLE `task` (`task_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '任务ID',`task_name` varchar(200) NOT NULL COMMENT '任务名',`cron` varchar(200) NOT NULL COMMENT 'CRON表达式',`status` int(1) NOT NULL DEFAULT '0' COMMENT '状态:0未启动,1运行中。',`content` varchar(200) NOT NULL COMMENT '业务内容',PRIMARY KEY (`task_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='任务表'
CREATE TABLE `task_record` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '版本号',`task_id` int(11) NOT NULL COMMENT '任务ID',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COMMENT='任务记录表'

 

示例:

源码:https://gitee.com/gloweds/quartz

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/15010.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

09_Linux内核定时器

目录 Linux时间管理和内核定时器简介 内核定时器简介 Linux内核短延时函数 定时器驱动程序编写 编写测试APP 运行测试 Linux时间管理和内核定时器简介 学习过UCOS或FreeRTOS的同学应该知道, UCOS或FreeRTOS是需要一个硬件定时器提供系统时钟,一般使用Systick作为系统时钟…

人工智能(pytorch)搭建模型17-pytorch搭建ReitnNet模型,加载数据进行模型训练与预测

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(pytorch)搭建模型17-pytorch搭建ReitnNet模型&#xff0c;加载数据进行模型训练与预测&#xff0c;RetinaNet 是一种用于目标检测任务的深度学习模型&#xff0c;旨在解决目标检测中存在的困难样本和不平衡…

Maven学习

1.配置环境变量 1.M2_HOME Maven的安装目录 2.修改Path %M2_HOME%\bin2.配置IDEA 配置文件的地址 本地仓库的地址 修改配置文件的路径 修改本地仓库的目录 注意&#xff0c;这里的路径的分隔符必须是/ 配置镜像 <mirror><id>aliyunmaven</id><mi…

在idea中使用Git技术

1.配置git环境 打开idea,点击file->setting->搜索git&#xff0c; 将git的安装路径填写进去 2.去gitee创建一个远程仓库 3.拉入一个.gitignore文件&#xff0c;过滤掉不需要管理的文件 4.在idea进行如下操作 5.选择要提交的内容 目前只是保存在了本地仓库 6.推送到远端…

SEGA: Semantic Guided Attention on Visual Prototype for Few-Shot Learning

方法比较简单&#xff0c;利用语义改进prototype&#xff0c;能促进性提升

十二、Docker Compose 介绍与安装

学习参考&#xff1a;尚硅谷Docker实战教程、Docker官网、其他优秀博客(参考过的在文章最后列出) 目录 前言一、docker compose介绍二、docker compose能干嘛三、docker compose安装与卸载3.1 docker-compose安装3.2 docker-compose卸载 总结 前言 在使用k8s之前&#xff0c;随…

LNMP架构及部署、skyuc电影网站部署

目录 一、安装nginx 1、关闭防火墙 2.创建管理nginx用户 3.配置nginx 4.命令优化 5.创建nginx脚本 二、安装mysql数据库 三、安装PHP 1.上传php安装包 2.上传 zend-loader-hph5.6 3.创建用户 四、LNMP平台中部署skyuc电影网站 1.解压 SKYUC.v3.4.2.srouce 2.创建数据…

光场1.0——非聚焦型光场相机

本文概要 本文讲主要从光场硬件结构设计以及软件处理方式的层面来介绍一下光场的相关内容&#xff0c;关于光场的优势和具体应用点并不在本文的主要范围内。 光场1.0 1. 结构原理说明 首先来介绍一下光场相机&#xff0c;那么什么是光场相机呢&#xff0c;光场相机经历了两…

SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试

下载镜像 CentOS 2 3 4 5 6 等历史老版本下载地址 国内镜像地址_hkNaruto的博客-CSDN博客 下载CentOS 5.0 1-7 ISO文件 注意&#xff1a;尝试过下载DVD版本&#xff0c;速度太慢了。还是通过国内镜像下载这几个iso快。 安装虚拟机 VirtualBox 挂载第一个iso&#xff0c;启动…

突破数据边界,开启探索之旅!隐语开源Meetup一周年专场7月22日上海见

小伙伴们&#xff0c;&#x1f4e2;「隐语开源一周年 Meetup 」即将来袭&#xff01;&#x1f389;在一周年 Meetup 上&#xff0c;不仅会对隐语 1.0 版本进行详解&#xff0c;还有新鲜出炉的隐语 MVP 部署体验包&#xff0c;让你秒变高手&#xff01;更有机会与隐私计算行业的…

DAY37:贪心算法(四)跳跃游戏+跳跃游戏Ⅱ

文章目录 55.跳跃游戏思路完整版总结 45.跳跃游戏Ⅱ思路完整版为什么next覆盖到了终点可以直接break&#xff0c;不用加上最后一步逻辑梳理 总结 55.跳跃游戏 给定一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃…

【LeetCode周赛】2022上半年题目精选集——贪心

文章目录 2136. 全部开花的最早一天&#xff08;贪心&#xff09;⭐⭐⭐⭐⭐思路代码语法解析&#xff1a;Integer[] id IntStream.range(0, plantTime.length).boxed().toArray(Integer[]::new); 2141. 同时运行 N 台电脑的最长时间&#xff08;贪心&#xff09;⭐⭐⭐⭐⭐解…