若依框架的编写之定时任务

此项目使用的技术栈如下:后端:SpringBoot,SpringMVC,Mybatis,Thyme leaf,Quartz。前端:html,css,js,layui框架

**应用场景:**定时生成报表、邮件;定时清理数据等任务。

优点:可以达到动态控制定时任务的启动、暂停、重启、删除、添加、修改等操作,极大的方便了开发的过程。

我们这里采用手动持久化,就是将操作和任务通过手动来保存到数据库,而不是使用JobStore。

所以导入的依赖为Quartz的包而不是SpringBoot的整合包!

项目结构

-src

​	-java

​		-com

​			-li

​				-SpringBootStrat

​					-common 一些常量类
						-Constants
						-ScheduleConstants

​					-commonUtils 一些工具类的重写
						-SpringUtils
						-StrFormatter
						-StringUtils
						
​					-config 配置类
						-MyInterceptors
						-MyMvcConfig
						
​					-controller 控制层
						-MyJobController
						
​					-exception 自定义异常类
						-TaskException
						
​					-mapper dao层
						-SysJobMapper
						
​					-pojo 实体类
						-MyJob
						
​					-service 服务层
						-ISysJobService
						-ISysJobServiceImpl

​					-text 文本转换类
						-CharsetKit
						-Convert

​					-utils工具类
						-AbstractQuartzJob
						-CronUtils
						-JobInvokeUtils
						-QuartzDisallowConcurentExecution
						-QuartzJobExecution
						-ScheduleUtils

					-RuoYiScheduler01Application Springboot启动类
			

​	-resources
		-config
			-mybatis-config.xml
			
		-mapper
			-SysJobMapper.xml
			
		-static
			-css
			-font
			-images
			-js
			-layui.js
			
		-templates
			-add.html
			-index.html
			-update.html
			
		-application.yaml
		
		-application.properties
		
		log4j.properties

1.导入依赖

必须使用springboot3.x以上才能与th配合使用,是一个大坑

 <!--web依赖-->
   <dependencies>
        <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>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.12.7</version>
        </dependency>
        <!--定时任务-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>
        <!--springboot启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.7.3</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>3.3.1</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>


        <!--数据库依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!--Mybatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
        <!--更改数据源为druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.20</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>

 <!--log4j-->

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!--开启邮箱的服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
            <version>3.3.2</version>
        </dependency>
 <!--lombook-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

2.编写配置文件

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis核心配置文件-->
<configuration>
<!--    日志输出为log4j-->
<settings>
    <setting name="logImpl" value="LOG4J"/>
<!--    开启驼峰命名-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>

application.yaml

#服务端口
server:
  port: 9999


#数据库连接配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    #先不配置druid



mybatis:
  config-location: classpath:config/mybatis-config.xml
  mapper-locations: classpath:mapper/SysJobMapper.xml
  type-aliases-package: com.li.SpringBootStart.pojo

application.properties(邮件发送配置)

[email protected]

spring.mail.password=wbjihvkprrdnchhh

spring.mail.host=smtp.qq.com

spring.mail.properties.smtp.ssl.enable=true

日志log4j

log4j.rootLogger=DEBUG,console,file


log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n


log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n


log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3.创建定时任务表 sys_job

create table sys_job (
  job_id              bigint(20)    not null auto_increment    comment '任务ID',
  job_name            varchar(64)   default ''                 comment '任务名称',
  job_group           varchar(64)   default 'DEFAULT'          comment '任务组名',
  invoke_target       varchar(500)  not null                   comment '调用目标字符串',
  cron_expression     varchar(255)  default ''                 comment 'cron执行表达式',
  misfire_policy      varchar(20)   default '3'                comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
  concurrent          char(1)       default '1'                comment '是否并发执行(0允许 1禁止)',
  status              char(1)       default '0'                comment '状态(0正常 1暂停)',
  create_by           varchar(64)   default ''                 comment '创建者',
  create_time         datetime                                 comment '创建时间',
  update_by           varchar(64)   default ''                 comment '更新者',
  update_time         datetime                                 comment '更新时间',
  remark              varchar(500)  default ''                 comment '备注信息',
  primary key (job_id, job_name, job_group)
) engine=innodb auto_increment=100 comment = '定时任务调度表';

4.常量类的编写

Constants类

package com.li.SpringBootStart.common;

import java.util.Locale;

/**
 * 通用常量信息
 * 
 * @author ruoyi
 */
public class Constants
{
    /**
     * UTF-8 字符集
     */
    public static final String UTF8 = "UTF-8";

    /**
     * GBK 字符集
     */
    public static final String GBK = "GBK";

    /**
     * 系统语言
     */
    public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;

    /**
     * http请求
     */
    public static final String HTTP = "http://";

    /**
     * https请求
     */
    public static final String HTTPS = "https://";

    /**
     * 通用成功标识
     */
    public static final String SUCCESS = "0";

    /**
     * 通用失败标识
     */
    public static final String FAIL = "1";

    /**
     * 登录成功
     */
    public static final String LOGIN_SUCCESS = "Success";

    /**
     * 注销
     */
    public static final String LOGOUT = "Logout";

    /**
     * 注册
     */
    public static final String REGISTER = "Register";

    /**
     * 登录失败
     */
    public static final String LOGIN_FAIL = "Error";

    /**
     * 系统用户授权缓存
     */
    public static final String SYS_AUTH_CACHE = "sys-authCache";

    /**
     * 参数管理 cache name
     */
    public static final String SYS_CONFIG_CACHE = "sys-config";

    /**
     * 参数管理 cache key
     */
    public static final String SYS_CONFIG_KEY = "sys_config:";

    /**
     * 字典管理 cache name
     */
    public static final String SYS_DICT_CACHE = "sys-dict";

    /**
     * 字典管理 cache key
     */
    public static final String SYS_DICT_KEY = "sys_dict:";

    /**
     * 资源映射路径 前缀
     */
    public static final String RESOURCE_PREFIX = "/profile";

    /**
     * RMI 远程方法调用
     */
    public static final String LOOKUP_RMI = "rmi:";

    /**
     * LDAP 远程方法调用
     */
    public static final String LOOKUP_LDAP = "ldap:";

    /**
     * LDAPS 远程方法调用
     */
    public static final String LOOKUP_LDAPS = "ldaps:";

    /**
     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
     */
    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };

    /**
     * 定时任务违规的字符
     */
    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
}

ScheduleConstants类

package com.li.SpringBootStart.common;
/**
 * @author xiaoli
 * 任务调度通用常量
 * */
public class ScheduleConstants
{
    public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";

    /** 执行目标key */
    public static final String TASK_PROPERTIES = "TASK_PROPERTIES";

    /** 默认 */
    public static final String MISFIRE_DEFAULT = "0";

    /** 立即触发执行 */
    public static final String MISFIRE_IGNORE_MISFIRES = "1";

    /** 触发一次执行 */
    public static final String MISFIRE_FIRE_AND_PROCEED = "2";

    /** 不触发立即执行 */
    public static final String MISFIRE_DO_NOTHING = "3";

    public enum Status
    {
        /**
         * 正常
         */
        NORMAL("0"),
        /**
         * 暂停
         */
        PAUSE("1");

        private String value;

        private Status(String value)
        {
            this.value = value;
        }

        public String getValue()
        {
            return value;
        }
    }
}

5.系统工具类的封装和重写

SpringUtils类

package com.li.SpringBootStart.commonUtils;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;


/**
 * spring工具类 方便在非spring管理环境中获取bean
 * 
 * @author ruoyi
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
    {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        SpringUtils.applicationContext = applicationContext;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws org.springframework.beans.BeansException
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws org.springframework.beans.BeansException
     *
     */
    public static <T> T getBean(Class<T> clz) throws BeansException
    {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name)
    {
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getAliases(name);
    }

    /**
     * 获取aop代理对象
     * 
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker)
    {
        return (T) AopContext.currentProxy();
    }

    /**
     * 获取当前的环境配置,无配置返回null
     *
     * @return 当前的环境配置
     */
    public static String[] getActiveProfiles()
    {
        return applicationContext.getEnvironment().getActiveProfiles();
    }

    /**
     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
     *
     * @return 当前的环境配置
     */
    public static String getActiveProfile()
    {
        final String[] activeProfiles = getActiveProfiles();
        return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
    }

    /**
     * 获取配置文件中的值
     *
     * @param key 配置文件的key
     * @return 当前的配置文件的值
     *
     */
    public static String getRequiredProperty(String key)
    {
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }

}

StrFormmatter类

package com.li.SpringBootStart.commonUtils;

import com.li.SpringBootStart.commonUtils.StringUtils;
import com.li.SpringBootStart.text.Convert;

/**
 * 字符串格式化
 * 
 * @author xiaoli
 */
public class StrFormatter
{
    public static final String EMPTY_JSON = "{}";
    public static final char C_BACKSLASH = '\\';
    public static final char C_DELIM_START = '{';
    public static final char C_DELIM_END = '}';

    /**
     * 格式化字符串<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例:<br>
     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
     * 
     * @param strPattern 字符串模板
     * @param argArray 参数列表
     * @return 结果
     */
    public static String format(final String strPattern, final Object... argArray)
    {
        if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray))
        {
            return strPattern;
        }
        final int strPatternLength = strPattern.length();

        // 初始化定义好的长度以获得更好的性能
        StringBuilder sbuf = new StringBuilder(strPatternLength + 50);

        int handledPosition = 0;
        int delimIndex;// 占位符所在位置
        for (int argIndex = 0; argIndex < argArray.length; argIndex++)
        {
            delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
            if (delimIndex == -1)
            {
                if (handledPosition == 0)
                {
                    return strPattern;
                }
                else
                { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
                    sbuf.append(strPattern, handledPosition, strPatternLength);
                    return sbuf.toString();
                }
            }
            else
            {
                if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH)
                {
                    if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH)
                    {
                        // 转义符之前还有一个转义符,占位符依旧有效
                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
                        sbuf.append(Convert.utf8Str(argArray[argIndex]));
                        handledPosition = delimIndex + 2;
                    }
                    else
                    {
                        // 占位符被转义
                        argIndex--;
                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
                        sbuf.append(C_DELIM_START);
                        handledPosition = delimIndex + 1;
                    }
                }
                else
                {
                    // 正常占位符
                    sbuf.append(strPattern, handledPosition, delimIndex);
                    sbuf.append(Convert.utf8Str(argArray[argIndex]));
                    handledPosition = delimIndex + 2;
                }
            }
        }
        // 加入最后一个占位符后所有的字符
        sbuf.append(strPattern, handledPosition, strPattern.length());

        return sbuf.toString();
    }
}

