This commit is contained in:
2025-11-30 18:38:38 +08:00
parent edf26b0b18
commit 71cc66ea9b
61 changed files with 5547 additions and 45 deletions

View File

@@ -0,0 +1,140 @@
package com.jeesite.modules.app.Job;
import com.jeesite.modules.app.dao.info.CpuInfo;
import com.jeesite.modules.app.dao.info.DiskInfo;
import com.jeesite.modules.app.dao.info.ServerInfo;
import com.jeesite.modules.app.utils.SystemUtil;
import com.jeesite.modules.app.utils.vo;
import com.jeesite.modules.biz.entity.BizDeviceInfo;
import com.jeesite.modules.biz.entity.BizMonitorAccount;
import com.jeesite.modules.biz.entity.BizMonitorHost;
import com.jeesite.modules.biz.entity.BizServerInfo;
import com.jeesite.modules.biz.service.BizDeviceInfoService;
import com.jeesite.modules.biz.service.BizMonitorAccountService;
import com.jeesite.modules.biz.service.BizMonitorHostService;
import com.jeesite.modules.app.utils.LoggerUtils;
import com.jeesite.modules.app.utils.NetworkUtils;
import com.jeesite.modules.biz.service.BizServerInfoService;
import jakarta.annotation.Resource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@Controller
public class hostJob {
@Resource
private BizMonitorHostService monitorHostService;
@Resource
private BizMonitorAccountService accountService;
@Resource
private BizServerInfoService serverInfoService;
@Resource
private BizDeviceInfoService deviceInfoService;
@Resource(name = "hostMonitorExecutor")
private ThreadPoolTaskExecutor hostMonitorExecutor;
private static final LoggerUtils logger = LoggerUtils.getInstance();
/**
* 主机运行与磁盘监测
*/
@Scheduled(cron = "0 0/15 * * * ?")
public void getHostStatus() {
List<BizMonitorHost> hosts = monitorHostService.findList(new BizMonitorHost());
List<CompletableFuture<Void>> futures = new ArrayList<>(hosts.size());
for (BizMonitorHost monitorHost : hosts) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
boolean isReachable = NetworkUtils.isNetworkReachable(monitorHost.getIpAddress());
monitorHost.setUstatus(isReachable ? "1" : "0");
if (isReachable) {
monitorHost.setLastOnlineTime(new Date());
syncServerInfo(monitorHost);
}
monitorHostService.update(monitorHost);
}, hostMonitorExecutor); // 指定使用配置的线程池
futures.add(future);
}
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.get(60, TimeUnit.SECONDS); // 超时时间可根据业务调整
} catch (Exception e) {
logger.error(e.getMessage());
}
}
private void syncServerInfo(BizMonitorHost host) {
try {
BizMonitorAccount monitorAccount = new BizMonitorAccount();
monitorAccount.setHostId(host.getHostId());
monitorAccount.setSshUsername("ogsapp");
BizMonitorAccount account = accountService.get(monitorAccount);
CpuInfo cpuInfo = SystemUtil.getCpuMemUsage(host.getIpAddress(), account.getSshPort(), account.getSshUsername(), vo.getDecode(account.getSshPassword()));
ServerInfo info = SystemUtil.getServerBasicInfo(host.getIpAddress(), account.getSshPort(), account.getSshUsername(), vo.getDecode(account.getSshPassword()), host.getIpAddress());
List<DiskInfo> diskInfos = SystemUtil.getDiskInfos(host.getIpAddress(), account.getSshPort(), account.getSshUsername(), vo.getDecode(account.getSshPassword()));
BizServerInfo bizServerInfo = new BizServerInfo();
bizServerInfo.setHostId(host.getHostId());
BizServerInfo serverInfo = serverInfoService.get(bizServerInfo);
if (serverInfo.getIsNewRecord()) {
getServerInfo(cpuInfo, info, bizServerInfo);
} else {
getServerInfo(cpuInfo, info, serverInfo);
}
syncDeviceInfo(host, diskInfos);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
private void syncDeviceInfo(BizMonitorHost host, List<DiskInfo> diskInfos) {
try {
for (DiskInfo diskInfo : diskInfos) {
BizDeviceInfo bizDeviceInfo = new BizDeviceInfo();
bizDeviceInfo.setHostId(host.getHostId());
bizDeviceInfo.setDevice(diskInfo.getDevice());
bizDeviceInfo.setMountPoint(diskInfo.getMountPoint());
BizDeviceInfo deviceInfo = deviceInfoService.get(bizDeviceInfo);
if (deviceInfo.getIsNewRecord()) {
bizDeviceInfo.setTotalSize(diskInfo.getTotalSize());
bizDeviceInfo.setUsedSize(diskInfo.getUsedSize());
bizDeviceInfo.setUsageRate(diskInfo.getUsageRate());
bizDeviceInfo.setLastOnlineTime(new Date());
deviceInfoService.save(bizDeviceInfo);
} else {
deviceInfo.setTotalSize(diskInfo.getTotalSize());
deviceInfo.setUsedSize(diskInfo.getUsedSize());
deviceInfo.setUsageRate(diskInfo.getUsageRate());
deviceInfo.setLastOnlineTime(new Date());
deviceInfoService.save(deviceInfo);
}
}
} catch (Exception e) {
logger.error(e.getMessage());
}
}
private void getServerInfo(CpuInfo cpuInfo, ServerInfo info, BizServerInfo serverInfo) {
serverInfo.setUptime(info.getUptime());
serverInfo.setOs(info.getOs());
serverInfo.setKernelVersion(info.getKernelVersion());
serverInfo.setHostname(info.getHostname());
serverInfo.setIpAddress(info.getIpAddress());
serverInfo.setCpuModel(info.getCpuModel());
serverInfo.setCpuUsage(cpuInfo.getCpuUsage());
serverInfo.setMemoryUsage(cpuInfo.getMemoryUsage());
serverInfoService.save(serverInfo);
}
}

View File

@@ -0,0 +1,30 @@
package com.jeesite.modules.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync // 启用异步任务支持
public class MonitorExecutorConfig {
/**
* 配置主机监控专用线程池
*/
@Bean(name = "hostMonitorExecutor")
public ThreadPoolTaskExecutor hostMonitorExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int corePoolSize = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(corePoolSize * 2);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("Host-Monitor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}

View File

@@ -0,0 +1,34 @@
package com.jeesite.modules.app.dao;
import com.jeesite.modules.biz.entity.BizListItem;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
public class TabItem implements Serializable {
private String key;
private String name;
private Integer count;
private String btnHref;
private String btnText;
private List<BizListItem> list = new ArrayList<>();
public TabItem() {
}
public TabItem(String key, String name, Integer count, List<BizListItem> list) {
this.key = key;
this.name = name;
this.count = count;
this.list = list;
}
}

View File

@@ -0,0 +1,22 @@
package com.jeesite.modules.app.dao.info;
import lombok.Data;
import java.io.Serializable;
@Data
public class CpuInfo implements Serializable {
private double cpuUsage; // CPU使用率
private double memoryUsage; // 内存使用率
public CpuInfo() {
}
public CpuInfo(double cpuUsage, double memoryUsage) {
this.cpuUsage = cpuUsage;
this.memoryUsage = memoryUsage;
}
}

View File

@@ -0,0 +1,26 @@
package com.jeesite.modules.app.dao.info;
import lombok.Data;
import java.io.Serializable;
@Data
public class DiskInfo implements Serializable {
private String device; // 设备
private String mountPoint; // 挂载点
private String totalSize; // 总容量
private String usedSize; // 已使用
private double usageRate; // 使用率
public DiskInfo() {
}
public DiskInfo(String device, String mountPoint, String totalSize, String usedSize, double usageRate) {
this.device = device;
this.mountPoint = mountPoint;
this.totalSize = totalSize;
this.usedSize = usedSize;
this.usageRate = usageRate;
}
}

View File

@@ -0,0 +1,30 @@
package com.jeesite.modules.app.dao.info;
import lombok.Data;
import java.io.Serializable;
@Data
public class ServerInfo implements Serializable {
private String uptime; // 主机运行时间
private String os; // 操作系统
private String kernelVersion; // 内核版本
private String hostname; // 主机名
private String ipAddress; // IP地址
private String cpuModel; // CPU型号
private String memoryTotal; // 内存总量
public ServerInfo() {
}
public ServerInfo(String uptime, String os, String kernelVersion, String hostname, String ipAddress, String cpuModel, String memoryTotal) {
this.uptime = uptime;
this.os = os;
this.kernelVersion = kernelVersion;
this.hostname = hostname;
this.ipAddress = ipAddress;
this.cpuModel = cpuModel;
this.memoryTotal = memoryTotal;
}
}

View File

@@ -0,0 +1,24 @@
package com.jeesite.modules.app.dict;
public enum NotifyType {
NOTIFICATION("1", "通知"),
MESSAGE("2", "消息"),
TODO("3", "待办");
private final String code;
private final String name;
NotifyType(String code, String name) {
this.code = code;
this.name = name;
}
// 新增getter方法否则lambda中无法访问
public String getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,89 @@
package com.jeesite.modules.app.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BigDecimalUtils {
/**
* 加法运算
*
* @param num1 第一个数
* @param num2 第二个数
* @return 两数之和
*/
public static BigDecimal add(BigDecimal num1, BigDecimal num2) {
// 避免空指针,默认空值为 0
if (num1 == null) {
num1 = BigDecimal.ZERO;
}
if (num2 == null) {
num2 = BigDecimal.ZERO;
}
return num1.add(num2);
}
/**
* 减法运算
*
* @param num1 被减数
* @param num2 减数
* @return 两数之差num1 - num2
*/
public static BigDecimal subtract(BigDecimal num1, BigDecimal num2) {
if (num1 == null) {
num1 = BigDecimal.ZERO;
}
if (num2 == null) {
num2 = BigDecimal.ZERO;
}
return num1.subtract(num2);
}
/**
* 乘法运算
*
* @param num1 第一个数
* @param num2 第二个数
* @return 两数之积
*/
public static BigDecimal multiply(BigDecimal num1, BigDecimal num2) {
if (num1 == null || num2 == null) {
return BigDecimal.ZERO;
}
return num1.multiply(num2);
}
/**
* 除法运算处理除不尽的情况默认保留2位小数四舍五入
*
* @param num1 被除数
* @param num2 除数
* @return 两数之商num1 / num2
*/
public static BigDecimal divide(BigDecimal num1, BigDecimal num2) {
return divide(num1, num2, 2, RoundingMode.HALF_UP);
}
/**
* 除法运算(自定义精度和舍入模式)
*
* @param num1 被除数
* @param num2 除数
* @param scale 保留小数位数
* @param roundingMode 舍入模式(如四舍五入、向上取整等)
* @return 两数之商num1 / num2
*/
public static BigDecimal divide(BigDecimal num1, BigDecimal num2, int scale, RoundingMode roundingMode) {
if (num1 == null) {
return BigDecimal.ZERO;
}
if (num2 == null || num2.compareTo(BigDecimal.ZERO) == 0) {
throw new ArithmeticException("除数不能为 null 或 0");
}
// 注意divide 方法必须指定精度和舍入模式,否则除不尽时会抛异常
return num1.divide(num2, scale, roundingMode);
}
}

View File

@@ -0,0 +1,78 @@
package com.jeesite.modules.app.utils;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class DateUtils {
// 日期格式化器(线程安全,复用提升性能)
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; // yyyy-MM-dd
private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); // yyyy-MM
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
public static String calculateStartCycleCode(String cycleType) {
// 默认使用当前日期计算,留参便于测试时指定日期
return calculateStartCycleCode(cycleType, LocalDate.now());
}
/**
* 重载方法支持指定基准日期计算起始cycleCode便于测试
*
* @param cycleType 周期类型D:日, M:月, Q:季度, Y:年)
* @param baseDate 基准日期(以此日期为起点计算时间范围)
* @return 起始cycleCode符合对应格式未识别类型返回null
*/
public static String calculateStartCycleCode(String cycleType, LocalDate baseDate) {
if (baseDate == null) {
throw new IllegalArgumentException("基准日期不能为null");
}
if (cycleType == null) {
return null;
}
return switch (cycleType) {
case "D" ->
// 日最近30天格式 yyyy-MM-dd
baseDate.minusDays(30).format(DAY_FORMATTER);
case "M" ->
// 月最近1年格式 yyyy-MM
baseDate.minusYears(1).format(MONTH_FORMATTER);
case "Q" ->
// 季度最近3年格式 yyyy-Qx
getQuarterCycleCode(baseDate.minusYears(3));
case "Y" ->
// 年最近6年格式 yyyy
String.valueOf(baseDate.minusYears(6).getYear());
default ->
// 未识别的周期类型返回null
null;
};
}
/**
* 计算指定日期对应的季度cycleCode格式yyyy-Qx
*
* @param date 日期
* @return 季度cycleCode如2023-Q3
*/
private static String getQuarterCycleCode(LocalDate date) {
int month = date.getMonthValue(); // 1-12月
int quarter = (month - 1) / 3 + 1;
return String.format("%d-Q%d", date.getYear(), quarter);
}
public static String dsValue() {
LocalDate currentDate = LocalDate.now();
// 格式化日期为yyyymmdd
return currentDate.format(DATE_FORMATTER);
}
public static String dsValueDaysAgo(long days) {
LocalDate targetDate = LocalDate.now().minusDays(days);
return targetDate.format(DATE_FORMATTER);
}
}

View File

@@ -0,0 +1,142 @@
package com.jeesite.modules.app.utils;
import com.jeesite.common.config.Global;
import com.jeesite.common.io.FileUtils;
import java.net.*;
import java.util.Enumeration;
/**
* 获取当前运行程序的IP地址工具类
*/
public class IpUtils {
/**
* 获取本地有效IP地址优先IPv4排除回环/虚拟网卡)
*
* @return 本地IP地址获取失败返回null
*/
public static String getLocalIp() {
// 1. 优先读取宿主机注入的环境变量
String hostIp = System.getenv("HOST_MACHINE_IP");
if (isValidIp(hostIp)) {
return hostIp;
}
// 2. 尝试解析Docker网关宿主机IP
try {
InetAddress gatewayAddr = InetAddress.getByName("gateway");
if (gatewayAddr.isReachable(1000)) {
String gatewayIp = gatewayAddr.getHostAddress();
if (isValidIp(gatewayIp)) {
return gatewayIp;
}
}
} catch (Exception e) {
System.out.print(e.getMessage());
}
// 3. 过滤容器网卡获取宿主机IP
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()
|| ni.getName().startsWith("docker")
|| ni.getName().startsWith("veth")
|| ni.getName().startsWith("cni0")) {
continue;
}
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
String ip = addr.getHostAddress();
// 过滤容器内网段
if (!ip.startsWith("172.")
&& !ip.startsWith("192.168.99.")
&& !ip.startsWith("10.0.")
&& isValidIp(ip)) {
return ip;
}
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
// 4. 兜底返回回环地址
return getLoopbackIp();
}
/**
* 验证是否为合法IPv4地址
*/
private static boolean isValidIp(String ip) {
if (ip == null || ip.isEmpty()) {
return false;
}
String[] parts = ip.split("\\.");
if (parts.length != 4) {
return false;
}
for (String part : parts) {
try {
int num = Integer.parseInt(part);
if (num < 0 || num > 255) {
return false;
}
} catch (NumberFormatException e) {
return false;
}
}
return true;
}
/**
* 获取回环地址127.0.0.1
*
* @return 回环IP
*/
private static String getLoopbackIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
return "127.0.0.1";
}
}
/**
* 获取当前机器的所有网卡IP含IPv4/IPv6
*
* @return 所有IP数组
*/
public static String[] getAllLocalIps() {
try {
java.util.List<String> ipList = new java.util.ArrayList<>();
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (!ni.isUp()) continue;
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
ipList.add(addr.getHostAddress());
}
}
return ipList.toArray(new String[0]);
} catch (SocketException e) {
e.printStackTrace();
return new String[0];
}
}
public static String getServerHttp() {
return "http://" + getLocalIp() + ":" + Global.getProperty("server.port") + FileUtils.path("/"
+ Global.getProperty("server.servlet.context-path"));
}
}

