Bläddra i källkod

正在重写机器监控功能和前端页面

reghao 5 år sedan
förälder
incheckning
99e864fcc1

+ 84 - 16
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/controller/MonitorController.java

@@ -1,6 +1,8 @@
 package cn.reghao.autodop.dmaster.monitor.controller;
 
+import cn.reghao.autodop.dmaster.monitor.entity.AppMonitor;
 import cn.reghao.autodop.dmaster.monitor.entity.MachineMonitor;
+import cn.reghao.autodop.dmaster.monitor.entity.MonitorJob;
 import cn.reghao.autodop.dmaster.monitor.repository.AppMonitorRepository;
 import cn.reghao.autodop.dmaster.monitor.repository.MachineMonitorRepository;
 import cn.reghao.autodop.dmaster.monitor.service.MonitorScheduler;
@@ -14,6 +16,7 @@ import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
+import javax.validation.Valid;
 import java.util.List;
 
 /**
@@ -39,30 +42,26 @@ public class MonitorController {
 
     @ApiOperation(value = "设置机器状态监控任务")
     @PostMapping(value = "/machine/job/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<String> machineStatusMonitor(@PathVariable("id") MachineMonitor machineMonitor,
-                                                 @RequestParam("cronExp") String cronExp) throws SchedulerException {
-        String jobId = machineMonitor.getJobId();
-        if (jobId == null) {
-            jobId = machineMonitor.getMachineId() + "-heartbeat";
-            machineMonitor.setJobId(jobId);
-        }
-        // TODO 验证 cronExp 是否有效
-        machineMonitor.setCronExp(cronExp);
-        machineMonitor.setEnable(true);
+    public ResponseEntity<String> machineMonitorJob(@PathVariable("id") MachineMonitor machineMonitor,
+                                                    @Valid MonitorJob monitorJob) throws SchedulerException {
+        String jobId = machineMonitor.getMachineId() + "-heartbeat";
+        monitorJob.setJobId(jobId);
+        monitorJob.setEnable(true);
 
+        List<MonitorJob> monitorJobs = machineMonitor.getMonitorJobs();
+        monitorJobs.add(monitorJob);
         // 这两个操作应该在一个事务中
-        monitorScheduler.addMachineStatusMonitorJob(machineMonitor);
+        //monitorScheduler.addMachineHeartbeatCheckJob(machineMonitor);
         machineMonitorRepository.save(machineMonitor);
-
         return ResponseEntity.ok().body(WebBody.success());
     }
 
-    @ApiOperation(value = "是否开始监控")
+    @ApiOperation(value = "是否开始监控机器状态")
     @PostMapping(value = "/machine/{status}/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<String> disableMachineMonitor(@PathVariable("status") String status,
+    public ResponseEntity<String> machineMonitorStatus(@PathVariable("status") String status,
                                                         @PathVariable("id") MachineMonitor machineMonitor)
             throws SchedulerException {
-        Boolean enable = machineMonitor.getEnable();
+        /*Boolean enable = machineMonitor.getEnable();
         if ("enable".equals(status)) {
             if (enable) {
                 return ResponseEntity.ok().body(WebBody.successMsg("当前正在监控中"));
@@ -90,7 +89,9 @@ public class MonitorController {
             machineMonitorRepository.save(machineMonitor);
 
             return ResponseEntity.ok().body(WebBody.successMsg("监控已停止"));
-        }
+        }*/
+
+        return ResponseEntity.ok().body(WebBody.successMsg("监控已停止"));
     }
 
     @ApiOperation(value = "设置机器状态监控通知")
@@ -101,4 +102,71 @@ public class MonitorController {
         machineMonitorRepository.save(machineMonitor);
         return ResponseEntity.ok().body(WebBody.success());
     }
