修改
This commit is contained in:
140
web-api/src/main/java/com/jeesite/modules/app/Job/hostJob.java
Normal file
140
web-api/src/main/java/com/jeesite/modules/app/Job/hostJob.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
142
web-api/src/main/java/com/jeesite/modules/app/utils/IpUtils.java
Normal file
142
web-api/src/main/java/com/jeesite/modules/app/utils/IpUtils.java
Normal 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"));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
54
web-api/src/main/java/com/jeesite/modules/app/utils/vo.java
Normal file
54
web-api/src/main/java/com/jeesite/modules/app/utils/vo.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user