SpringBoot整合quartz2.2.3自定义数据源和websocket服务端主动发起通信

背景:

       某小白看了《Netty权威指南2》看到了websocket这里,感觉很有意义,于是就一路踩坑,首先搞了websocket,加上之前一直对quartz框架的兴趣很高,然后觉得坑踩得还不够多,真的是一踩坑一时爽,一直踩一直爽,于是就有了这篇文章。

参考资料:

https://blog.csdn.net/u010176542/article/details/79774470

https://blog.csdn.net/moshowgame/article/details/80275084

quartz2.2.3使用的是官网的数据库,我用的是tables_oracle.sql和tables_mysql_innodb.sql这两个数据库,下载地址链接:

http://www.quartz-scheduler.org/downloads/files/quartz-2.2.3-distribution.tar.gz

     使用springboot多模块项目开发结构,有版本的maven依赖放到common模块的pom.xml,没有版本的放到父模块,我这里是rabbitmqs的pom.xml,因为是个例子项目,依赖比较多,有经验的自己看着配置就行了。

1.common的pom.xml

<dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>${lombok.version}</version>
        </dependency>
        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.6.0</version>
        </dependency>
        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.6.0</version>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <!-- oracle -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>${oracle.version}</version>
        </dependency>
        <!--json依赖-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <classifier>jdk15</classifier>
        </dependency>
        <!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha1</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.3</version>
        </dependency>
    </dependencies>

2.rabbitmqs的pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--开发web项目相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--thymeleaf模板-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

3.quartz.properties文件配置

# 固定前缀org.quartz
org.quartz.scheduler.instanceName = DefaultQuartzScheduler 
org.quartz.scheduler.instanceId = AUTO 
org.quartz.scheduler.rmi.export = false 
org.quartz.scheduler.rmi.proxy = false 
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false 
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool 
org.quartz.threadPool.threadPriority = 5 
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true 
org.quartz.jobStore.misfireThreshold = 5000 
#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_ 
org.quartz.jobStore.clusterCheckinInterval = 15000 
## 集群模式,true为开启,false为关闭
org.quartz.jobStore.isClustered = false
## 防止因为数据库出现blob等字段时可能会出现问题
org.quartz.jobStore.useProperties = true

4.application.yml文件配置

server:
  port: 8090
  tomcat:
    uri-encoding: utf-8
spring:
   datasource:
     url: jdbc:oracle:thin:@localhost:1521:orcl
     username: scott
     password: 123456
     name: datasource
     type: com.alibaba.druid.pool.DruidDataSource
     driver-class-name: oracle.jdbc.OracleDriver
     # 下面为连接池的补充设置,应用到上面所有数据源中
     # 初始化大小,最小,最大
     initialSize: 1
     minIdle: 3
     maxActive: 216
         # 配置获取连接等待超时的时间
     maxWait: 30000
         # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
     timeBetweenEvictionRunsMillis: 60000
         # 配置一个连接在池中最小生存的时间,单位是毫秒
     minEvictableIdleTimeMillis: 30000
     validationQuery: select 1 from dual
     testWhileIdle: true
     testOnBorrow: false
     testOnReturn: false
         # 打开PSCache,并且指定每个连接上PSCache的大小
     poolPreparedStatements: true
     maxPoolPreparedStatementPerConnectionSize: 20
         # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
     filters: stat,wall,slf4j
         # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
     connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
         # 合并多个DruidDataSource的监控数据
         #useGlobalDataSourceStat: true
         # 设置,mapper 接口路径,mapper 接口对应的xml 配置文件
# mysql 数据源配置
#       url: jdbc:mysql://127.0.0.1:3306/rabbit?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=true
#       username: root
#       password:
#       driver-class-name: com.mysql.cj.jdbc.Driver
   thymeleaf:
       prefix: classpath:/templates/
       suffix: .html
       mode: HTML5
       encoding: utf-8
       cache: false
       servlet:
         content-type: text/html
   resources:
     chain:
       strategy:
         content:
           enabled: true
           paths: /**
logging:
  config: classpath:logback-spring.xml
  level:
#    com.rabbit.rabbitmps: debug
  ## 打印 mybaits sql语句方式一(推荐)
    com.rabbit.rabbitmqs.dao: debug

mybatis:
   mapper-locations: classpath:mybatis/mapper/*.xml
   ## 打印 mybaits sql语句方式二(不推荐)
#   configuration:
#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- 从高到地低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则  根据当前ROOT 级别,日志输出时,级别高于root默认的级别时  会输出 -->
<!-- 以下  每个配置的 filter 是过滤掉输出文件里面,会出现高级别文件,依然出现低级别的日志信息,通过filter 过滤只记录本级别的日志-->
<configuration debug="false" scan="true" scanPeriod="10 seconds">
    <!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
    <!--<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>-->

    <contextName>logback</contextName>
    <!--输出sql语句-->
    <logger name="com.rabbit.rabbitmqs.dao" level="debug"/>
    <property name="path" value="C:/upLoad/logs"></property>
    <property name="maxHistory" value="-1"/>
    <property name="maxFileSize" value="50MB"/>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <pattern>%green([%thread]线程) %highlight(%-5level) %red(%d{yyyy-MM-dd HH:mm:ss})  %boldMagenta(%logger) - %cyan(%msg%n)</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <appender name="debug_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${path}/debug/logback_debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天一归档 -->
            <fileNamePattern>${path}/debug/logback_debug.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${maxFileSize}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="info_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${path}/info/logback_info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天一归档 -->
            <fileNamePattern>${path}/info/logback_info.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${maxFileSize}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="warn_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${path}/warn/logback_warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天一归档 -->
            <fileNamePattern>${path}/warn/logback_warn.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${maxFileSize}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <appender name="error_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${path}/error/logback_error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天一归档 -->
            <fileNamePattern>${path}/error/logback_error.log.%d{yyyy-MM-dd}-%i.zip</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${maxFileSize}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <root>
        <level value="info"/>
        <appender-ref ref="console"/>
        <appender-ref ref="debug_file"/>
        <appender-ref ref="info_file"/>
        <appender-ref ref="warn_file"/>
        <appender-ref ref="error_file"/>
    </root>

</configuration>

5.springboot注入quartz的bean配置JobFactory

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

@Component
public class JobFactory extends AdaptableJobFactory {
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        //调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        //进行注入
        autowireCapableBeanFactory.autowireBean(jobInstance); return jobInstance;
    }
}

6.quartz创建和管理job的组件,这里包括了quartz的增删改查,很详细了,可能每个人的quartz需求都不同,有其他需求的可以多看一下资料。

https://www.w3cschool.cn/quartz_doc/quartz_doc-kixe2cq3.html

import com.rabbit.common.utils.DateUtil;
import com.rabbit.rabbitmqs.scheduler.BaseSchedulerJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author quite
 * @version V1.0
 * @Description:  job任务创建工具
 * @date 2019/3/21.
 */
@Component
@Slf4j
public class JobUtil {
    private static JobUtil jobUtil;

    @Autowired
    private Scheduler scheduler;

    public JobUtil(){
        log.info("===========================init jobUtil==================================");
        jobUtil = this;
    }

    public static JobUtil getInstance(){
        log.info("==========================retun  JobCreateUtil=============================");
        return JobUtil.jobUtil;
    }