View File

@@ -0,0 +1,191 @@
package com.jeesite.modules.app.utils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
public class LoggerUtils {
// 日志级别
public enum Level {
DEBUG, INFO, WARN, ERROR
}
// 单例实例
private static volatile LoggerUtils instance;
private String baseLogPath;
// 日期格式文件名yyyyMMdd日志内容时间戳yyyy-MM-dd HH:mm:ss.SSS
private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd");
private final SimpleDateFormat logDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
// 线程安全锁
private final ReentrantLock lock = new ReentrantLock();
// 禁止外部实例化
private LoggerUtils(String baseLogPath) {
this.baseLogPath = baseLogPath;
initLogDir(); // 初始化日志目录
}
public static LoggerUtils getInstance() {
return getInstance("/ogsapp/logs");
}
/**
* 获取单例实例(自定义日志路径)
*
* @param baseLogPath 日志根目录(支持相对路径或绝对路径)
*/
public static LoggerUtils getInstance(String baseLogPath) {
if (instance == null) {
synchronized (LoggerUtils.class) {
if (instance == null) {
instance = new LoggerUtils(baseLogPath);
}
}
}
return instance;
}
/**
* 初始化日志目录(若不存在则创建多级目录)
*/
private void initLogDir() {
try {
Files.createDirectories(Paths.get(baseLogPath));
} catch (IOException e) {
throw new RuntimeException("初始化日志目录失败:" + baseLogPath, e);
}
}
/**
* 获取当前日志文件路径按日期拆分baseLogPath/log_yyyyMMdd.txt
*/
private String getCurrentLogFilePath(Level level) {
String fileName = level + "_" + fileDateFormat.format(new Date()) + ".log";
return baseLogPath + File.separator + fileName;
}
// ------------------------------ 日志方法(支持多类型可变参数) ------------------------------
/**
* 记录DEBUG级别日志支持1到多个任意类型参数
*/
public void debug(Object... messages) {
log(Level.DEBUG, joinMessages(messages), null);
}
/**
* 记录INFO级别日志支持1到多个任意类型参数
*/
public void info(Object... messages) {
log(Level.INFO, joinMessages(messages), null);
}
/**
* 记录WARN级别日志支持1到多个任意类型参数
*/
public void warn(Object... messages) {
log(Level.WARN, joinMessages(messages), null);
}
/**
* 记录WARN级别日志支持1到多个任意类型参数+异常)
*/
public void warn(Object[] messages, Throwable throwable) {
log(Level.WARN, joinMessages(messages), throwable);
}
/**
* 记录ERROR级别日志支持1到多个任意类型参数
*/
public void error(Object... messages) {
log(Level.ERROR, joinMessages(messages), null);
}
/**
* 记录ERROR级别日志支持1到多个任意类型参数+异常)
*/
public void error(Object[] messages, Throwable throwable) {
log(Level.ERROR, joinMessages(messages), throwable);
}
// ------------------------------ 核心方法 ------------------------------
/**
* 核心日志写入逻辑
*/
private void log(Level level, String message, Throwable throwable) {
// 构建日志内容
StringBuilder logContent = new StringBuilder();
logContent.append("[").append(logDateFormat.format(new Date())).append("] "); // 时间戳
logContent.append("[").append(level.name()).append("] "); // 日志级别
logContent.append("[Thread-").append(Thread.currentThread().getId()).append("] "); // 线程ID
logContent.append(message); // 拼接后的消息
// 追加异常堆栈信息(如果有)
if (throwable != null) {
logContent.append("\n").append(getStackTrace(throwable));
}
logContent.append("\n"); // 每条日志换行
// 加锁写入文件(保证线程安全)
lock.lock();
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(getCurrentLogFilePath(level), true), // 追加模式
StandardCharsets.UTF_8 // 避免中文乱码
)
)) {
writer.write(logContent.toString());
writer.flush();
} catch (IOException e) {
System.err.println("日志写入失败:" + e.getMessage());
} finally {
lock.unlock();
}
}
/**
* 拼接多类型可变参数为单个字符串自动转换任意类型为字符串处理null
*
* @param messages 1到多个任意类型参数不可为空数组
* @return 拼接后的字符串
*/
private String joinMessages(Object... messages) {
if (messages == null || messages.length == 0) {
throw new IllegalArgumentException("日志消息至少需要1个参数");
}
StringBuilder sb = new StringBuilder();
for (Object msg : messages) {
// 任意类型转换为字符串null转为"null"其他类型调用String.valueOf()
sb.append(String.valueOf(msg));
}
return sb.toString();
}
/**
* 异常堆栈信息转为字符串
*/
private String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
/**
* 动态修改日志路径
*/
public void setBaseLogPath(String baseLogPath) {
this.baseLogPath = baseLogPath;
initLogDir();
}
}