StringUtils类

package com.li.SpringBootStart.commonUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.li.SpringBootStart.common.Constants;
import org.springframework.util.AntPathMatcher;


/**
 * 字符串工具类
 * 
 * @author ruoyi
 */
public class StringUtils extends org.apache.commons.lang3.StringUtils
{
    /** 空字符串 */
    private static final String NULLSTR = "";

    /** 下划线 */
    private static final char SEPARATOR = '_';

    /** 星号 */
    private static final char ASTERISK = '*';

    /**
     * 获取参数不为空值
     * 
     * @param value defaultValue 要判断的value
     * @return value 返回值
     */
    public static <T> T nvl(T value, T defaultValue)
    {
        return value != null ? value : defaultValue;
    }

    /**
     * * 判断一个Collection是否为空, 包含List,Set,Queue
     * 
     * @param coll 要判断的Collection
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Collection<?> coll)
    {
        return isNull(coll) || coll.isEmpty();
    }

    /**
     * * 判断一个Collection是否非空,包含List,Set,Queue
     * 
     * @param coll 要判断的Collection
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Collection<?> coll)
    {
        return !isEmpty(coll);
    }

    /**
     * * 判断一个对象数组是否为空
     * 
     * @param objects 要判断的对象数组
     ** @return true:为空 false:非空
     */
    public static boolean isEmpty(Object[] objects)
    {
        return isNull(objects) || (objects.length == 0);
    }

    /**
     * * 判断一个对象数组是否非空
     * 
     * @param objects 要判断的对象数组
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Object[] objects)
    {
        return !isEmpty(objects);
    }

    /**
     * * 判断一个Map是否为空
     * 
     * @param map 要判断的Map
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Map<?, ?> map)
    {
        return isNull(map) || map.isEmpty();
    }

    /**
     * * 判断一个Map是否为空
     * 
     * @param map 要判断的Map
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Map<?, ?> map)
    {
        return !isEmpty(map);
    }

    /**
     * * 判断一个字符串是否为空串
     * 
     * @param str String
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(String str)
    {
        return isNull(str) || NULLSTR.equals(str.trim());
    }

    /**
     * * 判断一个字符串是否为非空串
     * 
     * @param str String
     * @return true:非空串 false:空串
     */
    public static boolean isNotEmpty(String str)
    {
        return !isEmpty(str);
    }

    /**
     * * 判断一个对象是否为空
     * 
     * @param object Object
     * @return true:为空 false:非空
     */
    public static boolean isNull(Object object)
    {
        return object == null;
    }

    /**
     * * 判断一个对象是否非空
     * 
     * @param object Object
     * @return true:非空 false:空
     */
    public static boolean isNotNull(Object object)
    {
        return !isNull(object);
    }

    /**
     * * 判断一个对象是否是数组类型(Java基本型别的数组)
     * 
     * @param object 对象
     * @return true:是数组 false:不是数组
     */
    public static boolean isArray(Object object)
    {
        return isNotNull(object) && object.getClass().isArray();
    }

    /**
     * 去空格
     */
    public static String trim(String str)
    {
        return (str == null ? "" : str.trim());
    }

    /**
     * 替换指定字符串的指定区间内字符为"*"
     *
     * @param str 字符串
     * @param startInclude 开始位置(包含)
     * @param endExclude 结束位置(不包含)
     * @return 替换后的字符串
     */
    public static String hide(CharSequence str, int startInclude, int endExclude)
    {
        if (isEmpty(str))
        {
            return NULLSTR;
        }
        final int strLength = str.length();
        if (startInclude > strLength)
        {
            return NULLSTR;
        }
        if (endExclude > strLength)
        {
            endExclude = strLength;
        }
        if (startInclude > endExclude)
        {
            // 如果起始位置大于结束位置,不替换
            return NULLSTR;
        }
        final char[] chars = new char[strLength];
        for (int i = 0; i < strLength; i++)
        {
            if (i >= startInclude && i < endExclude)
            {
                chars[i] = ASTERISK;
            }
            else
            {
                chars[i] = str.charAt(i);
            }
        }
        return new String(chars);
    }

    /**
     * 截取字符串
     * 
     * @param str 字符串
     * @param start 开始
     * @return 结果
     */
    public static String substring(final String str, int start)
    {
        if (str == null)
        {
            return NULLSTR;
        }

        if (start < 0)
        {
            start = str.length() + start;
        }

        if (start < 0)
        {
            start = 0;
        }
        if (start > str.length())
        {
            return NULLSTR;
        }

        return str.substring(start);
    }

    /**
     * 截取字符串
     * 
     * @param str 字符串
     * @param start 开始
     * @param end 结束
     * @return 结果
     */
    public static String substring(final String str, int start, int end)
    {
        if (str == null)
        {
            return NULLSTR;
        }

        if (end < 0)
        {
            end = str.length() + end;
        }
        if (start < 0)
        {
            start = str.length() + start;
        }

        if (end > str.length())
        {
            end = str.length();
        }

        if (start > end)
        {
            return NULLSTR;
        }

        if (start < 0)
        {
            start = 0;
        }
        if (end < 0)
        {
            end = 0;
        }

        return str.substring(start, end);
    }

    /**
     * 格式化文本, {} 表示占位符<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例:<br>
     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
     * 
     * @param template 文本模板,被替换的部分用 {} 表示
     * @param params 参数值
     * @return 格式化后的文本
     */
    public static String format(String template, Object... params)
    {
        if (isEmpty(params) || isEmpty(template))
        {
            return template;
        }
        return StrFormatter.format(template, params);
    }

    /**
     * 是否为http(s)://开头
     * 
     * @param link 链接
     * @return 结果
     */
    public static boolean ishttp(String link)
    {
        return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
    }

    /**
     * 字符串转set
     * 
     * @param str 字符串
     * @param sep 分隔符
     * @return set集合
     */
    public static final Set<String> str2Set(String str, String sep)
    {
        return new HashSet<String>(str2List(str, sep, true, false));
    }