    /**
     * 创建使用Cron表达式的Job
     * @param jobClassName
     * @param jobGroupName
     * @param cronExpression
     * @throws Exception
     */
    public  void addCronJob(String jobClassName,
                            String jobGroupName,
                            String cronExpression,
                            String startTime,
                            String endTime,
                            String remark
    ) throws Exception {
        log.info("==============prepare to start scheduler===================");
        // 启动调度器
        scheduler.start();
        //构建job信息
        JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass())
                .withIdentity(jobClassName, jobGroupName)
                .build();
        //表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .startAt(DateUtil.stringToDate(startTime)) // 设置开始时间
                .endAt(DateUtil.stringToDate(endTime)) // 设置结束时间
                .withDescription(remark) // 任务描述
                .withIdentity(jobClassName, jobGroupName)
                .withSchedule(scheduleBuilder).build();
        scheduler.scheduleJob(jobDetail, trigger);
    }



    /**
     * 暂停job
     * @param jobClassName
     * @param jobGroupName
     * @throws SchedulerException
     */
    public void pauseJob(String jobClassName,String jobGroupName) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
    }

    /**
     * 恢复job
     * @param jobClassName
     * @param jobGroupName
     * @throws SchedulerException
     */
    public void resumeJob(String jobClassName,String jobGroupName) throws SchedulerException {

        scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
    }


    /**
     * job 更新
     * @param jobClassName
     * @param jobGroupName
     * @param cronExpression
     * @throws Exception
     */
    public void jobreschedule(String jobClassName,
                              String jobGroupName,
                              String cronExpression,
                              String startTime,
                              String endTime,
                              String remark
    ) throws Exception
    {
        TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
        // 表达式调度构建器
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

        // 按新的cronExpression表达式重新构建trigger
        trigger = trigger.getTriggerBuilder()
                .startAt(DateUtil.stringToDate(startTime)) // 设置开始时间
                .endAt(DateUtil.stringToDate(endTime)) // 设置结束时间
                .withDescription(remark) // 任务描述
                .withIdentity(triggerKey)
                .withSchedule(scheduleBuilder)
                .build();

        // 按新的trigger重新设置job执行
        scheduler.rescheduleJob(triggerKey, trigger);

    }

    /**
     * job 删除
     * @param jobClassName
     * @param jobGroupName
     * @throws Exception
     */
    public void jobdelete(String jobClassName, String jobGroupName) throws Exception
    {
        scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
        scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
        scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
    }

    /**
     * 获取Job状态
     * @param jobClassName
     * @param jobGroupName
     * @return
     * @throws SchedulerException
     */
    public String getJobState(String jobClassName, String jobGroupName) throws SchedulerException {
        TriggerKey triggerKey = new TriggerKey(jobClassName, jobGroupName);
        return scheduler.getTriggerState(triggerKey).name();
    }

    /**
     * 暂停所有任务
     * @throws SchedulerException
     */
    public void pauseAllJob() throws SchedulerException {
        scheduler.pauseAll();
    }

    /**
     * 恢复所有任务
     * @throws SchedulerException
     */
    public void resumeAllJob() throws SchedulerException {
        scheduler.resumeAll();
    }

    /**
     *  根据包名得到类创建实例
     * @param classname
     * @return
     * @throws Exception
     */
    private  static BaseSchedulerJob getClass(String classname) throws Exception
    {
        Class<?> class1 = Class.forName(classname);
        return (BaseSchedulerJob)class1.newInstance();
    }
}

9.新建一系列需要的业务逻辑接口实现类...

一些job任务的查询列表要依赖的mvc代码。

JobAndTrigger.java类

import com.rabbit.common.base.BaseDO;
import lombok.Data;

import java.math.BigInteger;
@Data
public class JobAndTrigger extends BaseDO {
	private String jobName;
	private String jobroup;
	private String jobClassName; // 类名
	private String jobGroupName; // 组名称
	private String triggerName; // 调度名称
	private String triggerGroup; // 调度组
	private String triggerSTATE; // 运行状态
	private String startTmie; // 开始时间
	private String endTime; // 结束时间
	private String description; // 定时任务描述
	private BigInteger repeatInterval;
	private BigInteger timesTriggered;
	private String cronExpression;
	private String timeZoneId;
	private boolean running;

}

IJobAndTriggerDAO.java DAO接口