+
+    @ApiOperation(value = "设置应用健康检查监控任务")
+    @PostMapping(value = "/app/job/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> appMonitorJob(@PathVariable("id") AppMonitor appMonitor,
+                                                @RequestParam("cronExp") String cronExp) throws SchedulerException {
+        /*String jobId = appMonitor.getJobId();
+        if (jobId == null) {
+            jobId = String.format("%s-%s-healthcheck", appMonitor.getAppId(), appMonitor.getMachineId());
+            appMonitor.setJobId(jobId);
+        }
+        // TODO 验证 cronExp 是否有效
+        appMonitor.setCronExp(cronExp);
+        appMonitor.setEnable(true);
+
+        // TODO 这两个操作应该在一个事务中
+        monitorScheduler.addAppHealthCheckJob(appMonitor);
+        appMonitorRepository.save(appMonitor);*/
+
+        return ResponseEntity.ok().body(WebBody.success());
+    }
+
+    @ApiOperation(value = "是否开始应用健康检查监控")
+    @PostMapping(value = "/app/{status}/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> appMonitorStatus(@PathVariable("status") String status,
+                                                   @PathVariable("id") AppMonitor appMonitor)
+            throws SchedulerException {
+        /*Boolean enable = appMonitor.getEnable();
+        if ("enable".equals(status)) {
+            if (enable) {
+                return ResponseEntity.ok().body(WebBody.successMsg("当前正在监控中"));
+            }
+
+            String jobId = appMonitor.getJobId();
+            if (jobId == null) {
+                return ResponseEntity.ok().body(WebBody.failWithMsg("请先设置监控任务"));
+            }
+
+            // TODO 应该在一个事务中
+            appMonitor.setEnable(true);
+            monitorScheduler.resume(appMonitor.getJobId());
+            appMonitorRepository.save(appMonitor);
+
+            return ResponseEntity.ok().body(WebBody.successMsg("监控已开启"));
+        } else {
+            if (!enable) {
+                return ResponseEntity.ok().body(WebBody.successMsg("当前没有监控"));
+            }
+
+            // TODO 应该在一个事务中
+            appMonitor.setEnable(false);
+            monitorScheduler.pause(appMonitor.getJobId());
+            appMonitorRepository.save(appMonitor);
+
+            return ResponseEntity.ok().body(WebBody.successMsg("监控已停止"));
+        }*/
+
+        return ResponseEntity.ok().body(WebBody.successMsg("监控已停止"));
+    }
+
+    @ApiOperation(value = "设置机器状态监控通知")
+    @PostMapping(value = "/app/notify", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> appMonitorNotify(@RequestParam("id") AppMonitor appMonitor,
+                                                       @RequestParam("groupId") List<NotifyGroup> notifyGroups) {
+        appMonitor.setNotifyGroups(notifyGroups);
+        appMonitorRepository.save(appMonitor);
+        return ResponseEntity.ok().body(WebBody.success());
+    }
 }

+ 67 - 9
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/controller/MonitorPageController.java

@@ -1,11 +1,15 @@
 package cn.reghao.autodop.dmaster.monitor.controller;
 
+import cn.reghao.autodop.dmaster.DmasterApplication;
+import cn.reghao.autodop.dmaster.app.vo.KeyValue;
 import cn.reghao.autodop.dmaster.monitor.entity.AppMonitor;
 import cn.reghao.autodop.dmaster.monitor.entity.MachineMonitor;
+import cn.reghao.autodop.dmaster.monitor.entity.MonitorJob;
 import cn.reghao.autodop.dmaster.monitor.repository.AppMonitorRepository;
 import cn.reghao.autodop.dmaster.monitor.repository.MachineMonitorRepository;
 import cn.reghao.autodop.dmaster.notification.entity.NotifyGroup;
 import cn.reghao.autodop.dmaster.notification.repository.NotifyGroupRepository;
+import cn.reghao.autodop.dmaster.utils.clazz.PackageScanner;
 import cn.reghao.autodop.dmaster.utils.db.PageList;
 import cn.reghao.autodop.dmaster.utils.db.PageSort;
 import io.swagger.annotations.Api;
@@ -19,6 +23,8 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -56,6 +62,46 @@ public class MonitorPageController {
         return "/monitor/machine";
     }
 
