├── .gitignore ├── LICENSE ├── README.md ├── bc.sh ├── pom.xml └── src ├── main ├── java │ └── me │ │ └── ctf │ │ └── lm │ │ ├── LiteMonitorApplication.java │ │ ├── cmdexecutor │ │ ├── AbstractCmdExecutor.java │ │ ├── LogCmdExecutor.java │ │ ├── PsCmdExecutor.java │ │ └── SqlExecutor.java │ │ ├── config │ │ ├── ExceptionHandlers.java │ │ ├── JacksonConfiguration.java │ │ ├── JavaTimeModule.java │ │ ├── MonitorConfig.java │ │ ├── MybatisPlusConfig.java │ │ └── ValidateConfig.java │ │ ├── controller │ │ └── LiteMonitorController.java │ │ ├── dao │ │ ├── MonitorConfigMapper.java │ │ └── MonitorExecSupportInfoMapper.java │ │ ├── dto │ │ └── MapResult.java │ │ ├── entity │ │ ├── MonitorConfigEntity.java │ │ └── MonitorExecSupportInfoEntity.java │ │ ├── enums │ │ ├── DingTypeEnum.java │ │ ├── DistributedLockTypeEnum.java │ │ ├── FrequencyEnum.java │ │ ├── HostStateEnum.java │ │ ├── MonitorEnableEnum.java │ │ └── MonitorTypeEnum.java │ │ ├── schedule │ │ ├── DynamicScheduleTask.java │ │ └── ScheduleCmdExecutor.java │ │ ├── service │ │ ├── DistributeTaskService.java │ │ ├── DistributedLockService.java │ │ ├── LiteMonitorConfigService.java │ │ └── impl │ │ │ ├── LiteMonitorConfigServiceImpl.java │ │ │ ├── distributedlock │ │ │ ├── DbDistributedLockServiceImpl.java │ │ │ └── RedisDistributedLockServiceImpl.java │ │ │ └── distributetask │ │ │ ├── HttpDistributeTaskServiceImpl.java │ │ │ ├── KafkaDistributeTaskServiceImpl.java │ │ │ └── RedisDistributeTaskServiceImpl.java │ │ └── util │ │ ├── BizException.java │ │ ├── CmdExecutorUtil.java │ │ ├── Constant.java │ │ ├── DateTimeRegex.java │ │ ├── DingMarkdownMessage.java │ │ ├── DingSendResult.java │ │ ├── DingTalkHelper.java │ │ ├── DingTextMessage.java │ │ ├── FeishuTalkHelper.java │ │ ├── MonitorConfigUtil.java │ │ ├── PageUtils.java │ │ ├── PlaceholderUtil.java │ │ ├── ProcessStdoutUtil.java │ │ ├── Query.java │ │ ├── SQLFilter.java │ │ ├── UpdateUtils.java │ │ └── validator │ │ ├── LogGroup.java │ │ └── ValidatorUtil.java └── resources │ ├── application.yml │ ├── db │ └── schema.sql │ ├── logback-spring.xml │ └── static │ ├── img │ ├── bg.jpg │ └── favicon.ico │ ├── index.html │ ├── js │ └── app.js │ └── monitor-add-or-update.vue └── test └── java └── me └── ctf └── lm └── LiteMonitorApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | logs/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 chentiefeng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |
7 | 8 | ## 介绍 9 | 10 | `lite-monitor` 一款基于shell命令的监控系统,可以根据项目中输出的日志定时输出或者统计输出,并发送钉钉机器人报警消息。 11 | 12 | `lite-monitor`能做什么: 13 | 14 | - 定时监控某个服务进程是否还存在,不存在则钉钉告警。 15 | - 定时统计近一段时间内具体日志文件中关键字出现的次数,并对次数做一个阈值比较,超出阈值则钉钉告警并输出日志。 16 | - 进阶监控(qps/计算效率等)可以根据`awk`等命令自定义实现。 17 | 18 | `lite-monitor`的特点: 19 | 20 | - 每个监控可配置不同钉钉群机器人,可配置@具体人或者@all 21 | - 对已有项目无任何入侵,不需要重启或者其他操作。 22 | - 可以单机版极简配置(服务器安装有Java就行),或者集群部署(除非监控很多,否则基本没有必要 :smile:)。 23 | 24 | > 在使用或开发过程中有任何疑问都可[联系我](https://chentiefeng.top) 。 25 | 26 | ## Todo List 27 | 28 | * [x] [集群模式支持分发监控任务]。 29 | * [x] [增加可选择`redis/kafka`分发任务]。 30 | * [ ] 根据监控的阀值可定制推送/触发接口。 31 | 32 | ## 流程图 33 | 34 | ![](https://chen_tiefeng.gitee.io/cloudimg/img/lite-monitor-flow.jpg) 35 | 36 | ### 流程说明 37 | 1. 定时任务触发。 38 | 2. 查询可触发任务(事先配置好),分批分发给集群内部其他应用。 39 | 3. 通过`ssh`方式执行配置的任务,返回结果和阈值比较。 40 | 4. 超过阈值的任务拼装钉钉消息发送。 41 | 42 | ### 系统架构说明 43 | - `lite-monitor` 采用 `SpringBoot` 构建。 44 | - 定时任务暂时直接通过`SpringBoot`的`Schedule`注册,增加分布式锁防止重复执行。 45 | - 分发机制默认采用`Http`方式,可选`Redis`生产消费队列 或者 `Kafka`消息 46 | - 基于`ganymed-ssh2`连接执行`Shell`命令。 47 | 48 | 49 | ## 快速启动 50 | 51 | 首先需要安装 `JDK1.8`或者以上并保证网络通畅。 52 | 53 | ### 打包 54 | ```shell 55 | git clone https://github.com/chentiefeng/lite-monitor.git 56 | cd lite-monitor 57 | mvn -Dmaven.test.skip=true clean package 58 | ``` 59 | 60 | ### 部署 61 | 62 | ```shell 63 | mkdir ~/lite-monitor-server 64 | cp target/lite-monitor-0.0.1-SNAPSHOT.jar ~/lite-monitor-server 65 | cd ~/lite-monitor-server 66 | nohup java -jar lite-monitor-0.0.1-SNAPSHOT.jar 2>&1 & 67 | ``` 68 | 69 | > 日志文件位置:`~/lite-monitor-server/logs/m.log`。 70 | 71 | ### 配置监控 72 | 73 | 浏览器输入地址`http://xx.xx.xx.xx:10003/`打开主页。 74 | ![](https://chen_tiefeng.gitee.io/cloudimg/img/Clip_20191223_160005.png) 75 | 76 | #### 进程监控 77 | 78 | 我要监控本地机器(测试方便)的indicator-service进程(本来就没有)。 79 | 80 | 81 | 新增。 82 | ![](https://chen_tiefeng.gitee.io/cloudimg/img/Clip_20191223_160658.png) 83 | 84 | 85 | 确认-立即执行(测试一下)。 86 | ![](https://chen_tiefeng.gitee.io/cloudimg/img/Clip_20191223_160917.png) 87 | 88 | 89 | 钉钉消息。 90 | ![](https://chen_tiefeng.gitee.io/cloudimg/img/ps-monitor.jpg) 91 | 92 | #### 日志监控 93 | 我要监控本地机器(可以替换其他机器)的lite-monitor服务的近1分钟出现`frequency`关键字的日志数量,超过2个就报警,钉钉展示10条消息。 94 | 95 | 复制,改改信息。 96 | ![](https://chen_tiefeng.gitee.io/cloudimg/img/Clip_20191223_162355.png) 97 | 98 | 99 | 确认-立即执行(测试一下),钉钉消息。 100 | ![](https://chen_tiefeng.gitee.io/cloudimg/img/log-monitor.jpg) 101 | 102 | #### 试用 103 | [lite-monitor](https://lite-monitor.chentiefeng.top),可以将钉钉机器人token改为自己的试用 104 | 105 | ## 字段说明 106 | 107 | - 监控类型:日志/进程,主要是生成的命令不同。 108 | 109 | - 监控频率:定时触发的频率,cron表达式。 110 | > 如果需要新增删改可以在源码的`me.ctf.lm.enums.FrequencyEnum`枚举类中修改。 111 | 112 | - ip地址:被监控的机器的IP地址。 113 | 114 | - 端口:被监控的机器的ssh端口,默认22。 115 | 116 | - 用户:被监控的机器可以ssh登录的用户名。 117 | 118 | - **密码/密钥文件地址:两者填写一个即可,密钥文件需要提前上传到部署机器上。** 119 | > 两者都填,优先使用密码登录ssh。 120 | > 两者都为空,需要实现免密登录,将部署机器的ssh的id_rsa.pub内容复制到被监控机器的`~/.ssh/authorized_keys`,例子中我就做了这事。 121 | 122 | - 日志文件:监控类型为日志的时候需要填写,日志的绝对路径。 123 | 124 | - 统计范围:监控类型为日志的时候需要填写,单位秒,范围:触发时间点往前减去N秒至触发时间点。 125 | 126 | - 阈值:监控类型为日志的时候需要填写,日志在统计范围内出现多少次就报警。 127 | 128 | - 展示条数:监控类型为日志的时候需要填写,在钉钉消息里面展示的日志数量。 129 | 130 | - **命令:监控类型为日志的时候需要填写,在系统生成的命令后面增加管道命令。** 131 | > 系统生成的命令为:`grep -e '2019-12-23 16:55:[3-9][0-9]' -e '2019-12-23 16:55:2[7-9]' /root/lite-monitor-server/logs/m.logs`,在后面增加`grep frequency`,最后生成的命令为`grep -e '2019-12-23 16:55:[3-9][0-9]' -e '2019-12-23 16:55:2[7-9]' /root/lite-monitor-server/logs/m.logs|grep frequency`。 132 | >> 时间的正则表达式生成参考:[时间正则](https://chentiefeng.top/2019/03/09/datetime-regex/) 133 | > 这里也可以填写`awk`等命令,统计一些效率/QPS等,比如统计QPS`grep '计算结束'|awk '{sum += 1} END {if(sum>60){print "近1分钟服务请求数量:" sum; print "近1分钟服务QPS:" sum/60}}'`。![](https://chen_tiefeng.gitee.io/cloudimg/img/qps-monitor.jpg) 134 | 135 | - **关键字:监控类型为进程的时候需要填写,进程的关键字。** 136 | > 监控进程的命令`ps -ef|grep 'xx'|grep -v grep|awk '{print $2}'`,打印进程id,不存在则报警。 137 | 138 | - 钉钉标题:钉钉消息Markdown格式的标题 139 | 140 | - 钉钉机器人token:钉钉机器人token,[钉钉机器人官方教程](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq) 141 | 142 | - 钉钉@人员:@人员的手机号码,多个以,分开 143 | > @all的话直接输入all 144 | 145 | ## 配置项说明 146 | 147 | 配置文件位置:/lite-monitor/src/main/resources/application.yml 148 | 149 | ```yml 150 | server: 151 | port: 10003 152 | 153 | spring: 154 | #============== redis =================== 155 | # redis: 156 | # host: 172.16.157.239 157 | # port: 6379 158 | # timeout: 6000 159 | # password: 160 | # database: 3 161 | # pool: 162 | # max-active: 1000 163 | # max-wait: -1 164 | # max-idle: 10 165 | # min-idle: 5 166 | 167 | #============== kafka =================== 168 | # kafka: 169 | # bootstrap-servers: cm02:9092,cm03:9092,cm04:9092 170 | # producer: 171 | # key-serializer: org.apache.kafka.common.serialization.StringSerializer 172 | # value-serializer: org.apache.kafka.common.serialization.StringSerializer 173 | # acks: all 174 | # consumer: 175 | # group-id: lite-monitor 176 | # key-serializer: org.apache.kafka.common.serialization.StringSerializer 177 | # value-serializer: org.apache.kafka.common.serialization.StringSerializer 178 | # max-poll-records: 10 179 | # listener: 180 | # type: batch 181 | # concurrency: 4 182 | # ack-mode: MANUAL 183 | # missing-topics-fatal: false 184 | 185 | #============== dataSource ================== 186 | datasource: 187 | # driver-class-name: com.mysql.cj.jdbc.Driver 188 | # url: jdbc:mysql://localhost:3306/lite_monitor?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true 189 | # username: root 190 | # password: 123456 191 | driver-class-name: org.h2.Driver 192 | url: jdbc:h2:~/lite-monitor 193 | username: sa 194 | password: 123456 195 | jpa: 196 | # database: mysql 197 | database: h2 198 | hibernate: 199 | ddl-auto: update 200 | show-sql: false 201 | 202 | monitor: 203 | #是否集群,集群模式请用MySQL,注释掉h2部分,会分发定时任务 false/true 204 | cluster: false 205 | #集群模式下,集群检查分钟数,3分钟没有反应则表明机器挂了 206 | # duration: 3 207 | #集群模式下,是否注册本机 online/offline 208 | # hostState: online 209 | #集群模式下分布式锁类型,db/redis,选择redis把redis的配置注释打开 210 | # distributed-lock-type: db 211 | #集群模式下分发类型,http/redis/kafka,选择其他需要把注释打开 212 | # distribute-task-type: http 213 | ``` 214 | 215 | > 默认配置端口10003,采用单机模式,数据库为`h2` 216 | 217 | ### 集群模式配置说明 218 | 219 | - `monitor.cluster`:`true/false`是否集群模式,默认`false`。改为`true`开启集群模式,集群模式下下面的配置才会生效。 220 | > 采用集群模式需把数据库改为`Mysql`,并执行脚本,脚本位置:`/lite-monitor/db.sql` 221 | 222 | - `monitor.hostState`:`online/offline`是否上线应用。默认`online`,改为`offline`后本应用会从集群模式中退出,分发任务会忽略本应用。 223 | 224 | - `monitor.duration`:应用检查时间,单位分钟,不支持小数。默认3分钟。 225 | 226 | - `monitor.distributed-lock-type`:`db/redis`分布式锁类型,默认`db`。`redis`类型支持锁过期设置、可重入锁,`db`类型均不支持。改为`redis`需要增加对应配置。 227 | > db类型重启应用后应检查和删除数据`select * from lite_monitor_exec_support_info where info_type = 'LOCK' ` 228 | 229 | - `monitor.distribute-task-type`:`http/redis/kafka`分发任务类型,默认`http`。`http`类型为每10个任务分发一次,分发路由默认顺序分发,改为`redis`或者`kafka`需要增加对应配置。 230 | > `redis`分发类型:基于`List`数据结构和`lpush/brpop`命令的生产者消费者模式。 231 | > `kafka`分发类型:逐条发送消息,批量消费(默认一次`poll`10条消息)。 232 | 233 | ## 联系作者 234 | - [chentiefeng@aliyun.com](mailto:chentiefeng@aliyun.com) 235 | - ![](https://chen_tiefeng.gitee.io/cloudimg/img/wx_chentiefeng.jpg) 236 | 237 | 238 | -------------------------------------------------------------------------------- /bc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | mvn install package -Dmaven.test.skip=true 3 | scp -P 22 target/lite-monitor-0.0.1-SNAPSHOT.jar admin@172.16.158.55:/home/admin/lite-monitor/lite-monitor-0.0.1-SNAPSHOT.jar 4 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.2.RELEASE 9 | 10 | 11 | me.ctf.lab 12 | lite-monitor 13 | 0.0.1-SNAPSHOT 14 | lite-monitor 15 | lite-monitor 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | com.baomidou 24 | mybatis-plus-boot-starter 25 | 3.4.0 26 | 27 | 28 | com.baomidou 29 | dynamic-datasource-spring-boot-starter 30 | 3.2.0 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | mysql 40 | mysql-connector-java 41 | runtime 42 | 43 | 44 | com.h2database 45 | h2 46 | runtime 47 | 48 | 49 | org.projectlombok 50 | lombok 51 | true 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | org.junit.vintage 60 | junit-vintage-engine 61 | 62 | 63 | 64 | 65 | ch.ethz.ganymed 66 | ganymed-ssh2 67 | build210 68 | 69 | 70 | 71 | org.apache.commons 72 | commons-lang3 73 | 3.9 74 | 75 | 76 | com.google.code.gson 77 | gson 78 | 2.8.5 79 | 80 | 81 | 82 | com.google.guava 83 | guava 84 | 28.1-jre 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | org.springframework.boot 100 | spring-boot-maven-plugin 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/LiteMonitorApplication.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @EnableScheduling 8 | @SpringBootApplication 9 | public class LiteMonitorApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(LiteMonitorApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/cmdexecutor/AbstractCmdExecutor.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.cmdexecutor; 2 | 3 | import me.ctf.lm.entity.MonitorConfigEntity; 4 | import me.ctf.lm.enums.MonitorTypeEnum; 5 | import me.ctf.lm.schedule.ScheduleCmdExecutor; 6 | import me.ctf.lm.util.CmdExecutorUtil; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.io.IOException; 11 | 12 | /** 13 | * @author: chentiefeng[chentiefeng@linzikg.com] 14 | * @create: 2019-12-18 13:41 15 | */ 16 | public abstract class AbstractCmdExecutor { 17 | /** 18 | * 命令执行 19 | * 20 | * @param monitor 21 | */ 22 | public abstract void execute(MonitorConfigEntity monitor); 23 | 24 | @PostConstruct 25 | public void register() { 26 | ScheduleCmdExecutor.register(monitorType(), this); 27 | } 28 | 29 | /** 30 | * 监控类型 31 | * 32 | * @return 33 | */ 34 | protected abstract MonitorTypeEnum monitorType(); 35 | 36 | /** 37 | * 命令执行 38 | * 39 | * @param monitor 40 | * @param cmd 41 | * @return 42 | */ 43 | protected String cmdExecute(MonitorConfigEntity monitor, String cmd) throws IOException { 44 | if (StringUtils.isNoneBlank(monitor.getPwd())) { 45 | // 用户名密码认证 46 | return CmdExecutorUtil.authPasswordAndExecute(monitor.getHostName(), monitor.getPort(), monitor.getUsername(), monitor.getPwd(), cmd); 47 | } else if (StringUtils.isNoneBlank(monitor.getPem())) { 48 | // 用户密钥认证 49 | return CmdExecutorUtil.authPublicKeyAndExecute(monitor.getHostName(), monitor.getPort(), monitor.getUsername(), monitor.getPem(), cmd); 50 | } else { 51 | // 默认密钥认证 52 | return CmdExecutorUtil.authPublicKeyAndExecute(monitor.getHostName(), monitor.getPort(), monitor.getUsername(), cmd); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/cmdexecutor/LogCmdExecutor.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.cmdexecutor; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import me.ctf.lm.entity.MonitorConfigEntity; 5 | import me.ctf.lm.enums.DingTypeEnum; 6 | import me.ctf.lm.enums.MonitorTypeEnum; 7 | import me.ctf.lm.util.*; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.apache.commons.lang3.time.DateFormatUtils; 10 | import org.apache.commons.lang3.time.DateUtils; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.io.IOException; 14 | import java.io.UnsupportedEncodingException; 15 | import java.security.InvalidKeyException; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.util.Arrays; 18 | import java.util.Date; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | * @author: chentiefeng[chentiefeng@linzikg.com] 24 | * @create: 2019-12-18 10:52 25 | */ 26 | @Slf4j 27 | @Component 28 | public class LogCmdExecutor extends AbstractCmdExecutor { 29 | 30 | public static final String ALL = "all"; 31 | 32 | /** 33 | * 日志命令执行 34 | * 35 | * @param monitor 36 | */ 37 | @Override 38 | public void execute(MonitorConfigEntity monitor) { 39 | Date now = new Date(); 40 | Date pre = DateUtils.addSeconds(now, -monitor.getStatSecond().intValue()); 41 | String nowStr = DateFormatUtils.format(now, "yyyy-MM-dd HH:mm:ss"); 42 | String preStr = DateFormatUtils.format(pre, "yyyy-MM-dd HH:mm:ss"); 43 | //命令构建 44 | String cmd = cmdBuilder(monitor, pre); 45 | //执行命令 46 | List list = executeAndParse(monitor, cmd); 47 | if (list == null) { 48 | return; 49 | } 50 | if (list.size() < monitor.getThreshold()) { 51 | return; 52 | } 53 | //发送订单消息 54 | if (DingTypeEnum.FEISHU.name().equalsIgnoreCase(monitor.getDingType())) { 55 | feishu(monitor, nowStr, preStr, list); 56 | } else { 57 | ding(monitor, nowStr, preStr, list); 58 | } 59 | } 60 | 61 | /** 62 | * 命令执行 63 | * 64 | * @param monitor 65 | * @param cmd 66 | * @return 67 | */ 68 | protected List executeAndParse(MonitorConfigEntity monitor, String cmd) { 69 | String rst = null; 70 | try { 71 | rst = super.cmdExecute(monitor, cmd); 72 | } catch (IOException e) { 73 | log.error(e.getMessage(), e); 74 | } 75 | if (StringUtils.isBlank(rst)) { 76 | return null; 77 | } 78 | return Arrays.stream(StringUtils.split(rst, "\n")).collect(Collectors.toList()); 79 | } 80 | 81 | 82 | /** 83 | * 构建命令 84 | * 85 | * @param monitor 86 | * @param pre 87 | * @return 88 | */ 89 | private String cmdBuilder(MonitorConfigEntity monitor, Date pre) { 90 | StringBuilder cmd = new StringBuilder("grep"); 91 | for (String regex : DateTimeRegex.dateUntilNowFormat(pre)) { 92 | cmd.append(" -e '").append(regex).append("'"); 93 | } 94 | //2019.12.26 日志文件路径支持日期占位符 95 | cmd.append(" ").append(PlaceholderUtil.analyze(monitor.getFilePath(), null)); 96 | if (StringUtils.isNoneBlank(monitor.getScript())) { 97 | cmd.append(" | ").append(monitor.getScript()); 98 | } 99 | return cmd.toString(); 100 | } 101 | 102 | /** 103 | * 钉钉消息发送 104 | * 105 | * @param monitor 106 | * @param nowStr 107 | * @param preStr 108 | * @param list 109 | */ 110 | private void ding(MonitorConfigEntity monitor, String nowStr, String preStr, List list) { 111 | DingMarkdownMessage message = new DingMarkdownMessage(); 112 | String title = monitor.getHostName() + "," + monitor.getDingTitle() + "[" + list.size() + "]"; 113 | if (monitor.getShowCount() == null) { 114 | monitor.setShowCount(10); 115 | } 116 | if (list.size() > monitor.getShowCount()) { 117 | title = title + "[展示前" + monitor.getShowCount() + "个]"; 118 | list = list.subList(0, monitor.getShowCount()); 119 | } 120 | String dingAt = monitor.getDingAt(); 121 | boolean atAll = ALL.equalsIgnoreCase(dingAt); 122 | message.setAtAll(atAll); 123 | if (!atAll && StringUtils.isNoneBlank(dingAt)) { 124 | String[] atMobiles = dingAt.split(","); 125 | message.setAtMobiles(atMobiles); 126 | title = title + ",@" + String.join(",@", atMobiles); 127 | } 128 | if (atAll) { 129 | title = title + ",@all"; 130 | } 131 | message.setTitle(title); 132 | message.add(DingMarkdownMessage.getHeaderText(3, title)); 133 | message.add(DingMarkdownMessage.getReferenceText("统计时段:" + preStr + "--" + nowStr + "\n")); 134 | for (String s : list) { 135 | message.add(DingMarkdownMessage.getReferenceText(s + "\n")); 136 | } 137 | if(StringUtils.isNotBlank(monitor.getSignKey())) { 138 | long timestamp = System.currentTimeMillis(); 139 | try { 140 | String sign = DingTalkHelper.sign(timestamp, monitor.getSignKey()); 141 | DingTalkHelper.sendMarkdownMsg(message, monitor.getDingToken(), timestamp, sign); 142 | } catch (NoSuchAlgorithmException | UnsupportedEncodingException | InvalidKeyException e) { 143 | log.error(e.getMessage(), e); 144 | } 145 | }else { 146 | DingTalkHelper.sendMarkdownMsg(message, monitor.getDingToken()); 147 | } 148 | } 149 | 150 | /** 151 | * 飞书消息发送 152 | * 153 | * @param monitor 154 | * @param nowStr 155 | * @param preStr 156 | * @param list 157 | */ 158 | private void feishu(MonitorConfigEntity monitor, String nowStr, String preStr, List list) { 159 | String title = monitor.getHostName() + "," + monitor.getDingTitle() + "[" + list.size() + "]"; 160 | if (monitor.getShowCount() == null) { 161 | monitor.setShowCount(10); 162 | } 163 | if (list.size() > monitor.getShowCount()) { 164 | title = title + "[展示前" + monitor.getShowCount() + "个]"; 165 | list = list.subList(0, monitor.getShowCount()); 166 | } 167 | String dingAt = monitor.getDingAt(); 168 | boolean atAll = ALL.equalsIgnoreCase(dingAt); 169 | if (!atAll && StringUtils.isNoneBlank(dingAt)) { 170 | String[] atMobiles = dingAt.split(","); 171 | title = title + ",@" + String.join(",@", atMobiles); 172 | } 173 | if (atAll) { 174 | title = title + ",@all"; 175 | } 176 | StringBuilder content = new StringBuilder("统计时段:" + preStr + "--" + nowStr + "\n"); 177 | for (String s : list) { 178 | content.append(s + "\n"); 179 | } 180 | FeishuTalkHelper.sendTextMsg(title, content.toString(), monitor.getDingToken()); 181 | } 182 | 183 | @Override 184 | protected MonitorTypeEnum monitorType() { 185 | return MonitorTypeEnum.LOG; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/cmdexecutor/PsCmdExecutor.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.cmdexecutor; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import me.ctf.lm.entity.MonitorConfigEntity; 5 | import me.ctf.lm.enums.DingTypeEnum; 6 | import me.ctf.lm.enums.MonitorTypeEnum; 7 | import me.ctf.lm.util.DingMarkdownMessage; 8 | import me.ctf.lm.util.DingTalkHelper; 9 | import me.ctf.lm.util.FeishuTalkHelper; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.io.IOException; 14 | import java.io.UnsupportedEncodingException; 15 | import java.security.InvalidKeyException; 16 | import java.security.NoSuchAlgorithmException; 17 | 18 | /** 19 | * @author: chentiefeng[chentiefeng@linzikg.com] 20 | * @create: 2019-12-18 11:35 21 | */ 22 | @Slf4j 23 | @Component 24 | public class PsCmdExecutor extends AbstractCmdExecutor { 25 | private static final String ALL = "all"; 26 | 27 | /** 28 | * 日志命令执行 29 | * 30 | * @param monitor 31 | */ 32 | @Override 33 | public void execute(MonitorConfigEntity monitor) { 34 | //命令构建 35 | String cmd = "ps -ef|grep '" + monitor.getScript() + "'|grep -v grep|awk '{print $2}'"; 36 | //执行命令 37 | String psId = null; 38 | try { 39 | psId = cmdExecute(monitor, cmd); 40 | } catch (IOException e) { 41 | log.error(e.getMessage(), e); 42 | } 43 | //进程号为空发送钉钉消息 44 | if (StringUtils.isBlank(psId)) { 45 | //发送订单消息 46 | if (DingTypeEnum.FEISHU.name().equalsIgnoreCase(monitor.getDingType())) { 47 | feishu(monitor); 48 | } else { 49 | ding(monitor); 50 | } 51 | } 52 | } 53 | 54 | 55 | /** 56 | * 钉钉消息发送 57 | * 58 | * @param monitor 59 | */ 60 | private void ding(MonitorConfigEntity monitor) { 61 | DingMarkdownMessage message = new DingMarkdownMessage(); 62 | String title = monitor.getHostName() + "," + monitor.getRemark(); 63 | String dingAt = monitor.getDingAt(); 64 | boolean atAll = ALL.equalsIgnoreCase(dingAt); 65 | message.setAtAll(atAll); 66 | if (!atAll && StringUtils.isNoneBlank(dingAt)) { 67 | String[] atMobiles = dingAt.split(","); 68 | message.setAtMobiles(atMobiles); 69 | title = title + String.join(",@", atMobiles); 70 | } 71 | if (atAll) { 72 | title = title + ",@all"; 73 | } 74 | message.setTitle(title); 75 | message.add(DingMarkdownMessage.getHeaderText(3, title)); 76 | message.add(DingMarkdownMessage.getReferenceText("进程不存在,请及时处理")); 77 | if (StringUtils.isNotBlank(monitor.getSignKey())) { 78 | long timestamp = System.currentTimeMillis(); 79 | try { 80 | String sign = DingTalkHelper.sign(timestamp, monitor.getSignKey()); 81 | DingTalkHelper.sendMarkdownMsg(message, monitor.getDingToken(), timestamp, sign); 82 | } catch (NoSuchAlgorithmException | UnsupportedEncodingException | InvalidKeyException e) { 83 | log.error(e.getMessage(), e); 84 | } 85 | } else { 86 | DingTalkHelper.sendMarkdownMsg(message, monitor.getDingToken()); 87 | } 88 | } 89 | 90 | /** 91 | * 钉钉消息发送 92 | * 93 | * @param monitor 94 | */ 95 | private void feishu(MonitorConfigEntity monitor) { 96 | String title = monitor.getHostName() + "," + monitor.getRemark(); 97 | String dingAt = monitor.getDingAt(); 98 | boolean atAll = ALL.equalsIgnoreCase(dingAt); 99 | if (!atAll && StringUtils.isNoneBlank(dingAt)) { 100 | String[] atMobiles = dingAt.split(","); 101 | title = title + String.join(",@", atMobiles); 102 | } 103 | if (atAll) { 104 | title = title + ",@all"; 105 | } 106 | FeishuTalkHelper.sendTextMsg(title, "进程不存在,请及时处理", monitor.getDingToken()); 107 | } 108 | 109 | @Override 110 | protected MonitorTypeEnum monitorType() { 111 | return MonitorTypeEnum.PROCESS; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/cmdexecutor/SqlExecutor.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.cmdexecutor; 2 | 3 | import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; 4 | import lombok.extern.slf4j.Slf4j; 5 | import me.ctf.lm.entity.MonitorConfigEntity; 6 | import me.ctf.lm.enums.DingTypeEnum; 7 | import me.ctf.lm.enums.MonitorTypeEnum; 8 | import me.ctf.lm.util.DingMarkdownMessage; 9 | import me.ctf.lm.util.DingTalkHelper; 10 | import me.ctf.lm.util.FeishuTalkHelper; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.springframework.jdbc.core.JdbcTemplate; 13 | import org.springframework.stereotype.Component; 14 | 15 | import javax.annotation.Resource; 16 | import java.io.UnsupportedEncodingException; 17 | import java.security.InvalidKeyException; 18 | import java.security.NoSuchAlgorithmException; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Objects; 23 | import java.util.stream.Collectors; 24 | 25 | import static me.ctf.lm.cmdexecutor.LogCmdExecutor.ALL; 26 | 27 | /** 28 | * @author chentiefeng 29 | * @date 2020-09-11 16:18 30 | */ 31 | @Component 32 | @Slf4j 33 | public class SqlExecutor extends AbstractCmdExecutor { 34 | 35 | @Resource 36 | private JdbcTemplate jdbcTemplate; 37 | 38 | @Override 39 | public void execute(MonitorConfigEntity monitor) { 40 | try { 41 | DynamicDataSourceContextHolder.push(monitor.getSchemaName()); 42 | List> maps = jdbcTemplate.queryForList(monitor.getScript()); 43 | List values = new ArrayList<>(); 44 | for (Map map : maps) { 45 | values.addAll(map.values()); 46 | } 47 | List list = values.stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList()); 48 | if (list.size() < monitor.getThreshold()) { 49 | return; 50 | } 51 | //发送订单消息 52 | if (DingTypeEnum.FEISHU.name().equalsIgnoreCase(monitor.getDingType())) { 53 | feishu(monitor, list); 54 | } else { 55 | ding(monitor, list); 56 | } 57 | } finally { 58 | DynamicDataSourceContextHolder.poll(); 59 | } 60 | } 61 | 62 | /** 63 | * 钉钉消息发送 64 | * 65 | * @param monitor 66 | * @param list 67 | */ 68 | private void ding(MonitorConfigEntity monitor, List list) { 69 | DingMarkdownMessage message = new DingMarkdownMessage(); 70 | String title = monitor.getSchemaName() + "," + monitor.getDingTitle() + "[" + list.size() + "]"; 71 | if (monitor.getShowCount() == null) { 72 | monitor.setShowCount(10); 73 | } 74 | if (list.size() > monitor.getShowCount()) { 75 | title = title + "[展示前" + monitor.getShowCount() + "个]"; 76 | list = list.subList(0, monitor.getShowCount()); 77 | } 78 | String dingAt = monitor.getDingAt(); 79 | boolean atAll = ALL.equalsIgnoreCase(dingAt); 80 | message.setAtAll(atAll); 81 | if (!atAll && StringUtils.isNoneBlank(dingAt)) { 82 | String[] atMobiles = dingAt.split(","); 83 | message.setAtMobiles(atMobiles); 84 | title = title + ",@" + String.join(",@", atMobiles); 85 | } 86 | if (atAll) { 87 | title = title + ",@all"; 88 | } 89 | message.setTitle(title); 90 | message.add(DingMarkdownMessage.getHeaderText(3, title)); 91 | for (String s : list) { 92 | message.add(DingMarkdownMessage.getReferenceText(s + "\n")); 93 | } 94 | if(StringUtils.isNotBlank(monitor.getSignKey())) { 95 | long timestamp = System.currentTimeMillis(); 96 | try { 97 | String sign = DingTalkHelper.sign(timestamp, monitor.getSignKey()); 98 | DingTalkHelper.sendMarkdownMsg(message, monitor.getDingToken(), timestamp, sign); 99 | } catch (NoSuchAlgorithmException | UnsupportedEncodingException | InvalidKeyException e) { 100 | log.error(e.getMessage(), e); 101 | } 102 | }else { 103 | DingTalkHelper.sendMarkdownMsg(message, monitor.getDingToken()); 104 | } 105 | } 106 | 107 | /** 108 | * 飞书消息发送 109 | * 110 | * @param monitor 111 | * @param list 112 | */ 113 | private void feishu(MonitorConfigEntity monitor, List list) { 114 | String title = monitor.getSchemaName() + "," + monitor.getDingTitle() + "[" + list.size() + "]"; 115 | if (monitor.getShowCount() == null) { 116 | monitor.setShowCount(10); 117 | } 118 | if (list.size() > monitor.getShowCount()) { 119 | title = title + "[展示前" + monitor.getShowCount() + "个]"; 120 | list = list.subList(0, monitor.getShowCount()); 121 | } 122 | String dingAt = monitor.getDingAt(); 123 | boolean atAll = ALL.equalsIgnoreCase(dingAt); 124 | if (!atAll && StringUtils.isNoneBlank(dingAt)) { 125 | String[] atMobiles = dingAt.split(","); 126 | title = title + ",@" + String.join(",@", atMobiles); 127 | } 128 | if (atAll) { 129 | title = title + ",@all"; 130 | } 131 | StringBuilder content = new StringBuilder(); 132 | for (String s : list) { 133 | content.append(s).append("\n"); 134 | } 135 | FeishuTalkHelper.sendTextMsg(title, content.toString(), monitor.getDingToken()); 136 | } 137 | 138 | @Override 139 | protected MonitorTypeEnum monitorType() { 140 | return MonitorTypeEnum.SQL; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/config/ExceptionHandlers.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.config; 2 | 3 | import me.ctf.lm.dto.MapResult; 4 | import me.ctf.lm.util.BizException; 5 | import org.springframework.validation.BindException; 6 | import org.springframework.validation.BindingResult; 7 | import org.springframework.validation.ObjectError; 8 | import org.springframework.web.bind.MethodArgumentNotValidException; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | /** 13 | * @author: chentiefeng[chentiefeng@linzikg.com] 14 | * @create: 2019-12-12 14:35 15 | */ 16 | @RestControllerAdvice 17 | public class ExceptionHandlers { 18 | /** 19 | * json 格式错误验证 20 | * 21 | * @param ex 22 | * @return 23 | */ 24 | @ExceptionHandler(value = MethodArgumentNotValidException.class) 25 | public MapResult errorHandler(MethodArgumentNotValidException ex) { 26 | StringBuilder errorMsg = new StringBuilder(); 27 | BindingResult re = ex.getBindingResult(); 28 | for (ObjectError error : re.getAllErrors()) { 29 | errorMsg.append(error.getDefaultMessage()).append(","); 30 | } 31 | errorMsg.delete(errorMsg.length() - 1, errorMsg.length()); 32 | return MapResult.error(errorMsg.toString()); 33 | } 34 | 35 | 36 | /** 37 | * 表单异常 38 | * 39 | * @param ex 40 | * @return 41 | */ 42 | @ExceptionHandler(value = BindException.class) 43 | public MapResult errorHandler(BindException ex) { 44 | BindingResult result = ex.getBindingResult(); 45 | StringBuilder errorMsg = new StringBuilder(); 46 | for (ObjectError error : result.getAllErrors()) { 47 | errorMsg.append(error.getDefaultMessage()).append(","); 48 | } 49 | errorMsg.delete(errorMsg.length() - 1, errorMsg.length()); 50 | return MapResult.error(errorMsg.toString()); 51 | } 52 | 53 | /** 54 | * 表单异常 55 | * 56 | * @param ex 57 | * @return 58 | */ 59 | @ExceptionHandler(value = BizException.class) 60 | public MapResult errorHandler(BizException ex) { 61 | return MapResult.error(ex.getCode(), ex.getMsg()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/config/JacksonConfiguration.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.config; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 9 | import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.Primary; 13 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 14 | 15 | import java.text.SimpleDateFormat; 16 | import java.time.ZoneId; 17 | import java.util.Locale; 18 | import java.util.TimeZone; 19 | 20 | /** 21 | * @author chentiefeng 22 | * @date 2020-05-18 23:19 23 | */ 24 | @Configuration 25 | @ConditionalOnClass(ObjectMapper.class) 26 | @AutoConfigureBefore(JacksonAutoConfiguration.class) 27 | public class JacksonConfiguration { 28 | 29 | @Primary 30 | @Bean 31 | public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { 32 | builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss"); 33 | //创建ObjectMapper 34 | ObjectMapper objectMapper = builder.createXmlMapper(false).build(); 35 | //设置地点为中国 36 | objectMapper.setLocale(Locale.getDefault()); 37 | //去掉默认的时间戳格式 38 | objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 39 | //设置为中国上海时区 40 | objectMapper.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault())); 41 | //序列化时,日期的统一格式 42 | objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)); 43 | //失败处理 44 | objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 45 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 46 | //单引号处理 47 | objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); 48 | //反序列化时,属性不存在的兼容处理 49 | objectMapper.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 50 | //日期格式化 51 | objectMapper.findAndRegisterModules(); 52 | objectMapper.registerModule(new JavaTimeModule()); 53 | return objectMapper; 54 | } 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/config/JavaTimeModule.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.config; 2 | 3 | import com.fasterxml.jackson.databind.module.SimpleModule; 4 | import com.fasterxml.jackson.datatype.jsr310.PackageVersion; 5 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; 6 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 7 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; 8 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; 9 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 10 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; 11 | 12 | import java.time.LocalDate; 13 | import java.time.LocalDateTime; 14 | import java.time.LocalTime; 15 | import java.time.format.DateTimeFormatter; 16 | 17 | /** 18 | * @author chentiefeng 19 | * @date 2020-05-18 23:22 20 | */ 21 | public class JavaTimeModule extends SimpleModule { 22 | 23 | public JavaTimeModule() { 24 | super(PackageVersion.VERSION); 25 | this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); 26 | this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); 27 | this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); 28 | this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); 29 | this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); 30 | this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/config/MonitorConfig.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author: chentiefeng[chentiefeng@linzikg.com] 9 | * @create: 2019-12-20 11:17 10 | */ 11 | @Data 12 | @Configuration 13 | @ConfigurationProperties(prefix = "monitor") 14 | public class MonitorConfig { 15 | /** 16 | * 主机状态 17 | */ 18 | private String hostState; 19 | /** 20 | * 是否集群 21 | */ 22 | private Boolean cluster; 23 | /** 24 | * 主机检查时间,分钟 25 | */ 26 | private Integer duration; 27 | /** 28 | * 分发任务模式 29 | */ 30 | private String distributedLockType; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-2019 人人开源 All rights reserved. 3 | *

