前后端分离项目中 springboot整合quartz
springboot+ant design pro of vue 前后端分离项目整合quartz,加三个小案例
前言
基于之前做的前后端分离项目,整合quartz。前后端分离项目可以进我的主页查看。
任务调度在很多系统中都会用到,比如定时发送邮件短信,定时生成报表数据等等。如果是一些简单的定时任务,使用 @Schedule 注解就可以完成,但是当应用场景越来越复杂,@Schedule 已经不能满足需要,所以需要Quartz
上场,Quartz
目前是 Java
体系中最完善的定时方案。
在代码中实现了三个接口,分别是使用simpletrigger和crontrigger,然后基于crontrigger提供一个公共接口,所有前端都调用这个接口,按照格式传递数据给后台,后台实现公共的job方法就可以。
一、Quartz中API基本概念
Scheduler: Quartz
中的任务调度器,相当于一个容器,里边装着 jobdetail和trigger。容器中可以有多个jobdetail和trigger,但是各自的组及名称必须唯一,因为组及名称是Scheduler
查找定位容器中某一对象的依据- Trigger:触发器,表示什么时候去触发任务,有多个实现类,常用的是SimpleTrigger和CronTrigger。SimpleTrigger一般用于定义简单的触发规则,比如从2022年10月19日开始,到10月31日结束,每隔24小时触发一次任务,这样的规则一般使用SimpleTrigger就可以实现。如果是下边这种复杂的场景,比如 2022年10月19日开始,到12月31日结束,每个月最后一天触发一次任务,只能使用CronTrigger来实现
- Job:表示一个任务接口,里边只有一个方法,表示执行逻辑
void execute(JobExecutionContext context)
- JobDetail:任务执行实例,表示一个具体的可执行的调度程序,包括了任务的唯一标识和具体要执行的任务,
二、整合
1.导入包
新增quartz和fastjson包,fastjson用于json字符串和对象转换
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- druid依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.0</version>
</dependency>
<!-- mybatisPlus 核心库 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.iherus.shiro</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.10</version>
</dependency>
</dependencies>
2.application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/my?serverTimezone=GMT%2B8&useSSL=FALSE
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
quartz:
#数据库方式
job-store-type: jdbc
#相关属性配置
properties:
org:
quartz:
scheduler:
instanceName: DefaultQuartzScheduler
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_
#自定义的数据源别名,与最下方一致
dataSource: qzDB
#与cron的调度有关,忽略重启期间的任务
misfireThreshold: 1000
#设置为true开启集群
isClustered: false
clusterCheckinInterval: 10000
#如果开启,会没法给任务传参数
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
dataSource:
qzDB:
#quartz默认使用c3p0连接池,修改成 Druid,需要自己提供链接类
connectionProvider.class: com.han.myapp.config.quartz.DruidConnectionProvider
maxConnection: 10
driver: com.mysql.cj.jdbc.Driver
URL: jdbc:mysql://127.0.0.1:3306/my?serverTimezone=GMT%2B8&useSSL=FALSE
user: root
password: root
redis:
port: 6379
host: localhost
database: 0
keyPrefix: 'shiro:redis'
server:
port: 9001
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.han.myapp.model
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
shiro:
salt: 'han'
hashIterations: 1024
3.QuartzConfig.java
package com.han.myapp.config.quartz;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import java.io.IOException;
@Configuration
public class QuartzConfig {
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Bean
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean.getScheduler();
}
}
4.DruidConnectionProvider 连接类
package com.han.myapp.config.quartz;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.quartz.SchedulerException;
import org.quartz.utils.ConnectionProvider;
import java.sql.Connection;
import java.sql.SQLException;
@Data
public class DruidConnectionProvider implements ConnectionProvider {
//常量配置,需提供set方法,Quartz框架自动注入值。(这里用了Lombok的@Data,会自动生成getter和setter)
/**
* JDBC驱动
*/
public String driver;
/**
* JDBC连接串,必须是URL
*/
public String URL;
/**
* 数据库用户名,不是username
*/
public String user;
/**
* 数据库用户密码
*/
public String password;
/**
* 数据库最大连接数
*/
public int maxConnection;
/**
* 数据库SQL查询每次连接返回执行到连接池,以确保它仍然是有效的。
*/
public String validationQuery;
private boolean validateOnCheckout;
private int idleConnectionValidationSeconds;
public String maxCachedStatementsPerConnection;
private String discardIdleConnectionsSeconds;
public static final int DEFAULT_DB_MAX_CONNECTIONS = 10;
public static final int DEFAULT_DB_MAX_CACHED_STATEMENTS_PER_CONNECTION = 120;
/**
* Druid连接池
*/
private DruidDataSource datasource;
//接口实现
@Override
public Connection getConnection() throws SQLException {
return datasource.getConnection();
}
@Override
public void shutdown() {
datasource.close();
}
@Override
public void initialize() throws SQLException {
if (this.URL == null) {
throw new SQLException("DBPool could not be created: DB URL cannot be null");
}
if (this.driver == null) {
throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
}
if (this.maxConnection < 0) {
throw new SQLException("DBPool maxConnections could not be created: Max connections must be greater than zero!");
}
datasource = new DruidDataSource();
try {
datasource.setDriverClassName(this.driver);
} catch (Exception e) {
try {
throw new SchedulerException("Problem setting driver class name on datasource: " + e.getMessage(), e);
} catch (SchedulerException ignored) {
}
}
datasource.setUrl(this.URL);
datasource.setUsername(this.user);
datasource.setPassword(this.password);
datasource.setMaxActive(this.maxConnection);
datasource.setMinIdle(1);
datasource.setMaxWait(0);
datasource.setMaxPoolPreparedStatementPerConnectionSize(DEFAULT_DB_MAX_CONNECTIONS);
if (this.validationQuery != null) {
datasource.setValidationQuery(this.validationQuery);
if (!this.validateOnCheckout) {
datasource.setTestOnReturn(true);
} else {
datasource.setTestOnBorrow(true);
}
datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
}
}
}
5.sql,基本sql在org.quartz.impl.jdbcjobstore包下
另外我们需要一张额外的表记录任务调度信息,字段可以根据自己需要定义
-- ----------------------------
-- Table structure for `scheduler_data`
-- ----------------------------
DROP TABLE IF EXISTS `scheduler_data`;
CREATE TABLE `scheduler_data` (
`id` varchar(40) NOT NULL,
`taskName` varchar(100) NOT NULL,
`cron` varchar(50) NOT NULL,
`type` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
6.SchedulerUtilService 生成调度任务方法接口
package com.han.myapp.service;
import org.quartz.JobDataMap;
/**
* @author han
*/
public interface SchedulerUtilService {
/**
* 添加定时任务Job
* @param jobName
* @param jobGroup
* @param jobClassName
* @param triggerName
* @param triggerGroup
* @param cronExpression
*/
void addSchedule(String jobName, String jobGroup, String jobClassName, String triggerName, String triggerGroup, String cronExpression);
/**
*
* @param jobName
* @param jobGroup
* @param jobClassName
* @param triggerName
* @param triggerGroup
* @param cronExpression
* @param data 对象转为 jsonString
*/
void addSchedule(String jobName, String jobGroup, String jobClassName, String triggerName, String triggerGroup, String cronExpression,String data);
/**
*
* @param jobName
* @param jobGroup
* @param jobClassName
* @param triggerName
* @param triggerGroup
* @param cronExpression
* @param jobDataMap
*/
void addSchedule(String jobName, String jobGroup, String jobClassName, String triggerName, String triggerGroup, String cronExpression, JobDataMap jobDataMap);
/**
* 暂停定时任务
* @param jobName
* @param jobGroup
*/
void pauseSchedule(String jobName, String jobGroup);
/**
* 重启定时任务
* @param jobName
* @param jobGroup
*/
void resumeSchedule(String jobName, String jobGroup);
/**
* 删除定时任务
* @param jobName
* @param jobGroup
*/
void deleteSchedule(String jobName, String jobGroup);
}
生成调度任务实现类
package com.han.myapp.service.impl;
import com.han.myapp.service.SchedulerUtilService;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;
@Service
public class SchedulerUtilServiceImpl implements SchedulerUtilService {
@Autowired
private Scheduler scheduler;
@Override
public void addSchedule(String jobName, String jobGroup, String jobClassName, String triggerName, String triggerGroup, String cronExpression) {
try {
Class className = Class.forName(jobClassName);
QuartzJobBean jobBean = (QuartzJobBean) className.newInstance();
JobDetail jobDetail = JobBuilder.newJob(jobBean.getClass())
.withIdentity(jobName, jobGroup)
.storeDurably()
.build();
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName, triggerGroup)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
} catch (SchedulerException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public void addSchedule(String jobName, String jobGroup, String jobClassName, String triggerName, String triggerGroup, String cronExpression, String data) {
try {
Class className = Class.forName(jobClassName);
QuartzJobBean jobBean = (QuartzJobBean) className.newInstance();
JobDetail jobDetail = JobBuilder.newJob(jobBean.getClass())
.withIdentity(jobName, jobGroup)
.usingJobData("data",data)
.storeDurably()
.build();
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName, triggerGroup)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
} catch (SchedulerException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public void addSchedule(String jobName, String jobGroup, String jobClassName, String triggerName, String triggerGroup, String cronExpression, JobDataMap jobDataMap) {
try {
Class className = Class.forName(jobClassName);
Job jobBean = (Job) className.newInstance();
JobDetail jobDetail = JobBuilder.newJob(jobBean.getClass())
.withIdentity(jobName, jobGroup)
.usingJobData(jobDataMap)
.storeDurably()
.build();
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName, triggerGroup)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
} catch (SchedulerException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public void pauseSchedule(String jobName, String jobGroup) {
try {
scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));
} catch (SchedulerException e) {
e.printStackTrace();
}
}
@Override
public void resumeSchedule(String jobName, String jobGroup) {
try {
scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup));
} catch (SchedulerException e) {
e.printStackTrace();
}
}
@Override
public void deleteSchedule(String jobName, String jobGroup) {
try {
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
7.任务调度信息
SchedulerData.java
package com.han.myapp.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
public class SchedulerData implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
@TableField("taskName")
private String taskName;
private String cron;
private String type;
public enum TYPE{
EMAILJOB(1,"emailServiceImpl"),
SMSJOB(2,"smsServiceImpl");
public int code;
public String className;
TYPE(int code,String className){
this.code = code;
this.className = className;
}
public String getClassName(){return this.className;}
public static TYPE get(Integer code) {
if (code == null) {return null;}
for (TYPE value : values()) {
if (value.code == code) {
return value;
}
}
return null;
}
}
}
mapper是逆向生成的,省略。。
vo
EmailVo 用于接收前台参数
package com.han.myapp.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class EmailVo implements Serializable {
private List<String> names;
private String content;
private String taskName;
private String cron;
}
job emailJob
package com.han.myapp.job;
import com.alibaba.fastjson.JSON;
import com.han.myapp.vo.EmailVo;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Slf4j
public class EmailJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd hh:mm:ss");
JobDataMap mergedJobDataMap = context.getMergedJobDataMap();
String data = mergedJobDataMap.getString("data");
EmailVo emailVo = JSON.parseObject(data, EmailVo.class);
/*EmailVo emailVo = (EmailVo) mergedJobDataMap.get("data");*/
List<String> names = emailVo.getNames();
for (String name : names) {
log.info(dateTimeFormatter.format(now)+"向"+name+"发送Email:"+emailVo.getContent());
}
}
}
生成调度任务 controller service impl
controller
package com.han.myapp.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.han.myapp.common.Result;
import com.han.myapp.model.SchedulerData;
import com.han.myapp.service.ISchedulerDataService;
import com.han.myapp.vo.EmailVo;
import com.han.myapp.vo.SchedulerDataVo;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/scheduler/")
public class SchedulerController {
@Autowired
private ISchedulerDataService schedulerDataService;
@GetMapping("jobList")
public Result jobList(Page<SchedulerData> page,SchedulerData scheduler){
LambdaQueryWrapper<SchedulerData> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(StringUtils.hasLength(scheduler.getTaskName()),SchedulerData::getTaskName,scheduler.getTaskName());
Page<SchedulerData> page1 = schedulerDataService.page(page, queryWrapper);
return new Result().success().setData(page1);
}
/**
* 测试 simpleTrigger
* @return
*/
@PostMapping("sendEmail")
public Result sendEmail(@RequestBody EmailVo emailVo) throws SchedulerException {
schedulerDataService.sendEmail(emailVo);
return new Result().success();
}
@PostMapping("cronSendEmail")
public Result cronSendEmail(@RequestBody EmailVo emailVo) {
schedulerDataService.cronSendEmail(emailVo);
return new Result().success();
}
}
service
package com.han.myapp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.han.myapp.model.SchedulerData;
import com.han.myapp.vo.EmailVo;
import com.han.myapp.vo.SchedulerDataVo;
import org.quartz.SchedulerException;
public interface ISchedulerDataService extends IService<SchedulerData> {
/**
* 发送
* @param emailVo
*/
void sendEmail(EmailVo emailVo) throws SchedulerException;
/**
* cron 表达式发送
* @param emailVo
*/
void cronSendEmail(EmailVo emailVo);
}
impl
package com.han.myapp.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.han.myapp.job.EmailJob;
import com.han.myapp.mapper.SchedulerDataMapper;
import com.han.myapp.model.SchedulerData;
import com.han.myapp.service.ISchedulerDataService;
import com.han.myapp.service.SchedulerUtilService;
import com.han.myapp.vo.EmailVo;
import com.han.myapp.vo.SchedulerDataVo;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Service
public class SchedulerDataServiceImpl extends ServiceImpl<SchedulerDataMapper, SchedulerData> implements ISchedulerDataService {
@Autowired
private Scheduler scheduler;
@Autowired
private SchedulerUtilService schedulerUtilService;
@Override
public void sendEmail(EmailVo emailVo) throws SchedulerException {
Date date = new Date();
date.setTime(1666656000000L);
String s = JSON.toJSONString(emailVo);
JobDetail jobDetail = JobBuilder.newJob(EmailJob.class).usingJobData("data", s).build();
SimpleTrigger simpleTrigger =
TriggerBuilder.newTrigger().
startNow().endAt(date).withSchedule(
SimpleScheduleBuilder.
simpleSchedule().
withIntervalInSeconds(10).
withRepeatCount(10)).
withIdentity("name","group").build();
scheduler.scheduleJob(jobDetail,simpleTrigger);
scheduler.start();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cronSendEmail(EmailVo emailVo) {
SchedulerData schedulerData = new SchedulerData();
schedulerData.setTaskName(emailVo.getTaskName());
schedulerData.setCron(emailVo.getCron());
schedulerData.setType("emailJob.class");
save(schedulerData);
JobDataMap jobDataMap = new JobDataMap();
String s = JSON.toJSONString(emailVo);
jobDataMap.put("data",s);
//加入到定时任务
schedulerUtilService.addSchedule(schedulerData.getId(),
"email","com.han.myapp.job.EmailJob",
schedulerData.getId(),"email", emailVo.getCron(),jobDataMap);
}
}
8. 前台
router.config
{
path: '/timeTask',
name: 'timeTask',
component: RouteView,
meta: {
title: '定时任务',
hidden: true,
keepAlive: true,
redirect:"/timeTask/manger",
},
children: [
{
path: '/timeTask/manger',
name: 'timeTask-manger1',
key:'timeTask-manger1',
component: () => import('@/views/page/timeTask/TimeTaskList.vue'),
meta: {
title: '定时任务管理',
hidden: false,
keepAlive: false,
}
},
{
path: '/timeTask/simpleTrigger',
name: 'timeTask-simpleTrigger',
key:'timeTask-simpleTrigger',
component: () => import('@/views/page/timeTask/SimpleTrigger.vue'),
meta: {
title: 'simpleTrigger',
hidden: false,
keepAlive: false,
}
},
{
path: '/timeTask/CronTrigger',
name: 'timeTask-CronTrigger',
key:'timeTask-CronTrigger',
component: () => import('@/views/page/timeTask/CronTrigger.vue'),
meta: {
title: 'CronTrigger',
hidden: false,
keepAlive: false,
}
},
{
path: '/timeTask/pubCron',
name: 'timeTask-pubCron',
key:'timeTask-pubCron',
component: () => import('@/views/page/timeTask/CronTrigger.vue'),
meta: {
title: 'CronTrigger',
hidden: false,
keepAlive: false,
}
},
{
path: '/timeTask/pubEmailCron',
name: 'timeTask-pubEmailCron',
key:'timeTask-pubEmailCron',
component: () => import('@/views/page/timeTask/PubSchedulerEmail.vue'),
meta: {
title: 'pubEmailCron',
hidden: false,
keepAlive: false,
}
},
{
path: '/timeTask/pubSmsCron',
name: 'timeTask-pubSmsCron',
key:'timeTask-pubSmsCron',
component: () => import('@/views/page/timeTask/PubSchedulerSms.vue'),
meta: {
title: 'pubSmsCron',
hidden: false,
keepAlive: false,
}
}
]
}
TimeTaskList.vue
<template>
<page-header-wrapper>
<a-card :bordered="false">
<div class="table-page-search-wrapper">
<a-form layout="inline">
<a-row :gutter="48">
<a-col :md=" 4|| 24" :sm="24">
<a-form-model-item label="任务名称">
<a-input v-model="queryParam.taskName"/>
</a-form-model-item>
</a-col>
<a-col :md=" 4|| 24" :sm="24">
<span class="table-page-search-submitButtons" :style=" { float: 'right', overflow: 'hidden' } || {} ">
<a-button type="primary"@click="$refs.table.refresh(true)">查询</a-button>
<a-button style="margin-left: 8px" @click="() => this.queryParam = {}">重置</a-button>
</span>
</a-col>
</a-row>
</a-form>
</div>
<div class="table-operator">
<a-button v-if="$auth('project:apply')" type="primary" @click="openDrawer('新增',null)">新增任务</a-button>
</div>
<s-table
ref="table"
size="default"
rowKey="id"
:columns="columns"
:data="loadData"
:alert="true"
:rowSelection="rowSelection"
showPagination="auto">
<span slot="serial" slot-scope="text, record, index">
{{ index + 1 }}
</span>
<span slot="action" slot-scope="text, record">
<template>
<a-space >
<a-button type="primary" size="small">button</a-button>
</a-space>
</template>
</span>
</s-table>
</a-card>
<a-drawer
:visible="drawer.visible"
:title="drawer.title"
placement="right"
:width="500"
:destroyOnClose="true"
@close="drawer.visible=false"
>
<AddorEdit
:record="record"
:title="drawer.title"
@closeDrawer="closeDrawer"
@refrshTable="refrshTable"
></AddorEdit>
</a-drawer>
</page-header-wrapper>
</template>
<script>
import { STable, Ellipsis } from '@/components'
import AddorEdit from '@/views/page/timeTask/AddorEdit'
import {jobList} from "@/api/task";
const columns = [
{
title: '#',
scopedSlots: { customRender: 'serial' }
},
{
title: '任务名称',
dataIndex: 'taskName'
},
{
title: 'cron',
dataIndex: 'cron'
},
{
title: '类型',
dataIndex: 'type',
width: '150px',
},
{
title: '操作',
dataIndex: 'action',
width: '150px',
scopedSlots: { customRender: 'action' }
},
]
export default {
name: 'ProjectList',
components: {
AddorEdit,
STable
},
data () {
return {
columns: columns,
// create model
visible: false,
confirmLoading: false,
// 查询参数
queryParam: {},
selectedRowKeys: [],
selectedRows: [],
addOrEdit: '新增',
drawer: {
visible: false,
title: ''
},
record: {},
projectData: [],
queryType:'',
// 加载数据方法 必须为 Promise 对象
loadData: parameter => {
const requestParameters = Object.assign({}, parameter, this.queryParam)
return jobList(requestParameters).then(res => {
console.log(res)
return res
})
},
}
},
created () {
},
computed: {
rowSelection () {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.onSelectChange
}
},
},
methods: {
openDrawer (title, row) {
this.drawer.visible = true
this.drawer.title = title
this.record = row
},
closeDrawer () {
this.drawer.visible = false
},
refrshTable () {
this.$refs.table.refresh()
},
onSelectChange (selectedRowKeys, selectedRows) {
this.selectedRowKeys = selectedRowKeys
this.selectedRows = selectedRows
},
resetSearchForm () {
this.queryParam = {
}
},
},
}
</script>
simpleTrigger
<template>
<page-header-wrapper>
<a-card :body-style="{padding: '24px 32px'}" :bordered="false">
<a-form-model ref="form" :model="triggerForm" v-bind="formLayout">
<a-form-model-item label="发送给" prop="names">
<a-select
mode="multiple"
placeholder="Please select"
style="width: 200px"
@change="handleChange"
>
<a-select-option v-for="o in optionData" :key="o.value">
{{o.value}}
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="发送内容" prop="triggerForm">
<a-textarea v-model="triggerForm.content" :maxLenght="1500" :autoSize="{minRows:3,maxRows:7}" ></a-textarea>
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="sendEmail">
发送
</a-button>
</a-form-model-item>
</a-form-model>
</a-card>
</page-header-wrapper>
</template>
<script>
import {sendEmail} from "@/api/task";
export default {
name: 'SimpleTrigger',
components: {
},
data () {
return {
triggerForm:{
names:[],
content:''
},
formLayout: {
labelCol: { span: 4 },
wrapperCol: {offset:1,span: 14 }
},
optionData:[
{label:'张三',value:'张三'},
{label:'李四',value:'李四'},
{label:'王五',value:'王五'},
{label:'钱六',value:'钱六'},
]
}
},
created () {
},
computed: {
},
methods: {
handleChange(value) {
this.triggerForm.names = value
},
sendEmail(){
sendEmail(this.triggerForm).then(res=>{
this.$message.success("发送成功")
})
}
},
watch:{
}
}
</script>
CronTrigger
<template>
<page-header-wrapper>
<a-card :body-style="{padding: '24px 32px'}" :bordered="false">
<a-form-model ref="form" :model="form" v-bind="formLayout">
<a-form-model-item label="任务名称" prop="taskName">
<a-input v-model="form.taskName"/>
</a-form-model-item>
<a-form-model-item label="生成规律" prop="cron">
<!-- <a-cron ref="innerVueCron" v-decorator="['cronExpression', {'initialValue':'0 0 0 2 * ?',rules:
[{ required: true, message: '请输入cron表达式!' }]}]" @change="setCorn"></a-cron>-->
<j-cron ref="innerVueCron" v-model="form.cron"
></j-cron>
</a-form-model-item>
<a-form-model-item label="发送给" prop="names">
<a-select
mode="multiple"
placeholder="Please select"
style="width: 200px"
@change="handleChange"
>
<a-select-option v-for="o in optionData" :key="o.value">
{{o.value}}
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="发送内容" prop="triggerForm">
<a-textarea v-model="form.content" :maxLenght="1500" :autoSize="{minRows:3,maxRows:7}" ></a-textarea>
</a-form-model-item>
</a-form-model>
<footer-tool-bar :style="{width:'100%'}">
<a-space>
<a-button type="primary" @click="cronSendEmail()">保存</a-button>
</a-space>
</footer-tool-bar>
</a-card>
</page-header-wrapper>
</template>
<script>
import JCron from "@/views/Jcron/JCron";
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
import {cronSendEmail} from "@/api/task";
export default {
name: 'CronTrigger',
components: {
JCron,
FooterToolBar
},
data () {
return {
formLayout: {
labelCol: { span: 4 },
wrapperCol: { span: 14 }
},
form:{
taskName:'',
cron:''
},
optionData:[
{label:'张三',value:'张三'},
{label:'李四',value:'李四'},
{label:'王五',value:'王五'},
{label:'钱六',value:'钱六'},
],
}
},
created () {
},
computed: {
},
methods: {
setCorn(data){
if(data !== undefined){
this.$nextTick(() => {
this.form.cron=data
})
}
},
handleChange(value) {
this.form.names = value
},
cronSendEmail(){
cronSendEmail(this.form).then(res=>{
this.$message.success("发送成功")
})
}
},
watch:{
}
}
</script>
task.js
import request from '@/utils/request'
export function addJob(data) {
return request({
url: '/scheduler/addJob',
method: 'post',
data: data
})
}
export function jobList(params) {
return request({
url: '/scheduler/jobList',
method: 'get',
params: params
})
}
export function sendEmail(data) {
return request({
url: '/scheduler/sendEmail',
method: 'post',
data: data
})
}
export function cronSendEmail(data) {
return request({
url: '/scheduler/cronSendEmail',
method: 'post',
data: data
})
}
export function pubScheduler(data) {
return request({
url: '/scheduler/pubScheduler',
method: 'post',
data: data
})
}
cron 效果
根据cron表达式 向这几个人发送消息,看一下后台控制台
9.cron公共接口方式
上边的这两种方式都比较简单,但是如果在系统中很多模块要用到任务调度的话,每个模块都要编写一遍 controller service,所以编写了一种通用的方式,具体代码如下
controller
@PostMapping("pubScheduler")
public Result pubScheduler(@RequestBody SchedulerDataVo schedulerDataVo) {
schedulerDataService.pubScheduler(schedulerDataVo);
return new Result().success();
}
service
/**
* 公共 定时任务
* @param schedulerDataVo
*/
void pubScheduler(SchedulerDataVo schedulerDataVo);
impl
@Override
public void pubScheduler(SchedulerDataVo schedulerDataVo) {
//type 转换成 对应的class
String type = schedulerDataVo.getType();
String className = SchedulerData.TYPE.get(Integer.parseInt(type)).className;
schedulerDataVo.setType(className);
save(schedulerDataVo);
Map<String, Object> data = schedulerDataVo.getData();
JobDataMap jobDataMap = new JobDataMap();
String s = JSON.toJSONString(data);
jobDataMap.put("data",s);
jobDataMap.put("className",className);
//加入到定时任务
schedulerUtilService.addSchedule(schedulerDataVo.getId(),
"pubCronScheduler","com.han.myapp.job.SchedulerJob",
schedulerDataVo.getId(),"email", schedulerDataVo.getCron(),jobDataMap);
}
job SchedulerJob,job中没有具体执行逻辑,job中根据对应的类型,决定执行哪个接口实现类的方法
package com.han.myapp.job;
import com.han.myapp.service.PubScheduler;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map;
@Slf4j
public class SchedulerJob implements Job {
@Autowired
private Map<String, PubScheduler> pubSchedulerMap;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
String className = mergedJobDataMap.getString("className");
String data = mergedJobDataMap.getString("data");
log.info(pubSchedulerMap.toString());
PubScheduler pubScheduler = pubSchedulerMap.get(className);
pubScheduler.doTask(data);
}
}
PubScheduler统一接口
package com.han.myapp.service;
/**
* 任务 公共接口,没有具体逻辑,只决定调用哪个接口
* 需要的地方直接实现该接口就可以
*/
public interface PubScheduler {
void doTask(String data);
}
接口实现类,后续其他模块需要调度任务,需要实现该接口
测试代码
emailServiceImpl
package com.han.myapp.service.impl;
import com.alibaba.fastjson.JSON;
import com.han.myapp.job.SchedulerJob;
import com.han.myapp.service.PubScheduler;
import com.han.myapp.vo.EmailVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Service
@Slf4j
public class EmailServiceImpl implements PubScheduler {
public void SendEmail(EmailVo emailVo){
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd hh:mm:ss");
List<String> names = emailVo.getNames();
for (String name : names) {
log.info(dateTimeFormatter.format(now)+"向"+name+"发送Email:"+emailVo.getContent());
}
}
@Override
public void doTask(String data) {
EmailVo emailVo = JSON.parseObject(data, EmailVo.class);
SendEmail(emailVo);
}
}
SmsServiceImpl
package com.han.myapp.service.impl;
import com.alibaba.fastjson2.JSON;
import com.han.myapp.service.PubScheduler;
import com.han.myapp.vo.SmsVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Slf4j
@Service
public class SmsServiceImpl implements PubScheduler {
public void SendSms(SmsVo smsVo){
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd hh:mm:ss");
List<String> phoneNumbers = smsVo.getPhoneNumber();
for (String phoneNumber : phoneNumbers) {
log.info(dateTimeFormatter.format(now)+"向"+phoneNumber+"发送短信:"+smsVo.getContent());
}
}
@Override
public void doTask(String data) {
SmsVo smsVo = JSON.parseObject(data, SmsVo.class);
SendSms(smsVo);
}
}
前台代码
router.config
{
path: '/timeTask/pubEmailCron',
name: 'timeTask-pubEmailCron',
key:'timeTask-pubEmailCron',
component: () => import('@/views/page/timeTask/PubSchedulerEmail.vue'),
meta: {
title: 'pubEmailCron',
hidden: false,
keepAlive: false,
}
},
{
path: '/timeTask/pubSmsCron',
name: 'timeTask-pubSmsCron',
key:'timeTask-pubSmsCron',
component: () => import('@/views/page/timeTask/PubSchedulerSms.vue'),
meta: {
title: 'pubSmsCron',
hidden: false,
keepAlive: false,
}
}
PubSchedulerEmail
<template>
<page-header-wrapper>
<a-card :body-style="{padding: '24px 32px'}" :bordered="false">
<a-form-model ref="form" :model="form" v-bind="formLayout">
<a-form-model-item label="任务名称" prop="taskName">
<a-input v-model="form.taskName"/>
</a-form-model-item>
<a-form-model-item label="生成规律" prop="cron">
<!-- <a-cron ref="innerVueCron" v-decorator="['cronExpression', {'initialValue':'0 0 0 2 * ?',rules:
[{ required: true, message: '请输入cron表达式!' }]}]" @change="setCorn"></a-cron>-->
<j-cron ref="innerVueCron" v-model="form.cron"
></j-cron>
</a-form-model-item>
<a-form-model-item label="发送给" prop="names">
<a-select
placeholder="Please select"
mode="multiple"
style="width: 200px"
@change="handleChange"
>
<a-select-option value="张三">
张三
</a-select-option>
<a-select-option value="李四">
李四
</a-select-option>
<a-select-option value="王五">
王五
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="发送内容" prop="triggerForm">
<a-textarea v-model="form.content" :maxLenght="1500" :autoSize="{minRows:3,maxRows:7}" ></a-textarea>
</a-form-model-item>
</a-form-model>
<footer-tool-bar :style="{width:'100%'}">
<a-space>
<a-button type="primary" @click="pubScheduler()">保存</a-button>
</a-space>
</footer-tool-bar>
</a-card>
</page-header-wrapper>
</template>
<script>
import JCron from "@/views/Jcron/JCron";
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
import {cronSendEmail, pubScheduler} from "@/api/task";
export default {
name: 'CronTrigger',
components: {
JCron,
FooterToolBar
},
data () {
return {
formLayout: {
labelCol: { span: 4 },
wrapperCol: { span: 14 }
},
form:{
taskName:'',
cron:''
},
optionData:[
{label:'张三',value:'张三'},
{label:'李四',value:'李四'},
{label:'王五',value:'王五'},
{label:'钱六',value:'钱六'},
],
}
},
created () {
},
computed: {
},
methods: {
setCorn(data){
if(data !== undefined){
this.$nextTick(() => {
this.form.cron=data
})
}
},
handleChange(value) {
this.form.names = value
},
pubScheduler(){
this.form.type=1
this.form.data={
names:this.form.names,
content:this.form.content
}
pubScheduler(this.form).then(res=>{
this.$message.success("发送成功")
})
}
},
watch:{
}
}
</script>
PubSchedulerSms
<template>
<page-header-wrapper>
<a-card :body-style="{padding: '24px 32px'}" :bordered="false">
<a-form-model ref="form" :model="form" v-bind="formLayout">
<a-form-model-item label="任务名称" prop="taskName">
<a-input v-model="form.taskName"/>
</a-form-model-item>
<a-form-model-item label="生成规律" prop="cron">
<!-- <a-cron ref="innerVueCron" v-decorator="['cronExpression', {'initialValue':'0 0 0 2 * ?',rules:
[{ required: true, message: '请输入cron表达式!' }]}]" @change="setCorn"></a-cron>-->
<j-cron ref="innerVueCron" v-model="form.cron"
></j-cron>
</a-form-model-item>
<a-form-model-item label="发送给" prop="phoneNumber">
<a-select
placeholder="Please select"
mode="multiple"
style="width: 200px"
@change="handleChange"
>
<a-select-option value="1567777">
1567777
</a-select-option>
<a-select-option value="15677799">
15677799
</a-select-option>
<a-select-option value="1567779911">
1567779911
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item label="发送内容" prop="triggerForm">
<a-textarea v-model="form.content" :maxLenght="1500" :autoSize="{minRows:3,maxRows:7}" ></a-textarea>
</a-form-model-item>
</a-form-model>
<footer-tool-bar :style="{width:'100%'}">
<a-space>
<a-button type="primary" @click="pubScheduler()">保存</a-button>
</a-space>
</footer-tool-bar>
</a-card>
</page-header-wrapper>
</template>
<script>
import JCron from "@/views/Jcron/JCron";
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
import { pubScheduler} from "@/api/task";
export default {
name: 'CronTrigger',
components: {
JCron,
FooterToolBar
},
data () {
return {
formLayout: {
labelCol: { span: 4 },
wrapperCol: { span: 14 }
},
form:{
taskName:'',
cron:'',
phoneNumber:[]
},
}
},
created () {
},
computed: {
},
methods: {
setCorn(data){
if(data !== undefined){
this.$nextTick(() => {
this.form.cron=data
})
}
},
handleChange(value) {
this.form.phoneNumber = value
},
pubScheduler(){
this.form.type=2
this.form.data={
phoneNumber:this.form.phoneNumber,
content:this.form.content
}
pubScheduler(this.form).then(res=>{
this.$message.success("发送成功")
})
}
},
watch:{
}
}
</script>
前台部分就是把 除了公共字段之外的字段 放到了 form.data中,后台使用map接收,这个map会先转换成jsonString,在接口实现类中转换成类中需要的类型,所以前台form.data中的字段要和最终转换的类字段一致
总结
以上就是我在前后端分离项目中整合quartz的过程,最后一种方法应该算是策略模式吧,不知道这样写有没有问题,如果有问题,还请大家多多指教
代码地址
前台gitee 地址
后台gitee地址
更多推荐
所有评论(0)