public interface IJobAndTriggerDAO {

   List<JobAndTrigger> getJobAndTriggerDetails() throws Exception;
}

IJobAndTriggerDAO.xml xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  
<mapper namespace="com.rabbit.rabbitmqs.dao.IJobAndTriggerDAO">
    
    <select id="getJobAndTriggerDetails" resultType="com.rabbit.common.quartz.domain.JobAndTrigger">
	  SELECT
       QRTZ_JOB_DETAILS.JOB_NAME AS jobName, qrtz_job_details.JOB_GROUP AS jobGroup,
       QRTZ_JOB_DETAILS.JOB_CLASS_NAME AS jobClassName, qrtz_triggers.TRIGGER_GROUP AS jobGroupName ,
       QRTZ_TRIGGERS.TRIGGER_NAME AS triggerName,
       QRTZ_TRIGGERS.TRIGGER_GROUP AS triggerGroup ,
       QRTZ_TRIGGERS.TRIGGER_STATE AS triggerSTATE,
       QRTZ_TRIGGERS.START_TIME AS startTmie,
       QRTZ_TRIGGERS.END_TIME AS endTime,
       QRTZ_TRIGGERS.DESCRIPTION AS description,
       QRTZ_CRON_TRIGGERS.CRON_EXPRESSION AS cronExpression,
       QRTZ_CRON_TRIGGERS.TIME_ZONE_ID AS timeZoneId
  FROM QRTZ_JOB_DETAILS
  JOIN QRTZ_TRIGGERS
       ON QRTZ_JOB_DETAILS.JOB_NAME = QRTZ_TRIGGERS.JOB_NAME
  JOIN QRTZ_CRON_TRIGGERS
       ON QRTZ_TRIGGERS.TRIGGER_NAME = QRTZ_CRON_TRIGGERS.TRIGGER_NAME
       AND QRTZ_TRIGGERS.TRIGGER_GROUP = QRTZ_CRON_TRIGGERS.TRIGGER_GROUP
    </select>
</mapper>

service就不贴了,案例一般都是写得稀巴烂。

controller JobController.java控制层文件,有一些代码是我自己的返回封装,这里就不贴了。

import com.rabbit.common.quartz.domain.JobAndTrigger;
import com.rabbit.rabbitmqs.bean.JobUtil;
import com.rabbit.rabbitmqs.result.ApiReturnObject;
import com.rabbit.rabbitmqs.result.ReturnObject;
import com.rabbit.rabbitmqs.services.IJobAndTriggerService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RestController
@RequestMapping(value="/job")
public class JobController {
	@Autowired
	private IJobAndTriggerService jobAndTriggerService;

	@Autowired
	private JobUtil jobUtil;

	@PostMapping(value="/addCronJob")
	public ApiReturnObject<String> addjob(@RequestParam(value="jobClassName")String jobClassName,
										  @RequestParam(value="jobGroupName")String jobGroupName,
										  @RequestParam(value="cronExpression")String cronExpression,
										  @RequestParam(value="startTime")String startTime,
										  @RequestParam(value="endTime")String endTime,
										  @RequestParam(value="remark")String remark
										  ){
		try {
			jobUtil.addCronJob(jobClassName, jobGroupName, cronExpression, startTime, endTime,remark);
//			JobUtil.getInstance().addJob(jobClassName, jobGroupName, cronExpression);
		} catch (Exception e) {
			log.error("创建任务调度失败"+e.getMessage(),e);
			return ReturnObject.error(1,"创建任务调度失败");
		}
		return ReturnObject.success("任务调度增加成功");
	}
	


	@PostMapping(value="/pausejob")
	public ApiReturnObject<String> pausejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName)

	{
		try {
			jobUtil.pauseJob(jobClassName, jobGroupName);
//			JobUtil.getInstance().pauseJob(jobClassName, jobGroupName);
		} catch (Exception e) {
			log.error("任务调度停止失败"+e.getMessage(),e);
			return ReturnObject.error(1,"任务调度停止失败");
		}
		return ReturnObject.success("任务调度停止成功");
	}

	@PostMapping(value="/resume")
	public  ApiReturnObject<String> resumejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
	{
		try {
			jobUtil.resumeJob(jobClassName, jobGroupName);
//			JobUtil.getInstance().resumeJob(jobClassName, jobGroupName);
		} catch (Exception e) {
			log.error("任务调度恢复失败"+e.getMessage(),e);
			return ReturnObject.error(1,"任务调度恢复失败");
		}
		return ReturnObject.success("任务调度恢复成功");
	}



	@PostMapping(value="/reschedule")
	public ApiReturnObject<String> rescheduleJob(@RequestParam(value="jobClassName")String jobClassName,
			                                     @RequestParam(value="jobGroupName")String jobGroupName,
			                                     @RequestParam(value="cronExpression")String cronExpression,
												 @RequestParam(value="startTime")String startTime,
												 @RequestParam(value="endTime")String endTime,
												 @RequestParam(value="remark")String remark
	) throws Exception
	{			
		try {
			jobUtil.jobreschedule(jobClassName, jobGroupName, cronExpression, startTime, endTime, remark);
//			JobUtil.getInstance().jobreschedule(jobClassName, jobGroupName, cronExpression);
		} catch (Exception e) {
			log.error("更新定时任务失败"+e.getMessage(),e);
			return ReturnObject.error(1,"更新定时任务失败");
		}
		return ReturnObject.success("更新定时任务成功");
	}
	


	@PostMapping(value="/delete")
	public  ApiReturnObject<String> deletejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
	{			
		try {
			jobUtil.jobdelete(jobClassName, jobGroupName);
//			JobUtil.getInstance().jobdelete(jobClassName, jobGroupName);
		} catch (Exception e) {
			log.error("删除定时任务失败"+e.getMessage(),e);
			return ReturnObject.error(1,"删除定时任务失败");
		}
		return ReturnObject.success("删除定时任务成功");
	}

	@PostMapping(value="/getTriggerState")
	public ApiReturnObject<String> getTriggerState(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName){
		String jobState = null;
		try {
			jobState = jobUtil.getJobState(jobClassName, jobGroupName);
		} catch (SchedulerException e) {
			log.error("获取定时任务运行状态失败"+e.getMessage(), e);
			return ReturnObject.error(1, "获取定时任务运行状态失败");
		}
		return ReturnObject.success(jobState);
	}


	@GetMapping(value="/findAll")
	public  ApiReturnObject<List<JobAndTrigger>> queryjob()
	{
		List<JobAndTrigger> jobAndTrigger;
		try {
			jobAndTrigger = jobAndTriggerService.getJobAndTriggerDetails();
		} catch (Exception e) {
			log.error("定时任务查询失败"+e.getMessage(),e);
			return ReturnObject.error(1,"定时任务查询失败");
		}
		return ReturnObject.success(jobAndTrigger);
	}
}

Springboot启动类

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(value = "com.rabbit.rabbitmqs")
@MapperScan("com.rabbit.rabbitmqs.dao")
public class RabbitstartApplication {
    public static void main(String[] args) {
        SpringApplication.run(RabbitstartApplication.class, args);
    }
}

对应的测试数据已经结果,因为好久没有纯手写前端了,就不写页面了,postman测试一样的效果,截几张效果图。

添加

查询

暂停任务

 

删除任务

10.下面是用quartz配置Websocket部分

