2025-09-10 15:36:49 +08:00
|
|
|
|
package com.tongran.agent.client.utils;
|
|
|
|
|
|
|
2025-10-21 14:16:25 +08:00
|
|
|
|
import cn.hutool.json.JSONObject;
|
2025-09-10 15:36:49 +08:00
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
2025-10-28 14:24:19 +08:00
|
|
|
|
import com.tongran.agent.client.core.config.GlobalConfig;
|
2025-10-21 14:16:25 +08:00
|
|
|
|
import com.tongran.agent.client.core.vo.NetworkInterfaceInfo;
|
2025-10-31 17:18:30 +08:00
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
2025-09-26 12:57:31 +08:00
|
|
|
|
import org.snmp4j.smi.OID;
|
2025-09-10 15:36:49 +08:00
|
|
|
|
import oshi.hardware.NetworkIF;
|
|
|
|
|
|
|
2025-10-28 14:24:19 +08:00
|
|
|
|
import javax.annotation.Resource;
|
2025-10-21 14:16:25 +08:00
|
|
|
|
import java.io.*;
|
|
|
|
|
|
import java.net.*;
|
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
import java.nio.file.*;
|
2025-10-28 14:24:19 +08:00
|
|
|
|
import java.nio.file.attribute.BasicFileAttributes;
|
2025-10-21 14:16:25 +08:00
|
|
|
|
import java.security.MessageDigest;
|
|
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
2025-09-10 15:36:49 +08:00
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
|
import java.time.ZoneId;
|
|
|
|
|
|
import java.time.temporal.ChronoUnit;
|
2025-09-26 14:13:19 +08:00
|
|
|
|
import java.util.*;
|
2025-10-28 14:24:19 +08:00
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
|
import java.util.regex.Pattern;
|
2025-10-27 10:27:48 +08:00
|
|
|
|
import java.util.stream.Collectors;
|
2025-09-10 15:36:49 +08:00
|
|
|
|
|
|
|
|
|
|
public class AgentUtil {
|
|
|
|
|
|
|
2025-10-28 14:24:19 +08:00
|
|
|
|
@Resource
|
|
|
|
|
|
private GlobalConfig globalConfig;
|
|
|
|
|
|
|
2025-09-10 15:36:49 +08:00
|
|
|
|
public static String getMotherboardUUID() {
|
|
|
|
|
|
String hostName = getHostname();
|
|
|
|
|
|
String primaryIp = getPrimaryIp();
|
|
|
|
|
|
// SystemInfo si = new SystemInfo();
|
|
|
|
|
|
// HardwareAbstractionLayer hal = si.getHardware();
|
|
|
|
|
|
// Baseboard baseboard = hal.getComputerSystem().getBaseboard();
|
|
|
|
|
|
// if(StringUtils.isNotBlank(baseboard.getSerialNumber())){
|
|
|
|
|
|
// return baseboard.getSerialNumber();
|
|
|
|
|
|
// }
|
|
|
|
|
|
return hostName+":"+primaryIp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static String getHostname() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return InetAddress.getLocalHost().getHostName();
|
|
|
|
|
|
} catch (UnknownHostException e) {
|
|
|
|
|
|
return "unknown-host";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取本机首选 IP 地址(通常是第一个非回环、非虚拟网卡的 IPv4 地址)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static String getPrimaryIp() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
InetAddress localHost = InetAddress.getLocalHost();
|
|
|
|
|
|
String ip = localHost.getHostAddress();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是 127.x.x.x,说明可能 hosts 配置有问题,需要手动查找
|
|
|
|
|
|
if (ip.startsWith("127.")) {
|
|
|
|
|
|
return getExternalIp();
|
|
|
|
|
|
}
|
|
|
|
|
|
return ip;
|
|
|
|
|
|
} catch (UnknownHostException e) {
|
|
|
|
|
|
return "127.0.0.1";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取第一个非回环、非虚拟网卡的 IPv4 地址
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static String getExternalIp() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
|
|
|
|
|
while (interfaces.hasMoreElements()) {
|
|
|
|
|
|
NetworkInterface iface = interfaces.nextElement();
|
|
|
|
|
|
// 跳过虚拟网卡(如 docker, veth, lo)
|
|
|
|
|
|
if (iface.isLoopback() || iface.isVirtual() || !iface.isUp()) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
Enumeration<InetAddress> addresses = iface.getInetAddresses();
|
|
|
|
|
|
while (addresses.hasMoreElements()) {
|
|
|
|
|
|
InetAddress addr = addresses.nextElement();
|
|
|
|
|
|
if (addr.isLoopbackAddress()) {
|
|
|
|
|
|
continue; // 跳过 127.0.0.1
|
|
|
|
|
|
}
|
|
|
|
|
|
if (addr.getHostAddress().contains(":")) {
|
|
|
|
|
|
continue; // 跳过 IPv6
|
|
|
|
|
|
}
|
|
|
|
|
|
return addr.getHostAddress();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (SocketException e) {
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
return "127.0.0.1";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static String toJsonString(List<Map<String, String>> list) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
ObjectMapper mapper = new ObjectMapper();
|
|
|
|
|
|
return mapper.writeValueAsString(list);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
throw new RuntimeException("JSON转换失败", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void base64ToFile(String base64String, String filePath) throws IOException {
|
|
|
|
|
|
// 解码Base64字符串
|
|
|
|
|
|
byte[] decodedBytes = Base64.getDecoder().decode(base64String);
|
|
|
|
|
|
|
|
|
|
|
|
// 写入文件
|
|
|
|
|
|
try (FileOutputStream fos = new FileOutputStream(filePath)) {
|
|
|
|
|
|
fos.write(decodedBytes);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static String getInterfaceType(NetworkIF net) {
|
|
|
|
|
|
String name = net.getName().toLowerCase();
|
|
|
|
|
|
String displayName = net.getDisplayName().toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
if (name.startsWith("eth") || name.startsWith("en") || displayName.contains("ethernet")) {
|
|
|
|
|
|
return "Ethernet";
|
|
|
|
|
|
} else if (name.startsWith("wlan") || name.startsWith("wl") || displayName.contains("wireless")) {
|
|
|
|
|
|
return "Wi-Fi";
|
|
|
|
|
|
} else if (name.startsWith("lo") || displayName.contains("loopback")) {
|
|
|
|
|
|
return "Loopback";
|
|
|
|
|
|
} else if (name.startsWith("ppp") || displayName.contains("point-to-point")) {
|
|
|
|
|
|
return "PPP";
|
|
|
|
|
|
} else if (name.startsWith("vmnet") || displayName.contains("virtual")) {
|
|
|
|
|
|
return "Virtual";
|
|
|
|
|
|
} else if (name.startsWith("tun") || name.startsWith("tap")) {
|
|
|
|
|
|
return "TUN/TAP";
|
|
|
|
|
|
} else if (name.startsWith("br") || displayName.contains("bridge")) {
|
|
|
|
|
|
return "Bridge";
|
|
|
|
|
|
} else if (name.startsWith("bond") || displayName.contains("bond")) {
|
|
|
|
|
|
return "Bond";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return "Unknown";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前时间距离下一个“分钟为 0 或 5”的时间点还差多少毫秒
|
|
|
|
|
|
* 例如:xx:00, xx:05, xx:10, ..., xx:55
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static long millisecondsToNext5Minute() {
|
|
|
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
|
|
|
|
|
|
|
// 当前分钟
|
|
|
|
|
|
int minute = now.getMinute();
|
|
|
|
|
|
|
|
|
|
|
|
// 计算下一个 5 分钟整点(向上取整)
|
|
|
|
|
|
int nextMinute = ((minute / 5) + 1) * 5;
|
|
|
|
|
|
|
|
|
|
|
|
LocalDateTime nextTime;
|
|
|
|
|
|
if (nextMinute < 60) {
|
|
|
|
|
|
// 在当前小时内
|
|
|
|
|
|
nextTime = now.withMinute(nextMinute).withSecond(0).withNano(0);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 跨小时,如 10:58 → 11:00
|
|
|
|
|
|
nextTime = now.plusHours(1).withMinute(0).withSecond(0).withNano(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算相差的毫秒数
|
|
|
|
|
|
return ChronoUnit.MILLIS.between(now, nextTime);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取距离下一个分钟整点的毫秒数
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static long getMillisToNextMinute() {
|
|
|
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取下一个分钟整点时间
|
|
|
|
|
|
LocalDateTime nextMinute = now
|
|
|
|
|
|
.truncatedTo(ChronoUnit.MINUTES) // 截断到当前分钟
|
|
|
|
|
|
.plusMinutes(1); // 加1分钟
|
|
|
|
|
|
|
|
|
|
|
|
// 计算时间差(毫秒)
|
|
|
|
|
|
return ChronoUnit.MILLIS.between(now, nextMinute);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取距离下一个指定分钟整点的毫秒数
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static long getMillisToNextMinuteInterval(int intervalMinutes) {
|
|
|
|
|
|
if (intervalMinutes <= 0) {
|
|
|
|
|
|
throw new IllegalArgumentException("Interval must be positive");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
|
|
|
|
|
|
|
// 计算当前分钟在间隔中的位置
|
|
|
|
|
|
int currentMinute = now.getMinute();
|
|
|
|
|
|
int remainder = currentMinute % intervalMinutes;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算需要增加的分钟数
|
|
|
|
|
|
int minutesToAdd = remainder == 0 ? intervalMinutes : intervalMinutes - remainder;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取下一个间隔整点时间
|
|
|
|
|
|
LocalDateTime nextInterval = now
|
|
|
|
|
|
.truncatedTo(ChronoUnit.MINUTES) // 截断到当前分钟
|
|
|
|
|
|
.plusMinutes(minutesToAdd) // 增加到下一个整点
|
|
|
|
|
|
.withSecond(0) // 秒设为0
|
|
|
|
|
|
.withNano(0); // 纳秒设为0
|
|
|
|
|
|
|
|
|
|
|
|
return ChronoUnit.MILLIS.between(now, nextInterval);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static long roundMinutes(){
|
|
|
|
|
|
LocalDateTime now = LocalDateTime.now();
|
2025-09-28 11:00:38 +08:00
|
|
|
|
int minute = now.getMinute();
|
|
|
|
|
|
int roundedMinute = (int) (Math.round(minute / 5.0) * 5);
|
|
|
|
|
|
|
|
|
|
|
|
LocalDateTime roundedTime ;
|
|
|
|
|
|
if (roundedMinute == 60) {
|
|
|
|
|
|
roundedTime = now.plusHours(1).withMinute(0).withSecond(0).withNano(0);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
roundedTime = now.withMinute(roundedMinute).withSecond(0).withNano(0);
|
2025-09-10 15:36:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 转换为10位时间戳
|
|
|
|
|
|
long timestamp = roundedTime.atZone(ZoneId.systemDefault()).toEpochSecond();
|
|
|
|
|
|
return timestamp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 18:52:17 +08:00
|
|
|
|
public static LocalDateTime toLocalDateTime(long timestamp, boolean isSeconds) {
|
|
|
|
|
|
java.time.Instant instant = isSeconds ?
|
|
|
|
|
|
java.time.Instant.ofEpochSecond(timestamp) :
|
|
|
|
|
|
java.time.Instant.ofEpochMilli(timestamp);
|
|
|
|
|
|
|
|
|
|
|
|
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-26 12:57:31 +08:00
|
|
|
|
public static String getLastOid(OID oid){
|
|
|
|
|
|
if(oid.size() > 0){
|
|
|
|
|
|
int lastValue = oid.get(oid.size() - 1);
|
|
|
|
|
|
return String.valueOf(lastValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-26 14:13:19 +08:00
|
|
|
|
public static LinkedHashMap<String, String> swapMap(LinkedHashMap<String, String> original) {
|
|
|
|
|
|
LinkedHashMap<String, String> swapped = new LinkedHashMap<>();
|
|
|
|
|
|
for (Map.Entry<String, String> entry : original.entrySet()) {
|
|
|
|
|
|
if (entry.getValue() != null) { // 避免 null key
|
|
|
|
|
|
swapped.put(entry.getValue(), entry.getKey());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return swapped;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 16:07:26 +08:00
|
|
|
|
// 存储网关信息:接口名 -> 网关IP
|
|
|
|
|
|
private static Map<String, String> gatewayMap = new HashMap<>();
|
|
|
|
|
|
|
2025-10-28 14:24:19 +08:00
|
|
|
|
// 正则表达式匹配:当前 IP:xxx.xxx.xxx.xxx 来自于:中国 江苏省 南京市 电信
|
|
|
|
|
|
private static final String REGEX = "来自于:(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)";
|
2025-10-21 14:16:25 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 收集所有 Ethernet 类型网卡信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static List<NetworkInterfaceInfo> collectNetworkInfo() throws Exception {
|
|
|
|
|
|
List<NetworkInterfaceInfo> result = new ArrayList<>();
|
|
|
|
|
|
// String publicIp = getPublicIp(); // 获取公网 IP(全局出口)
|
2025-10-31 17:18:30 +08:00
|
|
|
|
// String ipInfo = PublicIpFetcher.getPublicIp();
|
|
|
|
|
|
// String publicIp = PublicIpFetcher.extractIp(ipInfo);
|
|
|
|
|
|
// if (publicIp != null) {
|
|
|
|
|
|
// System.out.println("公网 IP: " + publicIp);
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// System.out.println("未能提取 IP 地址");
|
|
|
|
|
|
// }
|
2025-10-21 14:16:25 +08:00
|
|
|
|
|
|
|
|
|
|
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
|
|
|
|
|
|
|
|
|
|
|
while (interfaces.hasMoreElements()) {
|
|
|
|
|
|
NetworkInterface ni = interfaces.nextElement();
|
|
|
|
|
|
|
|
|
|
|
|
// 跳过回环、虚拟、关闭的接口
|
|
|
|
|
|
if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为 Ethernet(通过名称约定:eth*, en*, 等)
|
|
|
|
|
|
String name = ni.getName();
|
|
|
|
|
|
if (!isEthernetInterface(name)) continue;
|
|
|
|
|
|
|
2025-10-31 16:07:26 +08:00
|
|
|
|
// 检查是否“已连接”(物理链路状态)
|
|
|
|
|
|
if (!isInterfaceConnected(name)) continue;
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
String ipv4 = getIPv4Address(ni);
|
2025-10-31 16:07:26 +08:00
|
|
|
|
// 检查IP4信息
|
2025-10-31 17:18:30 +08:00
|
|
|
|
if (ipv4.equals("N/A")) continue;
|
|
|
|
|
|
|
|
|
|
|
|
String gateway = getGatewayAddress();
|
|
|
|
|
|
// 检查IP4信息
|
|
|
|
|
|
if (gateway.equals("N/A")) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 添加临时路由
|
|
|
|
|
|
addTempRoute(ipv4, name, gateway);
|
|
|
|
|
|
String ipInfo = PublicIpFetcher.getPublicIp();
|
|
|
|
|
|
String publicIp = PublicIpFetcher.extractIp(ipInfo);
|
|
|
|
|
|
if (publicIp != null) {
|
|
|
|
|
|
System.out.println("公网 IP: " + publicIp);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
System.out.println("未能提取 IP 地址");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (StringUtils.isBlank(publicIp)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 删除临时路由
|
|
|
|
|
|
delTempRoute(ipv4, name, gateway);
|
|
|
|
|
|
|
2025-10-31 16:07:26 +08:00
|
|
|
|
|
2025-10-21 14:16:25 +08:00
|
|
|
|
NetworkInterfaceInfo info = NetworkInterfaceInfo.builder()
|
|
|
|
|
|
.name(name)
|
2025-10-28 14:24:19 +08:00
|
|
|
|
.type("Ethernet")
|
2025-10-21 14:16:25 +08:00
|
|
|
|
.mac(getMacAddress(ni))
|
2025-10-31 17:18:30 +08:00
|
|
|
|
.ipv4(ipv4)
|
|
|
|
|
|
.gateway(gateway) // 网关通常是默认路由,全局一致
|
2025-10-21 14:16:25 +08:00
|
|
|
|
.publicIp(publicIp)
|
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有公网 IP,查询归属地
|
|
|
|
|
|
if (publicIp != null && !publicIp.isEmpty()) {
|
2025-10-28 14:24:19 +08:00
|
|
|
|
Pattern pattern = Pattern.compile(REGEX);
|
|
|
|
|
|
Matcher matcher = pattern.matcher(ipInfo);
|
|
|
|
|
|
if (matcher.find()) {
|
|
|
|
|
|
// group(1): 国家(通常是“中国”)
|
|
|
|
|
|
// group(2): 省份
|
|
|
|
|
|
// group(3): 城市
|
|
|
|
|
|
// group(4): 运营商
|
|
|
|
|
|
String province = matcher.group(2);
|
|
|
|
|
|
String city = matcher.group(3);
|
|
|
|
|
|
String isp = matcher.group(4);
|
|
|
|
|
|
// 去掉可能的标点符号(如句号)
|
|
|
|
|
|
province = province.replaceAll("[。]", "");
|
|
|
|
|
|
city = city.replaceAll("[。]", "");
|
|
|
|
|
|
isp = isp.replaceAll("[。]", "");
|
|
|
|
|
|
info.setCarrier(isp);
|
|
|
|
|
|
info.setProvince(province);
|
|
|
|
|
|
info.setCity(city);
|
2025-10-21 14:16:25 +08:00
|
|
|
|
}
|
2025-10-28 14:24:19 +08:00
|
|
|
|
// ipInfo.substring(ipInfo.indexOf("来自于:")+4,ipInfo.length())
|
|
|
|
|
|
// JSONObject location = queryIpLocation(publicIp);
|
|
|
|
|
|
// if (location != null) {
|
|
|
|
|
|
// info.setCarrier(location.getStr("org", "未知").replaceAll("^AS\\d+\\s*", ""));
|
|
|
|
|
|
// info.setProvince(location.getStr("region", "未知"));
|
|
|
|
|
|
// info.setCity(location.getStr("city", "未知"));
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// info.setCarrier("查询失败");
|
|
|
|
|
|
// info.setProvince("查询失败");
|
|
|
|
|
|
// info.setCity("查询失败");
|
|
|
|
|
|
// }
|
2025-10-21 14:16:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
result.add(info);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 17:18:30 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 添加临时路由
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static boolean addTempRoute(String ipv4, String name, String gateway) {
|
|
|
|
|
|
String prefix = "32";
|
|
|
|
|
|
AssertLog.info("添加临时路由,ipv4:{}",ipv4);
|
|
|
|
|
|
boolean flag = NetworkUtil.addRoute(ipv4,prefix,gateway,name);
|
|
|
|
|
|
if(flag){
|
|
|
|
|
|
AssertLog.info("添加临时路由成功,ipv4:{}",ipv4);
|
|
|
|
|
|
}else{
|
|
|
|
|
|
AssertLog.info("添加临时路由成功失败,ipv4:{}",ipv4);
|
|
|
|
|
|
}
|
|
|
|
|
|
return flag;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除临时路由
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static boolean delTempRoute(String ipv4, String name, String gateway) {
|
|
|
|
|
|
String prefix = "32";
|
|
|
|
|
|
AssertLog.info("删除临时路由,ipv4:{}",ipv4);
|
|
|
|
|
|
boolean flag = NetworkUtil.addRoute(ipv4,prefix,gateway,name);
|
|
|
|
|
|
if(flag){
|
|
|
|
|
|
AssertLog.info("删除临时路由成功,ipv4:{}",ipv4);
|
|
|
|
|
|
}else{
|
|
|
|
|
|
AssertLog.info("删除临时路由成功失败,ipv4:{}",ipv4);
|
|
|
|
|
|
}
|
|
|
|
|
|
return flag;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 14:16:25 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 判断是否为 Ethernet 类型网卡(基于常见命名)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static boolean isEthernetInterface(String name) {
|
|
|
|
|
|
return name.startsWith("eth") || // Linux 传统
|
|
|
|
|
|
name.startsWith("en") || // systemd 命名 (enp3s0)
|
|
|
|
|
|
name.startsWith("em"); // 有些主板网卡
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取 MAC 地址
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static String getMacAddress(NetworkInterface ni) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
byte[] mac = ni.getHardwareAddress();
|
|
|
|
|
|
if (mac == null) return "N/A";
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
for (int i = 0; i < mac.length; i++) {
|
|
|
|
|
|
sb.append(String.format("%02X", mac[i]));
|
|
|
|
|
|
if (i < mac.length - 1) sb.append(":");
|
|
|
|
|
|
}
|
|
|
|
|
|
return sb.toString();
|
|
|
|
|
|
} catch (SocketException e) {
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取第一个 IPv4 地址
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static String getIPv4Address(NetworkInterface ni) {
|
|
|
|
|
|
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
|
|
|
|
|
while (addresses.hasMoreElements()) {
|
|
|
|
|
|
InetAddress addr = addresses.nextElement();
|
|
|
|
|
|
if (addr instanceof Inet4Address) {
|
|
|
|
|
|
return addr.getHostAddress();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return "N/A";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取默认网关(调用 shell 命令)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static String getGatewayAddress() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
Process process = Runtime.getRuntime().exec("ip route");
|
|
|
|
|
|
java.util.Scanner scanner = new java.util.Scanner(process.getInputStream());
|
|
|
|
|
|
while (scanner.hasNextLine()) {
|
|
|
|
|
|
String line = scanner.nextLine();
|
|
|
|
|
|
if (line.startsWith("default")) {
|
|
|
|
|
|
String[] parts = line.split(" ");
|
|
|
|
|
|
return parts[2]; // default via <gateway> dev ...
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
scanner.close();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
return "N/A";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 16:07:26 +08:00
|
|
|
|
// 检查接口是否“已连接”(物理链路状态)
|
|
|
|
|
|
private static boolean isInterfaceConnected(String interfaceName) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
String os = System.getProperty("os.name").toLowerCase();
|
|
|
|
|
|
Process process;
|
|
|
|
|
|
|
|
|
|
|
|
if (os.contains("win")) {
|
|
|
|
|
|
// Windows: 使用 wmic 检查网卡是否启用
|
|
|
|
|
|
process = Runtime.getRuntime().exec(
|
|
|
|
|
|
"wmic nic where \"NetEnabled=true\" get Name");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Linux: 检查 operstate
|
|
|
|
|
|
process = Runtime.getRuntime().exec(
|
|
|
|
|
|
"cat /sys/class/net/" + interfaceName + "/operstate");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BufferedReader reader = new BufferedReader(
|
|
|
|
|
|
new InputStreamReader(process.getInputStream()));
|
|
|
|
|
|
String line;
|
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
|
|
if (os.contains("win")) {
|
|
|
|
|
|
if (line.trim().equalsIgnoreCase(interfaceName)) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return "up".equals(line.trim());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
reader.close();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
System.err.println("无法检查接口状态: " + interfaceName);
|
|
|
|
|
|
return false; // 保守处理
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 14:16:25 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取公网 IP
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static String getPublicIp() {
|
|
|
|
|
|
return sendHttpGet("https://ifconfig.me");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询 IP 归属地(使用 ipinfo.io)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static JSONObject queryIpLocation(String ip) {
|
|
|
|
|
|
String url = "https://ipinfo.io/" + ip + "/json";
|
|
|
|
|
|
String response = sendHttpGet(url);
|
|
|
|
|
|
AssertLog.info("查询 IP 归属地={}", response);
|
|
|
|
|
|
if (response != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return new JSONObject(response);
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发送 HTTP GET 请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static String sendHttpGet(String urlString) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
URL url = new URL(urlString);
|
|
|
|
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
|
|
|
|
conn.setRequestMethod("GET");
|
|
|
|
|
|
conn.setConnectTimeout(5000);
|
|
|
|
|
|
conn.setReadTimeout(5000);
|
|
|
|
|
|
|
|
|
|
|
|
if (conn.getResponseCode() == 200) {
|
|
|
|
|
|
java.util.Scanner scanner = new java.util.Scanner(conn.getInputStream());
|
|
|
|
|
|
String result = scanner.useDelimiter("\\A").next();
|
|
|
|
|
|
scanner.close();
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
System.err.println("HTTP 请求失败: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void bufferedWriter(String filePath, String[] lines){
|
|
|
|
|
|
try (BufferedWriter writer = Files.newBufferedWriter(
|
|
|
|
|
|
Paths.get(filePath),
|
|
|
|
|
|
StandardCharsets.UTF_8,
|
|
|
|
|
|
StandardOpenOption.CREATE, // 创建文件
|
|
|
|
|
|
StandardOpenOption.TRUNCATE_EXISTING // 覆盖写入
|
|
|
|
|
|
)) {
|
|
|
|
|
|
for (String line : lines) {
|
|
|
|
|
|
writer.write(line);
|
|
|
|
|
|
writer.newLine(); // 换行
|
|
|
|
|
|
}
|
|
|
|
|
|
System.out.println("文件写入完成!");
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
System.err.println("写入异常:" + e.getMessage());
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取 /etc/issue 文件的第二行内容
|
|
|
|
|
|
* @return 第二行字符串,如果不存在则返回 null
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static String getDeviceSN() {
|
|
|
|
|
|
Path issuePath = Paths.get("/etc/issue");
|
|
|
|
|
|
if (!Files.exists(issuePath)) {
|
|
|
|
|
|
System.err.println("文件不存在: /etc/issue");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Files.isReadable(issuePath)) {
|
|
|
|
|
|
System.err.println("无读取权限: /etc/issue");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
try (BufferedReader reader = Files.newBufferedReader(issuePath)) {
|
|
|
|
|
|
String line;
|
|
|
|
|
|
int lineNumber = 0;
|
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
|
|
line = line.trim();
|
|
|
|
|
|
if (!line.isEmpty()) { // 只计数非空行
|
|
|
|
|
|
lineNumber++;
|
|
|
|
|
|
if (lineNumber == 2) {
|
|
|
|
|
|
return line;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果文件少于两行
|
|
|
|
|
|
System.err.println("文件行数不足,只有 " + lineNumber + " 行");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
} catch (NoSuchFileException e) {
|
|
|
|
|
|
System.err.println("文件未找到: " + e.getMessage());
|
|
|
|
|
|
return null;
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
System.err.println("读取文件异常: " + e.getMessage());
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
|
|
|
|
List<NetworkInterfaceInfo> infos = collectNetworkInfo();
|
|
|
|
|
|
for (NetworkInterfaceInfo info : infos) {
|
|
|
|
|
|
System.out.println(info);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static String getFileMD5(String filePath) {
|
|
|
|
|
|
// 检查文件是否存在
|
|
|
|
|
|
Path path = Paths.get(filePath);
|
|
|
|
|
|
if (!Files.exists(path)) {
|
|
|
|
|
|
System.out.println("文件不存在: " + filePath);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 检查是否是文件(不是目录)
|
|
|
|
|
|
if (!Files.isRegularFile(path)) {
|
|
|
|
|
|
System.out.println("路径不是文件: " + filePath);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 检查文件是否可读
|
|
|
|
|
|
if (!Files.isReadable(path)) {
|
|
|
|
|
|
System.out.println("文件不可读: " + filePath);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
|
|
|
|
try (FileInputStream fis = new FileInputStream(filePath)) {
|
|
|
|
|
|
byte[] buffer = new byte[4096];
|
|
|
|
|
|
int length;
|
|
|
|
|
|
while ((length = fis.read(buffer)) != -1) {
|
|
|
|
|
|
md.update(buffer, 0, length);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
byte[] digest = md.digest();
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
for (byte b : digest) {
|
|
|
|
|
|
sb.append(String.format("%02x", b));
|
|
|
|
|
|
}
|
|
|
|
|
|
return sb.toString();
|
|
|
|
|
|
} catch (NoSuchAlgorithmException | IOException e) {
|
|
|
|
|
|
System.out.println("计算MD5失败: " + e.getMessage());
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 安排脚本在 3 分钟后执行
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static void scheduleScriptIn3Minutes(String SCRIPT_PATH) {
|
|
|
|
|
|
// 使用 at 命令:3 minutes from now
|
|
|
|
|
|
String atCommand = String.format("echo '%s' | at now + 3 minutes", SCRIPT_PATH);
|
|
|
|
|
|
|
|
|
|
|
|
ProcessBuilder pb = new ProcessBuilder("bash", "-c", atCommand);
|
|
|
|
|
|
pb.redirectErrorStream(true); // 合并 stdout 和 stderr
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
Process process = pb.start();
|
|
|
|
|
|
|
|
|
|
|
|
// 读取命令输出(at 通常会打印任务编号)
|
|
|
|
|
|
java.io.BufferedReader reader = new java.io.BufferedReader(
|
|
|
|
|
|
new java.io.InputStreamReader(process.getInputStream())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
String line;
|
|
|
|
|
|
StringBuilder output = new StringBuilder();
|
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
|
|
output.append(line).append("\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
reader.close();
|
|
|
|
|
|
|
|
|
|
|
|
int exitCode = process.waitFor();
|
|
|
|
|
|
if (exitCode == 0) {
|
|
|
|
|
|
System.out.println("✅ 成功安排脚本在 3 分钟后执行:");
|
|
|
|
|
|
System.out.println(" 脚本: " + SCRIPT_PATH);
|
|
|
|
|
|
if (output.length() > 0) {
|
|
|
|
|
|
System.out.println(" at 响应: " + output.toString().trim());
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
System.err.println("❌ 调度失败,exit code: " + exitCode);
|
|
|
|
|
|
System.err.println(" 输出: " + output.toString().trim());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (IOException | InterruptedException e) {
|
|
|
|
|
|
System.err.println("执行 at 命令时出错: " + e.getMessage());
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-27 10:27:48 +08:00
|
|
|
|
public static String getNetworkParam(String filePath, String key){
|
|
|
|
|
|
Properties props = new Properties();
|
|
|
|
|
|
String result = "";
|
|
|
|
|
|
try (InputStream input = Files.newInputStream(Paths.get(filePath))) {
|
|
|
|
|
|
// 加载配置文件
|
|
|
|
|
|
props.load(input);
|
|
|
|
|
|
result = props.getProperty(key, "");
|
|
|
|
|
|
System.out.println("配置文件加载成功");
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
System.err.println("无法加载配置文件,使用默认值");
|
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
|
System.err.println("配置文件格式错误: " + e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 读取文件,跳过空行,将所有非空行拼接成一个字符串
|
|
|
|
|
|
* @param filePath 文件路径
|
|
|
|
|
|
* @return 拼接后的字符串(空行已去除)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static String readNonEmptyLinesAsString(String filePath) throws Exception {
|
|
|
|
|
|
return Files.lines(Paths.get(filePath))
|
|
|
|
|
|
.filter(line -> !line.trim().isEmpty()) // 去除 null 和 空/空白行
|
|
|
|
|
|
.collect(Collectors.joining("\n")); // 用换行符连接
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-28 14:24:19 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取文件最后修改时间
|
|
|
|
|
|
* @param filePath
|
|
|
|
|
|
* @return
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static LocalDateTime getLastModifiedTime(String filePath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
BasicFileAttributes attrs = Files.readAttributes(Paths.get(filePath), BasicFileAttributes.class);
|
|
|
|
|
|
return attrs.lastModifiedTime()
|
|
|
|
|
|
.toInstant()
|
|
|
|
|
|
.atZone(ZoneId.systemDefault())
|
|
|
|
|
|
.toLocalDateTime();
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
System.err.println("无法读取文件时间: " + filePath);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// LocalDateTime mtime = FileUtils.getLastModifiedTime("/tmp/data.log");
|
|
|
|
|
|
// if (mtime != null) {
|
|
|
|
|
|
// System.out.println("修改时间: " + mtime);
|
|
|
|
|
|
// }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-30 16:37:08 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取系统 HZ(每秒 tick 数)
|
|
|
|
|
|
* 典型值:100, 250, 300, 1000
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static int getSystemHz() {
|
|
|
|
|
|
if (GlobalConfig.systemHz != null) {
|
|
|
|
|
|
return GlobalConfig.systemHz;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
Process process = Runtime.getRuntime().exec(
|
|
|
|
|
|
"grep CONFIG_HZ /boot/config-$(uname -r)"
|
|
|
|
|
|
);
|
|
|
|
|
|
BufferedReader reader = new BufferedReader(
|
|
|
|
|
|
new InputStreamReader(process.getInputStream())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
String line;
|
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
|
|
if (line.contains("CONFIG_HZ=")) {
|
|
|
|
|
|
String[] parts = line.split("=");
|
|
|
|
|
|
int hz = Integer.parseInt(parts[1].trim());
|
|
|
|
|
|
GlobalConfig.systemHz = hz;
|
|
|
|
|
|
return hz;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
reader.close();
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
System.err.println("无法读取系统 HZ,使用默认值 1000");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 默认值(大多数现代 Linux 发行版使用 1000 Hz)
|
|
|
|
|
|
GlobalConfig.systemHz = 1000;
|
|
|
|
|
|
return GlobalConfig.systemHz;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将 ticks 转换为秒
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static double ticksToSeconds(long ticks) {
|
|
|
|
|
|
int hz = getSystemHz();
|
|
|
|
|
|
return (double) ticks / hz;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-10-28 14:24:19 +08:00
|
|
|
|
|
2025-09-10 15:36:49 +08:00
|
|
|
|
}
|