View File

@@ -0,0 +1,58 @@
package com.jeesite.modules.app.utils;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* 网络连通性检测工具类支持IPv4和域名
*/
public class NetworkUtils {
// 默认检查端口保持80作为默认兼容通用场景
private static final int DEFAULT_PORT = 80;
// 默认超时时间3秒可根据需求调整
private static final int DEFAULT_TIMEOUT_MS = 3000;
private NetworkUtils() {
throw new UnsupportedOperationException("工具类不允许实例化");
}
/**
* 检查目标主机IP/域名的连通性使用默认端口80
*
* @param target 目标IP或域名不可为null/空)
* @return true可达false不可达含参数无效/检查失败)
*/
public static boolean isNetworkReachable(String target) {
// 调用重载方法,传入默认端口
return isNetworkReachable(target, DEFAULT_PORT);
}
/**
* 检查目标主机IP/域名)的连通性(自定义端口)
*/
public static boolean isNetworkReachable(String target, int port) {
// 参数校验:目标为空或端口无效,直接返回不可达
if (target == null || target.trim().isEmpty() || port < 1 || port > 65535) {
return false;
}
String normalizedTarget = target.trim();
try {
// 先解析目标地址(只解析一次,避免重复操作)
InetAddress address = InetAddress.getByName(normalizedTarget);
// 优先尝试 Socket 连接指定端口
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(address, port), DEFAULT_TIMEOUT_MS);
return true; // 端口连接成功,返回可达
} catch (Exception e) {
// Socket 连接失败,不中断流程,继续执行主机可达性检查
}
// Socket 失败后降级检查主机基础可达性ICMP
return address.isReachable(DEFAULT_TIMEOUT_MS);
} catch (Exception e) {
// 任何异常地址解析失败、ICMP 检查失败等)均返回不可达
return false;
}
}
}