4 | * https://www.renren.io 5 | *

6 | * 版权所有,侵权必究! 7 | */ 8 | 9 | package me.ctf.lm.config; 10 | 11 | import com.baomidou.mybatisplus.annotation.DbType; 12 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 13 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | 17 | /** 18 | * mybatis-plus配置 19 | * 20 | * @author Mark sunlightcs@gmail.com 21 | */ 22 | @Configuration 23 | public class MybatisPlusConfig { 24 | 25 | /** 26 | * 分页插件 27 | */ 28 | @Bean 29 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 30 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 31 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2)); 32 | return interceptor; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/config/ValidateConfig.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.config; 2 | 3 | import org.hibernate.validator.HibernateValidator; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import javax.validation.Validation; 8 | import javax.validation.Validator; 9 | import javax.validation.ValidatorFactory; 10 | 11 | /** 12 | * 验证配置 13 | * 14 | * @author: chentiefeng[chentiefeng@linzikg.com] 15 | * @create: 2019-12-12 14:34 16 | */ 17 | @Configuration 18 | public class ValidateConfig { 19 | @Bean 20 | public Validator validator() { 21 | ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) 22 | .configure() 23 | .addProperty("hibernate.validator.fail_fast", "true") 24 | .buildValidatorFactory(); 25 | return validatorFactory.getValidator(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/controller/LiteMonitorController.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import me.ctf.lm.dto.MapResult; 5 | import me.ctf.lm.entity.MonitorConfigEntity; 6 | import me.ctf.lm.enums.FrequencyEnum; 7 | import me.ctf.lm.schedule.ScheduleCmdExecutor; 8 | import me.ctf.lm.service.LiteMonitorConfigService; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.*; 13 | 14 | /** 15 | * @author: chentiefeng[chentiefeng@linzikg.com] 16 | * @create: 2019-12-18 09:23 17 | */ 18 | @RestController 19 | @RequestMapping("/liteMonitor") 20 | @Slf4j 21 | public class LiteMonitorController { 22 | 23 | @Resource 24 | private LiteMonitorConfigService liteMonitorConfigService; 25 | 26 | /** 27 | * 分页查询 28 | * 29 | * @return 30 | */ 31 | @GetMapping("/page") 32 | public MapResult page(@RequestParam Map params) { 33 | try { 34 | return MapResult.ok().put("page", liteMonitorConfigService.queryPage(params)); 35 | } catch (Exception e) { 36 | log.error(e.getMessage(), e); 37 | return MapResult.error(e.getMessage()); 38 | } 39 | } 40 | 41 | /** 42 | * 执行 43 | * 44 | * @param ids 45 | * @return 46 | */ 47 | @PostMapping("/execute") 48 | public MapResult execute(@RequestBody Long[] ids) { 49 | ScheduleCmdExecutor.execute(ids); 50 | return MapResult.ok(); 51 | } 52 | 53 | /** 54 | * 保存 55 | * 56 | * @param monitor 57 | * @return 58 | */ 59 | @PostMapping("/save") 60 | public MapResult save(@RequestBody MonitorConfigEntity monitor) { 61 | liteMonitorConfigService.submit(monitor); 62 | return MapResult.ok(); 63 | } 64 | 65 | /** 66 | * 删除 67 | * 68 | * @param id 69 | * @return 70 | */ 71 | @GetMapping("/delete") 72 | public MapResult delete(@RequestParam("id") Long id) { 73 | liteMonitorConfigService.removeById(id); 74 | return MapResult.ok(); 75 | } 76 | 77 | /** 78 | * 启用 79 | * 80 | * @param id 81 | * @return 82 | */ 83 | @GetMapping("/enabled") 84 | public MapResult enabled(@RequestParam("id") Long id) { 85 | liteMonitorConfigService.enabled(id); 86 | return MapResult.ok(); 87 | } 88 | 89 | /** 90 | * 启用 91 | * 92 | * @param id 93 | * @return 94 | */ 95 | @GetMapping("/info") 96 | public MapResult info(@RequestParam("id") Long id) { 97 | return MapResult.ok().put("entity", liteMonitorConfigService.info(id)); 98 | } 99 | 100 | /** 101 | * info 102 | * 103 | * @param id 104 | * @return 105 | */ 106 | @GetMapping("/disabled") 107 | public MapResult disabled(@RequestParam("id") Long id) { 108 | liteMonitorConfigService.disabled(id); 109 | return MapResult.ok(); 110 | } 111 | 112 | /** 113 | * frequency 114 | * 115 | * @return 116 | */ 117 | @GetMapping("/frequency") 118 | public MapResult frequency() { 119 | List> mapList = new ArrayList<>(); 120 | Arrays.stream(FrequencyEnum.values()).forEach(value -> { 121 | Map map = new HashMap<>(1); 122 | map.put("frequency", value.getCron()); 123 | map.put("frequencyDesc", value.getDesc()); 124 | mapList.add(map); 125 | }); 126 | return MapResult.ok().put("frequencyList", mapList); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/dao/MonitorConfigMapper.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.dao; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import me.ctf.lm.entity.MonitorConfigEntity; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * @author: chentiefeng[chentiefeng@linzikg.com] 9 | * @create: 2019-12-12 13:56 10 | */ 11 | @Mapper 12 | public interface MonitorConfigMapper extends BaseMapper { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/dao/MonitorExecSupportInfoMapper.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.dao; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import me.ctf.lm.entity.MonitorExecSupportInfoEntity; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | /** 8 | * @author: chentiefeng[chentiefeng@linzikg.com] 9 | * @create: 2019-12-12 13:56 10 | */ 11 | @Mapper 12 | public interface MonitorExecSupportInfoMapper extends BaseMapper { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/dto/MapResult.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.dto; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author: chentiefeng[chentiefeng@linzikg.com] 8 | * @create: 2019-12-12 14:38 9 | */ 10 | public class MapResult extends HashMap { 11 | 12 | 13 | private static final long serialVersionUID = 3323500050125496742L; 14 | 15 | public MapResult() { 16 | put("code", 0); 17 | put("msg", "success"); 18 | } 19 | 20 | public static MapResult error() { 21 | return error(500, "未知异常,请联系管理员"); 22 | } 23 | 24 | public static MapResult error(String msg) { 25 | return error(500, msg); 26 | } 27 | 28 | public static MapResult error(int code, String msg) { 29 | MapResult r = new MapResult(); 30 | r.put("code", code); 31 | r.put("msg", msg); 32 | return r; 33 | } 34 | 35 | public static MapResult ok(String msg) { 36 | MapResult r = new MapResult(); 37 | r.put("msg", msg); 38 | return r; 39 | } 40 | 41 | public static MapResult ok(Map map) { 42 | MapResult r = new MapResult(); 43 | r.putAll(map); 44 | return r; 45 | } 46 | 47 | public static MapResult ok() { 48 | return new MapResult(); 49 | } 50 | 51 | @Override 52 | public MapResult put(String key, Object value) { 53 | super.put(key, value); 54 | return this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/entity/MonitorConfigEntity.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import me.ctf.lm.util.validator.LogGroup; 9 | 10 | import javax.validation.constraints.NotBlank; 11 | import javax.validation.constraints.NotNull; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | * 轻量监控配置 16 | * 17 | * @author: chentiefeng[chentiefeng@linzikg.com] 18 | * @create: 2019-12-12 11:53 19 | */ 20 | @Data 21 | @TableName("lite_monitor_config") 22 | public class MonitorConfigEntity { 23 | /** 24 | * 主键 25 | */ 26 | @TableId(type = IdType.AUTO) 27 | private Long id; 28 | /** 29 | * 监控类型,进程:PROCESS,日志:LOG 30 | */ 31 | @NotBlank(message = "监控类型不能为空") 32 | private String monitorType; 33 | /** 34 | * 频率,cron表达式或者枚举 35 | */ 36 | @NotBlank(message = "监控频率不能为空") 37 | private String frequency; 38 | 39 | @TableField(exist = false) 40 | private String frequencyDesc; 41 | /** 42 | * schema 43 | */ 44 | private String schemaName; 45 | /** 46 | * 主机 47 | */ 48 | private String hostName; 49 | /** 50 | * 用户名 51 | */ 52 | private String username; 53 | /** 54 | * 密码 55 | */ 56 | private String pwd; 57 | /** 58 | * 密钥 59 | */ 60 | private String pem; 61 | /** 62 | * 端口 63 | */ 64 | @NotNull(message = "端口不能为空") 65 | private Integer port; 66 | /** 67 | * 文件地址 68 | */ 69 | @NotBlank(message = "日志文件不能为空", groups = LogGroup.class) 70 | private String filePath; 71 | /** 72 | * 统计范围,秒 73 | */ 74 | @NotNull(message = "统计范围不能为空", groups = LogGroup.class) 75 | private Long statSecond; 76 | /** 77 | * 阈值 78 | */ 79 | @NotNull(message = "阈值不能为空", groups = LogGroup.class) 80 | private Long threshold; 81 | /** 82 | * 脚本 83 | */ 84 | private String script; 85 | /** 86 | * 提醒类型 87 | */ 88 | private String dingType; 89 | /** 90 | * 钉钉标题 91 | */ 92 | @NotBlank(message = "钉钉标题不能为空", groups = LogGroup.class) 93 | private String dingTitle; 94 | /** 95 | * 钉钉token 96 | */ 97 | @NotBlank(message = "钉钉token不能为空") 98 | private String dingToken; 99 | /** 100 | * 钉钉签名Key 101 | */ 102 | private String signKey; 103 | /** 104 | * 钉钉展示条数 105 | */ 106 | @NotNull(message = "钉钉展示条数不能为空", groups = LogGroup.class) 107 | private Integer showCount; 108 | /** 109 | * 钉钉at 110 | */ 111 | private String dingAt; 112 | /** 113 | * 备注 114 | */ 115 | private String remark; 116 | /** 117 | * 是否启用,0未启用,1启用 118 | */ 119 | private Integer enabled; 120 | /** 121 | * 创建日期 122 | */ 123 | private LocalDateTime gmtCreate; 124 | /** 125 | * 修改日期 126 | */ 127 | private LocalDateTime gmtModified; 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/entity/MonitorExecSupportInfoEntity.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.Date; 10 | 11 | /** 12 | * @author: chentiefeng[chentiefeng@linzikg.com] 13 | * @create: 2019-12-18 14:54 14 | */ 15 | @Data 16 | @TableName("lite_monitor_exec_support_info") 17 | public class MonitorExecSupportInfoEntity { 18 | @TableId(type = IdType.AUTO) 19 | private Long id; 20 | /** 21 | * info type 22 | */ 23 | private String infoType; 24 | /** 25 | * info 26 | */ 27 | private String info; 28 | /** 29 | * 创建日期 30 | */ 31 | private LocalDateTime gmtCreate; 32 | /** 33 | * 修改日期 34 | */ 35 | private LocalDateTime gmtModified; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/enums/DingTypeEnum.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.enums; 2 | 3 | /** 4 | * @author chentiefeng 5 | * @date 2020-04-17 14:38 6 | */ 7 | public enum DingTypeEnum { 8 | FEISHU, 9 | DINGDING 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/enums/DistributedLockTypeEnum.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.enums; 2 | 3 | /** 4 | * @author: chentiefeng[chentiefeng@linzikg.com] 5 | * @create: 2019-12-19 09:26 6 | */ 7 | public enum DistributedLockTypeEnum { 8 | /** 9 | * DB 10 | */ 11 | DB, 12 | /** 13 | * REDIS 14 | */ 15 | REDIS; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/enums/FrequencyEnum.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @author: chentiefeng[chentiefeng@linzikg.com] 8 | * @create: 2019-12-19 15:48 9 | */ 10 | @AllArgsConstructor 11 | @Getter 12 | public enum FrequencyEnum { 13 | //监控服务-30秒 14 | FREQUENCY_30("*/30 * * * * ?", "30秒"), 15 | //监控服务-1分钟 16 | FREQUENCY_60("1 */1 * * * ?", "1分钟"), 17 | //监控服务-5分钟 18 | FREQUENCY_300("1 */5 * * * ?", "5分钟"), 19 | //监控服务-10分钟 20 | FREQUENCY_600("1 */10 * * * ?", "10分钟"), 21 | //监控服务-30分钟 22 | FREQUENCY_1800("1 */30 * * * ?", "30分钟"), 23 | //监控服务-1小时 24 | FREQUENCY_3600("1 0 */1 * * ?", "1小时"), 25 | //监控服务-2小时 26 | FREQUENCY_36000("1 0 */2 * * ?", "2小时"), 27 | //监控服务-6小时 28 | FREQUENCY_21600("1 0 */6 * * ?", "6小时"), 29 | //监控服务-12小时 30 | FREQUENCY_43200("1 0 */12 * * ?", "12小时"), 31 | ; 32 | private String cron; 33 | private String desc; 34 | 35 | public static FrequencyEnum getByValue(String frequency) { 36 | for (FrequencyEnum value : values()) { 37 | if (value.cron.equalsIgnoreCase(frequency)) { 38 | return value; 39 | } 40 | } 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/enums/HostStateEnum.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.enums; 2 | 3 | /** 4 | * @author: chentiefeng[chentiefeng@linzikg.com] 5 | * @create: 2019-12-19 09:26 6 | */ 7 | public enum HostStateEnum { 8 | /** 9 | * 上线 10 | */ 11 | ONLINE, 12 | /** 13 | * 下线 14 | */ 15 | OFFLINE; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/enums/MonitorEnableEnum.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @author: chentiefeng[chentiefeng@linzikg.com] 8 | * @create: 2019-12-18 09:54 9 | */ 10 | @AllArgsConstructor 11 | @Getter 12 | public enum MonitorEnableEnum { 13 | /** 14 | * 禁用 15 | */ 16 | DISABLED(0), 17 | /** 18 | * 启用 19 | */ 20 | ENABLED(1); 21 | private Integer val; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/enums/MonitorTypeEnum.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.enums; 2 | 3 | /** 4 | * 监控类型 5 | * 6 | * @author: chentiefeng[chentiefeng@linzikg.com] 7 | * @create: 2019-12-18 09:50 8 | */ 9 | public enum MonitorTypeEnum { 10 | /** 11 | * 日志 12 | */ 13 | LOG, 14 | /** 15 | * 进程 16 | */ 17 | PROCESS, 18 | /** 19 | * sql 20 | */ 21 | SQL, 22 | ; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/schedule/DynamicScheduleTask.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.schedule; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import me.ctf.lm.enums.FrequencyEnum; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.scheduling.annotation.SchedulingConfigurer; 7 | import org.springframework.scheduling.config.ScheduledTaskRegistrar; 8 | import org.springframework.scheduling.support.CronTrigger; 9 | 10 | /** 11 | * @author: chentiefeng[chentiefeng@linzikg.com] 12 | * @create: 2019-12-18 14:14 13 | */ 14 | @Slf4j 15 | @Configuration 16 | public class DynamicScheduleTask implements SchedulingConfigurer { 17 | 18 | @Override 19 | public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { 20 | for (FrequencyEnum frequency : FrequencyEnum.values()) { 21 | log.info("add trigger task by frequency {}", frequency); 22 | taskRegistrar.addTriggerTask( 23 | //添加任务内容(Runnable) 24 | () -> ScheduleCmdExecutor.distribute(frequency.getCron()), 25 | //设置执行周期(Trigger) 26 | new CronTrigger(frequency.getCron()) 27 | ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/schedule/ScheduleCmdExecutor.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.schedule; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import lombok.extern.slf4j.Slf4j; 5 | import me.ctf.lm.cmdexecutor.AbstractCmdExecutor; 6 | import me.ctf.lm.entity.MonitorConfigEntity; 7 | import me.ctf.lm.enums.MonitorEnableEnum; 8 | import me.ctf.lm.enums.MonitorTypeEnum; 9 | import me.ctf.lm.service.DistributeTaskService; 10 | import me.ctf.lm.service.DistributedLockService; 11 | import me.ctf.lm.service.LiteMonitorConfigService; 12 | import me.ctf.lm.util.MonitorConfigUtil; 13 | import me.ctf.lm.util.PageUtils; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.util.CollectionUtils; 16 | 17 | import javax.annotation.Resource; 18 | import java.util.*; 19 | import java.util.concurrent.LinkedBlockingQueue; 20 | import java.util.concurrent.ThreadPoolExecutor; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.stream.Collectors; 23 | 24 | /** 25 | * @author: chentiefeng[chentiefeng@linzikg.com] 26 | * @create: 2019-12-18 13:45 27 | */ 28 | @Slf4j 29 | @Component 30 | public class ScheduleCmdExecutor { 31 | /** 32 | * 命令执行器Map 33 | */ 34 | private static Map cmdExecutorMap = new HashMap<>(); 35 | private static LiteMonitorConfigService liteMonitorConfigService; 36 | private static DistributedLockService distributedLockService; 37 | private static DistributeTaskService distributeTaskService; 38 | private static ThreadPoolExecutor executor; 39 | 40 | static { 41 | executor = new ThreadPoolExecutor(5, 20, 1000, TimeUnit.MILLISECONDS, 42 | new LinkedBlockingQueue<>(100), 43 | new ThreadFactoryBuilder().setNameFormat("cmd-executor-%s").build()); 44 | } 45 | 46 | /** 47 | * 注册命令执行器 48 | * 49 | * @param monitorType 50 | * @param cmdExecutor 51 | */ 52 | public static void register(MonitorTypeEnum monitorType, AbstractCmdExecutor cmdExecutor) { 53 | cmdExecutorMap.put(monitorType, cmdExecutor); 54 | log.info("{} register cmd executor success.", monitorType); 55 | } 56 | 57 | /** 58 | * 执行monitor 59 | * 60 | * @param ids 61 | */ 62 | public static void execute(Long[] ids) { 63 | if (Objects.isNull(ids) || ids.length == 0) { 64 | return; 65 | } 66 | List monitors = liteMonitorConfigService.listByIds(Arrays.asList(ids)); 67 | execute(monitors); 68 | } 69 | 70 | /** 71 | * 执行 72 | * 73 | * @param monitors 74 | */ 75 | private static void execute(List monitors) { 76 | for (MonitorConfigEntity monitor : monitors) { 77 | int threadNum = executor.getPoolSize() + executor.getQueue().size(); 78 | //120 = queue.size + maximumPoolSize 79 | int waitTime = 0; 80 | while (threadNum >= 120) { 81 | try { 82 | TimeUnit.MILLISECONDS.sleep(500); 83 | } catch (InterruptedException e) { 84 | log.error(e.getMessage(), e); 85 | } 86 | threadNum = executor.getPoolSize() + executor.getQueue().size(); 87 | waitTime++; 88 | //等待超过5秒,直接抛弃,也可以重新分发到其他机器 89 | if (waitTime > 10) { 90 | return; 91 | } 92 | } 93 | executor.execute(() -> { 94 | try { 95 | log.info("monitor {} execute task start.", monitor.getRemark()); 96 | cmdExecutorMap.get(MonitorTypeEnum.valueOf(monitor.getMonitorType())).execute(monitor); 97 | } catch (Exception e) { 98 | log.error(e.getMessage(), e); 99 | } finally { 100 | log.info("monitor {} execute task finish.", monitor.getRemark()); 101 | } 102 | }); 103 | } 104 | } 105 | 106 | /** 107 | * 按照频率找到所有监控任务分发 108 | * 109 | * @param frequency 110 | */ 111 | public static void distribute(String frequency) { 112 | log.info("frequency {} schedule executor start.", frequency); 113 | if (MonitorConfigUtil.getCluster()) { 114 | cluster(frequency); 115 | } else { 116 | single(frequency); 117 | } 118 | log.info("frequency {} schedule executor finish.", frequency); 119 | } 120 | 121 | /** 122 | * 单机 123 | * 124 | * @param frequency 125 | */ 126 | private static void single(String frequency) { 127 | int p = 1; 128 | while (true) { 129 | Map params = new HashMap<>(8); 130 | params.put("page", p + ""); 131 | params.put("limit", "100"); 132 | params.put("frequency", frequency); 133 | params.put("enabled", MonitorEnableEnum.ENABLED.getVal()); 134 | PageUtils page = liteMonitorConfigService.queryPage(params); 135 | if (CollectionUtils.isEmpty(page.getList())) { 136 | return; 137 | } 138 | execute(page.getList().stream().map(r -> (MonitorConfigEntity) r).collect(Collectors.toList())); 139 | p = p + 1; 140 | } 141 | } 142 | 143 | /** 144 | * 集群 145 | * 146 | * @param frequency 147 | */ 148 | private static void cluster(String frequency) { 149 | try { 150 | boolean lock = distributedLockService.lock(frequency); 151 | if (!lock) { 152 | log.warn("frequency {} fetch lock failed.", frequency); 153 | return; 154 | } 155 | Long[] ids = new Long[10]; 156 | Arrays.fill(ids, -1L); 157 | int p = 1; 158 | while (true) { 159 | Map params = new HashMap<>(8); 160 | params.put("page", p + ""); 161 | params.put("limit", "100"); 162 | params.put("frequency", frequency); 163 | params.put("enabled", MonitorEnableEnum.ENABLED.getVal()); 164 | PageUtils page = liteMonitorConfigService.queryPage(params); 165 | if (CollectionUtils.isEmpty(page.getList())) { 166 | return; 167 | } 168 | // 每10个分发 169 | int idx = 0; 170 | for (Object monitor : page.getList()) { 171 | ids[idx] = ((MonitorConfigEntity) monitor).getId(); 172 | if (idx == 9) { 173 | idx = 0; 174 | send(ids); 175 | Arrays.fill(ids, -1L); 176 | } 177 | idx++; 178 | } 179 | if (ids[0] != -1L) { 180 | send(ids); 181 | } 182 | p = p + 1; 183 | } 184 | } finally { 185 | distributedLockService.release(frequency); 186 | } 187 | } 188 | 189 | /** 190 | * 发送 191 | * 192 | * @param ids 193 | */ 194 | private static void send(Long[] ids) { 195 | try { 196 | distributeTaskService.distribute(Arrays.stream(ids).filter(id -> id > -1L).toArray(Long[]::new)); 197 | } catch (Exception e) { 198 | log.error(e.getMessage(), e); 199 | } 200 | } 201 | 202 | @Resource 203 | public void setLiteMonitorService(LiteMonitorConfigService liteMonitorConfigService) { 204 | ScheduleCmdExecutor.liteMonitorConfigService = liteMonitorConfigService; 205 | } 206 | 207 | @Resource 208 | public void setDistributedLockService(DistributedLockService distributedLockService) { 209 | ScheduleCmdExecutor.distributedLockService = distributedLockService; 210 | } 211 | 212 | @Resource 213 | public void setDistributeTaskService(DistributeTaskService distributeTaskService) { 214 | ScheduleCmdExecutor.distributeTaskService = distributeTaskService; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/DistributeTaskService.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.service; 2 | 3 | /** 4 | * @author: chentiefeng[chentiefeng@linzikg.com] 5 | * @create: 2019-12-18 18:00 6 | */ 7 | public interface DistributeTaskService { 8 | /** 9 | * 分发任务 10 | * 11 | * @param t 12 | */ 13 | void distribute(T t); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/DistributedLockService.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.service; 2 | 3 | /** 4 | * 分布式锁 5 | * 6 | * @author: chentiefeng[chentiefeng@linzikg.com] 7 | * @create: 2019-12-18 14:56 8 | */ 9 | public interface DistributedLockService { 10 | /** 11 | * 锁 12 | * 13 | * @param lockName 14 | * @return 15 | */ 16 | boolean lock(String lockName); 17 | 18 | /** 19 | * 释放锁 20 | * 21 | * @param lockName 22 | */ 23 | boolean release(String lockName); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/LiteMonitorConfigService.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import me.ctf.lm.entity.MonitorConfigEntity; 5 | import me.ctf.lm.util.PageUtils; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @author: chentiefeng[chentiefeng@linzikg.com] 11 | * @create: 2019-12-12 14:00 12 | */ 13 | public interface LiteMonitorConfigService extends IService { 14 | /** 15 | * 保存 16 | * 17 | * @param entity 18 | * @return 19 | */ 20 | Long submit(MonitorConfigEntity entity); 21 | 22 | PageUtils queryPage(Map params); 23 | 24 | 25 | /** 26 | * 启用 27 | * 28 | * @param id 29 | */ 30 | void enabled(long id); 31 | 32 | /** 33 | * 禁用 34 | * 35 | * @param id 36 | */ 37 | void disabled(long id); 38 | 39 | 40 | /** 41 | * info 42 | * 43 | * @param id 44 | * @return 45 | */ 46 | MonitorConfigEntity info(Long id); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/impl/LiteMonitorConfigServiceImpl.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import me.ctf.lm.dao.MonitorConfigMapper; 8 | import me.ctf.lm.entity.MonitorConfigEntity; 9 | import me.ctf.lm.enums.FrequencyEnum; 10 | import me.ctf.lm.enums.MonitorEnableEnum; 11 | import me.ctf.lm.enums.MonitorTypeEnum; 12 | import me.ctf.lm.service.LiteMonitorConfigService; 13 | import me.ctf.lm.util.PageUtils; 14 | import me.ctf.lm.util.Query; 15 | import me.ctf.lm.util.UpdateUtils; 16 | import me.ctf.lm.util.validator.LogGroup; 17 | import me.ctf.lm.util.validator.ValidatorUtil; 18 | import org.springframework.stereotype.Service; 19 | import org.springframework.transaction.annotation.Transactional; 20 | 21 | import javax.annotation.Resource; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.Optional; 25 | 26 | /** 27 | * @author: chentiefeng[chentiefeng@linzikg.com] 28 | * @create: 2019-12-12 14:01 29 | */ 30 | @Service 31 | public class LiteMonitorConfigServiceImpl extends ServiceImpl implements LiteMonitorConfigService { 32 | @Resource 33 | private MonitorConfigMapper monitorConfigMapper; 34 | 35 | @Override 36 | @Transactional(rollbackFor = Exception.class) 37 | public Long submit(MonitorConfigEntity entity) { 38 | if (MonitorTypeEnum.LOG.name().equals(entity.getMonitorType())) { 39 | ValidatorUtil.validateEntity(entity, LogGroup.class); 40 | } else { 41 | ValidatorUtil.validateEntity(entity); 42 | } 43 | if (Objects.nonNull(entity.getId())) { 44 | MonitorConfigEntity monitor = this.getById(entity.getId()); 45 | if (monitor != null) { 46 | String dingAt = entity.getDingAt(); 47 | UpdateUtils.copyNullProperties(entity, monitor); 48 | entity = monitor; 49 | entity.setDingAt(dingAt); 50 | } 51 | } 52 | saveOrUpdate(entity); 53 | return entity.getId(); 54 | } 55 | 56 | @Override 57 | public PageUtils queryPage(Map params) { 58 | IPage page = this.page( 59 | new Query().getPage(params), 60 | new QueryWrapper().lambda() 61 | .like(params.get("name") != null, MonitorConfigEntity::getHostName, params.get("name")) 62 | .like(params.get("remark") != null, MonitorConfigEntity::getRemark, params.get("remark")) 63 | .eq(params.get("frequency") != null, MonitorConfigEntity::getFrequency, params.get("frequency")) 64 | .eq(params.get("enabled") != null, MonitorConfigEntity::getEnabled, params.get("enabled")) 65 | ); 66 | for (MonitorConfigEntity monitorConfigEntity : page.getRecords()) { 67 | monitorConfigEntity.setFrequencyDesc(Objects.requireNonNull(FrequencyEnum.getByValue(monitorConfigEntity.getFrequency())).getDesc()); 68 | } 69 | return new PageUtils(page); 70 | } 71 | 72 | 73 | @Override 74 | public void enabled(long id) { 75 | update(Wrappers.lambdaUpdate().set(MonitorConfigEntity::getEnabled, MonitorEnableEnum.ENABLED.getVal()).eq(MonitorConfigEntity::getId, id)); 76 | } 77 | 78 | @Override 79 | public void disabled(long id) { 80 | update(Wrappers.lambdaUpdate().set(MonitorConfigEntity::getEnabled, MonitorEnableEnum.DISABLED.getVal()).eq(MonitorConfigEntity::getId, id)); 81 | } 82 | 83 | @Override 84 | public MonitorConfigEntity info(Long id) { 85 | Optional optional = Optional.ofNullable(getById(id)); 86 | if (optional.isPresent()) { 87 | optional.get().setFrequencyDesc(Objects.requireNonNull(FrequencyEnum.getByValue(optional.get().getFrequency())).getDesc()); 88 | return optional.get(); 89 | } 90 | return null; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/impl/distributedlock/DbDistributedLockServiceImpl.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.service.impl.distributedlock; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 4 | import me.ctf.lm.dao.MonitorExecSupportInfoMapper; 5 | import me.ctf.lm.entity.MonitorExecSupportInfoEntity; 6 | import me.ctf.lm.service.DistributedLockService; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import javax.annotation.Resource; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | * @author: chentiefeng[chentiefeng@linzikg.com] 16 | * @create: 2019-12-18 14:57 17 | */ 18 | @ConditionalOnExpression(value = "'${monitor.distributed-lock-type:db}'.equalsIgnoreCase('db')") 19 | @Service 20 | public class DbDistributedLockServiceImpl implements DistributedLockService { 21 | private static final String LOCK = "LOCK"; 22 | @Resource 23 | private MonitorExecSupportInfoMapper monitorExecSupportInfoMapper; 24 | 25 | @Override 26 | @Transactional(rollbackFor = Exception.class) 27 | public boolean lock(String lockName) { 28 | try { 29 | MonitorExecSupportInfoEntity entity = new MonitorExecSupportInfoEntity(); 30 | entity.setInfoType(LOCK); 31 | entity.setInfo(lockName); 32 | entity.setGmtCreate(LocalDateTime.now()); 33 | entity.setGmtModified(LocalDateTime.now()); 34 | monitorExecSupportInfoMapper.insert(entity); 35 | } catch (Exception e) { 36 | return false; 37 | } 38 | return true; 39 | } 40 | 41 | @Override 42 | @Transactional(rollbackFor = Exception.class) 43 | public boolean release(String lockName) { 44 | monitorExecSupportInfoMapper.delete(Wrappers.lambdaQuery() 45 | .eq(MonitorExecSupportInfoEntity::getInfoType, LOCK) 46 | .eq(MonitorExecSupportInfoEntity::getInfo, lockName)); 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/impl/distributedlock/RedisDistributedLockServiceImpl.java: -------------------------------------------------------------------------------- 1 | //package me.ctf.lm.service.impl.distributedlock; 2 | // 3 | //import lombok.extern.slf4j.Slf4j; 4 | //import me.ctf.lm.service.DistributedLockService; 5 | //import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 6 | //import org.springframework.data.redis.core.StringRedisTemplate; 7 | //import org.springframework.data.redis.core.script.DefaultRedisScript; 8 | //import org.springframework.stereotype.Service; 9 | // 10 | //import javax.annotation.Resource; 11 | //import java.time.Duration; 12 | //import java.util.*; 13 | // 14 | ///** 15 | // * @author: chentiefeng[chentiefeng@linzikg.com] 16 | // * @create: 2019-12-20 14:09 17 | // */ 18 | //@ConditionalOnExpression(value = "'${monitor.distributed-lock-type:db}'.equalsIgnoreCase('redis')") 19 | //@Service 20 | //@Slf4j 21 | //public class RedisDistributedLockServiceImpl implements DistributedLockService { 22 | // 23 | // private ThreadLocal> threadLocalLockDeque = new ThreadLocal<>(); 24 | // @Resource 25 | // private StringRedisTemplate stringRedisTemplate; 26 | // 27 | // private static DefaultRedisScript defaultRedisScript; 28 | // private static final Long RELEASE_SUCCESS = 1L; 29 | // 30 | // static { 31 | // defaultRedisScript = new DefaultRedisScript<>(); 32 | // defaultRedisScript.setResultType(Long.class); 33 | // defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end"); 34 | // } 35 | // 36 | // @Override 37 | // public boolean lock(String lockName) { 38 | // String value = UUID.randomUUID().toString(); 39 | // try { 40 | // Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockName, value, Duration.ofSeconds(30)); 41 | // if (lock != null && lock) { 42 | // Deque lockDeque = threadLocalLockDeque.get(); 43 | // if (lockDeque == null) { 44 | // lockDeque = new ArrayDeque<>(); 45 | // threadLocalLockDeque.set(lockDeque); 46 | // } 47 | // lockDeque.offer(value); 48 | // return true; 49 | // } 50 | // return false; 51 | // } catch (Exception e) { 52 | // log.error(e.getMessage(), e); 53 | // return false; 54 | // } 55 | // } 56 | // 57 | // @Override 58 | // public boolean release(String lockName) { 59 | // try { 60 | // List args = new ArrayList<>(); 61 | // args.add(lockName); 62 | // args.add(threadLocalLockDeque.get().pop()); 63 | // Long result = stringRedisTemplate.execute(defaultRedisScript, args); 64 | // return RELEASE_SUCCESS.equals(result); 65 | // } catch (Exception e) { 66 | // log.error(e.getMessage(), e); 67 | // } finally { 68 | // if (threadLocalLockDeque.get() != null && threadLocalLockDeque.get().size() == 0) { 69 | // threadLocalLockDeque.remove(); 70 | // } 71 | // } 72 | // return false; 73 | // } 74 | //} -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/impl/distributetask/HttpDistributeTaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.service.impl.distributetask; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 4 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import lombok.extern.slf4j.Slf4j; 8 | import me.ctf.lm.dao.MonitorExecSupportInfoMapper; 9 | import me.ctf.lm.dto.MapResult; 10 | import me.ctf.lm.entity.MonitorExecSupportInfoEntity; 11 | import me.ctf.lm.enums.HostStateEnum; 12 | import me.ctf.lm.service.DistributeTaskService; 13 | import me.ctf.lm.util.MonitorConfigUtil; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.boot.CommandLineRunner; 16 | import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 17 | import org.springframework.boot.web.client.RestTemplateBuilder; 18 | import org.springframework.http.HttpEntity; 19 | import org.springframework.http.HttpHeaders; 20 | import org.springframework.stereotype.Service; 21 | import org.springframework.web.client.RestTemplate; 22 | 23 | import javax.annotation.Resource; 24 | import java.net.InetAddress; 25 | import java.net.NetworkInterface; 26 | import java.net.SocketException; 27 | import java.time.Duration; 28 | import java.time.LocalDateTime; 29 | import java.time.format.DateTimeFormatter; 30 | import java.util.Arrays; 31 | import java.util.Enumeration; 32 | import java.util.List; 33 | import java.util.Objects; 34 | import java.util.concurrent.LinkedBlockingQueue; 35 | import java.util.concurrent.ThreadPoolExecutor; 36 | import java.util.concurrent.TimeUnit; 37 | import java.util.concurrent.atomic.AtomicInteger; 38 | 39 | /** 40 | * @author: chentiefeng[chentiefeng@linzikg.com] 41 | * @create: 2019-12-18 18:01 42 | */ 43 | @ConditionalOnExpression(value = "'${monitor.distribute-task-type:http}'.equalsIgnoreCase('http')") 44 | @Service("httpDistributeTaskService") 45 | @Slf4j 46 | public class HttpDistributeTaskServiceImpl implements DistributeTaskService, CommandLineRunner { 47 | public static final String EXECUTE_URL = "%s/liteMonitor/execute"; 48 | /** 49 | * 每隔30秒检查一次 50 | */ 51 | public static final int CHECK_DURATION_SECOND = 60; 52 | private static Gson gson = new GsonBuilder().create(); 53 | private static RestTemplate restTemplate = new RestTemplateBuilder().setConnectTimeout(Duration.ofMillis(3000)).build(); 54 | @Resource 55 | private MonitorExecSupportInfoMapper monitorExecSupportInfoMapper; 56 | @Value("${server.port}") 57 | private Integer port; 58 | private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 59 | 0L, TimeUnit.MILLISECONDS, 60 | new LinkedBlockingQueue<>(), new ThreadFactoryBuilder().setNameFormat("thread-register-host-%s").build()); 61 | /** 62 | * support info type 63 | */ 64 | private final static String HOST = "HOST"; 65 | /** 66 | * host idx 67 | */ 68 | private AtomicInteger idx; 69 | 70 | @Override 71 | public void distribute(Long[] ids) { 72 | try { 73 | log.info("start distribute task."); 74 | LocalDateTime gmtModified = LocalDateTime.now().plusMinutes(-MonitorConfigUtil.getDuration()); 75 | log.info("ids is {}, duration is {}, gmtModified is {}", Arrays.toString(ids), MonitorConfigUtil.getDuration(), gmtModified); 76 | List list = monitorExecSupportInfoMapper.selectList(Wrappers.lambdaQuery() 77 | .ge(MonitorExecSupportInfoEntity::getGmtModified, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(gmtModified)) 78 | .eq(MonitorExecSupportInfoEntity::getInfo, HOST)); 79 | String[] hosts = list.stream().map(MonitorExecSupportInfoEntity::getInfo).toArray(String[]::new); 80 | log.info("distribute host list {}", Arrays.toString(hosts)); 81 | if (hosts.length == 0) { 82 | return; 83 | } 84 | if (idx == null) { 85 | idx = new AtomicInteger((int) (System.currentTimeMillis() % hosts.length)); 86 | } 87 | String host = hosts[idx.getAndIncrement() % hosts.length]; 88 | log.info("This distribution task host is {}", host); 89 | HttpHeaders headers = new HttpHeaders(); 90 | headers.add("Content-Type", "application/json;charset=UTF-8"); 91 | HttpEntity r = new HttpEntity<>(gson.toJson(ids), headers); 92 | restTemplate.postForObject(String.format(EXECUTE_URL, "http://" + host + ":" + port), r, MapResult.class); 93 | } catch (Exception e) { 94 | log.error(e.getMessage(), e); 95 | } finally { 96 | log.info("end distribute task."); 97 | } 98 | } 99 | 100 | @Override 101 | public void run(String... args) { 102 | threadPoolExecutor.execute(this::registerHost); 103 | } 104 | 105 | /** 106 | * 注册本机ip 107 | */ 108 | private void registerHost() { 109 | if (MonitorConfigUtil.getCluster()) { 110 | String host = getHost(); 111 | log.info("Http distribute task service register host {}", host); 112 | do { 113 | MonitorExecSupportInfoEntity supportInfo = monitorExecSupportInfoMapper.selectOne( 114 | Wrappers.lambdaQuery() 115 | .eq(MonitorExecSupportInfoEntity::getInfoType, HOST) 116 | .eq(MonitorExecSupportInfoEntity::getInfo, host)); 117 | if (Objects.isNull(supportInfo)) { 118 | supportInfo = new MonitorExecSupportInfoEntity(); 119 | supportInfo.setInfoType(HOST); 120 | supportInfo.setInfo(host); 121 | supportInfo.setGmtCreate(LocalDateTime.now()); 122 | } 123 | supportInfo.setGmtModified(LocalDateTime.now()); 124 | monitorExecSupportInfoMapper.insert(supportInfo); 125 | //每1秒检查一次 126 | try { 127 | TimeUnit.SECONDS.sleep(CHECK_DURATION_SECOND); 128 | } catch (Exception e) { 129 | log.info(e.getMessage(), e); 130 | } 131 | } while (HostStateEnum.ONLINE.name().equalsIgnoreCase(MonitorConfigUtil.getHostState())); 132 | } 133 | } 134 | 135 | /** 136 | * 获取ip地址 137 | * 138 | * @return 139 | */ 140 | private String getHost() { 141 | String host = "localhost"; 142 | try { 143 | Enumeration en = NetworkInterface.getNetworkInterfaces(); 144 | while (en.hasMoreElements()) { 145 | NetworkInterface net = en.nextElement(); 146 | Enumeration address = net.getInetAddresses(); 147 | while (address.hasMoreElements()) { 148 | InetAddress inetAddress = address.nextElement(); 149 | if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress() && inetAddress.isSiteLocalAddress()) { 150 | host = inetAddress.getHostAddress(); 151 | } 152 | } 153 | } 154 | } catch (SocketException var4) { 155 | host = "localhost"; 156 | } 157 | return host; 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/impl/distributetask/KafkaDistributeTaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | //package me.ctf.lm.service.impl.distributetask; 2 | // 3 | //import lombok.extern.slf4j.Slf4j; 4 | //import me.ctf.lm.schedule.ScheduleCmdExecutor; 5 | //import me.ctf.lm.service.DistributeTaskService; 6 | //import org.apache.kafka.clients.consumer.ConsumerRecord; 7 | //import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 8 | //import org.springframework.kafka.annotation.KafkaListener; 9 | //import org.springframework.kafka.core.KafkaTemplate; 10 | //import org.springframework.kafka.support.Acknowledgment; 11 | //import org.springframework.kafka.support.SendResult; 12 | //import org.springframework.stereotype.Component; 13 | //import org.springframework.util.concurrent.ListenableFuture; 14 | // 15 | //import javax.annotation.Resource; 16 | //import java.text.MessageFormat; 17 | //import java.util.List; 18 | // 19 | ///** 20 | // * @author: chentiefeng[chentiefeng@linzikg.com] 21 | // * @create: 2019-12-20 15:40 22 | // */ 23 | //@ConditionalOnExpression(value = "'${monitor.distribute-task-type:http}'.equalsIgnoreCase('kafka')") 24 | //@Component 25 | //@Slf4j 26 | //public class KafkaDistributeTaskServiceImpl implements DistributeTaskService { 27 | // private final static String MONITOR_TASK = "monitor-task"; 28 | // @Resource 29 | // private KafkaTemplate kafkaTemplate; 30 | // 31 | // @Override 32 | // public void distribute(Long[] longs) { 33 | // for (Long id : longs) { 34 | // ListenableFuture> listenableFuture = kafkaTemplate.send(MONITOR_TASK, String.valueOf(id)); 35 | // listenableFuture.addCallback(result -> { 36 | // if (log.isInfoEnabled()) { 37 | // log.info("发送kafka消息成功,topic={},msg={}", MONITOR_TASK, id); 38 | // } 39 | // }, 40 | // ex -> log.error(MessageFormat.format("发送kafka消息失败,topic={0},msgId={1}", MONITOR_TASK, id), ex)); 41 | // } 42 | // } 43 | // 44 | // 45 | // @KafkaListener(topics = MONITOR_TASK) 46 | // public void execute(List> records, Acknowledgment ack) { 47 | // try { 48 | // ScheduleCmdExecutor.execute(records.stream().map(record -> Long.valueOf(record.value())).toArray(Long[]::new)); 49 | // } catch (Exception e) { 50 | // log.error(e.getMessage(), e); 51 | // } finally { 52 | // ack.acknowledge(); 53 | // } 54 | // } 55 | //} 56 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/service/impl/distributetask/RedisDistributeTaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | //package me.ctf.lm.service.impl.distributetask; 2 | // 3 | //import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | //import lombok.extern.slf4j.Slf4j; 5 | //import me.ctf.lm.enums.HostStateEnum; 6 | //import me.ctf.lm.schedule.ScheduleCmdExecutor; 7 | //import me.ctf.lm.service.DistributeTaskService; 8 | //import me.ctf.lm.util.MonitorConfigUtil; 9 | //import org.apache.commons.lang3.StringUtils; 10 | //import org.springframework.boot.CommandLineRunner; 11 | //import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 12 | //import org.springframework.data.redis.core.StringRedisTemplate; 13 | //import org.springframework.stereotype.Component; 14 | // 15 | //import javax.annotation.Resource; 16 | //import java.util.Arrays; 17 | //import java.util.concurrent.LinkedBlockingQueue; 18 | //import java.util.concurrent.ThreadPoolExecutor; 19 | //import java.util.concurrent.TimeUnit; 20 | //import java.util.stream.Collectors; 21 | // 22 | ///** 23 | // * @author: chentiefeng[chentiefeng@linzikg.com] 24 | // * @create: 2019-12-20 14:59 25 | // */ 26 | //@ConditionalOnExpression(value = "'${monitor.distribute-task-type:http}'.equalsIgnoreCase('redis')") 27 | //@Component 28 | //@Slf4j 29 | //public class RedisDistributeTaskServiceImpl implements DistributeTaskService, CommandLineRunner { 30 | // private final static String MONITOR_TASK = "monitorTaskQueue"; 31 | // @Resource 32 | // private StringRedisTemplate stringRedisTemplate; 33 | // private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 34 | // 0L, TimeUnit.MILLISECONDS, 35 | // new LinkedBlockingQueue<>(), new ThreadFactoryBuilder().setNameFormat("thread-task-consumer-%s").build()); 36 | // 37 | // @Override 38 | // public void distribute(Long[] longs) { 39 | // String collect = Arrays.stream(longs).map(String::valueOf).collect(Collectors.joining(",")); 40 | // stringRedisTemplate.opsForList().leftPush(MONITOR_TASK, collect); 41 | // } 42 | // 43 | // 44 | // @Override 45 | // public void run(String... args) { 46 | // threadPoolExecutor.execute(() -> { 47 | // if (MonitorConfigUtil.getCluster()) { 48 | // log.info("redis distribute task service start consumption."); 49 | // do { 50 | // String value = stringRedisTemplate.opsForList().rightPop(MONITOR_TASK, 1, TimeUnit.SECONDS); 51 | // if (StringUtils.isBlank(value)) { 52 | // continue; 53 | // } 54 | // Long[] ids = Arrays.stream(value.split(",")).map(Long::valueOf).toArray(Long[]::new); 55 | // ScheduleCmdExecutor.execute(ids); 56 | // } while (HostStateEnum.ONLINE.name().equalsIgnoreCase(MonitorConfigUtil.getHostState())); 57 | // } 58 | // }); 59 | // } 60 | //} 61 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/BizException.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | /** 4 | * 业务异常 5 | * 6 | * @author: chentiefeng[chentiefeng@linzikg.com] 7 | * @create: 2019-12-12 15:25 8 | */ 9 | public class BizException extends RuntimeException { 10 | 11 | private static final long serialVersionUID = -4057170583348566998L; 12 | private String msg; 13 | private int code = 500; 14 | 15 | public BizException(String msg) { 16 | super(msg); 17 | this.msg = msg; 18 | } 19 | 20 | public BizException(String msg, Throwable e) { 21 | super(msg, e); 22 | this.msg = msg; 23 | } 24 | 25 | public BizException(String msg, int code) { 26 | super(msg); 27 | this.msg = msg; 28 | this.code = code; 29 | } 30 | 31 | public BizException(String msg, int code, Throwable e) { 32 | super(msg, e); 33 | this.msg = msg; 34 | this.code = code; 35 | } 36 | 37 | public String getMsg() { 38 | return msg; 39 | } 40 | 41 | public void setMsg(String msg) { 42 | this.msg = msg; 43 | } 44 | 45 | public int getCode() { 46 | return code; 47 | } 48 | 49 | public void setCode(int code) { 50 | this.code = code; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/CmdExecutorUtil.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import ch.ethz.ssh2.Connection; 4 | import ch.ethz.ssh2.Session; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | /** 12 | * @author: chentiefeng[chentiefeng@linzikg.com] 13 | * @create: 2019-12-18 10:18 14 | */ 15 | @Slf4j 16 | public class CmdExecutorUtil { 17 | 18 | /** 19 | * ssh key 20 | */ 21 | private static final String ID_RSA_PATH = System.getProperty("user.home") + "/.ssh/id_rsa"; 22 | 23 | /** 24 | * 命令执行 25 | * 26 | * @param hostName 27 | * @param port 28 | * @param username 29 | * @param cmd 30 | * @return 31 | * @throws IOException 32 | */ 33 | public static String authPasswordAndExecute(String hostName, int port, String username, String password, String cmd) throws IOException { 34 | Connection conn = new Connection(hostName, port); 35 | Session session = null; 36 | try { 37 | conn.connect(); 38 | authenticateWithPassword(username, password, conn); 39 | session = conn.openSession(); 40 | return execute(cmd, session); 41 | } finally { 42 | if (session != null) { 43 | session.close(); 44 | } 45 | conn.close(); 46 | } 47 | } 48 | 49 | private static String execute(String cmd, Session session) throws IOException { 50 | log.info("exec cmd: {}",cmd); 51 | session.execCommand(cmd); 52 | String result = ProcessStdoutUtil.processStdout(session.getStdout()); 53 | if (StringUtils.isEmpty(result)) { 54 | result = StringUtils.removeStart(result, "\n"); 55 | result = StringUtils.removeEnd(result, "\n"); 56 | } 57 | return result; 58 | } 59 | 60 | /** 61 | * 命令执行 62 | * 63 | * @param hostName 64 | * @param port 65 | * @param username 66 | * @param cmd 67 | * @return 68 | * @throws IOException 69 | */ 70 | public static String authPublicKeyAndExecute(String hostName, int port, String username, String pemPath, String cmd) throws IOException { 71 | Connection conn = new Connection(hostName, port); 72 | Session session = null; 73 | try { 74 | conn.connect(); 75 | authenticateWithPublicKey(username, pemPath, conn); 76 | session = conn.openSession(); 77 | return execute(cmd, session); 78 | } finally { 79 | if (session != null) { 80 | session.close(); 81 | } 82 | conn.close(); 83 | } 84 | } 85 | 86 | /** 87 | * 命令执行 88 | * 89 | * @param hostName 90 | * @param port 91 | * @param username 92 | * @param cmd 93 | * @return 94 | * @throws IOException 95 | */ 96 | public static String authPublicKeyAndExecute(String hostName, int port, String username, String cmd) throws IOException { 97 | Connection conn = new Connection(hostName, port); 98 | Session session = null; 99 | try { 100 | conn.connect(); 101 | authenticateWithPublicKey(username, conn); 102 | session = conn.openSession(); 103 | return execute(cmd, session); 104 | } finally { 105 | if (session != null) { 106 | session.close(); 107 | } 108 | conn.close(); 109 | } 110 | } 111 | 112 | /** 113 | * 认证 114 | * 115 | * @param username 116 | * @param conn 117 | * @throws IOException 118 | */ 119 | private static void authenticateWithPublicKey(String username, Connection conn) throws IOException { 120 | boolean isAuthenticated = conn.authenticateWithPublicKey(username, new File(ID_RSA_PATH), null); 121 | if (!isAuthenticated) { 122 | throw new RuntimeException("Authentication failed!"); 123 | } 124 | } 125 | 126 | /** 127 | * 认证 128 | * 129 | * @param username 130 | * @param conn 131 | * @throws IOException 132 | */ 133 | private static void authenticateWithPublicKey(String username, String pemPath, Connection conn) throws IOException { 134 | boolean isAuthenticated = conn.authenticateWithPublicKey(username, new File(pemPath), null); 135 | if (!isAuthenticated) { 136 | throw new RuntimeException("Authentication failed!"); 137 | } 138 | } 139 | 140 | /** 141 | * 认证 142 | * 143 | * @param username 144 | * @param conn 145 | * @throws IOException 146 | */ 147 | private static void authenticateWithPassword(String username, String password, Connection conn) throws IOException { 148 | boolean isAuthenticated = conn.authenticateWithPassword(username, password); 149 | if (!isAuthenticated) { 150 | throw new RuntimeException("Authentication failed!"); 151 | } 152 | } 153 | 154 | public static void main(String[] args) throws IOException { 155 | System.out.println(authPublicKeyAndExecute("172.16.158.109", 22, "admin", "grep '2019-12-19 16:51' /home/admin/lt-indicator/logs/risk-indicator/risk-indicator.log |grep '指标计算结果'")); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/Constant.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-2019 人人开源 All rights reserved. 3 | * 4 | * https://www.renren.io 5 | * 6 | * 版权所有,侵权必究! 7 | */ 8 | 9 | package me.ctf.lm.util; 10 | 11 | /** 12 | * 常量 13 | * 14 | * @author Mark sunlightcs@gmail.com 15 | */ 16 | public class Constant { 17 | /** 超级管理员ID */ 18 | public static final int SUPER_ADMIN = 1; 19 | /** 20 | * 当前页码 21 | */ 22 | public static final String PAGE = "page"; 23 | /** 24 | * 每页显示记录数 25 | */ 26 | public static final String LIMIT = "limit"; 27 | /** 28 | * 排序字段 29 | */ 30 | public static final String ORDER_FIELD = "sidx"; 31 | /** 32 | * 排序方式 33 | */ 34 | public static final String ORDER = "order"; 35 | /** 36 | * 升序 37 | */ 38 | public static final String ASC = "asc"; 39 | /** 40 | * 菜单类型 41 | * 42 | * @author chenshun 43 | * @email sunlightcs@gmail.com 44 | * @date 2016年11月15日 下午1:24:29 45 | */ 46 | public enum MenuType { 47 | /** 48 | * 目录 49 | */ 50 | CATALOG(0), 51 | /** 52 | * 菜单 53 | */ 54 | MENU(1), 55 | /** 56 | * 按钮 57 | */ 58 | BUTTON(2); 59 | 60 | private int value; 61 | 62 | MenuType(int value) { 63 | this.value = value; 64 | } 65 | 66 | public int getValue() { 67 | return value; 68 | } 69 | } 70 | 71 | /** 72 | * 定时任务状态 73 | * 74 | * @author chenshun 75 | * @email sunlightcs@gmail.com 76 | * @date 2016年12月3日 上午12:07:22 77 | */ 78 | public enum ScheduleStatus { 79 | /** 80 | * 正常 81 | */ 82 | NORMAL(0), 83 | /** 84 | * 暂停 85 | */ 86 | PAUSE(1); 87 | 88 | private int value; 89 | 90 | ScheduleStatus(int value) { 91 | this.value = value; 92 | } 93 | 94 | public int getValue() { 95 | return value; 96 | } 97 | } 98 | 99 | /** 100 | * 云服务商 101 | */ 102 | public enum CloudService { 103 | /** 104 | * 七牛云 105 | */ 106 | QINIU(1), 107 | /** 108 | * 阿里云 109 | */ 110 | ALIYUN(2), 111 | /** 112 | * 腾讯云 113 | */ 114 | QCLOUD(3); 115 | 116 | private int value; 117 | 118 | CloudService(int value) { 119 | this.value = value; 120 | } 121 | 122 | public int getValue() { 123 | return value; 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/DateTimeRegex.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import org.apache.commons.lang3.time.DateFormatUtils; 4 | import org.apache.commons.lang3.time.DateUtils; 5 | 6 | import java.util.*; 7 | import java.util.stream.Collectors; 8 | 9 | /** 10 | * @author: chentiefeng[chentiefeng@linzikg.com] 11 | * @create: 2019-12-11 16:47 12 | */ 13 | public class DateTimeRegex { 14 | 15 | 16 | public static void main(String[] args) { 17 | //近45分钟的时间 18 | Date date = new Date(); 19 | Date start = DateUtils.addMinutes(date, -45); 20 | System.out.println("start:" + DateFormatUtils.format(start, "yyyy-MM-dd HH:mm:ss")); 21 | System.out.println("end:" + DateFormatUtils.format(date, "yyyy-MM-dd HH:mm:ss")); 22 | List list = DateTimeRegex.dateUntilNowFormat(start); 23 | list.forEach(System.out::println); 24 | } 25 | 26 | /** 27 | * 大于start的时间正则 28 | * 29 | * @param start 30 | * @return 31 | */ 32 | public static List dateUntilNow(Date start) { 33 | String s = DateFormatUtils.format(start, "yyyyMMddHHmmss"); 34 | String e = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss"); 35 | List regexList = new ArrayList<>(); 36 | Map map = bigThanMin(s, e); 37 | regexList.add(map.get("regex")); 38 | int index = Integer.parseInt(map.get("index")); 39 | String end = map.get("end"); 40 | for (int i = index + 1; i < s.length(); i++) { 41 | if (end.equals(s)) { 42 | regexList.add(end); 43 | break; 44 | } 45 | map = bigThanMin(s, end); 46 | regexList.add(map.get("regex")); 47 | end = map.get("end"); 48 | } 49 | return regexList; 50 | } 51 | 52 | /** 53 | * 大于start的时间正则 54 | * 55 | * @param start 56 | * @return 57 | */ 58 | public static List dateUntilNowFormat(Date start) { 59 | List regexList = dateUntilNow(start); 60 | return regexList.stream().map(DateTimeRegex::formatDateRegex).collect(Collectors.toList()); 61 | } 62 | 63 | /** 64 | * 格式化生成正则 65 | * 66 | * @param ss 67 | * @return 68 | */ 69 | private static String formatDateRegex(String ss) { 70 | String[] d = new String[14]; 71 | int idx = 0; 72 | for (int i = 0; i < ss.length(); i++) { 73 | char c = ss.charAt(i); 74 | if (c != '[') { 75 | d[idx++] = String.valueOf(c); 76 | } else { 77 | d[idx++] = new String(Arrays.copyOfRange(ss.toCharArray(), i, i + 5)); 78 | i = i + 4; 79 | } 80 | } 81 | StringBuilder ds = new StringBuilder(); 82 | for (int i = 0; i < d.length; i++) { 83 | String dd = d[i]; 84 | if (i == 4 || i == 6) { 85 | ds.append("-"); 86 | } 87 | if (i == 8) { 88 | ds.append(" "); 89 | } 90 | if (i == 10 || i == 12) { 91 | ds.append(":"); 92 | } 93 | ds.append(dd); 94 | } 95 | return ds.toString(); 96 | } 97 | 98 | private static Map bigThanMin(String min, String max) { 99 | Map rst = new HashMap<>(8); 100 | StringBuilder regex = new StringBuilder(); 101 | char[] sCharArray = min.toCharArray(); 102 | char[] eCharArray = max.toCharArray(); 103 | //第一个不同数字的坐标 104 | int index = 0; 105 | for (int i = 0, charArrayLength = sCharArray.length; i < charArrayLength; i++) { 106 | char sc = sCharArray[i]; 107 | char ec = eCharArray[i]; 108 | if (sc == ec) { 109 | continue; 110 | } 111 | index = i; 112 | break; 113 | } 114 | //第一个不同数字 115 | int start = Integer.parseInt(String.valueOf(sCharArray[index])); 116 | if (start == 9) { 117 | start = Integer.parseInt(String.valueOf(sCharArray[index - 1])); 118 | index = index - 1; 119 | } 120 | rst.put("index", String.valueOf(index)); 121 | char[] prefix = Arrays.copyOfRange(sCharArray, 0, index); 122 | regex.append(prefix); 123 | regex.append("[").append(start + 1).append("-9]"); 124 | StringBuilder end = new StringBuilder(new String(prefix)); 125 | end.append(sCharArray[index]); 126 | for (int i = index + 1; i < sCharArray.length; i++) { 127 | regex.append("[0-9]"); 128 | end.append("9"); 129 | } 130 | rst.put("regex", regex.toString()); 131 | rst.put("end", end.toString()); 132 | return rst; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/DingMarkdownMessage.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author chentiefeng 12 | * @date 2019/02/19 10:01 13 | */ 14 | public class DingMarkdownMessage { 15 | private Gson gson = new Gson(); 16 | private String title; 17 | 18 | private List items = new ArrayList<>(); 19 | 20 | private String[] atMobiles; 21 | private Boolean atAll; 22 | 23 | public String getTitle() { 24 | return title; 25 | } 26 | 27 | public void setTitle(String title) { 28 | this.title = title; 29 | } 30 | 31 | public void add(String text) { 32 | items.add(text); 33 | } 34 | 35 | public static String getBoldText(String text) { 36 | return "**" + text + "**"; 37 | } 38 | 39 | public static String getItalicText(String text) { 40 | return "*" + text + "*"; 41 | } 42 | 43 | public static String getLinkText(String text, String href) { 44 | return "[" + text + "](" + href + ")"; 45 | } 46 | 47 | public static String getImageText(String imageUrl) { 48 | return "![image](" + imageUrl + ")"; 49 | } 50 | 51 | public static String getHeaderText(int headerType, String text) { 52 | if (headerType < 1 || headerType > 6) { 53 | throw new IllegalArgumentException("headerType should be in [1, 6]"); 54 | } 55 | 56 | StringBuffer numbers = new StringBuffer(); 57 | for (int i = 0; i < headerType; i++) { 58 | numbers.append("#"); 59 | } 60 | return numbers + " " + text; 61 | } 62 | 63 | public static String getReferenceText(String text) { 64 | return "> " + text; 65 | } 66 | 67 | public static String getOrderListText(List orderItem) { 68 | if (orderItem.isEmpty()) { 69 | return ""; 70 | } 71 | 72 | StringBuffer sb = new StringBuffer(); 73 | for (int i = 1; i <= orderItem.size() - 1; i++) { 74 | sb.append(String.valueOf(i) + ". " + orderItem.get(i - 1) + "\n"); 75 | } 76 | sb.append(String.valueOf(orderItem.size()) + ". " + orderItem.get(orderItem.size() - 1)); 77 | return sb.toString(); 78 | } 79 | 80 | public static String getUnorderListText(List unorderItem) { 81 | if (unorderItem.isEmpty()) { 82 | return ""; 83 | } 84 | 85 | StringBuffer sb = new StringBuffer(); 86 | for (int i = 0; i < unorderItem.size() - 1; i++) { 87 | sb.append("- " + unorderItem.get(i) + "\n"); 88 | } 89 | sb.append("- " + unorderItem.get(unorderItem.size() - 1)); 90 | return sb.toString(); 91 | } 92 | 93 | public void setAtMobiles(String[] atMobiles) { 94 | this.atMobiles = atMobiles; 95 | } 96 | 97 | public void setAtAll(Boolean atAll) { 98 | this.atAll = atAll; 99 | } 100 | 101 | public String toJsonString() { 102 | Map result = new HashMap<>(8); 103 | result.put("msgtype", "markdown"); 104 | 105 | Map markdown = new HashMap<>(8); 106 | markdown.put("title", title); 107 | 108 | StringBuilder markdownText = new StringBuilder(); 109 | for (String item : items) { 110 | markdownText.append(item).append("\n"); 111 | } 112 | markdown.put("text", markdownText.toString()); 113 | result.put("markdown", markdown); 114 | Map at = new HashMap<>(8); 115 | if (atMobiles != null && atMobiles.length > 0) { 116 | at.put("atMobiles", atMobiles); 117 | } 118 | at.put("isAtAll", atAll); 119 | result.put("at", at); 120 | return gson.toJson(result); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/DingSendResult.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import com.google.gson.Gson; 4 | import lombok.Data; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author chentiefeng 11 | */ 12 | @Data 13 | public class DingSendResult { 14 | private Gson gson = new Gson(); 15 | private boolean isSuccess; 16 | private Integer errorCode; 17 | private String errorMsg; 18 | 19 | @Override 20 | public String toString() { 21 | Map items = new HashMap<>(8); 22 | items.put("errorCode", errorCode); 23 | items.put("errorMsg", errorMsg); 24 | items.put("isSuccess", isSuccess); 25 | return gson.toJson(items); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/DingTalkHelper.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.tomcat.util.codec.binary.Base64; 5 | import org.springframework.boot.web.client.RestTemplateBuilder; 6 | import org.springframework.http.HttpEntity; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | import javax.crypto.Mac; 11 | import javax.crypto.spec.SecretKeySpec; 12 | import java.io.UnsupportedEncodingException; 13 | import java.net.URLEncoder; 14 | import java.nio.charset.StandardCharsets; 15 | import java.security.InvalidKeyException; 16 | import java.security.NoSuchAlgorithmException; 17 | 18 | /** 19 | * @author chentiefeng 20 | * @date 2019/02/18 17:57 21 | */ 22 | @Slf4j 23 | public class DingTalkHelper { 24 | private static final RestTemplate REST_TEMPLATE = new RestTemplateBuilder().build(); 25 | 26 | /** 27 | * text钉钉消息发送 28 | * 29 | * @param msg 消息 30 | * @param dingId 机器人id 31 | */ 32 | public static void sendTextMsg(String msg, String dingId) { 33 | DingTalkHelper.sendTextMsg(msg, dingId, false); 34 | } 35 | 36 | /** 37 | * text钉钉消息发送 38 | * 39 | * @param msg 消息 40 | * @param dingId 机器人id 41 | */ 42 | public static void sendTextMsg(String msg, String dingId, boolean isAtAll) { 43 | DingTextMessage dingTextMessage = new DingTextMessage(msg); 44 | dingTextMessage.setAtAll(isAtAll); 45 | HttpHeaders headers = new HttpHeaders(); 46 | headers.add("Content-Type", "application/json;charset=UTF-8"); 47 | HttpEntity r = new HttpEntity<>(dingTextMessage.toJsonString(), headers); 48 | REST_TEMPLATE.postForObject("https://oapi.dingtalk.com/robot/send?access_token=" + dingId, r, DingSendResult.class); 49 | } 50 | 51 | /** 52 | * 发送钉钉markdown消息 53 | * 54 | * @param message 55 | * @param dingId 56 | */ 57 | public static void sendMarkdownMsg(DingMarkdownMessage message, String dingId) { 58 | HttpHeaders headers = new HttpHeaders(); 59 | headers.add("Content-Type", "application/json;charset=UTF-8"); 60 | HttpEntity r = new HttpEntity<>(message.toJsonString(), headers); 61 | REST_TEMPLATE.postForObject("https://oapi.dingtalk.com/robot/send?access_token=" + dingId, r, DingSendResult.class); 62 | log.info(message.toJsonString()); 63 | } 64 | 65 | /** 66 | * 发送钉钉markdown消息 67 | * 68 | * @param message 69 | * @param dingId 70 | */ 71 | public static void sendMarkdownMsg(DingMarkdownMessage message, String dingId, long timestamp, String sign) { 72 | HttpHeaders headers = new HttpHeaders(); 73 | headers.add("Content-Type", "application/json;charset=UTF-8"); 74 | HttpEntity r = new HttpEntity<>(message.toJsonString(), headers); 75 | REST_TEMPLATE.postForObject("https://oapi.dingtalk.com/robot/send?access_token=" + dingId + "×tamp=" + timestamp + "&sign=" + sign, r, DingSendResult.class); 76 | log.info(message.toJsonString()); 77 | } 78 | 79 | public static String sign(Long timestamp, String secret) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException { 80 | String stringToSign = timestamp + "\n" + secret; 81 | Mac mac = Mac.getInstance("HmacSHA256"); 82 | mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); 83 | byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); 84 | return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/DingTextMessage.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import com.google.gson.Gson; 4 | import lombok.Data; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * @author chentiefeng 13 | * @date 2019/02/18 16:01 14 | */ 15 | @Data 16 | public class DingTextMessage { 17 | private Gson gson = new Gson(); 18 | private String text; 19 | private List atMobiles; 20 | private boolean isAtAll; 21 | 22 | public DingTextMessage(String text) { 23 | this.text = text; 24 | } 25 | 26 | 27 | public String toJsonString() { 28 | Map items = new HashMap<>(8); 29 | items.put("msgtype", "text"); 30 | 31 | Map textContent = new HashMap<>(8); 32 | if (StringUtils.isBlank(text)) { 33 | throw new IllegalArgumentException("text should not be blank"); 34 | } 35 | textContent.put("content", text); 36 | items.put("text", textContent); 37 | 38 | Map atItems = new HashMap<>(8); 39 | if (atMobiles != null && !atMobiles.isEmpty()) { 40 | atItems.put("atMobiles", atMobiles); 41 | } 42 | if (isAtAll) { 43 | atItems.put("isAtAll", isAtAll); 44 | } 45 | items.put("at", atItems); 46 | 47 | return gson.toJson(items); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/FeishuTalkHelper.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import com.google.gson.Gson; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.web.client.RestTemplateBuilder; 6 | import org.springframework.http.HttpEntity; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * @author chentiefeng 15 | * @date 2019/02/18 17:57 16 | */ 17 | @Slf4j 18 | public class FeishuTalkHelper { 19 | private static final RestTemplate REST_TEMPLATE = new RestTemplateBuilder().build(); 20 | private static final Gson GSON = new Gson(); 21 | 22 | /** 23 | * 飞书消息发送 24 | * 25 | * @param title 26 | * @param content 27 | * @param token 28 | */ 29 | public static void sendTextMsg(String title, String content, String token) { 30 | HttpHeaders headers = new HttpHeaders(); 31 | headers.add("Content-Type", "application/json;charset=UTF-8"); 32 | Map map = new HashMap<>(8); 33 | map.put("title", title); 34 | map.put("text", content); 35 | HttpEntity r = new HttpEntity<>(GSON.toJson(map), headers); 36 | REST_TEMPLATE.postForObject("https://open.feishu.cn/open-apis/bot/hook/" + token, r, String.class); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/MonitorConfigUtil.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import me.ctf.lm.config.MonitorConfig; 4 | import me.ctf.lm.enums.DistributedLockTypeEnum; 5 | import me.ctf.lm.enums.HostStateEnum; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.Resource; 10 | 11 | /** 12 | * @author: chentiefeng[chentiefeng@linzikg.com] 13 | * @create: 2019-12-20 11:22 14 | */ 15 | @Component 16 | public class MonitorConfigUtil { 17 | private static MonitorConfig monitorConfig; 18 | 19 | public static boolean getCluster() { 20 | return monitorConfig.getCluster() == null ? false : monitorConfig.getCluster(); 21 | } 22 | 23 | public static String getHostState() { 24 | if (StringUtils.isBlank(monitorConfig.getHostState())) { 25 | return HostStateEnum.ONLINE.name(); 26 | } 27 | return monitorConfig.getHostState(); 28 | } 29 | 30 | public static int getDuration() { 31 | return monitorConfig.getDuration() == null ? 3 : monitorConfig.getDuration(); 32 | } 33 | 34 | public static String getDistributedLockType() { 35 | if (StringUtils.isBlank(monitorConfig.getDistributedLockType())) { 36 | return DistributedLockTypeEnum.DB.name(); 37 | } 38 | return monitorConfig.getDistributedLockType(); 39 | } 40 | 41 | @Resource 42 | public void setMonitorConfig(MonitorConfig monitorConfig) { 43 | MonitorConfigUtil.monitorConfig = monitorConfig; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/PageUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-2019 人人开源 All rights reserved. 3 | * 4 | * https://www.renren.io 5 | * 6 | * 版权所有,侵权必究! 7 | */ 8 | 9 | package me.ctf.lm.util; 10 | 11 | import com.baomidou.mybatisplus.core.metadata.IPage; 12 | 13 | import java.io.Serializable; 14 | import java.util.List; 15 | 16 | /** 17 | * 分页工具类 18 | * 19 | * @author Mark sunlightcs@gmail.com 20 | */ 21 | public class PageUtils implements Serializable { 22 | private static final long serialVersionUID = 1L; 23 | /** 24 | * 总记录数 25 | */ 26 | private int totalCount; 27 | /** 28 | * 每页记录数 29 | */ 30 | private int pageSize; 31 | /** 32 | * 总页数 33 | */ 34 | private int totalPage; 35 | /** 36 | * 当前页数 37 | */ 38 | private int currPage; 39 | /** 40 | * 列表数据 41 | */ 42 | private List list; 43 | 44 | /** 45 | * 分页 46 | * @param list 列表数据 47 | * @param totalCount 总记录数 48 | * @param pageSize 每页记录数 49 | * @param currPage 当前页数 50 | */ 51 | public PageUtils(List list, int totalCount, int pageSize, int currPage) { 52 | this.list = list; 53 | this.totalCount = totalCount; 54 | this.pageSize = pageSize; 55 | this.currPage = currPage; 56 | this.totalPage = (int)Math.ceil((double)totalCount/pageSize); 57 | } 58 | 59 | /** 60 | * 分页 61 | */ 62 | public PageUtils(IPage page) { 63 | this.list = page.getRecords(); 64 | this.totalCount = (int)page.getTotal(); 65 | this.pageSize = (int)page.getSize(); 66 | this.currPage = (int)page.getCurrent(); 67 | this.totalPage = (int)page.getPages(); 68 | } 69 | 70 | public int getTotalCount() { 71 | return totalCount; 72 | } 73 | 74 | public void setTotalCount(int totalCount) { 75 | this.totalCount = totalCount; 76 | } 77 | 78 | public int getPageSize() { 79 | return pageSize; 80 | } 81 | 82 | public void setPageSize(int pageSize) { 83 | this.pageSize = pageSize; 84 | } 85 | 86 | public int getTotalPage() { 87 | return totalPage; 88 | } 89 | 90 | public void setTotalPage(int totalPage) { 91 | this.totalPage = totalPage; 92 | } 93 | 94 | public int getCurrPage() { 95 | return currPage; 96 | } 97 | 98 | public void setCurrPage(int currPage) { 99 | this.currPage = currPage; 100 | } 101 | 102 | public List getList() { 103 | return list; 104 | } 105 | 106 | public void setList(List list) { 107 | this.list = list; 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/PlaceholderUtil.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.format.DateTimeFormatter; 5 | import java.time.temporal.ChronoUnit; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | /** 12 | * 占位符工具类 13 | * 14 | * @author chentiefeng 15 | * @date 2019/12/26 08:30 16 | */ 17 | public class PlaceholderUtil { 18 | 19 | private static final Pattern PATTERN = Pattern.compile("\\$\\{[^}]+}"); 20 | 21 | /** 22 | * 解析占位符变量
23 | *
24 | * 占位符格式${abc}
25 | * 例子1:s = hello ${abc},analyze(s,{abc=world}) = hello world
26 | * 例子2:s = hello ${abc},analyze(s,{ab=world}) = hello ${abc}
27 | *
28 | * 日期占位符格式:${yyyyMMdd,addAmount,unit}
29 | * 例子1:s = hello ${yyyyMMdd},analyze(s,null) = hello 20191226
30 | * 例子2:s = hello ${yyyyMMdd,-1},analyze(s,null) = hello 20191225
31 | * 例子3:s = hello ${yyyyMMdd,-1,Months},analyze(s,null) = hello 20191126
32 | *

日期格式和单位请参考java.time.format.DateTimeFormatter和java.time.temporal.ChronoUnit
33 | * 34 | * @param varString 35 | * @param varMap 36 | * @return 解析后的字符串 37 | * @see DateTimeFormatter 38 | * @see ChronoUnit 39 | */ 40 | public static String analyze(String varString, Map varMap) { 41 | return analyze(varString, varMap, LocalDateTime.now(), null); 42 | } 43 | 44 | /** 45 | * 解析参数变量 46 | * 47 | * @param varString 48 | * @param varMap 49 | * @param now 50 | * @param specialString 51 | * @return 52 | */ 53 | private static String analyze(String varString, Map varMap, LocalDateTime now, String specialString) { 54 | if (varString == null || varString.trim().length() == 0) { 55 | return varString; 56 | } 57 | StringBuffer sb = new StringBuffer(); 58 | Matcher matcher = PATTERN.matcher(varString); 59 | String group, var; 60 | while (matcher.find() && (group = matcher.group()) != null) { 61 | var = group.substring(2, group.length() - 1); 62 | Object value; 63 | if ((value = specialString) != null || (value = getVarValue(var, varMap, now)) != null) { 64 | matcher.appendReplacement(sb, value.toString()); 65 | } 66 | } 67 | matcher.appendTail(sb); 68 | return sb.toString(); 69 | } 70 | 71 | /** 72 | * 获取变量值 73 | * 74 | * @param var 75 | * @param varMap 76 | * @param now 77 | * @return 78 | */ 79 | private static Object getVarValue(String var, Map varMap, LocalDateTime now) { 80 | Object value; 81 | if (varMap != null && (value = varMap.get(var)) != null) { 82 | //普通变量 83 | return value; 84 | } 85 | if (var != null && now != null) { 86 | String[] arr = var.split(","); 87 | LocalDateTime next; 88 | DateTimeFormatter dtf; 89 | try { 90 | dtf = DateTimeFormatter.ofPattern(arr[0]); 91 | } catch (IllegalArgumentException e) { 92 | return null; 93 | } 94 | switch (arr.length) { 95 | case 2: 96 | //时间变量有加减,默认加减天,${yyyyMMdd,2} 97 | next = getNextDate(now, arr[1].trim(), ChronoUnit.DAYS); 98 | break; 99 | case 3: 100 | //时间变量有加减,默认加减天,${yyyyMMdd,2,Months} 101 | next = getNextDate(now, arr[1].trim(), ChronoUnit.valueOf(arr[2].trim().toUpperCase())); 102 | break; 103 | default: 104 | //时间变量,无加减,${yyyyMMdd} 105 | next = now; 106 | break; 107 | } 108 | return dtf.format(next); 109 | } 110 | return null; 111 | } 112 | 113 | /** 114 | * 所有变量替换成"" 115 | * 116 | * @param varString 117 | * @return 118 | */ 119 | public static String analyzeToBlank(String varString) { 120 | return analyze(varString, null, null, ""); 121 | } 122 | 123 | /** 124 | * 用固定字符串解析 125 | * 126 | * @param varString 127 | * @param s 128 | * @return 129 | */ 130 | public static String analyzeToString(String varString, String s) { 131 | return analyze(varString, null, null, s); 132 | } 133 | 134 | /** 135 | * 所有变量替换成'' 136 | * 137 | * @param varString 138 | * @return 139 | */ 140 | public static String analyzeToSingleBlank(String varString) { 141 | return analyze(varString, null, null, "''"); 142 | } 143 | 144 | /** 145 | * 获取时间 146 | * 147 | * @param now 148 | * @param express 149 | * @param unit 150 | * @return 151 | */ 152 | private static LocalDateTime getNextDate(LocalDateTime now, String express, ChronoUnit unit) { 153 | int amount = express.startsWith("+") ? Integer.parseInt(express.substring(1)) : Integer.parseInt(express); 154 | return now.plus(amount, unit); 155 | } 156 | 157 | public static void main(String[] args) { 158 | String s = "hello ${yyyyMMdd}"; 159 | Map map = new HashMap<>(); 160 | map.put("ab", "world"); 161 | System.out.println(analyze(s, null)); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/ProcessStdoutUtil.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import ch.ethz.ssh2.StreamGobbler; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | /** 13 | * @author chentiefeng 14 | * @date 2019/02/18 14:55 15 | */ 16 | @Slf4j 17 | public class ProcessStdoutUtil { 18 | /** 19 | * 解析脚本执行返回的结果集 20 | *   21 | * 22 | * @param in 输入流对象 23 | * @return 以纯文本的格式返回 24 | */ 25 | public static String processStdout(InputStream in) { 26 | InputStream stdout = new StreamGobbler(in); 27 | StringBuilder buffer = new StringBuilder(); 28 | try { 29 | BufferedReader br = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8)); 30 | String line; 31 | while ((line = br.readLine()) != null) { 32 | buffer.append(line).append("\n"); 33 | } 34 | br.close(); 35 | } catch (IOException e) { 36 | log.error("解析脚本出错," + e.getMessage(), e); 37 | } 38 | return buffer.toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/Query.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-2019 人人开源 All rights reserved. 3 | *

4 | * https://www.renren.io 5 | *

6 | * 版权所有,侵权必究! 7 | */ 8 | 9 | package me.ctf.lm.util; 10 | 11 | import com.baomidou.mybatisplus.core.metadata.IPage; 12 | import com.baomidou.mybatisplus.core.metadata.OrderItem; 13 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 14 | import org.apache.commons.lang3.StringUtils; 15 | 16 | import java.util.Map; 17 | 18 | /** 19 | * 查询参数 20 | * 21 | * @author Mark sunlightcs@gmail.com 22 | */ 23 | public class Query { 24 | 25 | public IPage getPage(Map params) { 26 | return this.getPage(params, null, false); 27 | } 28 | 29 | public IPage getPage(Map params, String defaultOrderField, boolean isAsc) { 30 | //分页参数 31 | long curPage = 1; 32 | long limit = 10; 33 | 34 | if (params.get(Constant.PAGE) != null) { 35 | curPage = Long.parseLong((String) params.get(Constant.PAGE)); 36 | } 37 | if (params.get(Constant.LIMIT) != null) { 38 | limit = Long.parseLong((String) params.get(Constant.LIMIT)); 39 | } 40 | 41 | //分页对象 42 | Page page = new Page<>(curPage, limit); 43 | 44 | //分页参数 45 | params.put(Constant.PAGE, page); 46 | 47 | //排序字段 48 | //防止SQL注入(因为sidx、order是通过拼接SQL实现排序的,会有SQL注入风险) 49 | String orderField = SQLFilter.sqlInject((String) params.get(Constant.ORDER_FIELD)); 50 | String order = (String) params.get(Constant.ORDER); 51 | 52 | 53 | //前端字段排序 54 | if (StringUtils.isNotEmpty(orderField) && StringUtils.isNotEmpty(order)) { 55 | if (Constant.ASC.equalsIgnoreCase(order)) { 56 | return page.addOrder(OrderItem.asc(orderField)); 57 | } else { 58 | return page.addOrder(OrderItem.desc(orderField)); 59 | } 60 | } 61 | 62 | //没有排序字段,则不排序 63 | if (StringUtils.isBlank(defaultOrderField)) { 64 | return page; 65 | } 66 | 67 | //默认排序 68 | if (isAsc) { 69 | page.addOrder(OrderItem.asc(defaultOrderField)); 70 | } else { 71 | page.addOrder(OrderItem.desc(defaultOrderField)); 72 | } 73 | 74 | return page; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/SQLFilter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-2019 人人开源 All rights reserved. 3 | *

4 | * https://www.renren.io 5 | *

6 | * 版权所有,侵权必究! 7 | */ 8 | 9 | package me.ctf.lm.util; 10 | 11 | 12 | import org.apache.commons.lang3.StringUtils; 13 | 14 | /** 15 | * SQL过滤 16 | * 17 | * @author Mark sunlightcs@gmail.com 18 | */ 19 | public class SQLFilter { 20 | 21 | /** 22 | * SQL注入过滤 23 | * 24 | * @param str 待验证的字符串 25 | */ 26 | public static String sqlInject(String str) { 27 | if (StringUtils.isBlank(str)) { 28 | return null; 29 | } 30 | //去掉'|"|;|\字符 31 | str = StringUtils.replace(str, "'", ""); 32 | str = StringUtils.replace(str, "\"", ""); 33 | str = StringUtils.replace(str, ";", ""); 34 | str = StringUtils.replace(str, "\\", ""); 35 | 36 | //转换成小写 37 | str = str.toLowerCase(); 38 | 39 | //非法字符 40 | String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"}; 41 | 42 | //判断是否包含非法字符 43 | for (String keyword : keywords) { 44 | if (str.indexOf(keyword) != -1) { 45 | throw new RuntimeException("包含非法字符"); 46 | } 47 | } 48 | 49 | return str; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/UpdateUtils.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util; 2 | 3 | import org.springframework.beans.BeanUtils; 4 | import org.springframework.beans.BeanWrapper; 5 | import org.springframework.beans.BeanWrapperImpl; 6 | 7 | import java.beans.PropertyDescriptor; 8 | import java.util.HashSet; 9 | import java.util.Objects; 10 | import java.util.Set; 11 | 12 | /** 13 | * @author: chentiefeng[chentiefeng@linzikg.com] 14 | * @create: 2019-12-20 10:03 15 | */ 16 | public class UpdateUtils { 17 | 18 | /** 19 | * 所有为空值的属性都不copy 20 | * 21 | * @param source 22 | * @param target 23 | */ 24 | public static void copyNullProperties(Object source, Object target) { 25 | BeanUtils.copyProperties(source, target, getNullField(source)); 26 | } 27 | 28 | /** 29 | * 获取属性中为空的字段 30 | * 31 | * @param target 32 | * @return 33 | */ 34 | private static String[] getNullField(Object target) { 35 | BeanWrapper beanWrapper = new BeanWrapperImpl(target); 36 | PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors(); 37 | Set notNullFieldSet = new HashSet<>(); 38 | if (propertyDescriptors.length > 0) { 39 | for (PropertyDescriptor p : propertyDescriptors) { 40 | String name = p.getName(); 41 | Object value = beanWrapper.getPropertyValue(name); 42 | if (Objects.isNull(value)) { 43 | notNullFieldSet.add(name); 44 | } 45 | } 46 | } 47 | String[] notNullField = new String[notNullFieldSet.size()]; 48 | return notNullFieldSet.toArray(notNullField); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/validator/LogGroup.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util.validator; 2 | 3 | /** 4 | * 日志组 5 | * 6 | * @author: chentiefeng[chentiefeng@linzikg.com] 7 | * @create: 2019-12-12 15:19 8 | */ 9 | public interface LogGroup { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/ctf/lm/util/validator/ValidatorUtil.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm.util.validator; 2 | 3 | import me.ctf.lm.util.BizException; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.annotation.Resource; 7 | import javax.validation.ConstraintViolation; 8 | import javax.validation.Validator; 9 | import javax.validation.groups.Default; 10 | import java.util.Set; 11 | 12 | /** 13 | * @author: chentiefeng[chentiefeng@linzikg.com] 14 | * @create: 2019-12-12 15:22 15 | */ 16 | @Component 17 | public class ValidatorUtil { 18 | private static Validator validator; 19 | 20 | @Resource 21 | public void setValidator(Validator validator) { 22 | ValidatorUtil.validator = validator; 23 | } 24 | 25 | /** 26 | * 校验对象 27 | * 28 | * @param object 待校验对象 29 | * @param groups 待校验的组 30 | * @throws BizException 校验不通过,则报BizException异常 31 | */ 32 | public static void validateEntity(Object object, Class... groups) 33 | throws BizException { 34 | Class[] realGroups = new Class[groups.length + 1]; 35 | System.arraycopy(groups, 0, realGroups, 0, groups.length); 36 | realGroups[groups.length] = Default.class; 37 | Set> constraintViolations = validator.validate(object, realGroups); 38 | if (!constraintViolations.isEmpty()) { 39 | StringBuilder msg = new StringBuilder(); 40 | for (ConstraintViolation constraint : constraintViolations) { 41 | msg.append(constraint.getMessage()); 42 | } 43 | throw new BizException(msg.toString()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 10003 3 | 4 | spring: 5 | #============== redis =================== 6 | # redis: 7 | # host: 172.16.157.239 8 | # port: 6379 9 | # timeout: 6000 10 | # password: 11 | # database: 3 12 | # pool: 13 | # max-active: 1000 14 | # max-wait: -1 15 | # max-idle: 10 16 | # min-idle: 5 17 | 18 | #============== kafka =================== 19 | # kafka: 20 | # bootstrap-servers: cm02:9092,cm03:9092,cm04:9092 21 | # producer: 22 | # key-serializer: org.apache.kafka.common.serialization.StringSerializer 23 | # value-serializer: org.apache.kafka.common.serialization.StringSerializer 24 | # acks: all 25 | # consumer: 26 | # group-id: lite-monitor 27 | # key-serializer: org.apache.kafka.common.serialization.StringSerializer 28 | # value-serializer: org.apache.kafka.common.serialization.StringSerializer 29 | # max-poll-records: 10 30 | # listener: 31 | # type: batch 32 | # concurrency: 4 33 | # ack-mode: MANUAL 34 | # missing-topics-fatal: false 35 | 36 | #============== dataSource ================== 37 | datasource: 38 | 39 | dynamic: 40 | primary: master #设置默认的数据源或者数据源组,默认值即为master 41 | strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源. 42 | datasource: 43 | master: 44 | url: jdbc:h2:./lite-monitor 45 | username: sa 46 | password: 123456 47 | driver-class-name: org.h2.Driver 48 | schema: classpath:db/schema.sql 49 | risk: 50 | url: jdbc:mysql://rr-bp16cbk5q9llz2k20.mysql.rds.aliyuncs.com:3306/risk_biz?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8 51 | username: risk_readonly 52 | password: linzi@9527 53 | driver-class-name: com.mysql.jdbc.Driver 54 | 55 | 56 | monitor: 57 | #是否集群,集群模式请用MySQL,注释掉h2部分,会分发定时任务 false/true 58 | cluster: false 59 | #集群模式下,集群检查分钟数,3分钟没有反应则表明机器挂了 60 | # duration: 3 61 | #集群模式下,是否注册本机 online/offline 62 | # hostState: online 63 | #集群模式下分布式锁类型,db/redis,选择redis把redis的配置注释打开 64 | # distributed-lock-type: db 65 | #集群模式下分发类型,http/redis/kafka,选择其他需要把注释打开 66 | # distribute-task-type: http 67 | -------------------------------------------------------------------------------- /src/main/resources/db/schema.sql: -------------------------------------------------------------------------------- 1 | create table if not exists lite_monitor_config 2 | ( 3 | id bigint(20) auto_increment NOT NULL COMMENT 'ID', 4 | monitor_type varchar(16) not null comment '监控类型,进程:PROCESS,日志:LOG', 5 | frequency varchar(32) not null comment '频率,cron表达式或者枚举', 6 | schema_name varchar(64) null comment 'schema', 7 | host_name varchar(16) null comment '主机', 8 | username varchar(32) null comment '用户名', 9 | pwd varchar(256) null comment '密码', 10 | pem text null comment '密钥', 11 | port bigint null comment '端口', 12 | file_path varchar(512) null comment '文件地址', 13 | stat_second bigint null comment '统计范围,秒', 14 | threshold bigint null comment '阈值', 15 | script text null comment '脚本', 16 | ding_type varchar(64) null comment '提醒类型', 17 | ding_title varchar(64) null comment '钉钉标题', 18 | ding_token varchar(255) null comment '钉钉token', 19 | sign_key varchar(255) null comment '钉钉签名Key', 20 | ding_at varchar(256) null comment '钉钉at', 21 | show_count tinyint null comment '钉钉展示条数', 22 | remark varchar(256) null comment '备注', 23 | enabled tinyint(2) default 0 null comment '是否启用,0未启用,1启用', 24 | gmt_create datetime null comment '创建日期', 25 | gmt_modified datetime null comment '修改日期', 26 | PRIMARY KEY (`id`) 27 | ); 28 | 29 | create index IF NOT EXISTS lite_monitor_config_frequency_index_frequency 30 | on lite_monitor_config (frequency); 31 | 32 | create index IF NOT EXISTS lite_monitor_config_frequency_index_host 33 | on lite_monitor_config (host_name); 34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 9 | 10 | 11 | 12 | 13 | ${LOG_HOME}/m.log 14 | true 15 | 16 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 17 | 18 | 19 | INFO 20 | 21 | 22 | ${LOG_HOME}/%d{yyyyMMdd}/m.log.%i 23 | 10 24 | 100MB 25 | 1GB 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/static/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chentiefeng/lite-monitor/59db7173dbd5c5e973b924b51a5a7ad2acaaae9d/src/main/resources/static/img/bg.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chentiefeng/lite-monitor/59db7173dbd5c5e973b924b51a5a7ad2acaaae9d/src/main/resources/static/img/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lite-monitor 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 查询 30 | 重置 31 | 新增 32 | 33 | 34 | 39 | 45 | 51 | 52 | 53 | 58 | 59 | 65 | 66 | 72 | 73 | 79 | 80 | 86 | 87 | 93 | 97 | 98 | 104 | 116 | 117 | 118 | 126 | 127 | 128 | 129 |
130 | 131 |
132 | Blog Github 133 |
134 |
135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 288 | -------------------------------------------------------------------------------- /src/main/resources/static/js/app.js: -------------------------------------------------------------------------------- 1 | var hostName = '' 2 | window.onload=function(){ 3 | loadDomain(); 4 | } 5 | /** 6 | * 项目路径 7 | */ 8 | function loadDomain(){ 9 | hostName = window.location.protocol + '//' + window.location.hostname + ':' + window.location.port 10 | } 11 | 12 | function getUrl(url){ 13 | return hostName + url 14 | } -------------------------------------------------------------------------------- /src/main/resources/static/monitor-add-or-update.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 290 | -------------------------------------------------------------------------------- /src/test/java/me/ctf/lm/LiteMonitorApplicationTests.java: -------------------------------------------------------------------------------- 1 | package me.ctf.lm; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class LiteMonitorApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------