JFinal+Quartz动态任务调度控制台

师腾
2023-12-01

前言】因为项目需要后台动态调度 远程服务的任务,其中动态调度选用Quartz调度框架,远程服务还是沿用原来的Hessian,整合到JFinal后台(客户端)。本文主要记录JFinal下的Quartz控制台搭建,实现任务新建、修改和启停基本功能。

【正文】
参考资料(来源:JFinal中使用QuartzManager实现动态定时任务)感谢JFinal社区@wangqian0628

maven依赖(其他的就是JFinal项目通用依赖)

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.1</version>
</dependency>

根据实际需求,对参考资料做了修改,以下是修改后的工具类:

package top.rushpeak.edu03.admin.util;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import java.util.List;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;

import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;

import top.rushpeak.edu03.admin.job.DynamicJob;//这个是后面的任务实现类

public class QuartzManager {

    private Logger log = LogManager.getLogger(QuartzManager.class);

    private Scheduler scheduler = null;

    public QuartzManager() {
        try {
            scheduler = new StdSchedulerFactory().getScheduler();
            log.info("初始化调度器 ");
        } catch (SchedulerException ex) {
            log.error("初始化调度器=> [失败]:" + ex.getLocalizedMessage());
        }
    }
    //初始化启动任务
    public void initJob(){

        List<Record> jobs = Db.find("SELECT * FROM job_manager WHERE 1=1 AND is_enabled = 'Y'");

        for(Record job:jobs){
            if("Y".equals(job.getStr("is_enabled"))){
                String className = job.getStr("class");
                Class<? extends Job> jobClazz = null;
                try {
                    jobClazz = Class.forName(className).asSubclass(Job.class);
                } catch (Exception e) {
                    System.out.println(className+"没有继承job,e=="+e);
                    log.error(className+"没有继承job,e=="+e);
                    continue;
                }
                String name = job.getStr("name");
                String group = job.getStr("group");
                String cronExpression = job.getStr("cron_expression");
                this.addJob(name, group, jobClazz, cronExpression);

            }
        }
        this.start();

    }

     //添加任务
    public void addJob(String name, String group, Class<? extends Job> clazz, String cronExpression) {
        try {
            // 构造任务
            JobDetail job = newJob(clazz).withIdentity(name, group).build();
            // 构造任务触发器
            Trigger trg = newTrigger().withIdentity(name, group).withSchedule(cronSchedule(cronExpression)).build();
            // 将作业添加到调度器
            scheduler.scheduleJob(job, trg);
            log.info("创建作业=> [作业名称:" + name + " 作业组:" + group + "] ");
            System.out.println("创建作业=> [作业名称:" + name + " 作业组:" + group + "] ");
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("创建作业=> [作业名称:" + name + " 作业组:" + group + "]=> [失败]");
        }
    }
     //移除任务
    public void removeJob(String name, String group) {
        try {
            TriggerKey tk = TriggerKey.triggerKey(name, group);
            scheduler.pauseTrigger(tk);// 停止触发器
            scheduler.unscheduleJob(tk);// 移除触发器
            JobKey jobKey = JobKey.jobKey(name, group);
            scheduler.deleteJob(jobKey);// 删除作业
            log.info("删除作业=> [作业名称:" + name + " 作业组:" + group + "] ");
            System.out.println("删除作业=> [作业名称:" + name + " 作业组:" + group + "] ");
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("删除作业=> [作业名称:" + name + " 作业组:" + group + "]=> [失败]");
        }
    }

    public void start() {
        try {
            scheduler.start();
            log.info("启动调度器 ");
            System.out.println("启动调度器 ");
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("启动调度器=> [失败]");
        }
    }

    public void shutdown() {
        try {
            scheduler.shutdown();
            log.info("停止调度器 ");
            System.out.println("停止调度器 ");
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("停止调度器=> [失败]");
        }
    }
     //测试
    public static void main(String[] args) throws InterruptedException{
        QuartzManager qm = new QuartzManager();
        String name = "DynamicJobName";
        String group = "DynamicJobGroup";
        String cronExpression = "*/3 * * * * ?";
        String className = "top.rushpeak.edu03.admin.job.DynamicJob";
        Class<? extends Job> jobClazz = null;
        try {
            jobClazz = Class.forName(className).asSubclass(Job.class);
        } catch (Exception e) {
            System.out.println(className+"没有继承job,e=="+e);
        }
        if(jobClazz==null){
            System.exit(0);
        }
        qm.addJob(name, group, jobClazz, cronExpression);
        qm.start();
        Thread.sleep(7000);
        qm.removeJob(name, group);
        Thread.sleep(3000);
        String name2 = "DynamicJobName2";
        String group2 = "DynamicJobGroup2";
        String cronExpression2 = "*/3 * * * * ?";
        qm.addJob(name2, group2, DynamicJob.class, cronExpression2);
        Thread.sleep(9000);
        qm.shutdown();
    }
}