10.1 WebSocket服务 WebSocketServer.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@Slf4j
@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
    // 记录当前连接数
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    //接收sid
    private String sid="";

    /** * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
    webSocketSet.add(this);
    //加入set中
     addOnlineCount();
     //在线数加1
     log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
     this.sid=sid;
     try {
         sendMessage("连接成功");
     } catch (IOException e) {
         log.error("websocket IO异常");
     }
    }

    /** * 连接关闭调用的方法 */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        //从set中删除
        subOnlineCount();
        //在线数减1
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    /** * 收到客户端消息后调用的方法
     * *
     @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口"+sid+"的信息:"+message);
        //群发消息
        for (WebSocketServer item : webSocketSet) {
            try { item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /** * * @param session * @param error */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }
    /** * 实现服务器主动推送 */
    public void sendMessage(String message) throws IOException {
        log.info("服务器主动发送消息:"+message);
        this.session.getBasicRemote().sendText(message);
    }
    /** * 群发自定义消息 * */
    public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
        log.info("推送消息到窗口"+sid+",推送内容:"+message);
        for (WebSocketServer item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的,为null则全部推送
                if(sid==null) {
                    item.sendMessage(message);
                }else if(item.sid.equals(sid)){
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }


    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

10.2 SpringBoot开启WebSocket支持

/**
 * 开启webSocket支持
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

10.3 WebSocket控制层

import com.rabbit.rabbitmqs.WebSocket.WebSocketServer;
import com.rabbit.rabbitmqs.result.ApiReturnObject;
import com.rabbit.rabbitmqs.result.ReturnObject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;

/**
 * 后台数据主动推送
 */
@Controller
@RequestMapping("/websocket")
public class WebScoketController {

    @GetMapping("/socket/{cid}")
    public String socket(@PathVariable String cid, Model model){
        model.addAttribute("cid", cid);
        return "thymeleaf/socket";
    }

    @ResponseBody
    @RequestMapping("/socket/push/{cid}")
    public ApiReturnObject pushToweb(@PathVariable String cid, String mssage){
       try {
           WebSocketServer.sendInfo(mssage, cid);
       } catch (IOException e) {
           e.printStackTrace();
           return ReturnObject.error(2,e.getMessage());
       }
       return ReturnObject.success(cid);
   }

}

10.5 前端websocket.js,上面已经讲了,不想纯手写前端。。。万恶的前端。。。

var socket;
// 获取地址栏的参数数组
var search = window.location.href;
var sp = search.lastIndexOf("/");
var params = search.substring(sp+1, search.length);
console.log(params);


if(typeof(WebSocket) == "undefined") {
    console.log("您的浏览器不支持WebSocket");
}else {
    console.log("您的浏览器支持WebSocket");
  //实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
   //等同于
    socket = new WebSocket("ws://localhost:8090/websocket/"+params+"");
//     socket = new WebSocket("http://localhost:8090/websocket/1".replace("http", "ws"));
   //打开事件
    socket.onopen = function () {
        console.log("Socket 已打开");
        //socket.send("这是来自客户端的消息" + location.href + new Date());
    };

    // 获得消息事件
    socket.onmessage = function (msg) {
        console.log("来自服务器主动推送的消息:"+msg.data);
        //发现消息进入 开始处理前端触发逻辑
        //关闭事件
    }
    socket.onclose = function () {
        console.log("Socket已关闭");
    };
    //发生了错误事件
    socket.onerror = function () {
        alert("Socket发生了错误");
        //此时可以尝试刷新页面
    }
}

10.6 quartz的定时操作websocket后台消息推送组件,哈哈哈,有没有被坑的感觉。。。

import com.rabbit.common.utils.DateUtil;
import com.rabbit.rabbitmqs.WebSocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
@Slf4j
public class ImpleJob implements  BaseSchedulerJob{

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info("运行+++++++定时任务++++++++"+DateUtil.getDateTime()+"+++++++++++++++++++++");
        try {
            WebSocketServer.sendInfo("后台主动发送数据", "1");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

只要前后端建立了一次通讯,就能看到quartz配合的websocket通讯效果了

 

案例已上传到我的资源,分被定位有点高,有需要的再去看下源码,有问题也可以直接留言,我看到就回复。

https://download.csdn.net/download/i_quite/11045662

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值