    /**
     * 字符串转list
     * 
     * @param str 字符串
     * @param sep 分隔符
     * @param filterBlank 过滤纯空白
     * @param trim 去掉首尾空白
     * @return list集合
     */
    public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim)
    {
        List<String> list = new ArrayList<String>();
        if (StringUtils.isEmpty(str))
        {
            return list;
        }

        // 过滤空白字符串
        if (filterBlank && StringUtils.isBlank(str))
        {
            return list;
        }
        String[] split = str.split(sep);
        for (String string : split)
        {
            if (filterBlank && StringUtils.isBlank(string))
            {
                continue;
            }
            if (trim)
            {
                string = string.trim();
            }
            list.add(string);
        }

        return list;
    }

    /**
     * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
     *
     * @param collection 给定的集合
     * @param array 给定的数组
     * @return boolean 结果
     */
    public static boolean containsAny(Collection<String> collection, String... array)
    {
        if (isEmpty(collection) || isEmpty(array))
        {
            return false;
        }
        else
        {
            for (String str : array)
            {
                if (collection.contains(str))
                {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
     *
     * @param cs 指定字符串
     * @param searchCharSequences 需要检查的字符串数组
     * @return 是否包含任意一个字符串
     */
    public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences)
    {
        if (isEmpty(cs) || isEmpty(searchCharSequences))
        {
            return false;
        }
        for (CharSequence testStr : searchCharSequences)
        {
            if (containsIgnoreCase(cs, testStr))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 驼峰转下划线命名
     */
    public static String toUnderScoreCase(String str)
    {
        if (str == null)
        {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        // 前置字符是否大写
        boolean preCharIsUpperCase = true;
        // 当前字符是否大写
        boolean curreCharIsUpperCase = true;
        // 下一字符是否大写
        boolean nexteCharIsUpperCase = true;
        for (int i = 0; i < str.length(); i++)
        {
            char c = str.charAt(i);
            if (i > 0)
            {
                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
            }
            else
            {
                preCharIsUpperCase = false;
            }

            curreCharIsUpperCase = Character.isUpperCase(c);

            if (i < (str.length() - 1))
            {
                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
            }

            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase)
            {
                sb.append(SEPARATOR);
            }
            else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase)
            {
                sb.append(SEPARATOR);
            }
            sb.append(Character.toLowerCase(c));
        }

        return sb.toString();
    }

    /**
     * 是否包含字符串
     * 
     * @param str 验证字符串
     * @param strs 字符串组
     * @return 包含返回true
     */
    public static boolean inStringIgnoreCase(String str, String... strs)
    {
        if (str != null && strs != null)
        {
            for (String s : strs)
            {
                if (str.equalsIgnoreCase(trim(s)))
                {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 删除最后一个字符串
     *
     * @param str 输入字符串
     * @param spit 以什么类型结尾的
     * @return 截取后的字符串
     */
    public static String lastStringDel(String str, String spit)
    {
        if (!StringUtils.isEmpty(str) && str.endsWith(spit))
        {
            return str.subSequence(0, str.length() - 1).toString();
        }
        return str;
    }

    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
     * 
     * @param name 转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String convertToCamelCase(String name)
    {
        StringBuilder result = new StringBuilder();
        // 快速检查
        if (name == null || name.isEmpty())
        {
            // 没必要转换
            return "";
        }
        else if (!name.contains("_"))
        {
            // 不含下划线,仅将首字母大写
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        // 用下划线将原始字符串分割
        String[] camels = name.split("_");
        for (String camel : camels)
        {
            // 跳过原始字符串中开头、结尾的下换线或双重下划线
            if (camel.isEmpty())
            {
                continue;
            }
            // 首字母大写
            result.append(camel.substring(0, 1).toUpperCase());
            result.append(camel.substring(1).toLowerCase());
        }
        return result.toString();
    }

    /**
     * 驼峰式命名法
     * 例如:user_name->userName
     */
    public static String toCamelCase(String s)
    {
        if (s == null)
        {
            return null;
        }
        if (s.indexOf(SEPARATOR) == -1)
        {
            return s;
        }
        s = s.toLowerCase();
        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++)
        {
            char c = s.charAt(i);

            if (c == SEPARATOR)
            {
                upperCase = true;
            }
            else if (upperCase)
            {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            }
            else
            {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
     * 
     * @param str 指定字符串
     * @param strs 需要检查的字符串数组
     * @return 是否匹配
     */
    public static boolean matches(String str, List<String> strs)
    {
        if (isEmpty(str) || isEmpty(strs))
        {
            return false;
        }
        for (String pattern : strs)
        {
            if (isMatch(pattern, str))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断url是否与规则配置: 
     * ? 表示单个字符; 
     * * 表示一层路径内的任意字符串,不可跨层级; 
     * ** 表示任意层路径;
     * 
     * @param pattern 匹配规则
     * @param url 需要匹配的url
     * @return
     */
    public static boolean isMatch(String pattern, String url)
    {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object obj)
    {
        return (T) obj;
    }

    /**
     * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
     * 
     * @param num 数字对象
     * @param size 字符串指定长度
     * @return 返回数字的字符串格式,该字符串为指定长度。
     */
    public static final String padl(final Number num, final int size)
    {
        return padl(num.toString(), size, '0');
    }

    /**
     * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
     * 
     * @param s 原始字符串
     * @param size 字符串指定长度
     * @param c 用于补齐的字符
     * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
     */
    public static final String padl(final String s, final int size, final char c)
    {
        final StringBuilder sb = new StringBuilder(size);
        if (s != null)
        {
            final int len = s.length();
            if (s.length() <= size)
            {
                for (int i = size - len; i > 0; i--)
                {
                    sb.append(c);
                }
                sb.append(s);
            }
            else
            {
                return s.substring(len - size, len);
            }
        }
        else
        {
            for (int i = size; i > 0; i--)
            {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

6.配置类的编写

MyInterceptors类

package com.li.SpringBootStart.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

public class MyInterceptors implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;  //都不拦截
    }
}

MyMvcConfig类

package com.li.SpringBootStart.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptors()).addPathPatterns("/*").excludePathPatterns(
                "/index.html","/","/user/login","/static/css/**","static/image/**","static/js/**");
    }
}

7.文本转换类的编写

CharseKit类

package com.li.SpringBootStart.text;

import com.li.SpringBootStart.commonUtils.StringUtils;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;


/**
 * 字符集工具类
 * 
 * @author xiaoli
 */
public class CharsetKit
{
    /** ISO-8859-1 */
    public static final String ISO_8859_1 = "ISO-8859-1";
    /** UTF-8 */
    public static final String UTF_8 = "UTF-8";
    /** GBK */
    public static final String GBK = "GBK";

    /** ISO-8859-1 */
    public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);
    /** UTF-8 */
    public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);
    /** GBK */
    public static final Charset CHARSET_GBK = Charset.forName(GBK);

    /**
     * 转换为Charset对象
     * 
     * @param charset 字符集,为空则返回默认字符集
     * @return Charset
     */
    public static Charset charset(String charset)
    {
        return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
    }

    /**
     * 转换字符串的字符集编码
     * 
     * @param source 字符串
     * @param srcCharset 源字符集,默认ISO-8859-1
     * @param destCharset 目标字符集,默认UTF-8
     * @return 转换后的字符集
     */
    public static String convert(String source, String srcCharset, String destCharset)
    {
        return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
    }

    /**
     * 转换字符串的字符集编码
     * 
     * @param source 字符串
     * @param srcCharset 源字符集,默认ISO-8859-1
     * @param destCharset 目标字符集,默认UTF-8
     * @return 转换后的字符集
     */
    public static String convert(String source, Charset srcCharset, Charset destCharset)
    {
        if (null == srcCharset)
        {
            srcCharset = StandardCharsets.ISO_8859_1;
        }

        if (null == destCharset)
        {
            destCharset = StandardCharsets.UTF_8;
        }

        if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset))
        {
            return source;
        }
        return new String(source.getBytes(srcCharset), destCharset);
    }

    /**
     * @return 系统字符集编码
     */
    public static String systemCharset()
    {
        return Charset.defaultCharset().name();
    }
}

Convert类

package com.li.SpringBootStart.text;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.Set;

import com.li.SpringBootStart.commonUtils.StringUtils;
import org.apache.commons.lang3.ArrayUtils;

/**
 * 类型转换器
 *
 * @author xiaoli
 */
public class Convert
{
    /**
     * 转换为字符串<br>
     * 如果给定的值为null,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static String toStr(Object value, String defaultValue)
    {
        if (null == value)
        {
            return defaultValue;
        }
        if (value instanceof String)
        {
            return (String) value;
        }
        return value.toString();
    }

    /**
     * 转换为字符串<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static String toStr(Object value)
    {
        return toStr(value, null);
    }

    /**
     * 转换为字符<br>
     * 如果给定的值为null,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Character toChar(Object value, Character defaultValue)
    {
        if (null == value)
        {
            return defaultValue;
        }
        if (value instanceof Character)
        {
            return (Character) value;
        }

        final String valueStr = toStr(value, null);
        return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0);
    }

    /**
     * 转换为字符<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Character toChar(Object value)
    {
        return toChar(value, null);
    }

    /**
     * 转换为byte<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Byte toByte(Object value, Byte defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Byte)
        {
            return (Byte) value;
        }
        if (value instanceof Number)
        {
            return ((Number) value).byteValue();
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return Byte.parseByte(valueStr);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为byte<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Byte toByte(Object value)
    {
        return toByte(value, null);
    }

    /**
     * 转换为Short<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Short toShort(Object value, Short defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Short)
        {
            return (Short) value;
        }
        if (value instanceof Number)
        {
            return ((Number) value).shortValue();
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return Short.parseShort(valueStr.trim());
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为Short<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Short toShort(Object value)
    {
        return toShort(value, null);
    }

    /**
     * 转换为Number<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Number toNumber(Object value, Number defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Number)
        {
            return (Number) value;
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return NumberFormat.getInstance().parse(valueStr);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为Number<br>
     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Number toNumber(Object value)
    {
        return toNumber(value, null);
    }

    /**
     * 转换为int<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Integer toInt(Object value, Integer defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Integer)
        {
            return (Integer) value;
        }
        if (value instanceof Number)
        {
            return ((Number) value).intValue();
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return Integer.parseInt(valueStr.trim());
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为int<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Integer toInt(Object value)
    {
        return toInt(value, null);
    }

    /**
     * 转换为Integer数组<br>
     *
     * @param str 被转换的值
     * @return 结果
     */
    public static Integer[] toIntArray(String str)
    {
        return toIntArray(",", str);
    }

    /**
     * 转换为Long数组<br>
     *
     * @param str 被转换的值
     * @return 结果
     */
    public static Long[] toLongArray(String str)
    {
        return toLongArray(",", str);
    }

    /**
     * 转换为Integer数组<br>
     *
     * @param split 分隔符
     * @param split 被转换的值
     * @return 结果
     */
    public static Integer[] toIntArray(String split, String str)
    {
        if (StringUtils.isEmpty(str))
        {
            return new Integer[] {};
        }
        String[] arr = str.split(split);
        final Integer[] ints = new Integer[arr.length];
        for (int i = 0; i < arr.length; i++)
        {
            final Integer v = toInt(arr[i], 0);
            ints[i] = v;
        }
        return ints;
    }

    /**
     * 转换为Long数组<br>
     *
     * @param split 分隔符
     * @param str 被转换的值
     * @return 结果
     */
    public static Long[] toLongArray(String split, String str)
    {
        if (StringUtils.isEmpty(str))
        {
            return new Long[] {};
        }
        String[] arr = str.split(split);
        final Long[] longs = new Long[arr.length];
        for (int i = 0; i < arr.length; i++)
        {
            final Long v = toLong(arr[i], null);
            longs[i] = v;
        }
        return longs;
    }

    /**
     * 转换为String数组<br>
     *
     * @param str 被转换的值
     * @return 结果
     */
    public static String[] toStrArray(String str)
    {
        if (StringUtils.isEmpty(str))
        {
            return new String[] {};
        }
        return toStrArray(",", str);
    }

    /**
     * 转换为String数组<br>
     *
     * @param split 分隔符
     * @param split 被转换的值
     * @return 结果
     */
    public static String[] toStrArray(String split, String str)
    {
        return str.split(split);
    }

    /**
     * 转换为long<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Long toLong(Object value, Long defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Long)
        {
            return (Long) value;
        }
        if (value instanceof Number)
        {
            return ((Number) value).longValue();
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            // 支持科学计数法
            return new BigDecimal(valueStr.trim()).longValue();
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为long<br>
     * 如果给定的值为<code>null</code>,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Long toLong(Object value)
    {
        return toLong(value, null);
    }

    /**
     * 转换为double<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Double toDouble(Object value, Double defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Double)
        {
            return (Double) value;
        }
        if (value instanceof Number)
        {
            return ((Number) value).doubleValue();
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            // 支持科学计数法
            return new BigDecimal(valueStr.trim()).doubleValue();
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为double<br>
     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Double toDouble(Object value)
    {
        return toDouble(value, null);
    }

    /**
     * 转换为Float<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Float toFloat(Object value, Float defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Float)
        {
            return (Float) value;
        }
        if (value instanceof Number)
        {
            return ((Number) value).floatValue();
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return Float.parseFloat(valueStr.trim());
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为Float<br>
     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Float toFloat(Object value)
    {
        return toFloat(value, null);
    }

    /**
     * 转换为boolean<br>
     * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static Boolean toBool(Object value, Boolean defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Boolean)
        {
            return (Boolean) value;
        }
        String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        valueStr = valueStr.trim().toLowerCase();
        switch (valueStr)
        {
            case "true":
            case "yes":
            case "ok":
            case "1":
                return true;
            case "false":
            case "no":
            case "0":
                return false;
            default:
                return defaultValue;
        }
    }

    /**
     * 转换为boolean<br>
     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static Boolean toBool(Object value)
    {
        return toBool(value, null);
    }

    /**
     * 转换为Enum对象<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     *
     * @param clazz Enum的Class
     * @param value 值
     * @param defaultValue 默认值
     * @return Enum
     */
    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value, E defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (clazz.isAssignableFrom(value.getClass()))
        {
            @SuppressWarnings("unchecked")
            E myE = (E) value;
            return myE;
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return Enum.valueOf(clazz, valueStr);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为Enum对象<br>
     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
     *
     * @param clazz Enum的Class
     * @param value 值
     * @return Enum
     */
    public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value)
    {
        return toEnum(clazz, value, null);
    }

    /**
     * 转换为BigInteger<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static BigInteger toBigInteger(Object value, BigInteger defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof BigInteger)
        {
            return (BigInteger) value;
        }
        if (value instanceof Long)
        {
            return BigInteger.valueOf((Long) value);
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return new BigInteger(valueStr);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为BigInteger<br>
     * 如果给定的值为空,或者转换失败,返回默认值<code>null</code><br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static BigInteger toBigInteger(Object value)
    {
        return toBigInteger(value, null);
    }

    /**
     * 转换为BigDecimal<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @param defaultValue 转换错误时的默认值
     * @return 结果
     */
    public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof BigDecimal)
        {
            return (BigDecimal) value;
        }
        if (value instanceof Long)
        {
            return new BigDecimal((Long) value);
        }
        if (value instanceof Double)
        {
            return BigDecimal.valueOf((Double) value);
        }
        if (value instanceof Integer)
        {
            return new BigDecimal((Integer) value);
        }
        final String valueStr = toStr(value, null);
        if (StringUtils.isEmpty(valueStr))
        {
            return defaultValue;
        }
        try
        {
            return new BigDecimal(valueStr);
        }
        catch (Exception e)
        {
            return defaultValue;
        }
    }

    /**
     * 转换为BigDecimal<br>
     * 如果给定的值为空,或者转换失败,返回默认值<br>
     * 转换失败不会报错
     *
     * @param value 被转换的值
     * @return 结果
     */
    public static BigDecimal toBigDecimal(Object value)
    {
        return toBigDecimal(value, null);
    }

    /**
     * 将对象转为字符串<br>
     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
     *
     * @param obj 对象
     * @return 字符串
     */
    public static String utf8Str(Object obj)
    {
        return str(obj, CharsetKit.CHARSET_UTF_8);
    }

    /**
     * 将对象转为字符串<br>
     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
     *
     * @param obj 对象
     * @param charsetName 字符集
     * @return 字符串
     */
    public static String str(Object obj, String charsetName)
    {
        return str(obj, Charset.forName(charsetName));
    }

    /**
     * 将对象转为字符串<br>
     * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
     *
     * @param obj 对象
     * @param charset 字符集
     * @return 字符串
     */
    public static String str(Object obj, Charset charset)
    {
        if (null == obj)
        {
            return null;
        }

        if (obj instanceof String)
        {
            return (String) obj;
        }
        else if (obj instanceof byte[])
        {
            return str((byte[]) obj, charset);
        }
        else if (obj instanceof Byte[])
        {
            byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj);
            return str(bytes, charset);
        }
        else if (obj instanceof ByteBuffer)
        {
            return str((ByteBuffer) obj, charset);
        }
        return obj.toString();
    }

    /**
     * 将byte数组转为字符串
     *
     * @param bytes byte数组
     * @param charset 字符集
     * @return 字符串
     */
    public static String str(byte[] bytes, String charset)
    {
        return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset));
    }

    /**
     * 解码字节码
     *
     * @param data 字符串
     * @param charset 字符集,如果此字段为空,则解码的结果取决于平台
     * @return 解码后的字符串
     */
    public static String str(byte[] data, Charset charset)
    {
        if (data == null)
        {
            return null;
        }

        if (null == charset)
        {
            return new String(data);
        }
        return new String(data, charset);
    }

    /**
     * 将编码的byteBuffer数据转换为字符串
     *
     * @param data 数据
     * @param charset 字符集,如果为空使用当前系统字符集
     * @return 字符串
     */
    public static String str(ByteBuffer data, String charset)
    {
        if (data == null)
        {
            return null;
        }

        return str(data, Charset.forName(charset));
    }

    /**
     * 将编码的byteBuffer数据转换为字符串
     *
     * @param data 数据
     * @param charset 字符集,如果为空使用当前系统字符集
     * @return 字符串
     */
    public static String str(ByteBuffer data, Charset charset)
    {
        if (null == charset)
        {
            charset = Charset.defaultCharset();
        }
        return charset.decode(data).toString();
    }

    // ----------------------------------------------------------------------- 全角半角转换
    /**
     * 半角转全角
     *
     * @param input String.
     * @return 全角字符串.
     */
    public static String toSBC(String input)
    {
        return toSBC(input, null);
    }

    /**
     * 半角转全角
     *
     * @param input String
     * @param notConvertSet 不替换的字符集合
     * @return 全角字符串.
     */
    public static String toSBC(String input, Set<Character> notConvertSet)
    {
        char[] c = input.toCharArray();
        for (int i = 0; i < c.length; i++)
        {
            if (null != notConvertSet && notConvertSet.contains(c[i]))
            {
                // 跳过不替换的字符
                continue;
            }

            if (c[i] == ' ')
            {
                c[i] = '\u3000';
            }
            else if (c[i] < '\177')
            {
                c[i] = (char) (c[i] + 65248);

            }
        }
        return new String(c);
    }

    /**
     * 全角转半角
     *
     * @param input String.
     * @return 半角字符串
     */
    public static String toDBC(String input)
    {
        return toDBC(input, null);
    }

    /**
     * 替换全角为半角
     *
     * @param text 文本
     * @param notConvertSet 不替换的字符集合
     * @return 替换后的字符
     */
    public static String toDBC(String text, Set<Character> notConvertSet)
    {
        char[] c = text.toCharArray();
        for (int i = 0; i < c.length; i++)
        {
            if (null != notConvertSet && notConvertSet.contains(c[i]))
            {
                // 跳过不替换的字符
                continue;
            }

            if (c[i] == '\u3000')
            {
                c[i] = ' ';
            }
            else if (c[i] > '\uFF00' && c[i] < '\uFF5F')
            {
                c[i] = (char) (c[i] - 65248);
            }
        }
        String returnString = new String(c);

        return returnString;
    }

    /**
     * 数字金额大写转换 先写个完整的然后将如零拾替换成零
     *
     * @param n 数字
     * @return 中文大写数字
     */
    public static String digitUppercase(double n)
    {
        String[] fraction = { "角", "分" };
        String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" };
        String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } };

        String head = n < 0 ? "负" : "";
        n = Math.abs(n);

        String s = "";
        for (int i = 0; i < fraction.length; i++)
        {
            // 优化double计算精度丢失问题
            BigDecimal nNum = new BigDecimal(n);
            BigDecimal decimal = new BigDecimal(10);
            BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN);
            double d = scale.doubleValue();
            s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
        }
        if (s.length() < 1)
        {
            s = "整";
        }
        int integerPart = (int) Math.floor(n);

        for (int i = 0; i < unit[0].length && integerPart > 0; i++)
        {
            String p = "";
            for (int j = 0; j < unit[1].length && n > 0; j++)
            {
                p = digit[integerPart % 10] + unit[1][j] + p;
                integerPart = integerPart / 10;
            }
            s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s;
        }
        return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整");
    }
}