任务接口、实现类

package top.rushpeak.edu03.admin.job;

import org.apache.log4j.Logger;

public class AbstractJob {
    public static final Logger      LOG                 = Logger.getLogger(AbstractJob.class);
}

package top.rushpeak.edu03.admin.job;

import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import top.rushpeak.edu03.admin.util.DateUtil;

public class DynamicJob3 implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("任务3:"+DateUtil.dateFormat(new Date(), "yyyyMMddHHmmss"));
    }

}

上面基本配置完成后,下面就是业务了。

任务表

CREATE TABLE `job_manager` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '任务名',
  `group` varchar(50) NOT NULL DEFAULT '' COMMENT '组名',
  `clazz` varchar(50) NOT NULL DEFAULT '' COMMENT '类名',
  `cron_expression` varchar(50) NOT NULL DEFAULT '' COMMENT '定时表达式',
  `is_enabled` char(1) NOT NULL DEFAULT 'N' COMMENT '是否开启',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`,`group`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;


BaseController父类,通用方法

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.jfinal.core.Controller;

public class BaseController extends Controller {

    public static final Logger      LOG                 = Logger.getLogger(BaseController.class);

    @SuppressWarnings("rawtypes")
    protected Map<String, Object> buildPagination(List list, Integer count) {
        return buildPagination(list, count, null);
    }

    @SuppressWarnings("rawtypes")
    protected Map<String, Object> buildPagination(List list, Integer count,
                                                    List<Map<String, Object>> footer) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("total", count);
        map.put("rows", list);
        if (footer != null)
            map.put("footer", footer);
        return map;
    }

}

页面业务类

import java.sql.SQLException;
import org.quartz.Job;
import com.alibaba.druid.util.StringUtils;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.IAtom;
import com.jfinal.plugin.activerecord.Page;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.render.JsonRender;

import top.rushpeak.edu03.admin.util.QuartzManager;
import top.rushpeak.edu03.admin.util.Result;

public class JobmanageController extends BaseController {

    public void index() {
        render("index.html");
    }
    //获取任务信息
    public void post(){
        int pageNum = getParaToInt("page", 1);
        int rows = getParaToInt("rows", 30);
        String whereSql = " where 1=1 ";

        pageNum = pageNum > 0 ? pageNum : 1;
        Page<Record> page = Db.paginate(pageNum, rows, "select * ",
            " from  job_manager" + whereSql + "order by id desc");
        setAttrs(buildPagination(page.getList(), page.getTotalRow()));
        render(new JsonRender().forIE());

    }

    //新增任务
    public void add(){
        System.out.println("add");
        String id = getPara("id");
        String name = getPara("name");
        String group = getPara("group");
        String clazz = getPara("clazz");
        String cron_expression = getPara("cron_expression");
        String is_enabled = getPara("is_enabled");
        try {
            Class<? extends Job>  jobClazz = Class.forName(clazz).asSubclass(Job.class);

            Record r = new Record();
            r.set("name", name);
            r.set("group", group);
            r.set("clazz", clazz);
            r.set("cron_expression", cron_expression);
            r.set("is_enabled", "N");

            if(StringUtils.isEmpty(id)){//新增
                Db.save("job_manager", "id",r);
            }else{//修改
                r.set("id", id);
                Db.update("job_manager", "id",r);
            }
        } catch (Exception e) {
            LOG.error("e=="+e);
            renderJson(Result.error("异常"));
        }
        renderJson(Result.OK);
    }

    //启用关停
    public void delete(){
        try {
            final String id = getPara("id");
            final Record r = Db.findById("job_manager", id);
            if(r!=null){
                //事务管理,确保数据库状态与任务管理器一致
                boolean bl = Db.tx(new IAtom() {  
                    @Override  
                    public boolean run() throws SQLException {  
                        String name = r.getStr("name");
                        String group = r.getStr("group");
                        String clazz = r.getStr("clazz");
                        String cron_expression = r.getStr("cron_expression");
                        String is_enabled = r.getStr("is_enabled");
                        String change_enable = "N".equals(is_enabled)?"Y":"N";
                        r.set("is_enabled", change_enable);
                        Db.update("job_manager", "id", r);

                        Class<? extends Job> jobClazz;
                        try {
                            jobClazz = Class.forName(clazz).asSubclass(Job.class);
                        } catch (ClassNotFoundException e) {
                            LOG.error("job类异常:"+e);
                            throw new RuntimeException("job类异常");
                        }

                        QuartzManager qm = new QuartzManager();
                        if("Y".equals(r.getStr("is_enabled"))){
                            try {
                                qm.removeJob(r.getStr("name"), r.getStr("group"));                              
                            } catch (Exception e) {
                                LOG.error("removeJob_err:"+e);
                            }
                            qm.addJob(r.getStr("name"), r.getStr("group"), jobClazz, r.getStr("cron_expression"));
                        }else{
                            qm.removeJob(r.getStr("name"), r.getStr("group"));
                        }
                        return true;
                    }  
                }); 
            }

        } catch (Exception e) {
            LOG.error("e=="+e);
            renderJson(Result.error("异常"));
        }

        renderJson(Result.OK);
    }

    public void get(){
        Record r = null;
        try {
            String id = getPara("id");
            r = Db.findById("job_manager", id);
        } catch (Exception e) {
            LOG.error("e=="+e);
            renderJson(Result.error("异常"));
        }
        renderJson(Result.success(r));
    }
}

不规范的html,试验用的页面很粗糙。js和css很多走本地,没用cdn,不能直接用,抱歉,主题代码还是可以用的。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>JOB测试</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<link rel="bookmark" href="/favicon.ico" type="image/x-icon" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<meta
    content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    name="viewport">
<link rel="stylesheet" href="/dist/css/bootstrap/bootstrap.min.css">
<link rel="stylesheet"
    href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.css">
<link rel="stylesheet"
    href="https://cdn.bootcss.com/ionicons/2.0.1/css/ionicons.min.css">
<link rel="stylesheet" href="/dist/css/adminlte/AdminLTE.min.css">
<link rel="stylesheet" href="dist/css/skins/skin-blue.min.css">
<link rel="stylesheet" type="text/css" href="/dist/easyui/themes/gray/easyui.css">

</head>
<script src="/dist/plugins/jQuery/jquery-2.2.3.min.js"></script>
<script src="/dist/js/bootstrap.min.js"></script>
<script src="/dist/js/app.min.js"></script>
<script src="https://cdn.bootcss.com/layer/3.0.1/layer.min.js"></script>
<script type="text/javascript" src="/dist/easyui/jquery.easyui.min.js"></script>
<script src="http://code.jquery.com/jquery-migrate-1.1.1.js"></script>
<script type="text/javascript">
    $(document)
            .ready(
                    function() {
                        var _toolbar = [ {
                            id : 'btnRefresh',
                            text : '刷新',
                            iconCls : 'icon-reload',
                            handler : function() {
                                refresh();
                            }
                        }, '-', {
                            id : 'btnAddItem',
                            iconCls : 'icon-add',
                            text : '添加',
                            handler : function() {
                                add();
                            }
                        } ];
                        $('#datagrid')
                                .datagrid(
                                        {
                                            title : '任务调度管理',
                                            url : '/jobmanage/post',
                                            toolbar : _toolbar,
                                            columns : [ [
                                                    {
                                                        field : 'id',
                                                        title : 'ID',
                                                        width : 50,
                                                        align : 'center'
                                                    },
                                                    {
                                                        field : 'name',
                                                        title : '任务名',
                                                        width : 100,
                                                        align : 'center'
                                                    },
                                                    {
                                                        field : 'group',
                                                        title : '任务组名',
                                                        width : 100,
                                                        align : 'center'
                                                    },
                                                    {
                                                        field : 'clazz',
                                                        title : '类全名',
                                                        width : 300,
                                                        align : 'center'
                                                    },
                                                    {
                                                        field : 'cron_expression',
                                                        title : '定时表达式',
                                                        width : 100,
                                                        align : 'center'
                                                    },
                                                    {
                                                        field : 'is_enabled',
                                                        title : '是否启动',
                                                        width : 100,
                                                        align : 'center'
                                                    },
                                                    {
                                                        field : 'action',
                                                        title : '操作',
                                                        width : 150,
                                                        align : 'center',
                                                        formatter : function(
                                                                value, row,index) {

                                                            var a = "<a href='javascript:update("+row.id+")'>修改</a> ";
                                                            var b = "<a href='javascript:deleteItem("+row.id+")'>启停</a>";
                                                            return a + "   " +b;
                                                        }
                                                    } ] ],
                                            pagination : true,
                                            rownumbers : true,
                                            pageSize : 20,
                                            rowStyler : function(index, row) {
                                                return "height:100px;";
                                            },
                                            singleSelect : true
                                        });
                        $(document).on("click", ".addSubmitBtn", addSubmit);
                        $(document).on("click", ".updateBtn", update);
                        $(document).on("click", ".delBtn", deleteItem);
                    });
    /* 刷新 */
    function refresh() {
        $('#datagrid').datagrid('reload');
    }
    /* 添加 */
    function add() {
        $("#showAddId").hide();
        $("#myModalLabel").text("新增");
        $("#add_id").val("");
        $("#add_name").val("");
        $("#add_group").val("");
        $("#add_clazz").val("");
        $("#add_cron_expression").val("");
        $("#add_is_enabled").val("");
        $("#myModal").modal("show");
    }
    /* 更新 */
    function update(id) {
        console.log("update");
        /* var id = $(this).data("id"); */
        var id = id;
        $("#myModalLabel").text("修改");
        $.post("/jobmanage/get", {
            id : id
        }, function(data) {
            if (data.result == "success") {
                $("#add_id").val(data.data.id);
                $("#add_name").val(data.data.name);
                $("#add_group").val(data.data.group);
                $("#add_clazz").val(data.data.clazz);
                $("#add_cron_expression").val(data.data.cron_expression);
                $("#add_is_enabled").val(data.data.is_enabled);
            } else {
                alert(data.message);
            }
        });
        $("#showAddId").show();
        $("#myModal").modal("show");
    }
    /* 添加修改提交 */
    function addSubmit() {
        var req_params = {};
        req_params.id = $("#add_id").val();
        req_params.name = $("#add_name").val();
        req_params.group = $("#add_group").val();
        req_params.clazz = $("#add_clazz").val();
        req_params.cron_expression = $("#add_cron_expression").val();
        req_params.is_enabled = $("#add_is_enabled").val();
        $.post('/jobmanage/add', req_params, function(data) {
            if (data.result == "success") {
                var title = $("#myModalLabel").text();
                layer.msg(title + "完成");
                $("#myModal").modal("hide");
                refresh();
            } else {
                alert(data.message);
            }
        });
    }
    /* 启停 */
    function deleteItem(id) {
        /* var id = $(this).data("id"); */
        if (!window.confirm("确认启停")) {
            return;
        }
        $.post("/jobmanage/delete", {
            id : id
        }, function(data) {
            if (data.result == "success") {
                layer.msg("启停完成");
                refresh();
            } else {
                alert(data.message);
            }
        });
    }
    /* 搜索 */
    function search() {
        var id = $('#search_id').val();
        var name = $('#search_name').val();
        var group = $('#search_group').val();
        var is_enabled = $('#search_is_enabled').val();
        $('#datagrid').datagrid('load', {
            "id" : id,
            "name" : name,
            "group" : group,
            "is_enabled" : is_enabled
        });
    }
</script>

<body>

    <div id="p1" class="easyui-panel" title="查询"
        style="padding: 10px; background: #fafafa; margin-bottom: 10px;"
        iconCls="icon-sum" closable="false" collapsible="false" minimizable="false"
        maximizable="false">
        ID:<input id="search_id" name="search_id"
            class="txt_fm_mmp easyui-validatebox search"> 任务名:<input
            id="search_name" name="search_name"
            class="txt_fm_mmp easyui-validatebox search"> 任务组名:<input
            id="search_group" name="search_group"
            class="txt_fm_mmp easyui-validatebox search"> 是否启动:<input
            id="search_is_enabled" name="search_is_enabled"
            class="txt_fm_mmp easyui-validatebox search"> <a
            href="javascript:search();" class="easyui-linkbutton" plain="true"
            iconCls="icon-search">搜索</a>
    </div>

    <table id="datagrid"></table>


    <div class="modal fade" id="myModal" tabindex="-1" role="dialog"
        aria-labelledby="myModalLabel" data-backdrop="false">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                    <h4 class="modal-title" id="myModalLabel">新增</h4>
                </div>
                <div class="modal-body">
                    <form class="form-horizontal">
                        <div class="form-group" style="display: none" id="showAddId">
                            <label for="add_id" class="col-sm-2 control-label">id</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" id="add_id" placeholder="ID"
                                    readonly>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="add_name" class="col-sm-2 control-label">任务名</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" id="add_name" placeholder="任务名"
                                    required>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="add_group" class="col-sm-2 control-label">任务组名</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" id="add_group"
                                    placeholder="任务组名" required>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="add_clazz" class="col-sm-2 control-label">类全名</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" id="add_clazz" placeholder="类全名"
                                    required>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="add_cron_expression" class="col-sm-2 control-label">定时表达式</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" id="add_cron_expression"
                                    placeholder="定时表达式" required>
                            </div>
                        </div>
                        <div class="form-group" hidden="true">
                            <label class="col-sm-2 control-label">是否启动</label>
                            <div class="col-sm-10">
                                <select class="form-control" id="add_is_enabled">
                                    <option value="N">否</option>
                                    <option value="Y">是</option>
                                </select>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                    <button type="button" class="btn btn-primary addSubmitBtn">提交</button>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

配置jfinal路由,这个根据项目结构写的,就没必要放我的码子。

完成以上,便可以开启localhost模式了。









 类似资料: