diff --git a/common/src/main/java/com/usthe/common/util/AesUtil.java b/common/src/main/java/com/usthe/common/util/AesUtil.java new file mode 100644 index 0000000..f9b70ab --- /dev/null +++ b/common/src/main/java/com/usthe/common/util/AesUtil.java @@ -0,0 +1,120 @@ +package com.usthe.common.util; + +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.util.codec.binary.Base64; + +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; + +/** + * AES 对称加密解密工具 + * @author tomsun28 + * @date 20:04 2018/2/11 + */ +@Slf4j +public class AesUtil { + + /** + * 默认加密秘钥 AES加密秘钥为约定16位,小于16位会报错 + */ + private static final String ENCODE_RULES = "tomSun28HaHaHaHaHa"; + + /** + * 默认算法 + */ + private static final String ALGORITHM_STR = "AES/CBC/PKCS5Padding"; + + private AesUtil() {} + + public static String aesEncode(String content) { + return aesEncode(content, ENCODE_RULES); + } + + public static String aesDecode(String content) { + return aesDecode(content, ENCODE_RULES); + } + + /** + * 加密明文 aes cbc模式 + * + * @param content 明文 + * @param encryptKey 密钥 + * @return 密文 + */ + public static String aesEncode(String content, String encryptKey) { + try { + SecretKeySpec keySpec = new SecretKeySpec(encryptKey.getBytes(StandardCharsets.UTF_8), "AES"); + + //根据指定算法AES自成密码器 + Cipher cipher = Cipher.getInstance(ALGORITHM_STR); + //初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY + cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(encryptKey.getBytes(StandardCharsets.UTF_8))); + //获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码 + byte[] byteEncode = content.getBytes(StandardCharsets.UTF_8); + //根据密码器的初始化方式--加密:将数据加密 + byte[] byteAes = cipher.doFinal(byteEncode); + //将加密后的byte[]数据转换为Base64字符串 + return new String(Base64.encodeBase64(byteAes),StandardCharsets.UTF_8); + //将字符串返回 + } catch (Exception e) { + log.error("密文加密失败"+e.getMessage(),e); + throw new RuntimeException("密文加密失败"); + } + //如果有错就返加null + + + } + + /** + * 解密密文 + * + * @param content 密文 + * @param decryptKey 密钥 + * @return 明文 + */ + public static String aesDecode(String content, String decryptKey) { + try { + SecretKeySpec keySpec = new SecretKeySpec(decryptKey.getBytes(StandardCharsets.UTF_8), "AES"); + + //根据指定算法AES自成密码器 + Cipher cipher = Cipher.getInstance(ALGORITHM_STR); + //初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY + cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(decryptKey.getBytes(StandardCharsets.UTF_8))); + //8.将加密并编码base64后的字符串内容base64解码成字节数组 + byte[] bytesContent = Base64.decodeBase64(content); + /* + * 解密 + */ + byte[] byteDecode = cipher.doFinal(bytesContent); + return new String(byteDecode, StandardCharsets.UTF_8); + } catch (NoSuchAlgorithmException e) { + log.error("没有指定的加密算法::"+e.getMessage(),e); + } catch (IllegalBlockSizeException e) { + log.error("非法的块大小"+"::"+e.getMessage(),e); + throw new RuntimeException("密文解密失败"); + } catch (NullPointerException e) { + log.error("秘钥解析空指针异常"+"::"+e.getMessage(),e); + throw new RuntimeException("秘钥解析空指针异常"); + } catch (Exception e) { + log.error("秘钥AES解析出现未知错误"+"::"+e.getMessage(),e); + throw new RuntimeException("密文解密失败"); + } + //如果有错就返null + return null; + + } + + /** + * 判断是否已经被加密 + * @param text text + * @return true-是 false-否 + */ + public static boolean isCiphertext(String text) { + // 根据是否被base64来判断是否已经被加密 + return Base64.isBase64(text); + } +} diff --git a/common/src/main/java/com/usthe/common/util/IntervalExpressionUtil.java b/common/src/main/java/com/usthe/common/util/IntervalExpressionUtil.java new file mode 100644 index 0000000..b4eb789 --- /dev/null +++ b/common/src/main/java/com/usthe/common/util/IntervalExpressionUtil.java @@ -0,0 +1,88 @@ +package com.usthe.common.util; + +import lombok.extern.slf4j.Slf4j; + +/** + * 数值区间表达式计算 + * [a,b] = {a <= x <= b} + * [a,b) = {a <= x < b} + * [a,+∞) = {a <= x} + * (-∞,b] = {x <= b} + * (-∞,a]||[b,+∞) = {x <= a || x >= b} + * @author tomsun28 + * @date 2021/11/18 10:22 + */ +@Slf4j +public class IntervalExpressionUtil { + + private static final String SPLIT_OR = "\\|\\|"; + private static final String SPLIT_AND = ","; + private static final String SPLIT_EQU_LEFT = "("; + private static final String SPLIT_EQU_RIGHT = ")"; + private static final String SPLIT_EQ_LEFT = "["; + private static final String SPLIT_EQ_RIGHT = "]"; + private static final String NEGATIVE = "-∞"; + private static final String POSITIVE = "+∞"; + + /** + * 校验数值是否在区间范围 + * @param numberValue 数值 + * @param expression 区间表达式 + * @return true-是 false-否 + */ + public static boolean validNumberIntervalExpress(Double numberValue, String expression) { + if (expression == null || "".equals(expression)) { + return true; + } + if (numberValue == null) { + return false; + } + try { + String[] expressions = expression.split(SPLIT_OR); + for (String expr : expressions) { + String[] values = expr.substring(1, expr.length() - 1).split(SPLIT_AND); + if (values.length != 2) { + continue; + } + Double[] doubleValues = new Double[2]; + if (NEGATIVE.equals(values[0])) { + doubleValues[0] = Double.MIN_VALUE; + } else { + doubleValues[0] = Double.parseDouble(values[0]); + } + if (POSITIVE.equals(values[1])) { + doubleValues[1] = Double.MAX_VALUE; + } else { + doubleValues[1] = Double.parseDouble(values[1]); + } + String startBracket = expr.substring(0, 1); + String endBracket = expr.substring(expr.length() - 1); + if (SPLIT_EQU_LEFT.equals(startBracket)) { + if (SPLIT_EQU_RIGHT.equals(endBracket)) { + if (numberValue > doubleValues[0] && numberValue < doubleValues[1]) { + return true; + } + } else if (SPLIT_EQ_RIGHT.equals(endBracket)) { + if (numberValue > doubleValues[0] && numberValue <= doubleValues[1]) { + return true; + } + } + } else if (SPLIT_EQ_LEFT.equals(startBracket)) { + if (SPLIT_EQU_RIGHT.equals(endBracket)) { + if (numberValue >= doubleValues[0] && numberValue < doubleValues[1]) { + return true; + } + } else if (SPLIT_EQ_RIGHT.equals(endBracket)) { + if (numberValue >= doubleValues[0] && numberValue <= doubleValues[1]) { + return true; + } + } + } + } + return false; + } catch (Exception e) { + log.debug(e.getMessage(), e); + return false; + } + } +} diff --git a/common/src/main/java/com/usthe/common/util/IpDomainUtil.java b/common/src/main/java/com/usthe/common/util/IpDomainUtil.java index a4c5a58..159cab0 100644 --- a/common/src/main/java/com/usthe/common/util/IpDomainUtil.java +++ b/common/src/main/java/com/usthe/common/util/IpDomainUtil.java @@ -17,12 +17,17 @@ public class IpDomainUtil { private static final Pattern DOMAIN_PATTERN = Pattern.compile("^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)?(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\\/])+$"); + private static final String LOCALHOST = "localhost"; + /** * 校验判断是否是 ip或者domain * @param ipDomain ip domain string * @return true-yes false-no */ public static boolean validateIpDomain(String ipDomain) { + if (LOCALHOST.equalsIgnoreCase(ipDomain)) { + return true; + } if (IPAddressUtil.isIPv4LiteralAddress(ipDomain)) { return true; } 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 4d901fa..675884e 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 @@ -15,6 +15,7 @@ import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.Max; import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; import java.time.LocalDateTime; import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_ONLY; @@ -50,6 +51,7 @@ public class Param { */ @ApiModelProperty(value = "参数标识符字段", example = "port", accessMode = READ_WRITE, position = 2) @Length(max = 100) + @NotNull private String field; /** @@ -57,6 +59,7 @@ public class Param { */ @ApiModelProperty(value = "参数值", example = "8080", accessMode = READ_WRITE, position = 3) @Length(max = 255) + @NotNull private String value; /** diff --git a/manager/src/main/java/com/usthe/manager/pojo/entity/ParamDefine.java b/manager/src/main/java/com/usthe/manager/pojo/entity/ParamDefine.java index 4382450..e8aa94f 100644 --- a/manager/src/main/java/com/usthe/manager/pojo/entity/ParamDefine.java +++ b/manager/src/main/java/com/usthe/manager/pojo/entity/ParamDefine.java @@ -71,7 +71,7 @@ public class ParamDefine { /** * 当type为number时,用range表示范围 eg: 0-233 */ - @ApiModelProperty(value = "当type为number时,用range表示范围", example = "0-233", accessMode = READ_WRITE, position = 6) + @ApiModelProperty(value = "当type为number时,用range区间表示范围", example = "[0,233]", accessMode = READ_WRITE, position = 6) @Column(name = "param_range") private String range; @@ -80,7 +80,7 @@ public class ParamDefine { */ @ApiModelProperty(value = "当type为text时,用limit表示字符串限制大小.最大255", example = "30", accessMode = READ_WRITE, position = 7) @Column(name = "param_limit") - private short limit; + private Short limit; /** * 当type为radio单选框,checkbox复选框时,option表示可选项值列表 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 06600e2..93d1ec8 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 @@ -3,13 +3,17 @@ package com.usthe.manager.service.impl; import com.usthe.common.entity.job.Configmap; import com.usthe.common.entity.job.Job; import com.usthe.common.entity.message.CollectRep; +import com.usthe.common.util.AesUtil; import com.usthe.common.util.CommonConstants; +import com.usthe.common.util.IntervalExpressionUtil; +import com.usthe.common.util.IpDomainUtil; 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.pojo.entity.ParamDefine; import com.usthe.manager.service.AppService; import com.usthe.manager.service.MonitorService; import com.usthe.manager.support.exception.MonitorDatabaseException; @@ -21,6 +25,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -111,7 +116,69 @@ public class MonitorServiceImpl implements MonitorService { @Override @Transactional(readOnly = true) public void validate(MonitorDto monitorDto, boolean isModify) throws IllegalArgumentException { - + // 请求监控参数与监控参数定义映射校验匹配 + Monitor monitor = monitorDto.getMonitor(); + Map paramMap = monitorDto.getParams() + .stream() + .peek(param -> param.setMonitorId(monitor.getId())) + .collect(Collectors.toMap(Param::getField, param -> param)); + List paramDefines = appService.getAppParamDefines(monitorDto.getMonitor().getApp()); + if (paramDefines != null) { + for (ParamDefine paramDefine : paramDefines) { + String field = paramDefine.getField(); + Param param = paramMap.get(field); + if (paramDefine.isRequired() && param == null) { + throw new IllegalArgumentException("Params field " + field + " is required."); + } + if (param != null) { + switch (paramDefine.getType()) { + case "number": + double doubleValue; + try { + doubleValue = Double.parseDouble(param.getValue()); + } catch (Exception e) { + throw new IllegalArgumentException("Params field " + field + " type " + + paramDefine.getType() + " is invalid."); + } + if (paramDefine.getRange() != null) { + if (!IntervalExpressionUtil.validNumberIntervalExpress(doubleValue, + paramDefine.getRange())) { + throw new IllegalArgumentException("Params field " + field + " type " + + paramDefine.getType() + " over range " + paramDefine.getRange()); + } + } + break; + case "text": + Short limit = paramDefine.getLimit(); + if (limit != null) { + if (param.getValue() != null && param.getValue().length() > limit) { + throw new IllegalArgumentException("Params field " + field + " type " + + paramDefine.getType() + " over limit " + limit); + } + } + break; + case "host": + String hostValue = param.getValue(); + if (!IpDomainUtil.validateIpDomain(hostValue)) { + throw new IllegalArgumentException("Params field " + field + " value " + + hostValue + " is invalid host value."); + } + break; + case "password": + // 明文密码需加密传输存储 + String value = param.getValue(); + if (!AesUtil.isCiphertext(value)) { + value = AesUtil.aesEncode(value); + param.setValue(value); + } + break; + // todo 更多参数定义与实际值格式校验 + default: + throw new IllegalArgumentException("ParamDefine type " + paramDefine.getType() + " is invalid."); + } + } + } + } } @Override diff --git a/manager/src/main/resources/define/param/A-example.yml b/manager/src/main/resources/define/param/A-example.yml index 0624f23..8d909e3 100644 --- a/manager/src/main/resources/define/param/A-example.yml +++ b/manager/src/main/resources/define/param/A-example.yml @@ -14,7 +14,7 @@ param: name: 端口 type: number # 当type为number时,用range表示范围 - range: 0-255 + range: '[0,255]' required: true - field: username name: 用户名