View File

@@ -0,0 +1,181 @@
package com.jeesite.modules.app.utils;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jeesite.modules.app.dao.info.CpuInfo;
import com.jeesite.modules.app.dao.info.DiskInfo;
import com.jeesite.modules.app.dao.info.ServerInfo;
import org.springframework.util.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class SystemUtil {
// SSH 连接超时时间(毫秒)
private static final int SSH_TIMEOUT = 5000;
/**
* 建立SSH连接
*/
private static Session getSshSession(String host, int port, String username, String password) throws Exception {
JSch jsch = new JSch();
Session session = jsch.getSession(username, host, port);
session.setPassword(password);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setTimeout(SSH_TIMEOUT);
session.connect();
return session;
}
/**
* 执行SSH命令并返回结果
*/
private static String executeCommand(Session session, String command) throws Exception {
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
InputStream in = channel.getInputStream();
channel.connect();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in, StandardCharsets.UTF_8)
);
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
reader.close();
channel.disconnect();
return result.toString().trim();
}
/**
* 获取服务器基础信息
*/
public static ServerInfo getServerBasicInfo(String host, int port, String username, String password, String ipAddress) throws Exception {
ServerInfo info = new ServerInfo();
Session session = null;
try {
session = getSshSession(host, port, username, password);
// 主机运行时间
String uptimeResult = executeCommand(session, "uptime");
if (StringUtils.hasText(uptimeResult)) {
String[] parts = uptimeResult.split("up ");
if (parts.length > 1) {
info.setUptime(parts[1].split(", ")[0]);
}
}
// 操作系统
String osResult = executeCommand(session, "cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"'");
info.setOs(osResult.isEmpty() ? "Unknown" : osResult);
// 内核版本
info.setKernelVersion(executeCommand(session, "uname -r"));
// 主机名
info.setHostname(executeCommand(session, "hostname"));
// IP地址传入参数
info.setIpAddress(ipAddress);
// CPU型号
String cpuResult = executeCommand(session, "cat /proc/cpuinfo | grep 'model name' | head -n 1 | cut -d: -f2 | sed -e 's/^ *//'");
info.setCpuModel(cpuResult.isEmpty() ? "Unknown" : cpuResult);
// 内存总量
String memResult = executeCommand(session, "free -h | grep Mem | awk '{print $2}'");
info.setMemoryTotal(memResult.isEmpty() ? "Unknown" : memResult);
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
return info;
}
/**
* 获取CPU和内存使用率
*/
public static CpuInfo getCpuMemUsage(String host, int port, String username, String password) throws Exception {
CpuInfo usage = new CpuInfo();
Session session = null;
try {
session = getSshSession(host, port, username, password);
// CPU使用率
String cpuCommand = "top -bn2 | grep '%Cpu' | tail -n1 | awk '{print 100 - $8}'";
String cpuResult = executeCommand(session, cpuCommand);
if (StringUtils.hasText(cpuResult)) {
usage.setCpuUsage(Double.parseDouble(cpuResult));
}
// 内存使用率
String memCommand = "free | grep Mem | awk '{print $3/$2 * 100}'";
String memResult = executeCommand(session, memCommand);
if (StringUtils.hasText(memResult)) {
usage.setMemoryUsage(Double.parseDouble(memResult));
}
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
return usage;
}
/**
* 获取所有磁盘信息
*/
public static List<DiskInfo> getDiskInfos(String host, int port, String username, String password) throws Exception {
List<DiskInfo> diskInfos = new ArrayList<>();
Session session = null;
try {
session = getSshSession(host, port, username, password);
// 关键修改:强制英文输出 + 调整字段索引
String diskCommand = "LANG=en_US.UTF-8 df -h | awk '{print $1, $6, $2, $3, $5}'";
String diskResult = executeCommand(session, diskCommand);
if (StringUtils.hasText(diskResult)) {
String[] lines = diskResult.split("\n");
for (String line : lines) {
// 过滤空行和标题行
if (line.trim().isEmpty() || line.contains("Filesystem")) {
continue;
}
// 按任意空格分割(兼容多个空格)
String[] parts = line.trim().split("\\s+");
if (parts.length >= 5) {
String usageStr = parts[4].replace("%", "").trim();
DiskInfo diskInfo = new DiskInfo(parts[0], parts[1], parts[2], parts[3], Double.parseDouble(usageStr));
diskInfos.add(diskInfo);
}
}
}
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
return diskInfos;
}
}

View File

@@ -0,0 +1,54 @@
package com.jeesite.modules.app.utils;
import com.jeesite.common.codec.AesUtils;
import java.util.Date;
public class vo {
private static final String AES_KEY = "010e06108bb801e85f8738e01c396dd2";
/**
* 获取加密Key
*/
public static String getKey() {
return AesUtils.genKeyString();
}
/**
* 加密
*/
public static String getEncode(String s) {
try {
return AesUtils.encode(s, AES_KEY);
} catch (Exception e) {
System.out.println(e.getMessage());
return s;
}
}
/**
* 解密
*/
public static String getDecode(String s) {
try {
return AesUtils.decode(s, AES_KEY);
} catch (Exception e) {
System.out.println(e.getMessage());
return s;
}
}
/**
* 获取更新时间
*/
public static Date getUpdateTime(boolean isNew) {
if (isNew) {
return null;
}
return new Date();
}
}