Prechádzať zdrojové kódy

[monitor] 请求监控参数对照监控参数定义校验

tomsun28 4 rokov pred
rodič
commit
f1ed3b89f7

+ 120 - 0
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);
+    }
+}

+ 88 - 0
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;
+        }
+    }
+}

+ 5 - 0
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;
         }

+ 3 - 0
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;
 
     /**

+ 2 - 2
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表示可选项值列表

+ 68 - 1
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<String, Param> paramMap = monitorDto.getParams()
+                .stream()
+                .peek(param -> param.setMonitorId(monitor.getId()))
+                .collect(Collectors.toMap(Param::getField, param -> param));
+        List<ParamDefine> 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

+ 1 - 1
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: 用户名