8.自己的工具类封装

AbstractQuartzJob(抽象工具类)

package com.li.SpringBootStart.utils;

import com.li.SpringBootStart.common.ScheduleConstants;
import com.li.SpringBootStart.pojo.MyJob;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.BeanUtils;

import java.util.Date;
import java.util.Locale;

/**
 * @author xiaoli
 * 抽象任务类的定义
 * */
public abstract class AbstractQuartzJob implements Job {

    //使用线程本地变量
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    //实现Job的执行方法
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        MyJob myJob = new MyJob();//创建一个job实体类
        //将Map里的参数逐一复制给这个myJob对象
//        BeanUtils.copyProperties(myJob,
//                jobExecutionContext.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));

        myJob.setInvokeTarget((String) jobExecutionContext.getJobDetail().getJobDataMap().get("target"));
        //动态得到需要的参数,这里指的是最终方法的链接,得到了才能通过反射去执行
//

        try {//开始执行

            before(jobExecutionContext,myJob);  //执行前
            doExecute(jobExecutionContext,myJob); //执行中
            after(jobExecutionContext,myJob); //执行后
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }

    /**
     * 执行前
     *
     * @param context 工作执行上下文对象
     * @param sysJob 系统计划任务
     */
    protected void before(JobExecutionContext context, MyJob sysJob)
    {   //记录开始的时间
        threadLocal.set(System.currentTimeMillis());
    }

    /**
     * 执行后
     *
     * @param context 工作执行上下文对象
     * @param sysJob 系统计划任务
     */
    protected void after(JobExecutionContext context, MyJob sysJob)
    {
        Long startTime = threadLocal.get();


        Long endTime = System.currentTimeMillis();



        System.out.println("执行该任务总耗时:"+(endTime-startTime)+"豪秒");


    }

    /**
     * 执行方法,由子类重载
     *
     * @param context 工作执行上下文对象
     * @param sysJob 系统计划任务
     * @throws Exception 执行过程中的异常
     */
    protected abstract void doExecute(JobExecutionContext context, MyJob sysJob) throws Exception;
}

CronUtils类

package com.li.SpringBootStart.utils;

import org.quartz.CronExpression;
import org.quartz.TriggerUtils;
import org.quartz.impl.triggers.CronTriggerImpl;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * cron表达式工具类
 * 
 * @author xiaoli
 *
 */
public class CronUtils
{
    /**
     * 返回一个布尔值代表一个给定的Cron表达式的有效性
     *
     * @param cronExpression Cron表达式
     * @return boolean 表达式是否有效
     */
    public static boolean isValid(String cronExpression)
    {
        return CronExpression.isValidExpression(cronExpression);
    }

    /**
     * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
     *
     * @param cronExpression Cron表达式
     * @return String 无效时返回表达式错误描述,如果有效返回null
     */
    public static String getInvalidMessage(String cronExpression)
    {
        try
        {
            new CronExpression(cronExpression);
            return null;
        }
        catch (ParseException pe)
        {
            return pe.getMessage();
        }
    }

