一、设计目标
-
解决目前系统尚未做登陆的安全设置,同时不能新增其他的系统用户。
-
通过对用户权限的管理解决数据源安全管控的问题。
-
支持离线计算分析任务需要的定时任务。
-
需要将开发的项目与开发人员、数据源形成关联。
二、实现步骤
-
对于开发者来说,会有一些业务需求需要通过定时的去执行某些业务逻辑代码去实现,并且要对这些定时任务加以控制,需要的时候才开启,避免浪费资源。在原有的api开发平台是支持java代码编辑,我们可以利用平台原有的优势加以扩张,使其支持对定时任务的处理和控制。
-
对于定时任务的处理我选择在原有的平台上集成Quartz框架来实现对任务的动态管理,Quartz是一个开源的任务调度框架,基于定时、定期的策略来执行任务是它的核心功能。
-
Quartz有3个核心要素:调度器(Scheduler)、任务(Job)、触发器(Trigger)。
-
添加maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
-
添加quartz.properties配置文件,放在resource目录下。
spring.quartz.job-store-type=jdbc spring.quartz.auto-startup=true spring.quartz.jdbc.initialize-schema=embedded spring.quartz.properties.org.quartz.scheduler.instanceName=MyScheduler spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate spring.quartz.properties.org.quartz.jobStore.isClustered=true spring.quartz.properties.org.quartz.jobStore.useProperties=false
-
在application.yml中添加以下属性,确定线程池的数量,可以同时执行多少个定时任务。
org: quartz: threadPool: class: org.quartz.simpl.SimpleThreadPool threadCount: 10 threadPriority: 5 threadsInheritContextClassLoaderOfInitializingThread: true
-
创建job实例工厂(解决Spring注入问题,如果使用默认会导致Spring的@Autowired无法注入问题。)
@Component public class QuartzAdaptableJobFactory extends AdaptableJobFactory { //AutowireCapableBeanFactory 可以将一个对象添加到SpringIOC容器中,并且完成该对象注入 @Autowired private AutowireCapableBeanFactory autowireCapableBeanFactory; /** * 该方法需要将实例化的任务对象手动的添加到springIOC容器中并且完成对象的注入 */ @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Object obj = super.createJobInstance(bundle); // 通过以下方式,解决job任务无法使用spring中的Bean问题 this.autowireCapableBeanFactory.autowireBean(obj); return obj; } }
-
QuartzConfig.java配置类(注册调度工厂)
@Configuration public class QuartzConfig { @Autowired private QuartzAdaptableJobFactory jobFactory; @Bean(name = "SchedulerFactory") public SchedulerFactoryBean schedulerFactoryBean() throws IOException { //获取配置属性 PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); //在quartz.properties中的属性被读取并注入后再初始化对象 propertiesFactoryBean.afterPropertiesSet(); //创建SchedulerFactoryBean SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setQuartzProperties(propertiesFactoryBean.getObject()); factory.setJobFactory(jobFactory); return factory; } /* * 通过SchedulerFactoryBean获取Scheduler的实例 */ @Bean(name = "scheduler") public Scheduler scheduler() throws IOException, SchedulerException { Scheduler scheduler = schedulerFactoryBean().getScheduler(); return scheduler; } }
-
编写Job具体任务类,实现Job接口,重写execute()方法,此方法里编写对定时任务进行具体的逻辑处理代码。
public class QuartzJob implements Job { @Autowired @Lazy private IScriptParse scriptParse; @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 获取传入的参数 String apiStr =(String) context.getJobDetail().getJobDataMap().get("apiInfo"); JSONObject apiJson = JSON.parseObject(apiStr); // JSON字符串转成apiInfo对象 ApiInfo apiInfo= JSONObject.toJavaObject(apiJson, ApiInfo.class); ApiParams apiParams = ApiParams.builder() .header(null) .pathVar(null) .param(null) .body(null) .session(null) .build(); Object value = null; try { // 定时任务执行结果 value = scriptParse.runScript(apiInfo.getScript(),apiInfo,apiParams); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println(value); } }
-
编写Quartz对外服务层代码----开启定时任务(传入需要执行定时任务的ApiInfo对象)
public void addJob(ApiInfo apiInfo) throws Exception { // 启动调度器 scheduler.start(); // 构建job信息 JobDetail jobDetail = JobBuilder .newJob(QuartzJob.class) .withIdentity(apiInfo.getId(), "QuartzJob") .usingJobData("apiInfo", JSONObject.toJSONString(apiInfo)) .build(); // 构建trigger CronTrigger trigger = TriggerBuilder.newTrigger() .withIdentity(apiInfo.getId(), "QuartzJob") // 表达式调度构建器(即任务执行的时间) .withSchedule(CronScheduleBuilder.cronSchedule(apiInfo.getCron())) .build(); // 调度容器设置JobDetail和Trigger scheduler.scheduleJob(jobDetail, trigger); }
关闭定时任务
/** * 关闭job * * @throws SchedulerException * @author qianguojie * @date 2022/3/30. */ public void deleteJob(ApiInfo apiInfo) throws SchedulerException { scheduler.deleteJob(JobKey.jobKey(apiInfo.getId(), "QuartzJob")); }
-
由于我们编写的脚本都是存入Api_info数据表中,当执行定时任务时也就相当于到了特定时间来执行存入Api_info数据表中的脚本,所以需要对Api_info数据表加以改进,添加两个字段(cron表达式和定时任务状态)。
别忘了在实体类中加入这两个属性。
-
对于接口的编辑页面也需要加以改进,增加一个编写的cron表达式的输入框和一个开启\关闭按钮。
-
点击保存,将参数传入后台,根据传入的cron表达式和状态进行判断是否开启此定时任务。
//定时任务判断 if (apiInfo.getCron() != null || apiInfo.getCron() != ""){ if (apiInfo.getCron()==dbInfo.getCron()){ if (apiInfo.getCronStatus()==dbInfo.getCronStatus()){ // cron表达式和状态都未修改,不做处理 }else { if (apiInfo.getCronStatus() == 1 && dbInfo.getCronStatus() ==0){ // 开启定时任务 quartzService.addJob(apiInfo); logger.info("已开启定时任务"); }else if(apiInfo.getCronStatus() == 0 && dbInfo.getCronStatus() == 1){ // 关闭定时任务 quartzService.deleteJob(apiInfo); logger.info("已关闭定时任务"); } } }else { if (apiInfo.getCronStatus() == 1 && dbInfo.getCronStatus() == 1){ // 取消之前的定时任务 quartzService.deleteJob(dbInfo); logger.info("已取消之前的定时任务"); // 开启新的cron定时任务 quartzService.addJob(apiInfo); logger.info("开启新的定时任务"); }else if (apiInfo.getCronStatus() == 1 && dbInfo.getCronStatus() == 0){ // 开启定时任务 quartzService.addJob(apiInfo); logger.info("已开启定时任务"); }else if (apiInfo.getCronStatus() == 0 && dbInfo.getCronStatus() == 1){ // 关闭定时任务 quartzService.deleteJob(apiInfo); logger.info("已关闭定时任务"); } } }
-
点击开启定时任务,后台会调用Quartz服务层启动定时任务的方法,并把ApiInfo对象传过去,等cron表达式的时间一到,就会调用Job任务类QuartzJob的execute()方法,执行脚本。
-
并在后台管理界面编写定时任务列表页面,方便统一查看所有项目中的定时任务。