package com.tongran.agent.client.utils; import cn.hutool.json.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import com.tongran.agent.client.core.config.GlobalConfig; import com.tongran.agent.client.core.vo.NetworkInterfaceInfo; import org.apache.commons.lang3.StringUtils; import org.snmp4j.smi.OID; import oshi.hardware.NetworkIF; import javax.annotation.Resource; import java.io.*; import java.net.*; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; public class AgentUtil { @Resource private GlobalConfig globalConfig; 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 interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface iface = interfaces.nextElement(); // 跳过虚拟网卡(如 docker, veth, lo) if (iface.isLoopback() || iface.isVirtual() || !iface.isUp()) { continue; } Enumeration 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> 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(); 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); } // 转换为10位时间戳 long timestamp = roundedTime.atZone(ZoneId.systemDefault()).toEpochSecond(); return timestamp; } 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()); } public static String getLastOid(OID oid){ if(oid.size() > 0){ int lastValue = oid.get(oid.size() - 1); return String.valueOf(lastValue); } return null; } public static LinkedHashMap swapMap(LinkedHashMap original) { LinkedHashMap swapped = new LinkedHashMap<>(); for (Map.Entry entry : original.entrySet()) { if (entry.getValue() != null) { // 避免 null key swapped.put(entry.getValue(), entry.getKey()); } } return swapped; } // 存储网关信息:接口名 -> 网关IP private static Map gatewayMap = new HashMap<>(); // 正则表达式匹配:当前 IP:xxx.xxx.xxx.xxx 来自于:中国 江苏省 南京市 电信 private static final String REGEX = "来自于:(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)"; /** * 收集所有 Ethernet 类型网卡信息 */ public static List collectNetworkInfo() throws Exception { List result = new ArrayList<>(); // String publicIp = getPublicIp(); // 获取公网 IP(全局出口) // String ipInfo = PublicIpFetcher.getPublicIp(); // String publicIp = PublicIpFetcher.extractIp(ipInfo); // if (publicIp != null) { // System.out.println("公网 IP: " + publicIp); // } else { // System.out.println("未能提取 IP 地址"); // } Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface ni = interfaces.nextElement(); String ipv4 = getIPv4Address(ni); AssertLog.info("ipv4={},接口状态={}",ipv4,ni.isUp()); // 跳过回环、虚拟、关闭的接口 if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()) continue; // 判断是否为 Ethernet(通过名称约定:eth*, en*, 等) String name = ni.getName(); AssertLog.info("ipv4={},名称={}",ipv4,name); if (!isEthernetInterface(name)) continue; AssertLog.info("ipv4={},物理链路状态={}",ipv4,isInterfaceConnected(name)); // 检查是否“已连接”(物理链路状态) if (!isInterfaceConnected(name)) continue; // 检查IP4信息 if (ipv4.equals("N/A")) continue; String gateway = getGatewayAddress(); AssertLog.info("ipv4={},网关={}",ipv4,gateway); // 检查IP4信息 if (gateway.equals("N/A")) continue; // 添加临时路由 boolean flag = addTempRoute(ipv4, name, gateway); if (!flag) continue; 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); NetworkInterfaceInfo info = NetworkInterfaceInfo.builder() .name(name) .type("Ethernet") .mac(getMacAddress(ni)) .ipv4(ipv4) .gateway(gateway) // 网关通常是默认路由,全局一致 .publicIp(publicIp) .build(); // 如果有公网 IP,查询归属地 if (publicIp != null && !publicIp.isEmpty()) { 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); } // 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("查询失败"); // } } result.add(info); } return result; } /** * 添加临时路由 */ 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.deleteRoute(ipv4,prefix,gateway,name); if(flag){ AssertLog.info("删除临时路由成功,ipv4:{}",ipv4); }else{ AssertLog.info("删除临时路由失败,ipv4:{}",ipv4); } return flag; } /** * 判断是否为 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 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 dev ... } } scanner.close(); } catch (Exception e) { e.printStackTrace(); } return "N/A"; } // 检查接口是否“已连接”(物理链路状态) 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; // 保守处理 } } /** * 获取公网 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 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(); } } 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")); // 用换行符连接 } /** * 获取文件最后修改时间 * @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); // } } /** * 获取系统 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; } }