    /**
     * 返回下一个执行时间根据给定的Cron表达式
     *
     * @param cronExpression Cron表达式
     * @return Date 下次Cron表达式执行时间
     */
    public static Date getNextExecution(String cronExpression)
    {
        try
        {
            CronExpression cron = new CronExpression(cronExpression);
            return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
        }
        catch (ParseException e)
        {
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * 通过表达式获取近10次的执行时间
     * 
     * @param cron 表达式
     * @return 时间列表
     */
    public static List<String> getRecentTriggerTime(String cron)
    {
        List<String> list = new ArrayList<String>();
        try
        {
            CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
            cronTriggerImpl.setCronExpression(cron);
            List<Date> dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 10);
            for (Date date : dates)
            {
                list.add("yyyy:MM:dd HH:mm:ss");
            }
        }
        catch (ParseException e)
        {
            return null;
        }
        return list;
    }
}

JobInvokeUtil(任务调用类)

package com.li.SpringBootStart.utils;

import com.li.SpringBootStart.commonUtils.SpringUtils;
import com.li.SpringBootStart.commonUtils.StringUtils;
import com.li.SpringBootStart.pojo.MyJob;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

/**
 * 任务执行工具
 *
 * @author ruoyi
 */
public class JobInvokeUtil
{
    /**
     * 执行方法
     *
     * @param sysJob 系统任务
     */
    public static void invokeMethod(MyJob sysJob) throws Exception
    {
        String invokeTarget = sysJob.getInvokeTarget();  //获取要执行的任务路径:如:ryTask.ryParams('ry')
        String beanName = getBeanName(invokeTarget);   //获取bean名称
        String methodName = getMethodName(invokeTarget); //获取方法名称
        List<Object[]> methodParams = getMethodParams(invokeTarget); //获取参数


        if (!isValidClassName(beanName))
        {

            Object bean = SpringUtils.getBean(beanName);
            invokeMethod(bean, methodName, methodParams);  //如果不是类调用,直接传入bean即可
        }
        else
        {
            Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
            invokeMethod(bean, methodName, methodParams);  //吐过是,那就需要根据名字来创建一个新的bean实例
        }
    }

    /**
     * 调用任务方法
     *
     * @param bean 目标对象
     * @param methodName 方法名称
     * @param methodParams 方法参数
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException
    {
        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
        {       //通过这个实例化的bean获取对象
            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));  //通过反射来调用最终的任务
        }
        else
        {
            Method method = bean.getClass().getMethod(methodName);
            method.invoke(bean);
        }
    }

    /**
     * 校验是否为为class包名
     * 
     * @param invokeTarget 名称
     * @return true是 false否
     */
    public static boolean isValidClassName(String invokeTarget)
    {
        return StringUtils.countMatches(invokeTarget, ".") > 1;  //出现.的次数大于一肯定是类引用,因为bean调用只会有一个.
    }

    /**
     * 获取bean名称
     * 
     * @param invokeTarget 目标字符串
     * @return bean名称
     */
    public static String getBeanName(String invokeTarget)
    {
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringBeforeLast(beanName, ".");
    }

    /**
     * 获取bean方法
     * 
     * @param invokeTarget 目标字符串
     * @return method方法
     */
    public static String getMethodName(String invokeTarget)
    {
        String methodName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringAfterLast(methodName, ".");
    }

    /**
     * 获取method方法参数相关列表
     * 
     * @param invokeTarget 目标字符串
     * @return method方法相关参数列表
     */
    public static List<Object[]> getMethodParams(String invokeTarget)
    {
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr))
        {
            return null;
        }
        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
        List<Object[]> classs = new LinkedList<>();
        for (int i = 0; i < methodParams.length; i++)
        {
            String str = StringUtils.trimToEmpty(methodParams[i]);
            // String字符串类型,以'或"开头
            if (StringUtils.startsWithAny(str, "'", "\""))
            {
                classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class });
            }
            // boolean布尔类型,等于true或者false
            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str))
            {
                classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
            }
            // long长整形,以L结尾
            else if (StringUtils.endsWith(str, "L"))
            {
                classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class });
            }
            // double浮点类型,以D结尾
            else if (StringUtils.endsWith(str, "D"))
            {
                classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class });
            }
            // 其他类型归类为整形
            else
            {
                classs.add(new Object[] { Integer.valueOf(str), Integer.class });
            }
        }
        return classs;
    }

    /**
     * 获取参数类型
     * 
     * @param methodParams 参数相关列表
     * @return 参数类型列表
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
    {
        Class<?>[] classs = new Class<?>[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams)
        {
            classs[index] = (Class<?>) os[1];
            index++;
        }
        return classs;
    }

    /**
     * 获取参数值
     * 
     * @param methodParams 参数相关列表
     * @return 参数值列表
     */
    public static Object[] getMethodParamsValue(List<Object[]> methodParams)
    {
        Object[] classs = new Object[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams)
        {
            classs[index] = (Object) os[0];
            index++;
        }
        return classs;
    }
}

QuartzDisallowConcurrentExecution类

package com.li.SpringBootStart.utils;

import com.li.SpringBootStart.pojo.MyJob;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
/**@author xiaoli
 *
 * 禁止并发的任务调度
 * */

@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob{
    @Override
    protected void doExecute(JobExecutionContext context, MyJob sysJob) throws Exception {
        JobInvokeUtil.invokeMethod(sysJob);
    }
}

QuarztJobExecution类

package com.li.SpringBootStart.utils;

import com.li.SpringBootStart.pojo.MyJob;
import org.quartz.JobExecutionContext;
/**
 * @author xiaoli
 * 运行并发的任务调度
 * */
public class QuartzJobExecution extends AbstractQuartzJob{
    @Override
    protected void doExecute(JobExecutionContext context, MyJob sysJob) throws Exception {
        //通过工具类里的反射调用最终方法
        JobInvokeUtil.invokeMethod(sysJob);

    }
}

ScheduleUtils(任务调度类)

package com.li.SpringBootStart.utils;

import com.li.SpringBootStart.common.ScheduleConstants;
import com.li.SpringBootStart.commonUtils.StringUtils;
import com.li.SpringBootStart.exception.TaskException;
import com.li.SpringBootStart.pojo.MyJob;
import org.quartz.*;

/**
 * @author xiaoli
 * quartz的工具类
 * 自己再封装一下
 * */
public class ScheduleUtils {
/**
 * @param myJob 任务实体类
 * */
    private static Class<?extends AbstractQuartzJob> getQuartzJobClass(MyJob myJob){

        boolean flag = "0".equals(myJob.getConcurrent());

        return flag ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;

    }
/**
 *  获取TriggerKey
 * */
public static TriggerKey getTriggerKey(Long jobId,String jobGroup){

    return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME+jobId,jobGroup);
}

/**
 * 获取jobKey
 * */
public static JobKey getJobKey(Long jobId,String jobGroup){
    return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME+jobId,jobGroup);
}

/**
 *创建定时任务
 *
 */

public static void createScheduleJob(Scheduler scheduler,MyJob myJob) throws SchedulerException {

    Long jobId = myJob.getJobId();



    String jobGroup = myJob.getJobGroup();

    //创建JobDetail

    JobDetail jobDetail = JobBuilder.newJob(getQuartzJobClass(myJob))
            .withIdentity(getJobKey(jobId,jobGroup))
            .usingJobData("target",myJob.getInvokeTarget())
            .build();

    //cron调度构建器
    CronScheduleBuilder cronScheduleBuilder =
            CronScheduleBuilder.cronSchedule(myJob.getCronExpression());

    //错误处理机制


    //创建新的trigger,并把调度构造器放进去

    Trigger trigger = TriggerBuilder.newTrigger()
            .withIdentity(getTriggerKey(jobId,jobGroup))
            .withSchedule(cronScheduleBuilder)
            .build();

    // 放入参数,运行时的方法可以获取
    jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, myJob);

    // 判断是否存在
    if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
    {
        // 防止创建时存在数据问题 先移除,然后在执行创建操作
        scheduler.deleteJob(getJobKey(jobId, jobGroup));
    }

    // 判断任务是否过期
    if (StringUtils.isNotNull(CronUtils.getNextExecution(myJob.getCronExpression())))
    {
        // 执行调度任务
        scheduler.scheduleJob(jobDetail, trigger); //添加进去任务



    }

    // 暂停任务
    if (myJob.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
    {
        scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
    }

}
    public CronScheduleBuilder handleCronScheduleMisfirePolicy(MyJob job,CronScheduleBuilder cb) throws TaskException {

        switch (job.getMisfirePolicy()){
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb; //直接返回了,不需要break

            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();

            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();

            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();

            default:
                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
                        + "' cannot be used in cron schedule tasks", TaskException.Code.CONFIG_ERROR);

        }




    }



}

总体思路:

1.所有的任务都存储在数据库
2.从数据库取出任务明细,可以从中知道任务的目标,并发,执行状态和丢失策略。
3.根据是否并发就可以反射获得继承了抽象job类的两个类里的一个确定的对象
4.根据jobId和jobGroup就可以创建一个jobKey,以此来创建一个新的定时任务即(jobDetail)
5.运行继承了抽象job类的子类,其子类内部又通过传进来的参数(invokeTarget)反射获取到真实要执行的任务类,并调用该任务!!!
6.如此便任务调用完毕,切记传参,不然可能无法真实调用任务

JobDetail jobDetail = JobBuilder.newJob(getQuartzJobClass(myJob)) .withIdentity(getJobKey(jobId,jobGroup)) .usingJobData("target",myJob.getInvokeTarget()) .build();

如这段代码,必须传进去target这个参数,不然拿不到真实任务,是个大坑,纠错了五个小时!!!

以上内容就是定时任务的预先准备,这些都配置好了,才开始crud

9.编写pojo

MyJob类

package com.li.SpringBootStart.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


import java.io.Serializable;
/**
* @author xiaoli
* 任务调度表(实体类)
* */
@Data
@NoArgsConstructor
public class MyJob implements Serializable {
//任务id
    private long jobId;
//任务名称
    private String jobName;
//任务组
    private String jobGroup;
//任务真正调用的方法
    private String invokeTarget;
//cron表达式
    private String cronExpression;
//文件措施策略
    private String misfirePolicy;
//是否并发 0允许,1禁止
    private String concurrent;

    private String status;

    private String updateBy;

    private String createBy;

    private String remark;

    public MyJob(String jobName, String jobGroup, String invokeTarget, String cronExpression, String misfirePolicy, String concurrent, String status) {
        this.jobName = jobName;
        this.jobGroup = jobGroup;
        this.invokeTarget = invokeTarget;
        this.cronExpression = cronExpression;
        this.misfirePolicy = misfirePolicy;
        this.concurrent = concurrent;
        this.status = status;
    }
}

10.mapper层 (Dao层)

