增加主机信息功能
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
package com.jeesite.modules.apps.Module;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主机系统监控结果
|
||||||
|
*
|
||||||
|
* @author gaoxq
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class MonitorResult implements Serializable {
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 主机名 */
|
||||||
|
private String hostname;
|
||||||
|
|
||||||
|
/** 操作系统 */
|
||||||
|
private String os;
|
||||||
|
|
||||||
|
/** 在线时长描述 */
|
||||||
|
private String uptime;
|
||||||
|
|
||||||
|
/** CPU 占用率明细(key: us/sy/ni/id/wa/st) */
|
||||||
|
private Map<String, String> cpu;
|
||||||
|
|
||||||
|
/** 内存明细(total/used/free/available/usagePercent) */
|
||||||
|
private Map<String, String> memory;
|
||||||
|
|
||||||
|
/** 各挂载点磁盘信息 */
|
||||||
|
private List<Map<String, String>> disk;
|
||||||
|
|
||||||
|
// ---------------------- 构造工厂 ----------------------
|
||||||
|
|
||||||
|
public static MonitorResult ok(String message) {
|
||||||
|
MonitorResult r = new MonitorResult();
|
||||||
|
r.success = true;
|
||||||
|
r.message = message;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MonitorResult ok(String message, String hostname, String os, String uptime,
|
||||||
|
Map<String, String> cpu, Map<String, String> memory,
|
||||||
|
List<Map<String, String>> disk) {
|
||||||
|
MonitorResult r = ok(message);
|
||||||
|
r.hostname = hostname;
|
||||||
|
r.os = os;
|
||||||
|
r.uptime = uptime;
|
||||||
|
r.cpu = cpu;
|
||||||
|
r.memory = memory;
|
||||||
|
r.disk = disk;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MonitorResult fail(String message) {
|
||||||
|
MonitorResult r = new MonitorResult();
|
||||||
|
r.success = false;
|
||||||
|
r.message = message;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package com.jeesite.modules.utils;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AES加密解密工具类(CBC/PKCS5Padding)
|
|
||||||
* 优化说明:修复密钥BUG+解决换行问题+性能优化+安全加固+零依赖
|
|
||||||
*/
|
|
||||||
public class AesUtil {
|
|
||||||
|
|
||||||
|
|
||||||
private static final LoggerUtils logger = LoggerUtils.getInstance();
|
|
||||||
private static final String AES_KEY_HEX = "AD42F6697B035B7580E4FEF93BE20BAD"; // 你的32位16进制密钥
|
|
||||||
private static final String CHARSET = StandardCharsets.UTF_8.name();
|
|
||||||
private static final int IV_LENGTH = 16; // AES CBC IV固定16字节
|
|
||||||
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
|
||||||
private static final String ALGORITHM = "AES";
|
|
||||||
|
|
||||||
private static final ThreadLocal<Cipher> ENCRYPT_CIPHER = ThreadLocal.withInitial(() -> {
|
|
||||||
try {
|
|
||||||
return Cipher.getInstance(TRANSFORMATION);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
private static final ThreadLocal<Cipher> DECRYPT_CIPHER = ThreadLocal.withInitial(() -> {
|
|
||||||
try {
|
|
||||||
return Cipher.getInstance(TRANSFORMATION);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加密
|
|
||||||
*/
|
|
||||||
public static String encrypt(String content) {
|
|
||||||
return encrypt(content, hex2Bytes(AES_KEY_HEX));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解密
|
|
||||||
*/
|
|
||||||
public static String decrypt(String content) {
|
|
||||||
return decrypt(content, hex2Bytes(AES_KEY_HEX));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String encrypt(String content, byte[] key) {
|
|
||||||
if (content == null || content.isEmpty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(key, IV_LENGTH));
|
|
||||||
Cipher cipher = ENCRYPT_CIPHER.get();
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
|
|
||||||
byte[] encryptBytes = cipher.doFinal(content.getBytes(CHARSET));
|
|
||||||
return Base64.getEncoder().encodeToString(encryptBytes);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String decrypt(String content, byte[] key) {
|
|
||||||
if (content == null || content.isEmpty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(key, IV_LENGTH));
|
|
||||||
Cipher cipher = DECRYPT_CIPHER.get();
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
|
|
||||||
byte[] decryptBytes = cipher.doFinal(Base64.getDecoder().decode(content));
|
|
||||||
String result = new String(decryptBytes, CHARSET);
|
|
||||||
return result.trim();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 32位16进制字符串转16字节数组(解决你的密钥长度BUG的核心方法)
|
|
||||||
*/
|
|
||||||
private static byte[] hex2Bytes(String hexStr) {
|
|
||||||
if (hexStr == null || hexStr.length() % 2 != 0) {
|
|
||||||
throw new IllegalArgumentException("密钥必须是偶数长度的16进制字符串");
|
|
||||||
}
|
|
||||||
byte[] bytes = new byte[hexStr.length() / 2];
|
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
|
||||||
bytes[i] = (byte) Integer.parseInt(hexStr.substring(i * 2, i * 2 + 2), 16);
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成随机16字节IV向量(方案2:安全随机IV,新业务推荐使用)
|
|
||||||
*/
|
|
||||||
private static byte[] generateRandomIV() {
|
|
||||||
byte[] iv = new byte[IV_LENGTH];
|
|
||||||
ThreadLocalRandom.current().nextBytes(iv);
|
|
||||||
return iv;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +1,41 @@
|
|||||||
package com.jeesite.modules.utils;
|
package com.jeesite.modules.utils;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.Random;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
public class KeyUtil {
|
public class KeyUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机 key
|
||||||
|
*
|
||||||
|
* @param length key 长度
|
||||||
|
* @param type 1=纯数字, 2=大写字母+数字, 3=小写字母+数字, 4=混合大小写+数字
|
||||||
|
* @return 随机字符串
|
||||||
|
*/
|
||||||
public static String ObjKey(int length, int type) {
|
public static String ObjKey(int length, int type) {
|
||||||
Random random = new Random();
|
String str;
|
||||||
StringBuffer key = new StringBuffer();
|
switch (type) {
|
||||||
if (type == 1) {
|
case 1: str = "0123456789"; break;
|
||||||
String str = "0123456789";
|
case 2: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; break;
|
||||||
for (int i = 0; i < length; ++i) {
|
case 3: str = "abcdefghijklmnopqrstuvwxyz0123456789"; break;
|
||||||
//从62个的数字或字母中选择
|
default: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; break;
|
||||||
int number = random.nextInt(10);
|
}
|
||||||
//将产生的数字通过length次承载到key中
|
ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||||
key.append(str.charAt(number));
|
StringBuilder key = new StringBuilder(length);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
key.append(str.charAt(rnd.nextInt(str.length())));
|
||||||
}
|
}
|
||||||
return key.toString();
|
return key.toString();
|
||||||
} else if (type == 2) {
|
|
||||||
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
||||||
for (int i = 0; i < length; ++i) {
|
|
||||||
//从62个的数字或字母中选择
|
|
||||||
int number = random.nextInt(36);
|
|
||||||
//将产生的数字通过length次承载到key中
|
|
||||||
key.append(str.charAt(number));
|
|
||||||
}
|
|
||||||
return key.toString();
|
|
||||||
} else if (type == 3) {
|
|
||||||
String str = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
for (int i = 0; i < length; ++i) {
|
|
||||||
//从62个的数字或字母中选择
|
|
||||||
int number = random.nextInt(36);
|
|
||||||
//将产生的数字通过length次承载到key中
|
|
||||||
key.append(str.charAt(number));
|
|
||||||
}
|
|
||||||
return key.toString();
|
|
||||||
} else {
|
|
||||||
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
for (int i = 0; i < length; ++i) {
|
|
||||||
//从62个的数字或字母中选择
|
|
||||||
int number = random.nextInt(62);
|
|
||||||
//将产生的数字通过length次承载到key中
|
|
||||||
key.append(str.charAt(number));
|
|
||||||
}
|
|
||||||
return key.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机柔和颜色(RGB 100-239)
|
||||||
|
*/
|
||||||
public static String getColor() {
|
public static String getColor() {
|
||||||
int r = 100 + (int) (Math.random() * 140);
|
ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||||
int g = 100 + (int) (Math.random() * 140);
|
int r = 100 + rnd.nextInt(140);
|
||||||
int b = 100 + (int) (Math.random() * 140);
|
int g = 100 + rnd.nextInt(140);
|
||||||
|
int b = 100 + rnd.nextInt(140);
|
||||||
Color color = new Color(r, g, b);
|
Color color = new Color(r, g, b);
|
||||||
return "#" + Integer.toHexString(color.getRGB()).substring(2).toUpperCase();
|
return "#" + Integer.toHexString(color.getRGB()).substring(2).toUpperCase();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.jeesite.modules.utils;
|
package com.jeesite.modules.utils;
|
||||||
|
|
||||||
import com.jcraft.jsch.*;
|
import com.jcraft.jsch.*;
|
||||||
import com.jeesite.modules.apps.Module.SftpResult;
|
import com.jeesite.modules.apps.Module.MonitorResult;
|
||||||
import com.jeesite.modules.biz.entity.MySftpAccounts;
|
import com.jeesite.modules.biz.entity.MySftpAccounts;
|
||||||
import io.micrometer.common.util.StringUtils;
|
import io.micrometer.common.util.StringUtils;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import java.util.*;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 远程主机系统监控工具类
|
* 远程主机系统监控工具类
|
||||||
* 通过 SSH exec 采集 CPU、内存、磁盘、负载、在线时长等指标
|
* 通过 SSH exec 采集 CPU、内存、磁盘、负载、在线时长等指标(仅支持 Linux)
|
||||||
*
|
*
|
||||||
* @author gaoxq
|
* @author gaoxq
|
||||||
*/
|
*/
|
||||||
@@ -27,7 +27,7 @@ public class MonitorUtil {
|
|||||||
* @param account SSH 账号(复用 MySftpAccounts 的连接信息)
|
* @param account SSH 账号(复用 MySftpAccounts 的连接信息)
|
||||||
* @return MonitorResult
|
* @return MonitorResult
|
||||||
*/
|
*/
|
||||||
public static SftpResult monitor(MySftpAccounts account) {
|
public static MonitorResult monitor(MySftpAccounts account) {
|
||||||
Session session = null;
|
Session session = null;
|
||||||
ChannelExec exec = null;
|
ChannelExec exec = null;
|
||||||
try {
|
try {
|
||||||
@@ -41,12 +41,12 @@ public class MonitorUtil {
|
|||||||
String output = readStream(in);
|
String output = readStream(in);
|
||||||
String err = readStream(errIn);
|
String err = readStream(errIn);
|
||||||
if (!err.isEmpty() && output.isEmpty()) {
|
if (!err.isEmpty() && output.isEmpty()) {
|
||||||
return SftpResult.fail("采集失败: " + err);
|
return MonitorResult.fail("采集失败: " + err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SftpResult.ok("采集成功", parse(output));
|
return parseResult(output);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return SftpResult.fail("采集失败: " + e.getMessage());
|
return MonitorResult.fail("采集失败: " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
if (exec != null && exec.isConnected()) exec.disconnect();
|
if (exec != null && exec.isConnected()) exec.disconnect();
|
||||||
if (session != null && session.isConnected()) session.disconnect();
|
if (session != null && session.isConnected()) session.disconnect();
|
||||||
@@ -64,17 +64,17 @@ public class MonitorUtil {
|
|||||||
"echo '==MARK_DISK==' && df -h";
|
"echo '==MARK_DISK==' && df -h";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, Object> parse(String output) {
|
// ------------------------------------------------------------------ 解析
|
||||||
Map<String, Object> info = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
info.put("hostname", extract(output, "MARK_HOSTNAME", true));
|
private static MonitorResult parseResult(String output) {
|
||||||
info.put("os", extract(output, "MARK_OS", true));
|
String hostname = extract(output, "MARK_HOSTNAME", true);
|
||||||
info.put("uptime", extract(output, "MARK_UPTIME", true));
|
String os = extract(output, "MARK_OS", true);
|
||||||
info.put("cpu", parseCpu(extract(output, "MARK_CPU", false)));
|
String uptime = extract(output, "MARK_UPTIME", true);
|
||||||
info.put("memory", parseMemory(extract(output, "MARK_MEM", false)));
|
Map<String, String> cpu = parseCpu(extract(output, "MARK_CPU", false));
|
||||||
info.put("disk", parseDisk(extract(output, "MARK_DISK", false)));
|
Map<String, String> memory = parseMemory(extract(output, "MARK_MEM", false));
|
||||||
|
List<Map<String, String>> disk = parseDisk(extract(output, "MARK_DISK", false));
|
||||||
|
|
||||||
return info;
|
return MonitorResult.ok("采集成功", hostname, os, uptime, cpu, memory, disk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- CPU ----
|
// ---- CPU ----
|
||||||
@@ -87,7 +87,7 @@ public class MonitorUtil {
|
|||||||
Map<String, String> cpu = new LinkedHashMap<>();
|
Map<String, String> cpu = new LinkedHashMap<>();
|
||||||
for (String line : block.split("\n")) {
|
for (String line : block.split("\n")) {
|
||||||
if (line.startsWith("Cpu") || line.startsWith("%Cpu")) {
|
if (line.startsWith("Cpu") || line.startsWith("%Cpu")) {
|
||||||
String data = line.replaceFirst(".*?:\\s*", "").replaceFirst("^%Cpu\\(s?\\):\\s*", "");
|
String data = line.replaceFirst("^%?Cpu\\(s?\\):\\s*", "");
|
||||||
for (String part : data.split(",")) {
|
for (String part : data.split(",")) {
|
||||||
part = part.trim();
|
part = part.trim();
|
||||||
String[] kv = part.split("\\s+", 2);
|
String[] kv = part.split("\\s+", 2);
|
||||||
@@ -101,6 +101,8 @@ public class MonitorUtil {
|
|||||||
return cpu;
|
return cpu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 内存 ----
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 free -m 输出
|
* 解析 free -m 输出
|
||||||
* Mem: total used free shared buff/cache available
|
* Mem: total used free shared buff/cache available
|
||||||
@@ -157,7 +159,7 @@ public class MonitorUtil {
|
|||||||
return disks;
|
return disks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 段落提取 ----
|
// ---- 工具方法 ----
|
||||||
|
|
||||||
private static String extract(String output, String mark, boolean singleLine) {
|
private static String extract(String output, String mark, boolean singleLine) {
|
||||||
String start = "==" + mark + "==";
|
String start = "==" + mark + "==";
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
public class PageUtil<T> implements Serializable {
|
public class PageUtil<T> implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
private List<T> data;
|
private List<T> data;
|
||||||
private Integer curPage;// 当前页
|
private Integer curPage;// 当前页
|
||||||
private int totalCount;// 总条数
|
private int totalCount;// 总条数
|
||||||
@@ -18,10 +17,10 @@ public class PageUtil<T> implements Serializable {
|
|||||||
|
|
||||||
// 构造数据
|
// 构造数据
|
||||||
public PageUtil(int curPage, int pageSize, List<T> data) {
|
public PageUtil(int curPage, int pageSize, List<T> data) {
|
||||||
this.data = data;
|
this.data = data == null ? new ArrayList<>() : data;
|
||||||
this.curPage = curPage;
|
this.curPage = curPage;
|
||||||
this.pageSize = pageSize;
|
this.pageSize = pageSize;
|
||||||
this.totalCount = data.size();
|
this.totalCount = this.data.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int beginIndex(int pageSize, int curPage) {
|
public int beginIndex(int pageSize, int curPage) {
|
||||||
@@ -32,12 +31,20 @@ public class PageUtil<T> implements Serializable {
|
|||||||
return (pageSize * curPage) - 1;
|
return (pageSize * curPage) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回当前页数据(含边界保护)
|
||||||
|
*/
|
||||||
public List<T> OkData() {
|
public List<T> OkData() {
|
||||||
List<T> list = new ArrayList<T>();
|
// 页码保护:小于 1 → 第 1 页,超出范围 → 空列表
|
||||||
for (int i = beginIndex(pageSize, curPage); i <= endIndex(pageSize, curPage); i++) {
|
int safePage = (curPage == null || curPage < 1) ? 1 : curPage;
|
||||||
if (i < totalCount) {
|
int begin = beginIndex(pageSize, safePage);
|
||||||
list.add(data.get(i));
|
if (begin >= totalCount || pageSize <= 0) {
|
||||||
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
List<T> list = new ArrayList<>();
|
||||||
|
int end = Math.min(endIndex(pageSize, safePage), totalCount - 1);
|
||||||
|
for (int i = begin; i <= end; i++) {
|
||||||
|
list.add(data.get(i));
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user