+    @ApiOperation(value = "机器监控任务列表页面")
+    @GetMapping("/machine/jobs/{id}")
+    public String monitorAddPage(@PathVariable("id") MachineMonitor machineMonitor, Model model) {
+        List<MonitorJob> list = machineMonitor.getMonitorJobs();
+        model.addAttribute("machineMonitorId", machineMonitor.getId());
+        model.addAttribute("list", list);
+        return "/monitor/machinejobs";
+    }
+
+    @ApiOperation(value = "机器监控任务添加页面")
+    @GetMapping("/machine/jobs/add/{id}")
+    public String monitorJobAddPage(@PathVariable("id") MachineMonitor machineMonitor, Model model)
+            throws IOException {
+
+        setJobNames(model);
+        model.addAttribute("machineMonitorId", machineMonitor.getId());
+        return "/monitor/machinejob";
+    }
+
+    @ApiOperation(value = "机器监控任务编辑页面")
+    @GetMapping("/machine/jobs/edit/{id}")
+    public String monitorJobEditPage(@PathVariable("id") MonitorJob monitorJob, Model model) throws IOException {
+        setJobNames(model);
+        model.addAttribute("monitorJob", monitorJob);
+        return "/monitor/machinejob";
+    }
+
+    private void setJobNames(Model model) throws IOException {
+        PackageScanner packageScanner = new PackageScanner();
+        String pkg = "cn.reghao.autodop.dmaster.monitor.service.job.machine";
+        List<Class<?>> classList = packageScanner.doScan(DmasterApplication.class, pkg);
+
+        List<KeyValue> jobNames = new ArrayList<>();
+        jobNames.add(new KeyValue("请选择任务", ""));
+        for (Class<?> clazz : classList) {
+            jobNames.add(new KeyValue(clazz.getSimpleName(), clazz.getSimpleName()));
+        }
+        model.addAttribute("jobNames", jobNames);
+    }
+
     @ApiOperation(value = "机器监控通知页面")
     @GetMapping("/machine/notify/{id}")
     public String monitorNotifyPage(@PathVariable("id") MachineMonitor machineMonitor, Model model) {
@@ -68,15 +114,6 @@ public class MonitorPageController {
         return "/monitor/machinenotify";
     }
 
-    @ApiOperation(value = "机器监控任务页面")
-    @GetMapping("/machine/job/{id}")
-    public String monitorAddPage(@PathVariable("id") MachineMonitor machineMonitor, Model model) {
-        model.addAttribute("id", machineMonitor.getId());
-        model.addAttribute("jobId", machineMonitor.getJobId());
-        model.addAttribute("cronExp", machineMonitor.getCronExp());
-        return "/monitor/enablemonitor";
-    }
-
     @ApiOperation(value = "应用监控页面")
     @GetMapping("/app")
     public String appMonitorPage(Model model) {
@@ -89,6 +126,27 @@ public class MonitorPageController {
         return "/monitor/app";
     }
 
+    @ApiOperation(value = "应用监控任务页面")
+    @GetMapping("/app/job/{id}")
+    public String appJobPage(@PathVariable("id") AppMonitor appMonitor, Model model) {
+        model.addAttribute("id", appMonitor.getId());
+        model.addAttribute("jobId", "appMonitor.getJobId()");
+        model.addAttribute("cronExp", "appMonitor.getCronExp()");
+        return "/monitor/appjob";
+    }
+
+    @ApiOperation(value = "应用监控通知页面")
+    @GetMapping("/app/notify/{id}")
+    public String appNotifyPage(@PathVariable("id") AppMonitor appMonitor, Model model) {
+        Set<NotifyGroup> currentSet = new HashSet<>(appMonitor.getNotifyGroups());
+        List<NotifyGroup> list = receiverRepository.findAll();
+
+        model.addAttribute("id", appMonitor.getId());
+        model.addAttribute("currentSet", currentSet);
+        model.addAttribute("list", list);
+        return "/monitor/appnotify";
+    }
+
     @ApiOperation(value = "第三方应用监控页面")
     @GetMapping("/app3")
     public String app3MonitorPage(Model model) {

+ 14 - 2
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/entity/AppMonitor.java

@@ -1,11 +1,16 @@
 package cn.reghao.autodop.dmaster.monitor.entity;
 
 import cn.reghao.autodop.dmaster.app.entity.AppRunning;
+import cn.reghao.autodop.dmaster.common.orm.BaseEntity;
+import cn.reghao.autodop.dmaster.notification.entity.NotifyGroup;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
 
-import javax.persistence.Entity;
+import javax.persistence.*;
+import java.util.List;
 
 /**
  * @author reghao
@@ -15,11 +20,18 @@ import javax.persistence.Entity;
 @EqualsAndHashCode(callSuper = false)
 @Data
 @Entity
-public class AppMonitor extends MonitorJob {
+public class AppMonitor extends BaseEntity<Integer> {
     private String appId;
     private String appName;
     private String machineId;
     private String machineIpv4;
+    @ElementCollection(targetClass = MonitorJob.class)
+    @LazyCollection(LazyCollectionOption.FALSE)
+    private List<MonitorJob> monitorJobs;
+    @ManyToMany(cascade = CascadeType.REFRESH)
+    @JoinColumn(name = "notify_group_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    @LazyCollection(LazyCollectionOption.FALSE)
+    private List<NotifyGroup> notifyGroups;
 
     public AppMonitor(AppRunning appRunning) {
         this.appId = appRunning.getAppId();

+ 14 - 2
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/entity/MachineMonitor.java

@@ -1,11 +1,16 @@
 package cn.reghao.autodop.dmaster.monitor.entity;
 
+import cn.reghao.autodop.dmaster.common.orm.BaseEntity;
 import cn.reghao.autodop.dmaster.machine.entity.MachineInfo;
+import cn.reghao.autodop.dmaster.notification.entity.NotifyGroup;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
 
-import javax.persistence.Entity;
+import javax.persistence.*;
+import java.util.List;
 
 /**
  * @author reghao
@@ -15,9 +20,16 @@ import javax.persistence.Entity;
 @EqualsAndHashCode(callSuper = false)
 @Data
 @Entity
-public class MachineMonitor extends MonitorJob {
+public class MachineMonitor extends BaseEntity<Integer> {
     private String machineId;
     private String machineIpv4;
+    @ElementCollection(targetClass = MonitorJob.class)
+    @LazyCollection(LazyCollectionOption.FALSE)
+    private List<MonitorJob> monitorJobs;
+    @ManyToMany(cascade = CascadeType.REFRESH)
+    @JoinColumn(name = "notify_group_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    @LazyCollection(LazyCollectionOption.FALSE)
+    private List<NotifyGroup> notifyGroups;
 
     public MachineMonitor(MachineInfo machineInfo) {
         this.machineId = machineInfo.getMachineId();

+ 7 - 13
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/entity/MonitorJob.java

@@ -1,33 +1,27 @@
 package cn.reghao.autodop.dmaster.monitor.entity;
 
-import cn.reghao.autodop.dmaster.common.orm.BaseEntity;
-import cn.reghao.autodop.dmaster.notification.entity.NotifyGroup;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import org.hibernate.annotations.LazyCollection;
-import org.hibernate.annotations.LazyCollectionOption;
 
 import javax.persistence.*;
 import javax.validation.constraints.NotBlank;
-import java.util.List;
 
 /**
+ * 监控任务
+ *
  * @author reghao
  * @date 2021-06-23 20:50:41
  */
-@MappedSuperclass
 @EqualsAndHashCode(callSuper = false)
 @Data
-public class MonitorJob extends BaseEntity<Integer> {
-    //@NotBlank(message = "任务 ID 不能为空白字符串")
+@Embeddable
+public class MonitorJob {
     private String jobId;
-    //@NotBlank(message = "CRON 表达式不能为空白字符串")
+    @NotBlank(message = "任务 ID 不能为空白字符串")
+    private String jobClassName;
+    @NotBlank(message = "CRON 表达式不能为空白字符串")
     private String cronExp;
     private Boolean enable;
-    @ManyToMany(cascade = CascadeType.REFRESH)
-    @JoinColumn(name = "notify_group_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
-    @LazyCollection(LazyCollectionOption.FALSE)
-    private List<NotifyGroup> notifyGroups;
 
     public MonitorJob() {
         this.enable = false;

+ 31 - 9
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/service/MonitorScheduler.java

@@ -4,10 +4,10 @@ import cn.reghao.autodop.common.http.DefaultWebRequest;
 import cn.reghao.autodop.common.http.WebRequest;
 import cn.reghao.autodop.dmaster.machine.db.crud.MachineStatusCrudService;
 import cn.reghao.autodop.dmaster.app.repository.AppRunningRepository;
+import cn.reghao.autodop.dmaster.monitor.entity.AppMonitor;
 import cn.reghao.autodop.dmaster.monitor.entity.MachineMonitor;
+import cn.reghao.autodop.dmaster.monitor.repository.AppMonitorRepository;
 import cn.reghao.autodop.dmaster.monitor.repository.MachineMonitorRepository;
-import cn.reghao.autodop.dmaster.monitor.service.job.MachineHeartbeatMonitorJob;
-import cn.reghao.autodop.dmaster.notification.entity.NotifyGroup;
 import cn.reghao.autodop.dmaster.notification.service.NotifyService;
 import lombok.extern.slf4j.Slf4j;
 import org.quartz.*;
@@ -15,7 +15,6 @@ import org.quartz.impl.StdSchedulerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
-import java.util.List;
 
 /**
  * 监控使用的定时任务调度器
@@ -32,17 +31,20 @@ public class MonitorScheduler {
     private MachineStatusCrudService statusCrudService;
     private WebRequest webRequest;
     private MachineMonitorRepository machineMonitorRepository;
+    private AppMonitorRepository appMonitorRepository;
 
     public MonitorScheduler(NotifyService notifyService,
                             AppRunningRepository runningRepository,
                             MachineStatusCrudService statusCrudService,
-                            MachineMonitorRepository machineMonitorRepository) throws SchedulerException {
+                            MachineMonitorRepository machineMonitorRepository,
+                            AppMonitorRepository appMonitorRepository) throws SchedulerException {
         this.scheduler = StdSchedulerFactory.getDefaultScheduler();
         this.notifyService = notifyService;
         this.runningRepository = runningRepository;
         this.statusCrudService = statusCrudService;
         this.webRequest = new DefaultWebRequest();
         this.machineMonitorRepository = machineMonitorRepository;
+        this.appMonitorRepository = appMonitorRepository;
     }
 
     /**
@@ -53,10 +55,15 @@ public class MonitorScheduler {
     @PostConstruct
     public void startScheduler() throws SchedulerException {
         for (MachineMonitor machineMonitor : machineMonitorRepository.findAll()) {
-            addMachineStatusMonitorJob(machineMonitor);
+            //addMachineHeartbeatCheckJob(machineMonitor);
         }
+
+        for (AppMonitor appMonitor : appMonitorRepository.findAll()) {
+            addAppHealthCheckJob(appMonitor);
+        }
+
         // TODO 系统启动时启用所有存在的任务
-        //scheduler.start();
+        scheduler.start();
     }
 
     public void pauseScheduler() throws SchedulerException {
@@ -65,8 +72,8 @@ public class MonitorScheduler {
         }
     }
 
-    public void addMachineStatusMonitorJob(MachineMonitor machineMonitor) throws SchedulerException {
-        String jobId = machineMonitor.getJobId();
+    public void addMachineHeartbeatCheckJob(MachineMonitor machineMonitor) throws SchedulerException {
+        /*String jobId = machineMonitor.getJobId();
         String cronExp = machineMonitor.getCronExp();
         List<NotifyGroup> notifyGroups = machineMonitor.getNotifyGroups();
 
@@ -75,7 +82,22 @@ public class MonitorScheduler {
         jobDataMap.put("notifyGroups", notifyGroups);
         jobDataMap.put("machineId", machineMonitor.getMachineId());
         jobDataMap.put("statusCrudService", statusCrudService);
-        addAndStart(MachineHeartbeatMonitorJob.class, jobId, cronExp, jobDataMap);
+        addAndStart(MachineHeartbeatCheckJob.class, jobId, cronExp, jobDataMap);*/
+    }
+
+    public void addAppHealthCheckJob(AppMonitor appMonitor) throws SchedulerException {
+        /*String jobId = appMonitor.getJobId();
+        String cronExp = appMonitor.getCronExp();
+        List<NotifyGroup> notifyGroups = appMonitor.getNotifyGroups();
+
+        JobDataMap jobDataMap = new JobDataMap();
+        jobDataMap.put("notifyService", notifyService);
+        jobDataMap.put("notifyGroups", notifyGroups);
+        jobDataMap.put("webRequest", webRequest);
+        jobDataMap.put("appId", appMonitor.getAppId());
+        jobDataMap.put("machineId", appMonitor.getMachineId());
+        jobDataMap.put("runningRepository", runningRepository);
+        addAndStart(AppHealthCheckJob.class, jobId, cronExp, jobDataMap);*/
     }
 
     private void addAndStart(Class<? extends Job> clazz, String jobId, String cronExp, JobDataMap jobDataMap)

+ 19 - 22
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/service/job/AppHealthCheckJob.java → dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/service/job/app/AppHealthCheckJob.java

@@ -1,10 +1,11 @@
-package cn.reghao.autodop.dmaster.monitor.service.job;
+package cn.reghao.autodop.dmaster.monitor.service.job.app;
 
 import cn.reghao.autodop.common.http.WebRequest;
 import cn.reghao.autodop.dmaster.app.entity.AppRunning;
 import cn.reghao.autodop.dmaster.app.repository.AppRunningRepository;
 import cn.reghao.autodop.dmaster.notification.entity.NotifyGroup;
 import cn.reghao.autodop.dmaster.notification.service.NotifyService;
+import cn.reghao.autodop.dmaster.notification.service.notifier.ding.DingMsg;
 import lombok.extern.slf4j.Slf4j;
 import org.quartz.Job;
 import org.quartz.JobDataMap;
@@ -30,34 +31,30 @@ public class AppHealthCheckJob implements Job {
 
         NotifyService notifyService = (NotifyService) jobDataMap.get("notifyService");
         List<NotifyGroup> notifyGroups = (List<NotifyGroup>) jobDataMap.get("notifyGroups");
-
         WebRequest webRequest = (WebRequest) jobDataMap.get("webRequest");
-        AppRunningRepository runningRepository = (AppRunningRepository) jobDataMap.get("runningRepository");
         String appId = jobDataMap.getString("appId");
         String machineId = jobDataMap.getString("machineId");
+        AppRunningRepository runningRepository = (AppRunningRepository) jobDataMap.get("runningRepository");
 
         AppRunning appRunning = runningRepository.findByAppIdAndMachineId(appId, machineId);
         String machineIpv4 = appRunning.getMachineIpv4();
+        int httpPort = appRunning.getHttpPort();
         String healthCheck = appRunning.getHealthCheck();
-        if (healthCheck.startsWith("http")) {
-            int statusCode = webRequest.head(healthCheck);
-            if (statusCode != 200) {
-                log.error("{} 机器上的 {} 应用健康检查失败", machineIpv4, appId);
-            } else {
-                appRunning.setLastCheck(LocalDateTime.now());
-                runningRepository.save(appRunning);
-            }
-        } else if (healthCheck.startsWith("/")) {
-            int httpPort = appRunning.getHttpPort();
-            String url = "http://" + machineIpv4 + ":" + httpPort + healthCheck;
-            int statusCode = webRequest.head(url);
-            if (statusCode != 200) {
-                String msg = String.format("%s 机器上的 %s 应用健康检查失败", machineIpv4, appId);
-                notifyGroups.forEach(notifyGroup -> notifyService.notify(notifyGroup, msg));
-            } else {
-                appRunning.setLastCheck(LocalDateTime.now());
-                runningRepository.save(appRunning);
-            }
+
+        String url = getHealthCheckUrl(machineIpv4, httpPort, healthCheck);
+        int statusCode = webRequest.head(url);
+        if (statusCode != 200) {
+            String msg = String.format("%s 机器上的 %s 应用健康检查失败", machineIpv4, appId);
+            DingMsg dingMsg = new DingMsg("监控报警", msg);
+            notifyGroups.forEach(notifyGroup -> notifyService.notify(notifyGroup, dingMsg));
+        } else {
+            appRunning.setLastCheck(LocalDateTime.now());
+            runningRepository.save(appRunning);
         }
     }
+
+    private String getHealthCheckUrl(String machineIpv4, int httpPort, String healthCheck) {
+        return healthCheck.startsWith("http") ? healthCheck :
+                String.format("http://%s:%s%s", machineIpv4, httpPort, healthCheck);
+    }
 }

+ 2 - 2
dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/service/job/MachineHeartbeatMonitorJob.java → dmaster/src/main/java/cn/reghao/autodop/dmaster/monitor/service/job/machine/MachineHeartbeatCheckJob.java

@@ -1,4 +1,4 @@
-package cn.reghao.autodop.dmaster.monitor.service.job;
+package cn.reghao.autodop.dmaster.monitor.service.job.machine;
 
 import cn.reghao.autodop.common.utils.DateTimeConverter;
 import cn.reghao.autodop.dmaster.machine.db.crud.MachineStatusCrudService;
@@ -22,7 +22,7 @@ import java.util.List;
  * @date 2021-06-22 19:04:10
  */
 @Slf4j
-public class MachineHeartbeatMonitorJob implements Job {
+public class MachineHeartbeatCheckJob implements Job {
     @Override
     public void execute(JobExecutionContext context) {
         JobDetail jobDetail = context.getJobDetail();

+ 96 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/utils/clazz/PackageScanner.java

@@ -0,0 +1,96 @@
+package cn.reghao.autodop.dmaster.utils.clazz;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+/**
+ * 扫描指定包路径下的所有 class 文件
+ *
+ * @author reghao
+ * @date 2020-09-24 15:41:07
+ */
+public class PackageScanner {
+    public List<Class<?>> doScan(Class<?> clazz, String basePackage) throws IOException {
+        List<Class<?>> classList = new ArrayList<>();
+        String pkgPath = basePackage.replace(".", "/");
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+
+        URL url = cl.getResource(pkgPath);
+        assert url != null;
+        File file = new File(rootPath(url));
+        String protocol = url.getProtocol();
+        if ("jar".equals(protocol)) {
+            readFromJarFile(file, pkgPath, classList);
+        } else if ("file".equals(protocol)) {
+            if (file.isDirectory()) {
+                readFromDir(file, clazz, classList);
+            } else {
+                addClazz(file.getAbsolutePath(), clazz, classList);
+            }
+        }
+        return classList;
+    }
+
+    private String rootPath(URL url) {
+        String path = url.getPath();
+        int pos = path.indexOf("!");
+        if (pos == -1) {
+            return path.replace("file:", "");
+        } else {
+            return path.substring(0, pos).replace("file:", "");
+        }
+    }
+
+    private void readFromDir(File dir, Class<?> clazz, List<Class<?>> classList) {
+        File[] files = dir.listFiles();
+        assert files != null;
+        Arrays.asList(files).forEach(file -> {
+            if (file.isDirectory()) {
+                readFromDir(file, clazz, classList);
+            } else {
+                String path = file.getPath();
+                addClazz(path, clazz, classList);
+            }
+        });
+    }
+
+    private void addClazz(String filepath, Class<?> clazz, List<Class<?>> classList) {
+        String tmp = filepath.replace(clazz.getResource("/").getPath(), "");
+        String className = tmp.replace("/", ".")
+            .replace(".class", "");
+        try {
+            classList.add(Class.forName(className));
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void readFromJarFile(File jarFile, String pkgPath, List<Class<?>> classList) throws IOException {
+        if (!jarFile.exists()) {
+            System.out.println("jar 文件 " + jarFile.getAbsolutePath() + " 不存在...");
+        }
+
+        JarInputStream in = new JarInputStream(new FileInputStream(jarFile));
+        JarEntry entry = in.getNextJarEntry();
+        while (entry != null) {
+            String name = entry.getName();
+            if (name.startsWith(pkgPath) && name.endsWith(".class")) {
+                String pkg = name.replace("/", ".").replace(".class", "");
+                try {
+                    Class<?> clazz = Class.forName(pkg);
+                    classList.add(clazz);
+                } catch (ClassNotFoundException e) {
+                    e.printStackTrace();
+                }
+            }
+            entry = in.getNextJarEntry();
+        }
+    }
+}

+ 2 - 8
dmaster/src/main/resources/templates/monitor/app.html

@@ -6,7 +6,7 @@
 <body class="timo-layout-page">
 <div class="layui-card">
     <div class="layui-card-header timo-card-header">
-        <span><i class="fa fa-bars"></i> 应用监控列表</span>
+        <span><i class="fa fa-bars"></i> 应用健康检查任务</span>
         <i class="layui-icon layui-icon-refresh refresh-btn"></i>
     </div>
     <div class="layui-card-body">
@@ -45,9 +45,6 @@
                     </th>
                     <th class="sortable" data-field="appName">应用名</th>
                     <th class="sortable" data-field="machineIpv4">机器地址</th>
-                    <th class="sortable" data-field="enable">正在监控?</th>
-                    <th class="sortable" data-field="jobId">任务 ID</th>
-                    <th class="sortable" data-field="cronExp">CRON 表达式</th>
                     <th>操作</th>
                 </tr>
                 </thead>
@@ -57,11 +54,8 @@
                         <i class="layui-icon layui-icon-ok"></i></label></td>
                     <td th:text="${item.appName}">应用名</td>
                     <td th:text="${item.machineIpv4}">机器地址</td>
-                    <td th:text="${item.enable}">正在监控?</td>
-                    <td th:text="${item.jobId}">任务 ID</td>
-                    <td th:text="${item.cronExp}">CRON 表达式</td>
                     <td>
-                        <a class="open-popup" data-title="添加/修改监控任务" th:attr="data-url=@{'/monitor/machine/job/'+${item.id}}"
+                        <a class="open-popup" data-title="添加/修改监控任务" th:attr="data-url=@{'/monitor/app/job/'+${item.id}}"
                            data-size="600,400" href="#">设置监控任务</a>
                         <a class="ajax-post" th:href="@{'/api/monitor/app/enable/'+${item.id}}">开启监控</a>
                         <a class="ajax-post" th:href="@{'/api/monitor/app/disable/'+${item.id}}">停止监控</a>

+ 1 - 2
dmaster/src/main/resources/templates/monitor/enablemonitor.html → dmaster/src/main/resources/templates/monitor/appjob.html

@@ -4,8 +4,7 @@
 
 <body>
 <div class="layui-form timo-compile">
-        <form th:action="@{'/api/monitor/machine/job/'+${id}}">
-        <!--<form th:action="@{/api/monitor/machine/job}">-->
+        <form th:action="@{'/api/monitor/app/job/'+${id}}">
             <input type="hidden" name="id" th:value="${id}"/>
             <div class="layui-form-item">
                 <label class="layui-form-label required">CRON 表达式</label>

+ 39 - 0
dmaster/src/main/resources/templates/monitor/appnotify.html

@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})">
+    <style>
+        .layui-input-block{
+            margin-left: 20px;
+            margin-right: 20px;
+            margin-bottom: 70px;
+        }
+        .timo-compile .timo-finally{
+            position: fixed;
+            bottom: 0;
+            left: 0;
+            right: 0;
+            padding-bottom: 14px;
+            margin-bottom: 0;
+            background-color: #ffffff;
+        }
+    </style>
+</head>
+<body>
+<div class="layui-form timo-compile">
+    <form th:action="@{/api/monitor/app/notify/}">
+        <input type="hidden" name="id" th:value="${id}"/>
+        <div class="layui-form-item">
+            <div class="layui-input-block">
+                <input th:each="item:${list}" type="checkbox" name="groupId" th:title="${item.groupId}"
+                       th:value="${item.id}" th:checked="${#sets.contains(currentSet, item)}" lay-skin="primary">
+            </div>
+        </div>
+        <div class="layui-form-item timo-finally">
+            <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+            <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
+        </div>
+    </form>
+</div>
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 11 - 11
dmaster/src/main/resources/templates/monitor/machine.html

@@ -44,9 +44,8 @@
                             <i class="layui-icon layui-icon-ok"></i></label>
                     </th>
                     <th class="sortable" data-field="machineIpv4">机器地址</th>
-                    <th class="sortable" data-field="enable">正在监控?</th>
-                    <th class="sortable" data-field="jobId">任务 ID</th>
-                    <th class="sortable" data-field="cronExp">CRON 表达式</th>
+                    <th class="sortable" data-field="jobs">任务列表</th>
+                    <th class="sortable" data-field="notifyGroups">通知组</th>
                     <th>操作</th>
                 </tr>
                 </thead>
@@ -55,16 +54,17 @@
                     <td><label class="timo-checkbox"><input type="checkbox" th:value="${item.machineId}">
                         <i class="layui-icon layui-icon-ok"></i></label></td>
                     <td th:text="${item.machineIpv4}">机器地址</td>
-                    <td th:text="${item.enable}">正在监控?</td>
-                    <td th:text="${item.jobId}">任务 ID</td>
-                    <td th:text="${item.cronExp}">CRON 表达式</td>
                     <td>
-                        <a class="open-popup" data-title="添加/修改监控任务" th:attr="data-url=@{'/monitor/machine/job/'+${item.id}}"
-                           data-size="600,400" href="#">设置监控任务</a>
-                        <a class="ajax-post" th:href="@{'/api/monitor/machine/enable/'+${item.id}}">开启监控</a>
-                        <a class="ajax-post" th:href="@{'/api/monitor/machine/disable/'+${item.id}}">停止监控</a>
+                        <a class="open-popup" data-title="任务列表" th:attr="data-url=@{'/monitor/machine/jobs/'+${item.id}}"
+                           data-size="1200,600" href="#">查看</a>
+                    </td>
+                    <td>
                         <a class="open-popup" data-title="设置通知组" th:attr="data-url=@{'/monitor/machine/notify/'+${item.id}}"
-                           data-size="640,480" href="#">设置通知</a>
+                           data-size="640,480" href="#">设置</a>
+                    </td>
+                    <td>
+                        <a class="ajax-post" th:href="@{'/api/monitor/machine/enable/'+${item.id}}">开启所有任务</a>
+                        <a class="ajax-post" th:href="@{'/api/monitor/machine/disable/'+${item.id}}">停止所有任务</a>
                     </td>
                 </tr>
                 </tbody>

+ 31 - 0
dmaster/src/main/resources/templates/monitor/machinejob.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body>
+<div class="layui-form timo-compile">
+        <form th:action="@{'/api/monitor/machine/job/'+${machineMonitorId}}">
+            <input type="hidden" name="id" th:value="${monitorJob?.id}"/>
+            <div class="layui-form-item">
+                <label class="layui-form-label required">任务名</label>
+                <div class="layui-input-inline">
+                    <select name="jobClassName">
+                        <option th:each="item : ${jobNames}" th:value="${item.key}" th:selected="${monitorJob?.jobClassName} eq ${item.key}">[[${item.value}]]</option>
+                    </select>
+                </div>
+            </div>
+            <div class="layui-form-item">
+                <label class="layui-form-label required">CRON 表达式</label>
+                <div class="layui-input-inline">
+                    <input class="layui-input" type="text" name="cronExp" placeholder="请输入 CRON 表达式" required th:value="${monitorJob?.cronExp}">
+                </div>
+            </div>
+            <div class="layui-form-item timo-finally">
+                <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
+                <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
+            </div>
+        </form>
+    </div>
+<script th:replace="/common/template :: script"></script>
+</body>
+</html>

+ 51 - 0
dmaster/src/main/resources/templates/monitor/machinejobs.html

@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
+
+<body class="timo-layout-page">
+<div class="layui-card">
+    <div class="layui-card-body">
+        <div class="layui-row timo-card-screen put-row">
+            <div class="pull-right screen-btn-group">
+                <div class="btn-group-right">
+                    <button class="layui-btn open-popup" data-title="添加任务" data-size="640,480"
+                            th:attr="data-url=@{'/monitor/machine/jobs/add/'+${machineMonitorId}}">
+                        <i class="fa fa-plus"></i> 添加
+                    </button>
+                </div>
+            </div>
+        </div>
+        <div class="timo-table-wrap">
+            <table class="layui-table timo-table">
+                <thead>
+                <tr>
+                    <th class="sortable" data-field="jobId">任务 ID</th>
+                    <th class="sortable" data-field="jobClassName">任务名</th>
+                    <th class="sortable" data-field="cronExp">CRON 表达式</th>
+                    <th class="sortable" data-field="enable">是否启用</th>
+                    <th>操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr th:each="item:${list}">
+                    <td th:text="${item.jobId}">任务 ID</td>
+                    <td th:text="${item.jobClassName}">任务名</td>
+                    <td th:text="${item.cronExp}">CRON 表达式</td>
+                    <td th:text="${item.enable}">是否启用</td>
+                    <td>
+                        <a class="ajax-post" th:href="@{'/api/monitor/machine/enable/'+${item.jobId}}">启用</a>
+                        <a class="ajax-post" th:href="@{'/api/monitor/machine/disable/'+${item.jobId}}">禁用</a>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+        <!--<div th:replace="/common/fragment :: page"></div>-->
+    </div>
+</div>
+
+<script th:replace="/common/template :: script"></script>
+<script type="text/javascript">
+</script>
+</body>
+</html>