SysJobMapper类

package com.li.SpringBootStart.mapper;

import com.li.SpringBootStart.pojo.MyJob;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;
@Mapper
public interface SysJobMapper
{
    /**
     * 查询调度任务日志集合
     * 
     * @param job 调度信息
     * @return 操作日志集合
     * 模糊查询,那个不空查那个
     */
    public List<MyJob> selectJobList(MyJob job);

    /**
     * 查询所有调度任务
     * 
     * @return 调度任务列表
     */
    public List<MyJob> selectJobAll();

    /**
     * 通过调度ID查询调度任务信息
     * 
     * @param jobId 调度ID
     * @return 角色对象信息
     */
    public MyJob selectJobById(Long jobId);

    /**
     * 通过调度ID删除调度任务信息
     * 
     * @param jobId 调度ID
     * @return 结果
     */
    public int deleteJobById(Long jobId);

    /**
     * 批量删除调度任务信息
     * 
     * @param ids 需要删除的数据ID
     * @return 结果
     */
    public int deleteJobByIds(Long[] ids);

    /**
     * 修改调度任务信息
     *
        * @param job 调度任务信息
     * @return 结果
     */
    public int updateJob(MyJob job);

    /**
     * 新增调度任务信息
     *
     * @param job 调度任务信息
     * @return 结果
     */
    public int insertJob(MyJob job);
}

