diff --git a/common/src/main/java/com/usthe/common/entity/job/Configmap.java b/common/src/main/java/com/usthe/common/entity/job/Configmap.java index e21bc5f..388ad51 100644 --- a/common/src/main/java/com/usthe/common/entity/job/Configmap.java +++ b/common/src/main/java/com/usthe/common/entity/job/Configmap.java @@ -28,8 +28,9 @@ public class Configmap { private Object value; /** + * 参数类型 0:数字 1:字符串 2:加密串 * number,string,secret * 数字,非加密字符串,加密字符串 */ - private String type; + private byte type; } diff --git a/common/src/main/java/com/usthe/common/entity/job/Job.java b/common/src/main/java/com/usthe/common/entity/job/Job.java index c9723b3..719a6dd 100644 --- a/common/src/main/java/com/usthe/common/entity/job/Job.java +++ b/common/src/main/java/com/usthe/common/entity/job/Job.java @@ -50,11 +50,11 @@ public class Job { /** * 任务采集时间间隔(单位秒) eg: 30,60,600 */ - private long interval; + private long interval = 600L; /** * 是否是循环周期性任务 true为是,false为否 */ - private boolean isCyclic; + private boolean isCyclic = false; /** * 指标组配置 eg: cpu memory */ @@ -88,7 +88,7 @@ public class Job { .peek(metric -> { // 判断是否配置aliasFields 没有则配置默认 if (metric.getAliasFields() == null || metric.getAliasFields().isEmpty()) { - metric.setAliasFields(metric.getFields()); + metric.setAliasFields(metric.getFields().stream().map(Metrics.Field::getField).collect(Collectors.toList())); } // 设置默认的指标组执行优先级 if (metric.getPriority() == null) { diff --git a/common/src/main/java/com/usthe/common/entity/job/Metrics.java b/common/src/main/java/com/usthe/common/entity/job/Metrics.java index d69b559..da71dde 100644 --- a/common/src/main/java/com/usthe/common/entity/job/Metrics.java +++ b/common/src/main/java/com/usthe/common/entity/job/Metrics.java @@ -39,7 +39,7 @@ public class Metrics { /** * 公共属性-采集监控的最终结果属性集合 eg: speed | times | size */ - private List fields; + private List fields; /** * 公共属性-采集监控的前置查询属性集合 eg: size1 | size2 | speedSize */ @@ -74,7 +74,7 @@ public class Metrics { @Data @AllArgsConstructor @NoArgsConstructor - public class Field { + public static class Field { /** * 指标名称 */ diff --git a/common/src/main/java/com/usthe/common/entity/job/protocol/HttpProtocol.java b/common/src/main/java/com/usthe/common/entity/job/protocol/HttpProtocol.java index 95cd54c..281eaf8 100644 --- a/common/src/main/java/com/usthe/common/entity/job/protocol/HttpProtocol.java +++ b/common/src/main/java/com/usthe/common/entity/job/protocol/HttpProtocol.java @@ -21,6 +21,10 @@ public class HttpProtocol { * 对端主机ip或域名 */ private String host; + /** + * 对端主机端口 + */ + private String port; /** * http/https 请求访问的url链接 */ @@ -64,7 +68,7 @@ public class HttpProtocol { @Data @AllArgsConstructor @NoArgsConstructor - public class Authorization { + public static class Authorization { /** * 认证类型:Bearer Token, Basic Auth, Digest Auth */ diff --git a/common/src/main/java/com/usthe/common/util/CommonConstants.java b/common/src/main/java/com/usthe/common/util/CommonConstants.java index 0135d5e..3debb5b 100644 --- a/common/src/main/java/com/usthe/common/util/CommonConstants.java +++ b/common/src/main/java/com/usthe/common/util/CommonConstants.java @@ -8,23 +8,53 @@ package com.usthe.common.util; public interface CommonConstants { /** - * 成功 + * 响应状态码: 成功 */ byte SUCCESS = 0x00; /** - * 参数校验失败 + * 响应状态码: 参数校验失败 */ byte PARAM_INVALID = 0x01; /** - * 探测失败 + * 响应状态码: 探测失败 */ byte DETECT_FAILED = 0x02; /** - * 监控不存在 + * 响应状态码: 监控不存在 */ byte MONITOR_NOT_EXIST = 0x03; + /** + * 响应状态码: 监控服务冲突 + */ + byte MONITOR_CONFLICT = 0x04; + + /** + * 监控状态码: 未管理 + */ + byte UN_MANAGE = 0x00; + + /** + * 监控状态码: 可用 + */ + byte AVAILABLE = 0x01; + + /** + * 监控状态码: 不可用 + */ + byte UN_AVAILABLE = 0x02; + + /** + * 监控状态码: 不可达 + */ + byte UN_REACHABLE = 0x03; + + /** + * 监控状态码: 挂起 + */ + byte SUSPENDING = 0x04; + } diff --git a/common/src/main/java/com/usthe/common/util/SnowFlakeIdGenerator.java b/common/src/main/java/com/usthe/common/util/SnowFlakeIdGenerator.java new file mode 100644 index 0000000..6c1d235 --- /dev/null +++ b/common/src/main/java/com/usthe/common/util/SnowFlakeIdGenerator.java @@ -0,0 +1,19 @@ +package com.usthe.common.util; + +/** + * 雪花算法生成器工具 + * @author tomsun28 + * @date 2021/11/10 11:04 + */ +public class SnowFlakeIdGenerator { + + private final static SnowFlakeIdWorker ID_WORKER; + + static { + ID_WORKER = new SnowFlakeIdWorker(1, 0); + } + + public static long generateId() { + return ID_WORKER.nextId(); + } +} diff --git a/common/src/main/java/com/usthe/common/util/SnowFlakeIdWorker.java b/common/src/main/java/com/usthe/common/util/SnowFlakeIdWorker.java new file mode 100644 index 0000000..8c5a1ab --- /dev/null +++ b/common/src/main/java/com/usthe/common/util/SnowFlakeIdWorker.java @@ -0,0 +1,165 @@ +package com.usthe.common.util; + +/** + * 雪花算法生成器实例 + * @author from https://www.cnblogs.com/vchar/p/14857677.html + * @date 2021/11/10 10:58 + */ +public class SnowFlakeIdWorker { + + /** + * 开始时间戳,单位毫秒;这里是2021-06-01 + */ + private static final long TW_EPOCH = 1622476800000L; + + /** + * 机器 ID 所占的位数 + */ + private static final long WORKER_ID_BITS = 5L; + + /** + * 数据标识 ID 所占的位数 + */ + private static final long DATA_CENTER_ID_BITS = 5L; + + /** + * 支持的最大机器ID,最大为31 + *

+ * PS. Twitter的源码是 -1L ^ (-1L << workerIdBits);这里最后和-1进行异或运算,由于-1的二进制补码的特殊性,就相当于进行取反。 + */ + private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); + + /** + * 支持的最大机房ID,最大为31 + */ + private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); + + /** + * 序列在 ID 中占的位数 + */ + private static final long SEQUENCE_BITS = 12L; + + /** + * 机器 ID 向左移12位 + */ + private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; + + /** + * 机房 ID 向左移17位 + */ + private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; + + /** + * 时间截向左移22位 + */ + private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; + + /** + * 生成序列的掩码最大值,最大为4095 + */ + private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); + + /** + * 工作机器 ID(0~31) + */ + private final long workerId; + + /** + * 机房 ID(0~31) + */ + private final long dataCenterId; + + /** + * 毫秒内序列(0~4095) + */ + private long sequence = 0L; + + /** + * 上次生成 ID 的时间戳 + */ + private long lastTimestamp = -1L; + + /** + * 创建 ID 生成器的方式一: 使用工作机器的序号(也就是将机房的去掉给机器ID使用),范围是 [0, 1023],优点是方便给机器编号 + * + * @param workerId 工作机器 ID + */ + public SnowFlakeIdWorker(long workerId) { + // 计算最大值 + long maxMachineId = (MAX_DATA_CENTER_ID + 1) * (MAX_WORKER_ID + 1) - 1; + + if (workerId < 0 || workerId > maxMachineId) { + throw new IllegalArgumentException(String.format("Worker ID can't be greater than %d or less than 0", maxMachineId)); + } + + // 取高位部分作为机房ID部分 + this.dataCenterId = (workerId >> WORKER_ID_BITS) & MAX_DATA_CENTER_ID; + // 取低位部分作为机器ID部分 + this.workerId = workerId & MAX_WORKER_ID; + } + + /** + * 创建 ID 生成器的方式二: 使用工作机器 ID 和机房 ID,优点是方便分机房管理 + * + * @param dataCenterId 机房 ID (0~31) + * @param workerId 工作机器 ID (0~31) + */ + public SnowFlakeIdWorker(long dataCenterId, long workerId) { + if (workerId > MAX_WORKER_ID || workerId < 0) { + throw new IllegalArgumentException(String.format("Worker ID can't be greater than %d or less than 0", MAX_WORKER_ID)); + } + if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { + throw new IllegalArgumentException(String.format("DataCenter ID can't be greater than %d or less than 0", MAX_DATA_CENTER_ID)); + } + + this.workerId = workerId; + this.dataCenterId = dataCenterId; + } + + /** + * 获得下一个 ID(该方法是线程安全的) + * + * @return 返回一个长度位15的 long类型的数字 + */ + public synchronized long nextId() { + long timestamp = timeGen(); + // 如果当前时间小于上一次 ID 生成的时间戳,说明发生时钟回拨,为保证ID不重复抛出异常。 + if (timestamp < lastTimestamp) { + throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + if (lastTimestamp == timestamp) { + // 同一时间生成的,则序号+1 + sequence = (sequence + 1) & SEQUENCE_MASK; + // 毫秒内序列溢出:超过最大值 + if (sequence == 0) { + // 阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } else { + // 时间戳改变,毫秒内序列重置 + sequence = 0L; + } + // 上次生成 ID 的时间戳 + lastTimestamp = timestamp; + + // 移位并通过或运算拼到一起 + return ((timestamp - TW_EPOCH) << TIMESTAMP_LEFT_SHIFT) + | (dataCenterId << DATA_CENTER_ID_SHIFT) + | (workerId << WORKER_ID_SHIFT) + | sequence; + } + + private long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + private long timeGen() { + return System.currentTimeMillis(); + } + +} diff --git a/manager/pom.xml b/manager/pom.xml index db996c6..1fb27c5 100644 --- a/manager/pom.xml +++ b/manager/pom.xml @@ -75,6 +75,11 @@ snakeyaml ${snake.yaml.version} + + + org.springframework.boot + spring-boot-starter-validation + \ No newline at end of file diff --git a/manager/src/main/java/com/usthe/manager/controller/AppController.java b/manager/src/main/java/com/usthe/manager/controller/AppController.java index 9b41d2d..e4b8ee9 100644 --- a/manager/src/main/java/com/usthe/manager/controller/AppController.java +++ b/manager/src/main/java/com/usthe/manager/controller/AppController.java @@ -14,6 +14,8 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + /** * 监控类型管理API * @author tomsun28 @@ -21,7 +23,7 @@ import java.util.List; */ @Api(tags = "监控类型管理API") @RestController -@RequestMapping(path = "/apps") +@RequestMapping(path = "/apps", produces = {APPLICATION_JSON_VALUE}) public class AppController { @Autowired diff --git a/manager/src/main/java/com/usthe/manager/controller/MonitorController.java b/manager/src/main/java/com/usthe/manager/controller/MonitorController.java index 0b6c5ad..2e22ae7 100644 --- a/manager/src/main/java/com/usthe/manager/controller/MonitorController.java +++ b/manager/src/main/java/com/usthe/manager/controller/MonitorController.java @@ -7,6 +7,7 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -26,7 +27,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; */ @Api(tags = "监控管理API") @RestController -@RequestMapping(path = "/monitor", consumes = {APPLICATION_JSON_VALUE}, produces = {APPLICATION_JSON_VALUE}) +@RequestMapping(path = "/monitor", produces = {APPLICATION_JSON_VALUE}) public class MonitorController { @Autowired @@ -34,23 +35,23 @@ public class MonitorController { @PostMapping @ApiOperation(value = "新增监控", notes = "新增一个监控应用") - public ResponseEntity> addNewMonitor(@RequestBody MonitorDto monitorDto) { + public ResponseEntity> addNewMonitor(@Validated @RequestBody MonitorDto monitorDto) { // 校验请求数据 monitorService.validate(monitorDto, false); - if (monitorDto.isDetected()) { + if (monitorDto.getDetected()) { // 进行探测 monitorService.detectMonitor(monitorDto.getMonitor(), monitorDto.getParams()); } monitorService.addMonitor(monitorDto.getMonitor(), monitorDto.getParams()); - return ResponseEntity.ok().build(); + return ResponseEntity.ok(new Message<>("Add success")); } @PutMapping @ApiOperation(value = "修改监控", notes = "修改一个已存在监控应用") - public ResponseEntity> modifyMonitor(@RequestBody MonitorDto monitorDto) { + public ResponseEntity> modifyMonitor(@Validated @RequestBody MonitorDto monitorDto) { // 校验请求数据 monitorService.validate(monitorDto, true); - if (monitorDto.isDetected()) { + if (monitorDto.getDetected()) { // 进行探测 monitorService.detectMonitor(monitorDto.getMonitor(), monitorDto.getParams()); } @@ -82,7 +83,7 @@ public class MonitorController { @PostMapping(path = "/detect") @ApiOperation(value = "探测监控", notes = "根据监控信息去对此监控进行可用性探测") - public ResponseEntity> detectMonitor(@RequestBody MonitorDto monitorDto) { + public ResponseEntity> detectMonitor(@Validated @RequestBody MonitorDto monitorDto) { monitorService.validate(monitorDto, false); monitorService.detectMonitor(monitorDto.getMonitor(), monitorDto.getParams()); return ResponseEntity.ok(new Message<>("Detect success.")); diff --git a/manager/src/main/java/com/usthe/manager/dao/ParamDao.java b/manager/src/main/java/com/usthe/manager/dao/ParamDao.java index eeaa90c..da70bac 100644 --- a/manager/src/main/java/com/usthe/manager/dao/ParamDao.java +++ b/manager/src/main/java/com/usthe/manager/dao/ParamDao.java @@ -3,10 +3,19 @@ package com.usthe.manager.dao; import com.usthe.manager.pojo.entity.Param; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + /** * ParamDao 数据库操作 * @author tomsun28 * @date 2021/11/14 11:26 */ public interface ParamDao extends JpaRepository { + + /** + * 根据监控ID查询与之关联的参数列表 + * @param monitorId 监控ID + * @return 参数值列表 + */ + List findParamsByMonitorId(long monitorId); } diff --git a/manager/src/main/java/com/usthe/manager/dao/ParamDefineDao.java b/manager/src/main/java/com/usthe/manager/dao/ParamDefineDao.java index 6ed0d7d..a07cdb4 100644 --- a/manager/src/main/java/com/usthe/manager/dao/ParamDefineDao.java +++ b/manager/src/main/java/com/usthe/manager/dao/ParamDefineDao.java @@ -3,6 +3,9 @@ package com.usthe.manager.dao; import com.usthe.manager.pojo.entity.ParamDefine; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; +import java.util.Optional; + /** * ParamDefine数据库操作 * @author tomsun28 @@ -10,4 +13,10 @@ import org.springframework.data.jpa.repository.JpaRepository; */ public interface ParamDefineDao extends JpaRepository { + /** + * 根据监控类型查询其下的参数定义 + * @param app 监控类型 + * @return 参数定义列表 + */ + List findParamDefinesByApp(String app); } diff --git a/manager/src/main/java/com/usthe/manager/pojo/dto/MonitorDto.java b/manager/src/main/java/com/usthe/manager/pojo/dto/MonitorDto.java index e8da94c..cc128b7 100644 --- a/manager/src/main/java/com/usthe/manager/pojo/dto/MonitorDto.java +++ b/manager/src/main/java/com/usthe/manager/pojo/dto/MonitorDto.java @@ -6,6 +6,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import javax.validation.constraints.NotNull; import java.util.List; import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_WRITE; @@ -23,17 +24,19 @@ public class MonitorDto { * 监控实体 */ @ApiModelProperty(value = "监控实体", accessMode = READ_WRITE, position = 0) + @NotNull private Monitor monitor; /** * 参数 */ @ApiModelProperty(value = "监控参数", accessMode = READ_WRITE, position = 1) + @NotNull private List params; /** * 是否探测 */ @ApiModelProperty(value = "是否进行探测", accessMode = READ_WRITE, position = 2) - private boolean detected; + private Boolean detected; } diff --git a/manager/src/main/java/com/usthe/manager/pojo/entity/Monitor.java b/manager/src/main/java/com/usthe/manager/pojo/entity/Monitor.java index 4f30281..0fff1e6 100644 --- a/manager/src/main/java/com/usthe/manager/pojo/entity/Monitor.java +++ b/manager/src/main/java/com/usthe/manager/pojo/entity/Monitor.java @@ -6,12 +6,13 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; import java.time.LocalDateTime; import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_ONLY; @@ -32,7 +33,6 @@ import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_WRITE; public class Monitor { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) @ApiModelProperty(value = "监控ID", example = "87584674384", accessMode = READ_ONLY, position = 0) private Long id; @@ -46,36 +46,43 @@ public class Monitor { * 监控的名称 */ @ApiModelProperty(value = "监控名称", example = "Api-bing.com", accessMode = READ_WRITE, position = 2) + @Length(max = 100) private String name; /** * 监控的类型:linux,mysql,jvm... */ @ApiModelProperty(value = "监控类型", example = "api", accessMode = READ_WRITE, position = 3) + @Length(max = 100) private String app; /** * 监控的对端host:ipv4,ipv6,域名 */ @ApiModelProperty(value = "监控的对端host", example = "192.167.25.11", accessMode = READ_WRITE, position = 4) + @Length(max = 100) private String host; /** * 监控的采集间隔时间,单位秒 */ @ApiModelProperty(value = "监控的采集间隔时间,单位秒", example = "600", accessMode = READ_WRITE, position = 5) + @Min(10) private Integer intervals; /** * 监控状态 0:未监控,1:可用,2:不可用,3:不可达,4:挂起 */ @ApiModelProperty(value = "监控状态 0:未监控,1:可用,2:不可用,3:不可达,4:挂起", example = "1", accessMode = READ_WRITE, position = 6) + @Min(0) + @Max(4) private byte status; /** * 监控备注描述 */ @ApiModelProperty(value = "监控备注描述", example = "对搜索网站bing的可用性监控", accessMode = READ_WRITE, position = 7) + @Length(max = 255) private String description; /** diff --git a/manager/src/main/java/com/usthe/manager/pojo/entity/Param.java b/manager/src/main/java/com/usthe/manager/pojo/entity/Param.java index 571a785..20144dc 100644 --- a/manager/src/main/java/com/usthe/manager/pojo/entity/Param.java +++ b/manager/src/main/java/com/usthe/manager/pojo/entity/Param.java @@ -6,12 +6,15 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; import java.time.LocalDateTime; import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_ONLY; @@ -33,7 +36,7 @@ public class Param { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @ApiModelProperty(value = "参数ID", example = "87584674384", accessMode = READ_ONLY, position = 0) + @ApiModelProperty(value = "参数主键索引ID", example = "87584674384", accessMode = READ_ONLY, position = 0) private Long id; /** @@ -46,18 +49,22 @@ public class Param { * 参数字段标识符 */ @ApiModelProperty(value = "参数标识符字段", example = "port", accessMode = READ_WRITE, position = 2) + @Length(max = 100) private String field; /** * 参数值 */ @ApiModelProperty(value = "参数值", example = "8080", accessMode = READ_WRITE, position = 3) + @Length(max = 255) private String value; /** * 参数类型 0:数字 1:字符串 2:加密串 */ @ApiModelProperty(value = "参数类型 0:数字 1:字符串 2:加密串", example = "0", accessMode = READ_WRITE, position = 4) + @Min(0) + @Max(2) private byte type; /** diff --git a/manager/src/main/java/com/usthe/manager/service/AppService.java b/manager/src/main/java/com/usthe/manager/service/AppService.java index 2f4eecf..89894a5 100644 --- a/manager/src/main/java/com/usthe/manager/service/AppService.java +++ b/manager/src/main/java/com/usthe/manager/service/AppService.java @@ -1,5 +1,6 @@ package com.usthe.manager.service; +import com.usthe.common.entity.job.Job; import com.usthe.manager.pojo.entity.ParamDefine; import java.util.List; @@ -17,4 +18,12 @@ public interface AppService { * @return 参数结构列表 */ List getAppParamDefines(String app); + + /** + * 根据监控类型名称获取监控结构定义 + * @param app 监控类型名称 + * @return 监控结构定义 + * @throws IllegalArgumentException 当不存在即不支持对应名称的监控类型时抛出 + */ + Job getAppDefine(String app) throws IllegalArgumentException; } diff --git a/manager/src/main/java/com/usthe/manager/service/impl/AppServiceImpl.java b/manager/src/main/java/com/usthe/manager/service/impl/AppServiceImpl.java index 48e6a76..442b363 100644 --- a/manager/src/main/java/com/usthe/manager/service/impl/AppServiceImpl.java +++ b/manager/src/main/java/com/usthe/manager/service/impl/AppServiceImpl.java @@ -1,10 +1,26 @@ package com.usthe.manager.service.impl; +import com.usthe.common.entity.job.Job; +import com.usthe.manager.dao.ParamDefineDao; import com.usthe.manager.pojo.entity.ParamDefine; import com.usthe.manager.service.AppService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.yaml.snakeyaml.Yaml; +import javax.persistence.criteria.Join; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; /** * 监控类型管理实现 @@ -12,11 +28,54 @@ import java.util.List; * @date 2021/11/14 17:17 */ @Service -public class AppServiceImpl implements AppService { +@Transactional(rollbackFor = Exception.class) +@Slf4j +public class AppServiceImpl implements AppService, CommandLineRunner { + + private final Map appDefines = new ConcurrentHashMap<>(); + + @Autowired + private ParamDefineDao paramDefineDao; @Override public List getAppParamDefines(String app) { + List paramDefines = paramDefineDao.findParamDefinesByApp(app); + if (paramDefines == null) { + paramDefines = Collections.emptyList(); + } + return paramDefines; + } - return null; + @Override + public Job getAppDefine(String app) throws IllegalArgumentException { + Job appDefine = appDefines.get(app); + if (appDefine == null) { + throw new IllegalArgumentException("The app " + app + " not support."); + } + return appDefine; + } + + @Override + public void run(String... args) throws Exception { + // 读取app定义配置加载到内存中 define/app/*.yml + Yaml yaml = new Yaml(); + String defineAppPath = "define" + File.separator + "app"; + URL url = Thread.currentThread().getContextClassLoader().getResource(defineAppPath); + assert url != null; + File directory = new File(url.toURI()); + if (!directory.exists() || directory.listFiles() == null) { + throw new IllegalArgumentException("define app directory not exist"); + } + for (File appFile : Objects.requireNonNull(directory.listFiles())) { + if (appFile.exists()) { + try (FileInputStream fileInputStream = new FileInputStream(appFile)) { + Job app = yaml.loadAs(fileInputStream, Job.class); + appDefines.put(app.getApp(), app); + } catch (IOException e) { + log.error(e.getMessage(), e); + throw new IOException(e); + } + } + } } } diff --git a/manager/src/main/java/com/usthe/manager/service/impl/MonitorServiceImpl.java b/manager/src/main/java/com/usthe/manager/service/impl/MonitorServiceImpl.java index d564a86..d133301 100644 --- a/manager/src/main/java/com/usthe/manager/service/impl/MonitorServiceImpl.java +++ b/manager/src/main/java/com/usthe/manager/service/impl/MonitorServiceImpl.java @@ -1,13 +1,27 @@ package com.usthe.manager.service.impl; +import com.usthe.common.entity.job.Configmap; +import com.usthe.common.entity.job.Job; +import com.usthe.common.util.CommonConstants; +import com.usthe.common.util.SnowFlakeIdGenerator; +import com.usthe.manager.dao.MonitorDao; +import com.usthe.manager.dao.ParamDao; import com.usthe.manager.pojo.dto.MonitorDto; import com.usthe.manager.pojo.entity.Monitor; import com.usthe.manager.pojo.entity.Param; +import com.usthe.manager.service.AppService; import com.usthe.manager.service.MonitorService; +import com.usthe.manager.support.exception.MonitorDatabaseException; import com.usthe.manager.support.exception.MonitorDetectException; +import com.usthe.scheduler.JobScheduling; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; /** * 监控管理服务实现 @@ -15,20 +29,66 @@ import java.util.List; * @date 2021/11/14 13:06 */ @Service +@Slf4j public class MonitorServiceImpl implements MonitorService { + @Autowired + private AppService appService; + + @Autowired + private JobScheduling jobScheduling; + + @Autowired + private MonitorDao monitorDao; + + @Autowired + private ParamDao paramDao; @Override + @Transactional(readOnly = true) public void detectMonitor(Monitor monitor, List params) throws MonitorDetectException { } @Override + @Transactional(rollbackFor = Exception.class) public void addMonitor(Monitor monitor, List params) throws RuntimeException { - + // 申请 monitor id + long monitorId = SnowFlakeIdGenerator.generateId(); + // 构造采集任务Job实体 + Job appDefine = appService.getAppDefine(monitor.getApp()); + appDefine.setMonitorId(monitorId); + appDefine.setInterval(monitor.getIntervals()); + appDefine.setCyclic(true); + appDefine.setTimestamp(System.currentTimeMillis()); + List configmaps = params.stream().map(param -> { + param.setMonitorId(monitorId); + param.setGmtCreate(null); + param.setGmtUpdate(null); + return new Configmap(param.getField(), param.getValue(), param.getType()); + }).collect(Collectors.toList()); + appDefine.setConfigmap(configmaps); + // 下发采集任务得到jobId + long jobId = jobScheduling.addAsyncCollectJob(appDefine); + // 下发成功后刷库 + try { + monitor.setId(monitorId); + monitor.setJobId(jobId); + monitor.setStatus(CommonConstants.AVAILABLE); + monitor.setGmtCreate(null); + monitor.setGmtUpdate(null); + monitorDao.save(monitor); + paramDao.saveAll(params); + } catch (Exception e) { + log.error(e.getMessage(), e); + // 刷库异常取消之前的下发任务 + jobScheduling.cancelAsyncCollectJob(jobId); + throw new MonitorDatabaseException(e.getMessage()); + } } @Override + @Transactional(readOnly = true) public void validate(MonitorDto monitorDto, boolean isModify) throws IllegalArgumentException { } @@ -44,7 +104,17 @@ public class MonitorServiceImpl implements MonitorService { } @Override + @Transactional(readOnly = true) public MonitorDto getMonitor(long id) throws RuntimeException { - return null; + Optional monitorOptional = monitorDao.findById(id); + if (monitorOptional.isPresent()) { + MonitorDto monitorDto = new MonitorDto(); + monitorDto.setMonitor(monitorOptional.get()); + List params = paramDao.findParamsByMonitorId(id); + monitorDto.setParams(params); + return monitorDto; + } else { + return null; + } } } diff --git a/manager/src/main/java/com/usthe/manager/support/GlobalExceptionHandler.java b/manager/src/main/java/com/usthe/manager/support/GlobalExceptionHandler.java index 5d363fa..f0a45ff 100644 --- a/manager/src/main/java/com/usthe/manager/support/GlobalExceptionHandler.java +++ b/manager/src/main/java/com/usthe/manager/support/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ package com.usthe.manager.support; import com.usthe.common.entity.dto.Message; +import com.usthe.manager.support.exception.MonitorDatabaseException; import com.usthe.manager.support.exception.MonitorDetectException; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataAccessException; @@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; import static com.usthe.common.util.CommonConstants.DETECT_FAILED; +import static com.usthe.common.util.CommonConstants.MONITOR_CONFLICT; /** * controller exception handler @@ -36,6 +38,19 @@ public class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(message); } + /** + * 处理数据库操作异常 + * @param exception 探测异常 + * @return response + */ + @ExceptionHandler(MonitorDatabaseException.class) + @ResponseBody + ResponseEntity> handleMonitorDatabaseException(MonitorDatabaseException exception) { + Message message = Message.builder().msg(exception.getMessage()).code(MONITOR_CONFLICT).build(); + return ResponseEntity.status(HttpStatus.CONFLICT).body(message); + } + + /** * handler the exception thrown for data input verify * @param exception data input verify exception diff --git a/manager/src/main/java/com/usthe/manager/support/exception/MonitorDatabaseException.java b/manager/src/main/java/com/usthe/manager/support/exception/MonitorDatabaseException.java new file mode 100644 index 0000000..84ba1eb --- /dev/null +++ b/manager/src/main/java/com/usthe/manager/support/exception/MonitorDatabaseException.java @@ -0,0 +1,12 @@ +package com.usthe.manager.support.exception; + +/** + * 数据库操作异常 + * @author tomsun28 + * @date 2021/11/15 13:25 + */ +public class MonitorDatabaseException extends RuntimeException { + public MonitorDatabaseException(String message) { + super(message); + } +} diff --git a/manager/src/main/resources/db/schema.sql b/manager/src/main/resources/db/schema.sql index 3143151..36fb142 100644 --- a/manager/src/main/resources/db/schema.sql +++ b/manager/src/main/resources/db/schema.sql @@ -18,7 +18,8 @@ CREATE TABLE monitor modifier varchar(100) comment '最新修改者', gmt_create timestamp default current_timestamp comment 'create time', gmt_update datetime default current_timestamp on update current_timestamp comment 'update time', - primary key (id) + primary key (id), + index query_index (app, host, name) ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- @@ -35,7 +36,8 @@ CREATE TABLE param gmt_create timestamp default current_timestamp comment 'create time', gmt_update datetime default current_timestamp on update current_timestamp comment 'update time', primary key (id), - index monitor_id (monitor_id) + index monitor_id (monitor_id), + unique key unique_param (monitor_id, field) ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4; -- ---------------------------- @@ -57,5 +59,5 @@ CREATE TABLE param_define gmt_create timestamp default current_timestamp comment 'create time', gmt_update datetime default current_timestamp on update current_timestamp comment 'update time', primary key (id), - index app_index (app) + unique key unique_param_define (app, field) ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/manager/src/main/resources/define/collect/example.yml b/manager/src/main/resources/define/app/A-example.yml similarity index 89% rename from manager/src/main/resources/define/collect/example.yml rename to manager/src/main/resources/define/app/A-example.yml index 6ec62ba..d7d1a35 100644 --- a/manager/src/main/resources/define/collect/example.yml +++ b/manager/src/main/resources/define/app/A-example.yml @@ -4,15 +4,15 @@ app: cloud # 强制固定必须参数 - host configmap: - key: host - type: string + type: 1 - key: port - type: number + type: 0 - key: username - type: string + type: 1 - key: password - type: secret + type: 2 - key: param1 - type: string + type: 1 # 指标组列表 metrics: # 第一个监控指标组 cpu @@ -25,7 +25,7 @@ metrics: # 指标信息 包括 field名称, type字段类型:number数字,string字符串, unit:指标单位 - field: usage type: number - unit: % + unit: '%' - field: cores type: number - field: waitime @@ -60,11 +60,11 @@ metrics: ssl: false # 请求头内容 headers: - apiVersion: 'v1' + apiVersion: v1 # 请求参数内容 params: - - param1: param1 - - param2: param2 + param1: param1 + param2: param2 # 认证 authorization: # 认证方式: Basic Auth, Digest Auth, Bearer Token @@ -73,7 +73,7 @@ metrics: basicAuthPassword: ^_^password^_^ # 响应数据解析方式: default-系统规则,json_path-jsonPath脚本,xml_path-xmlPath脚本,prometheus-Prometheus数据规则 parseType: jsonPath - parseScript: "$.cpu[:1].*" + parseScript: '$.cpu[:1].*' - name: memory priority: 1 @@ -83,7 +83,7 @@ metrics: unit: kb - field: usage type: number - unit: % + unit: '%' - field: speed type: number protocol: http @@ -93,13 +93,13 @@ metrics: url: /memory method: GET headers: - apiVersion: 'v1' + apiVersion: v1 params: - - param1: param1 - - param2: param2 + param1: param1 + param2: param2 authorization: type: Basic Auth basicAuthUsername: ^_^username^_^ basicAuthPassword: ^_^password^_^ parseType: jsonPath - parseScript: "$.memory[:1].*" \ No newline at end of file + parseScript: '$.memory[:1].*' \ No newline at end of file diff --git a/manager/src/main/resources/define/param/example.yml b/manager/src/main/resources/define/param/A-example.yml similarity index 100% rename from manager/src/main/resources/define/param/example.yml rename to manager/src/main/resources/define/param/A-example.yml