SysJobmapper.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.li.SpringBootStart.mapper.SysJobMapper">
    <sql id="common">
        select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark from sys_job
    </sql>

    <select id="selectJobList" parameterType="MyJob" resultType="MyJob">
        <include refid="common"/>

        <where>
            <if test="jobName != null and jobName != ''">
                AND job_name like concat('%', #{jobName}, '%')
            </if>
            <if test="jobGroup != null and jobGroup != ''">
                AND job_group = #{jobGroup}
            </if>
            <if test="status != null and status != ''">
                AND status = #{status}
            </if>
            <if test="invokeTarget != null and invokeTarget != ''">
                AND invoke_target like concat('%', #{invokeTarget}, '%')
            </if>
        </where>


    </select>


    <select id="selectJobAll" resultType="MyJob">
        <include refid="common"/>
    </select>

    <select id="selectJobById" parameterType="Long" resultType="MyJob">
        <include refid="common"/>
    where job_id = #{jobId}
    </select>

    <delete id="deleteJobById" parameterType="Long">
        delete from sys_job where job_id = #{jobId}
    </delete>



    <!--删除一个id集合里的任务-->
    <delete id="deleteJobByIds" parameterType="Long">
        delete from sys_job where job_id in
                            <foreach collection="array" item="jobId"  open="("
                                     separator="," close=")">
                                #{jobId}
                            </foreach>
    </delete>


    <update id="updateJob" parameterType="MyJob">
        update sys_job  set job_name = #{jobName},
                            job_group = #{jobGroup},
                            invoke_target = #{invokeTarget},
                            cron_expression = #{cronExpression},
                            misfire_policy = #{misfirePolicy},
                            concurrent = #{concurrent},
                            status = #{status},
                            update_by = #{updateBy},
                            update_time = sysdate()
        where job_id = #{jobId}
    </update>

    <insert id="insertJob" parameterType="MyJob">
        insert into sys_job(job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark) VALUE
        (#{jobId}, #{jobName}, #{jobGroup}, #{invokeTarget}, #{cronExpression}, #{misfirePolicy}, #{concurrent}, #{status}, #{createBy}, sysdate() , #{remark})
    </insert>

</mapper>

11.Service层

ISysJobService接口

package com.li.SpringBootStart.service;

import java.util.List;

import com.li.SpringBootStart.exception.TaskException;
import com.li.SpringBootStart.pojo.MyJob;
import org.quartz.SchedulerException;


/**
 * 定时任务调度信息信息 服务层
 * 
 * @author xiaoli
 */
public interface ISysJobService
{
    /**
     * 获取quartz调度器的计划任务
     * 
     * @param job 调度信息
     * @return 调度任务集合
     */
    public List<MyJob> selectJobList(MyJob job);


    public List<MyJob> selectJobAll();

    /**
     * 通过调度任务ID查询调度信息
     * 
     * @param jobId 调度任务ID
     * @return 调度任务对象信息
     */
    public MyJob selectJobById(Long jobId);

    /**
     * 暂停任务
     * 
     * @param job 调度信息
     * @return 结果
     */
    public int pauseJob(MyJob job) throws SchedulerException;

    /**
     * 恢复任务
     * 
     * @param job 调度信息
     * @return 结果
     */
    public int resumeJob(MyJob job) throws SchedulerException;

    /**
     * 删除任务后,所对应的trigger也将被删除
     * 
     * @param job 调度信息
     * @return 结果
     */
    public int deleteJob(MyJob job) throws SchedulerException;

    /**
     * 批量删除调度信息
     * 
     * @param ids 需要删除的数据ID
     * @return 结果
     */
    public void deleteJobByIds(String ids) throws SchedulerException;

    /**
     * 任务调度状态修改
     * 
     * @param job 调度信息
     * @return 结果
     */
    public int changeStatus(MyJob job) throws SchedulerException;

    /**
     * 立即运行任务
     * 
     * @param job 调度信息
     * @return 结果
     */
    public boolean run(MyJob job) throws SchedulerException;

    /**
     * 新增任务
     * 
     * @param job 调度信息
     * @return 结果
     */
    public int insertJob(MyJob job) throws SchedulerException, TaskException;

    /**
     * 更新任务
     * 
     * @param job 调度信息
     * @return 结果
     */
    public int updateJob(MyJob job) throws SchedulerException, TaskException;

    /**
     * 校验cron表达式是否有效
     * 
     * @param cronExpression 表达式
     * @return 结果
     */
    public boolean checkCronExpressionIsValid(String cronExpression);
}

ISysJobServiceImpl

package com.li.SpringBootStart.service;

import com.li.SpringBootStart.common.ScheduleConstants;
import com.li.SpringBootStart.exception.TaskException;
import com.li.SpringBootStart.mapper.SysJobMapper;
import com.li.SpringBootStart.pojo.MyJob;
import com.li.SpringBootStart.text.Convert;
import com.li.SpringBootStart.utils.CronUtils;
import com.li.SpringBootStart.utils.ScheduleUtils;
import jakarta.annotation.PostConstruct;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import java.util.List;
@Service
public class ISysJobServiceImpl implements ISysJobService{
    @Autowired
    SysJobMapper jobMapper;

    @Autowired
    Scheduler scheduler;  //注入调度器


    /**
     * 项目启动时,初始化定时器
     * 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
     */
    @PostConstruct  //依赖注入完会被自动调用!
    public void init() throws SchedulerException, TaskException
    {
        scheduler.clear();
        List<MyJob> jobList = jobMapper.selectJobAll();  //读取数据库所有任务

        for (MyJob job : jobList)
        {
            ScheduleUtils.createScheduleJob(scheduler, job);  //创建定时任务
        }
    }
    @Override
    public List<MyJob> selectJobList(MyJob job) {
        return jobMapper.selectJobList(job);
    }

    @Override
    public List<MyJob> selectJobAll() {
        return jobMapper.selectJobAll();
    }

    @Override
    public MyJob selectJobById(Long jobId) {
        return jobMapper.selectJobById(jobId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int pauseJob(MyJob job) throws SchedulerException {

        Long jobId = job.getJobId();

        String jobGroup = job.getJobGroup();

        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());  //将状态改为暂停

        int row = jobMapper.updateJob(job);// 将更新完后的信息同步到数据库

        if(row>0){
            //如果成功了

            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId,jobGroup)); //通过调度器正式暂停该任务

        }

        return row;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int resumeJob(MyJob job) throws SchedulerException {
        Long jobId = job.getJobId();

        String jobGroup = job.getJobGroup();

        job.setStatus(ScheduleConstants.Status.NORMAL.getValue());

        int row = jobMapper.updateJob(job);

        if(row>0){

            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId,jobGroup));


        }
        //逻辑与暂停是一样的
        return row;
    }

    /**
     * 删除任务后,所对应的trigger也将被删除
     *
     * @param job 调度信息
     */

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteJob(MyJob job) throws SchedulerException {
        //因为要删除Trigger,所以不直接调用Mapper的delete方法
        Long jobId = job.getJobId();

        String jobGroup = job.getJobGroup();

        int row = jobMapper.deleteJobById(jobId);//从数据库删去

        if(row>0){

            scheduler.deleteJob(ScheduleUtils.getJobKey(jobId,jobGroup));
        }

        return row;

    }
    /**
     * 批量删除调度信息
     *
     * @param ids 需要删除的数据ID
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteJobByIds(String ids) throws SchedulerException {
        Long []ides = Convert.toLongArray(ids);

        for (Long id:ides){

            MyJob job = jobMapper.selectJobById(id);

            deleteJob(job);  //调用上面的方法
        }
    }
/**
 * 暂停变为恢复
 * 恢复变为暂停
 * */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int changeStatus(MyJob job) throws SchedulerException {
        String status = job.getStatus();
        int row;
        if(status.equals(ScheduleConstants.Status.NORMAL.getValue())){
            //正常变为暂停
             row = pauseJob(job);
        }
        else
        {   //恢复
             row = resumeJob(job);
        }

        return row;
    }
    /**
     * 立即运行任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean run(MyJob job) throws SchedulerException {
        boolean result = false;
        Long jobId = job.getJobId();
        MyJob tmpObj = selectJobById(job.getJobId());
        // 参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(ScheduleConstants.TASK_PROPERTIES, tmpObj);
        JobKey jobKey = ScheduleUtils.getJobKey(jobId, tmpObj.getJobGroup());//生成jobKey
        if (scheduler.checkExists(jobKey))//如果存在这个jobKey的作业
        {
            result = true;
            scheduler.triggerJob(jobKey, dataMap); //执行任务

        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertJob(MyJob job) throws SchedulerException, TaskException {
//        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());//默认状态为暂停,手动启动

        int row = jobMapper.insertJob(job);//存储到数据库

        if(row>0){
            ScheduleUtils.createScheduleJob(scheduler,job); //创建任务
        }

        return row;
    }


    /**
     * 更新任务的时间表达式
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateJob(MyJob job) throws SchedulerException, TaskException {
        //先查询保存旧的数据,根据id,创建一个新对象(类似复制)
        MyJob job1 = jobMapper.selectJobById(job.getJobId());

        int rows = jobMapper.updateJob(job);//更新数据库的这个传入的新的数据

        if(rows>0){

            updateSchedulerJob(job, job1.getJobGroup());  //调用自定义方法进行更新
        }

        return rows;
    }


    /**
     * 更新任务
     *
     * @param job 任务对象
     * @param jobGroup 任务组名
     *
     * 先删除再创建
     */
    public void updateSchedulerJob(MyJob job, String jobGroup) throws SchedulerException, TaskException
    {
        Long jobId = job.getJobId();
        // 判断是否存在
        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
        if (scheduler.checkExists(jobKey))
        {
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(jobKey);
        }
        ScheduleUtils.createScheduleJob(scheduler, job);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean checkCronExpressionIsValid(String cronExpression) {
        boolean valid = CronUtils.isValid(cronExpression);

        return valid;
    }
}

12.Controller层

MyJobController类

package com.li.SpringBootStart.cotroller;

import com.li.SpringBootStart.exception.TaskException;
import com.li.SpringBootStart.pojo.MyJob;
import com.li.SpringBootStart.service.ISysJobService;

import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author xiaoli
 *
 * */

@Controller
public class MyJobController {
    @Autowired
    ISysJobService service;


    /**
     *
     * 起始页
     * */
    @RequestMapping({"/","/index"})
    public String toIndex(Model model){

        List<MyJob> myJobs = service.selectJobAll();

        model.addAttribute("jobs",myJobs);


        return "index";
    }

    /**
     * 按ID查询
     * */

    @GetMapping("/selectById")
    public String selectById(@RequestParam("id") String id,Model model){
        long id1 = Long.parseLong(id);

        MyJob job = service.selectJobById(id1);

        model.addAttribute("jobs",job);

        return "index";
    }

    /**
     * 按名字模糊查询
     * */

    @GetMapping("/selectByName")
    public String selectByName(MyJob myJob,Model model){

        List<MyJob> jobs = service.selectJobList(myJob);//按照job模糊查询

        model.addAttribute("jobs",jobs);

        return "index";
    }

    /**
     * 跳转到添加页面
     * */

    @RequestMapping("/toAdd")
    public String toAdd(){

        return "add";
    }

    /**
     * 正式添加
     *
     */

    @PostMapping("/add")

    public String add(MyJob myJob) throws SchedulerException, TaskException {

        if(myJob.getStatus()==null){
            myJob.setStatus("1");
        }
        else
        {
            myJob.setStatus("0");  //根据按钮改变运行状态
        }

        myJob.setUpdateBy(myJob.getCreateBy());


        service.insertJob(myJob); //添加该任务




        return "redirect:/";


    }

    /**
     *跳转到更新页面
     *
     *   <a th:href="@{/toUpdate(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue"
     *               >编辑
     *            </a>  这样携带前端数据
     * */

    @GetMapping("/toUpdate")

    public String toUpdate(@RequestParam("jobId") Long jobId,Model model){
        //携带查询出来的数据并跳转到更新页面
        MyJob job = service.selectJobById(jobId);

        model.addAttribute("job",job);

        return "update";
    }

    /**
     * 更新
     * */

    @PostMapping("/update")

    public String update(MyJob myJob) throws SchedulerException, TaskException {


        if(myJob.getStatus()==null){
            myJob.setStatus("1");
        }
        else
        {
            myJob.setStatus("0");  //根据按钮改变运行状态
        }

        myJob.setUpdateBy("xiaoli");

        service.updateJob(myJob);//其实是先删除后添加!



        return "redirect:/";
    }

    /**
     * 删除
     * */

    @RequestMapping("/delete")
    public String delete(@RequestParam("jobId") Long jobId) throws SchedulerException {

        MyJob job = service.selectJobById(jobId); //先选后删除

        service.deleteJob(job);




        return "redirect:/";
    }


    /**
     * 运行
     * */
    @RequestMapping("/resume")
    public String resume(@RequestParam("jobId") Long jobId) throws SchedulerException {

        MyJob job = service.selectJobById(jobId);

        service.resumeJob(job);

        return "redirect:/";
    }




    /***
     *暂停
     */
    @RequestMapping("/pause")
    public String pause(@RequestParam("jobId") Long jobId) throws SchedulerException {

        MyJob job = service.selectJobById(jobId);

        service.pauseJob(job);

        return "redirect:/";
    }

}

13.前端的编写(不包含css需自己找)

add.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Demo</title>
    <!-- 请勿在项目正式环境中引用该 layui.css 地址 -->
    <link href="/css/layui.css" rel="stylesheet">
</head>
<body>
<form class="layui-form" action="/add" method="post">
    <div class="layui-form-item">
        <label class="layui-form-label">任务名称</label>
        <div class="layui-input-block">
            <input type="text" name="jobName" placeholder="请输入"  lay-verify="required"
                   class="layui-input">
        </div>
    </div>

    <div class="layui-form-item">
        <label class="layui-form-label">任务组</label>
        <div class="layui-input-block">
            <input type="text" name="jobGroup" placeholder="请输入"  lay-verify="required"
                   class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">任务目标</label>
        <div class="layui-input-block">
            <input type="text" name="invokeTarget" placeholder="请输入"  lay-verify="required"
                   class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">cron表达式</label>
        <div class="layui-input-block">
            <input type="text" name="cronExpression" placeholder="请输入"  lay-verify="required"
                   class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">作者</label>
        <div class="layui-input-block">
            <input type="text" name="createBy" placeholder="请输入"  lay-verify="required"
                   class="layui-input">
        </div>
    </div>


    <div class="layui-form-item">
        <label class="layui-form-label">哪种策略</label>
        <div class="layui-input-inline">
            <select name="misfirePolicy" lay-filter="aihao">
                <option value="0" selected>默认</option>
                <option value="1">立即触发执行</option>
                <option value="2">触发一次执行</option>
                <option value="3">不触发立即执行</option>
            </select>
        </div>
    </div>


    <div class="layui-form-item">
        <label class="layui-form-label">是否运行</label>
        <div class="layui-input-block">
            <input type="checkbox" name="status" lay-skin="switch" lay-filter="switchTest"
                   title="ON|OFF"
            >
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">是否并发</label>
        <div class="layui-input-block">
            <input type="radio" name="concurrent" value="0" title="是">
            <input type="radio" name="concurrent" value="1" title="否"checked>
        </div>
    </div>

    <div class="layui-form-item">
        <div class="layui-input-block">
            <button type="submit" class="layui-btn" lay-submit lay-filter="demo1">立即提交</button>
            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
        </div>
    </div>
</form>

<!-- 请勿在项目正式环境中引用该 layui.js 地址 -->
<script src="/layui.js"></script>


</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>定时任务管理系统</title>
<meta name="author" content="DeathGhost" />
<link rel="stylesheet" type="text/css" href="css/style.css" />
    <link href="/css/layui.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="js/html5.js"></script>
<![endif]-->
<script src="js/jquery.js"></script>
<script src="js/jquery.mCustomScrollbar.concat.min.js"></script>
<script>
	(function($){
		$(window).load(function(){

			$("a[rel='load-content']").click(function(e){
				e.preventDefault();
				var url=$(this).attr("href");
				$.get(url,function(data){
					$(".content .mCSB_container").append(data); //load new content inside .mCSB_container
					//scroll-to appended content
					$(".content").mCustomScrollbar("scrollTo","h2:last");
				});
			});

			$(".content").delegate("a[href='top']","click",function(e){
				e.preventDefault();
				$(".content").mCustomScrollbar("scrollTo",$(this).attr("href"));
			});

		});
	})(jQuery);
</script>
</head>
<body>
<!--header-->
<header>
 <h1>定时任务系统</h1>
 <ul class="rt_nav">

 </ul>
</header>


 <ul>






 </ul>
</aside>

<section class="rt_wrap content mCustomScrollbar">
 <div class="rt_content">






      <section>
      <h2><strong style="color:grey;">按照ID查询</strong></h2>
       <form th:action="@{/selectById}" method="get">
        <input type="text" class="textbox" name="id" placeholder="任务ID" required/>
        <input type="submit" value="查询" class="group_btn"/>
       </form>
          <br>
          <h2><strong style="color:grey;">多条件查询</strong></h2>
        <form th:action="@{/selectByName}" method="get">
            <label for="2">任务名称</label>
            <input id="2" type="text" class="textbox" name="jobID" />

           <label for="1">正在执行</label>
            <select id="1"  name="status">
                <option value="0">是</option>
                <option value="1">否</option>
            </select>
            <input type="submit" value="查询" class="group_btn"/>
        </form>

     </section>
     <br>
     <section>
      <h2><strong>任务列表</strong>

      <a th:href="@{/toAdd}" class="layui-btn layui-bg-orange" >添加</a>
      </h2>
         <br>

      <table class="table">
       <thead>
       <tr>
        <th>任务ID</th>
        <th>任务名称</th>
        <th>任务组</th>
        <th>任务目标</th>
        <th>cron表达式</th>
        <th>丢失策略</th>
        <th>是否并发</th>
        <th>是否执行</th>
        <th>更新者</th>
        <th>操作</th>
       </tr>
</thead>
       <tbody>
  <tr th:each="job:${jobs}">
 <td  th:text="${job.getJobId()}"></td>
   <td th:text="${job.getJobName()}"></td>
       <td th:text="${job.getJobGroup()}"></td>
       <td th:text="${job.getInvokeTarget()}"></td>
       <td th:text="${job.getCronExpression()}"></td>
       <td th:text="${job.getMisfirePolicy()}"></td>
       <td th:text="${job.getConcurrent()=='0' ? '是':'否'}"></td>
       <td th:text="${job.getStatus()=='0' ? '是':'否'}"></td>
       <td th:text="${job.getUpdateBy()}"></td>
   <td>
        <div class="layui-btn-group">
           <a th:href="@{/toUpdate(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue"
              >编辑
           </a>
           <a th:href="@{/delete(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue"
              lay-on="confirm">
               删除
           </a>



       <a th:href="@{/pause(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue">
           暂停
       </a>

       <a th:href="@{/resume(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue">
           运行
       </a>
</div>
   </td>


  </tr>


       </tbody>


      </table>
<!--      <aside class="paging">-->
<!--       <a>第一页</a>-->
<!--       <a>1</a>-->
<!--       <a>2</a>-->
<!--       <a>3</a>-->
<!--       <a>…</a>-->
<!--       <a>1004</a>-->
<!--       <a>最后一页</a>-->
<!--      </aside>-->
     </section>

    <script src="/js/jquery.min.js"></script>
     <script>
         layui.use(function(){
             var layer = layui.layer;
             var util = layui.util;
             // 批量事件
             util.on('lay-on', {
                 alert: function(){
                     layer.alert('对话框内容');
                 },
                 confirm: function(){
                     layer.confirm('一个询问框的示例?', {
                         btn: ['确定', '关闭'] //按钮
                     }, function(){
                         layer.msg('第一个回调', {icon: 1});
                     }, function(){
                         layer.msg('第二个回调', {
                             time: 20000, // 20s 后自动关闭
                             btn: ['明白了', '知道了']
                         });
                     });
                 },
                 msg: function(){
                     layer.msg('一段提示信息');
                 },
                 page: function(){
                     // 页面层
                     layer.open({
                         type: 1,
                         area: ['420px', '240px'], // 宽高
                         content: '<div style="padding: 11px;">任意 HTML 内容</div>'
                     });
                 },
                 iframe: function(){
                     // iframe 层
                     layer.open({
                         type: 2,
                         title: 'iframe test',
                         shadeClose: true,
                         shade: 0.8,
                         area: ['380px', '80%'],
                         content: '/layer/test/1.html' // iframe 的 url
                     });
                 },
                 load: function(){
                     var index = layer.load(0, {shade: false});
                     setTimeout(function(){
                         layer.close(index); // 关闭 loading
                     }, 5000);
                 },
                 tips: function(){
                     layer.tips('一个 tips 层', this, {
                         tips: 1
                     });
                 },
                 prompt: function(){
                     layer.prompt({title: '密令输入框', formType: 1}, function(pass, index){
                         layer.close(index);
                         layer.prompt({title: '文本输入框', formType: 2}, function(text, index){
                             layer.close(index);
                             alert('您输入的密令:'+ pass +';文本:'+ text);
                         });
                     });
                 },
                 photots: function(){
                     layer.photos({
                         photos: {
                             "title": "Photos Demo",
                             "start": 0,
                             "data": [
                                 {
                                     "alt": "layer",
                                     "pid": 1,
                                     "src": "https://unpkg.com/[email protected]/demo/layer.png",
                                 },
                                 {
                                     "alt": "壁纸",
                                     "pid": 3,
                                     "src": "https://unpkg.com/[email protected]/demo/000.jpg",
                                 },
                                 {
                                     "alt": "浩瀚宇宙",
                                     "pid": 5,
                                     "src": "https://unpkg.com/[email protected]/demo/outer-space.jpg",
                                 }
                             ]
                         }
                     });
                 }
             });
         });
     </script>

</body>
</html>

update.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>定时任务管理系统</title>
<meta name="author" content="DeathGhost" />
<link rel="stylesheet" type="text/css" href="css/style.css" />
    <link href="/css/layui.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="js/html5.js"></script>
<![endif]-->
<script src="js/jquery.js"></script>
<script src="js/jquery.mCustomScrollbar.concat.min.js"></script>
<script>
	(function($){
		$(window).load(function(){

			$("a[rel='load-content']").click(function(e){
				e.preventDefault();
				var url=$(this).attr("href");
				$.get(url,function(data){
					$(".content .mCSB_container").append(data); //load new content inside .mCSB_container
					//scroll-to appended content
					$(".content").mCustomScrollbar("scrollTo","h2:last");
				});
			});

			$(".content").delegate("a[href='top']","click",function(e){
				e.preventDefault();
				$(".content").mCustomScrollbar("scrollTo",$(this).attr("href"));
			});

		});
	})(jQuery);
</script>
</head>
<body>
<!--header-->
<header>
 <h1>定时任务系统</h1>
 <ul class="rt_nav">

 </ul>
</header>


 <ul>






 </ul>
</aside>

<section class="rt_wrap content mCustomScrollbar">
 <div class="rt_content">






      <section>
      <h2><strong style="color:grey;">按照ID查询</strong></h2>
       <form th:action="@{/selectById}" method="get">
        <input type="text" class="textbox" name="id" placeholder="任务ID" required/>
        <input type="submit" value="查询" class="group_btn"/>
       </form>
          <br>
          <h2><strong style="color:grey;">多条件查询</strong></h2>
        <form th:action="@{/selectByName}" method="get">
            <label for="2">任务名称</label>
            <input id="2" type="text" class="textbox" name="jobID" />

           <label for="1">正在执行</label>
            <select id="1"  name="status">
                <option value="0">是</option>
                <option value="1">否</option>
            </select>
            <input type="submit" value="查询" class="group_btn"/>
        </form>

     </section>
     <br>
     <section>
      <h2><strong>任务列表</strong>

      <a th:href="@{/toAdd}" class="layui-btn layui-bg-orange" >添加</a>
      </h2>
         <br>

      <table class="table">
       <thead>
       <tr>
        <th>任务ID</th>
        <th>任务名称</th>
        <th>任务组</th>
        <th>任务目标</th>
        <th>cron表达式</th>
        <th>丢失策略</th>
        <th>是否并发</th>
        <th>是否执行</th>
        <th>更新者</th>
        <th>操作</th>
       </tr>
</thead>
       <tbody>
  <tr th:each="job:${jobs}">
 <td  th:text="${job.getJobId()}"></td>
   <td th:text="${job.getJobName()}"></td>
       <td th:text="${job.getJobGroup()}"></td>
       <td th:text="${job.getInvokeTarget()}"></td>
       <td th:text="${job.getCronExpression()}"></td>
       <td th:text="${job.getMisfirePolicy()}"></td>
       <td th:text="${job.getConcurrent()=='0' ? '是':'否'}"></td>
       <td th:text="${job.getStatus()=='0' ? '是':'否'}"></td>
       <td th:text="${job.getUpdateBy()}"></td>
   <td>
        <div class="layui-btn-group">
           <a th:href="@{/toUpdate(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue"
              >编辑
           </a>
           <a th:href="@{/delete(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue"
              lay-on="confirm">
               删除
           </a>



       <a th:href="@{/pause(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue">
           暂停
       </a>

       <a th:href="@{/resume(jobId=${job.getJobId()})}" class="layui-btn layui-bg-blue">
           运行
       </a>
</div>
   </td>


  </tr>


       </tbody>


      </table>
<!--      <aside class="paging">-->
<!--       <a>第一页</a>-->
<!--       <a>1</a>-->
<!--       <a>2</a>-->
<!--       <a>3</a>-->
<!--       <a>…</a>-->
<!--       <a>1004</a>-->
<!--       <a>最后一页</a>-->
<!--      </aside>-->
     </section>

    <script src="/js/jquery.min.js"></script>
     <script>
         layui.use(function(){
             var layer = layui.layer;
             var util = layui.util;
             // 批量事件
             util.on('lay-on', {
                 alert: function(){
                     layer.alert('对话框内容');
                 },
                 confirm: function(){
                     layer.confirm('一个询问框的示例?', {
                         btn: ['确定', '关闭'] //按钮
                     }, function(){
                         layer.msg('第一个回调', {icon: 1});
                     }, function(){
                         layer.msg('第二个回调', {
                             time: 20000, // 20s 后自动关闭
                             btn: ['明白了', '知道了']
                         });
                     });
                 },
                 msg: function(){
                     layer.msg('一段提示信息');
                 },
                 page: function(){
                     // 页面层
                     layer.open({
                         type: 1,
                         area: ['420px', '240px'], // 宽高
                         content: '<div style="padding: 11px;">任意 HTML 内容</div>'
                     });
                 },
                 iframe: function(){
                     // iframe 层
                     layer.open({
                         type: 2,
                         title: 'iframe test',
                         shadeClose: true,
                         shade: 0.8,
                         area: ['380px', '80%'],
                         content: '/layer/test/1.html' // iframe 的 url
                     });
                 },
                 load: function(){
                     var index = layer.load(0, {shade: false});
                     setTimeout(function(){
                         layer.close(index); // 关闭 loading
                     }, 5000);
                 },
                 tips: function(){
                     layer.tips('一个 tips 层', this, {
                         tips: 1
                     });
                 },
                 prompt: function(){
                     layer.prompt({title: '密令输入框', formType: 1}, function(pass, index){
                         layer.close(index);
                         layer.prompt({title: '文本输入框', formType: 2}, function(text, index){
                             layer.close(index);
                             alert('您输入的密令:'+ pass +';文本:'+ text);
                         });
                     });
                 },
                 photots: function(){
                     layer.photos({
                         photos: {
                             "title": "Photos Demo",
                             "start": 0,
                             "data": [
                                 {
                                     "alt": "layer",
                                     "pid": 1,
                                     "src": "https://unpkg.com/[email protected]/demo/layer.png",
                                 },
                                 {
                                     "alt": "壁纸",
                                     "pid": 3,
                                     "src": "https://unpkg.com/[email protected]/demo/000.jpg",
                                 },
                                 {
                                     "alt": "浩瀚宇宙",
                                     "pid": 5,
                                     "src": "https://unpkg.com/[email protected]/demo/outer-space.jpg",
                                 }
                             ]
                         }
                     });
                 }
             });
         });
     </script>

</body>
</html>

成果展示

小结:

​ 这个笔记里只是实现类最基本的功能,甚至连服务层的很多功能都没有在控制层调用,但是基本的事物都能完成了,希望大家一起来学习和补充!

一些用到的的注解

@PostConstruct 注解:这个注解用于标注在方法上,表明该方法是在依赖注入完成后被自动调用的初始化方法。这是 Java EE 5 引入的,但在 Spring 框架中也得到了广泛的支持。

@Transactional 注解是Spring框架中用于声明式事务管理的一个关键注解。它可以被应用于类级别或方法级别,以指示被注解的方法或类中的所有方法应该运行在事务环境中。这意味着,当这些方法执行时,Spring容器会创建一个事务边界,并在方法执行成功时提交事务,或者在方法执行失败时回滚事务。

使用@Transactional注解的好处是,它提供了一种非侵入式的方式来管理事务,即你不需要在代码中显式地编写事务管理代码(如开启事务、提交事务、回滚事务等),而是将这些工作交给Spring容器来自动完成。

......