From 8ae81db4e868d53ec025df4d5523b65b887baaba Mon Sep 17 00:00:00 2001 From: gaoyutao Date: Tue, 2 Dec 2025 18:42:59 +0800 Subject: [PATCH] =?UTF-8?q?mtr=E6=8E=A2=E6=B5=8B=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E3=80=81=E4=B8=B4=E6=97=B6=E8=B7=AF=E7=94=B1=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tongran/agent/client/core/vo/MtrVO.java | 2 - .../scheduler/config/MtrThreadPoolConfig.java | 71 ++++ .../client/service/impl/MtrServiceImpl.java | 371 +++++++++++++----- .../service/impl/OnlineIPQueryService.java | 200 ++++++++++ .../tongran/agent/client/utils/AgentUtil.java | 42 +- .../agent/client/utils/NetworkUtil.java | 33 +- 6 files changed, 620 insertions(+), 99 deletions(-) create mode 100644 src/main/java/com/tongran/agent/client/scheduler/config/MtrThreadPoolConfig.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/OnlineIPQueryService.java diff --git a/src/main/java/com/tongran/agent/client/core/vo/MtrVO.java b/src/main/java/com/tongran/agent/client/core/vo/MtrVO.java index f4507f9..e27bc84 100644 --- a/src/main/java/com/tongran/agent/client/core/vo/MtrVO.java +++ b/src/main/java/com/tongran/agent/client/core/vo/MtrVO.java @@ -9,11 +9,9 @@ import java.util.List; public class MtrVO { private String targetIp; // 目标IP private String clientId; // 对应的clientId - private Long policyId; // 策略ID private double firstLossPercent; // 第一次探测丢包率 private double finalLossPercent; // 最终丢包率 private long timestamp; // 探测时间戳 - private boolean hasRetry; // 是否重试 private String errorMsg; // 错误信息 private List hopInfos; // 每跳路由信息列表 } \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/scheduler/config/MtrThreadPoolConfig.java b/src/main/java/com/tongran/agent/client/scheduler/config/MtrThreadPoolConfig.java new file mode 100644 index 0000000..5d6a646 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/scheduler/config/MtrThreadPoolConfig.java @@ -0,0 +1,71 @@ +package com.tongran.agent.client.scheduler.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * MTR线程池配置(优化版) + */ +@Configuration +public class MtrThreadPoolConfig { + + @Bean("mtrExecutor") + public ExecutorService mtrExecutor() { + int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; // 根据CPU核心数动态设置 + int maxPoolSize = corePoolSize * 2; + int queueCapacity = 200; + long keepAliveTime = 30L; // 缩短空闲线程存活时间 + + ThreadPoolExecutor executor = new ThreadPoolExecutor( + corePoolSize, + maxPoolSize, + keepAliveTime, + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(queueCapacity), + new MtrThreadFactory(), + new MtrRejectedExecutionHandler() // 自定义拒绝策略 + ); + + // 允许核心线程超时销毁 + executor.allowCoreThreadTimeOut(true); + + return executor; + } + + /** + * MTR线程工厂 + */ + private static class MtrThreadFactory implements ThreadFactory { + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix = "mtr-worker-"; + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, namePrefix + threadNumber.getAndIncrement()); + thread.setDaemon(false); // 设置为非守护线程 + thread.setPriority(Thread.NORM_PRIORITY); + return thread; + } + } + + /** + * MTR拒绝策略 + */ + private static class MtrRejectedExecutionHandler implements RejectedExecutionHandler { + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + // 记录拒绝的线程信息 + System.err.println("MTR任务被拒绝,线程池状态: " + + "活跃线程数: " + executor.getActiveCount() + + ", 队列大小: " + executor.getQueue().size()); + + // 使用调用者线程执行,避免任务丢失 + if (!executor.isShutdown()) { + r.run(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java index dc1223a..691973e 100644 --- a/src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java +++ b/src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java @@ -1,33 +1,57 @@ package com.tongran.agent.client.service.impl; import cn.hutool.core.collection.CollectionUtil; -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONObject; import com.tongran.agent.client.core.eo.MtrPolicyConfigEO; +import com.tongran.agent.client.core.vo.HopInfoVO; import com.tongran.agent.client.core.vo.MtrVO; -import com.tongran.agent.client.core.vo.NetVO; import com.tongran.agent.client.service.MtrService; -import com.tongran.agent.client.utils.AgentUtil; import com.tongran.agent.client.utils.AssertLog; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import oshi.SystemInfo; -import oshi.hardware.HardwareAbstractionLayer; -import oshi.hardware.NetworkIF; -import oshi.util.FormatUtil; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * MTR探测服务实现类 + * 支持异步批量探测,解析每一跳的路由信息 + */ @Service public class MtrServiceImpl implements MtrService { + + private static final int MTR_TIMEOUT_MINUTES = 3; + private static final int RETRY_DELAY_MS = 30000; + private static final double LOSS_PERCENT_THRESHOLD = 5.0; + private static final int MTR_PACKET_COUNT = 10; + + // 内网IP地址段正则表达式 + private static final Pattern PRIVATE_IP_PATTERN = Pattern.compile( + "^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.|127\\.|169\\.254\\.|0\\.|255\\.)" + ); + + private final OnlineIPQueryService onlineIPQueryService; + private final ExecutorService mtrExecutor; + + // 通过构造器注入线程池 + public MtrServiceImpl(OnlineIPQueryService onlineIPQueryService, + @Qualifier("mtrExecutor") ExecutorService mtrExecutor) { + this.onlineIPQueryService = onlineIPQueryService; + this.mtrExecutor = mtrExecutor; + } + /** * 执行MTR探测列表 - 异步版本 + * @param timestamp 探测时间戳 + * @param mtrPolicyConfigEOList 探测策略配置列表 + * @return MTR探测结果列表 */ + @Override public List mtrList(long timestamp, List mtrPolicyConfigEOList) { List> futures = new ArrayList<>(); List mtrResults = new ArrayList<>(); @@ -37,37 +61,23 @@ public class MtrServiceImpl implements MtrService { return mtrResults; } - // 为每个IP创建异步任务 + // 为每个IP创建异步探测任务 for (MtrPolicyConfigEO policy : mtrPolicyConfigEOList) { if (!isPolicyTimeValid(policy, timestamp)) { continue; } - for (Map.Entry> entry : policy.getClientIdToIpsMap().entrySet()) { - String clientId = entry.getKey(); - List ips = entry.getValue(); - if (ips != null) { - for (String ip : ips) { - String targetIp = ip.trim(); - if (!targetIp.isEmpty()) { - // 异步执行每个IP的探测 - CompletableFuture future = CompletableFuture.supplyAsync(() -> - executeSingleMtrProbe(targetIp, clientId, timestamp) - ); - futures.add(future); - } - } - } - } + createMtrTasksForPolicy(policy, timestamp, futures); } - // 等待所有任务完成 + // 等待所有异步任务完成 CompletableFuture allFutures = CompletableFuture.allOf( futures.toArray(new CompletableFuture[0]) ); - // 获取所有结果(设置超时时间,比如2分钟) - allFutures.get(2, TimeUnit.MINUTES); + // 设置总超时时间 + allFutures.get(MTR_TIMEOUT_MINUTES, TimeUnit.MINUTES); + // 收集所有结果 for (CompletableFuture future : futures) { MtrVO result = future.get(); if (result != null) { @@ -82,20 +92,59 @@ public class MtrServiceImpl implements MtrService { return mtrResults; } + /** + * 为策略创建MTR探测任务 + */ + private void createMtrTasksForPolicy(MtrPolicyConfigEO policy, long timestamp, + List> futures) { + for (Map.Entry> entry : policy.getClientIdToIpsMap().entrySet()) { + String clientId = entry.getKey(); + List ips = entry.getValue(); + + if (ips != null) { + for (String ip : ips) { + String targetIp = ip.trim(); + if (!targetIp.isEmpty() && !isPrivateIp(targetIp)) { + CompletableFuture future = CompletableFuture.supplyAsync(() -> + executeSingleMtrProbe(targetIp, clientId, timestamp), mtrExecutor + ); + futures.add(future); + } else if (isPrivateIp(targetIp)) { + AssertLog.info("跳过内网IP探测: {}", targetIp); + } + } + } + } + } + + /** + * 检查是否为内网IP + */ + private boolean isPrivateIp(String ip) { + if (ip == null || ip.isEmpty()) { + return true; + } + + // 检查常见的非IP地址情况 + if (ip.equals("???") || ip.equals("*") || ip.equals("localhost")) { + return true; + } + + // 使用正则表达式匹配内网IP段 + return PRIVATE_IP_PATTERN.matcher(ip).find(); + } /** * 检查策略时间是否有效 */ private boolean isPolicyTimeValid(MtrPolicyConfigEO policy, long currentTimestamp) { try { - // 将时间戳转换为Date对象进行比较 Date currentTime = new Date(currentTimestamp * 1000); Date startTime = policy.getStartTime(); Date endTime = policy.getEndTime(); - // 开始时间为空表示立即开始 + // 开始时间为空表示立即开始,结束时间为空表示永不过期 boolean startValid = startTime == null || !currentTime.before(startTime); - // 结束时间为空表示永不过期 boolean endValid = endTime == null || !currentTime.after(endTime); return startValid && endValid; @@ -109,83 +158,73 @@ public class MtrServiceImpl implements MtrService { * 执行单个IP的MTR探测 */ private MtrVO executeSingleMtrProbe(String targetIp, String clientId, long timestamp) { - // 构建IP到clientId的反向映射 - MtrVO mtrVO = new MtrVO(); - mtrVO.setClientId(clientId); - mtrVO.setTargetIp(targetIp); - mtrVO.setTimestamp(timestamp); + MtrVO mtrVO = createMtrVO(targetIp, clientId, timestamp); try { // 第一次MTR探测 - double firstLossPercent = executeMtrCommand(targetIp); + List firstHops = executeMtrCommand(targetIp); + double firstLossPercent = calculateFinalLossPercent(firstHops); mtrVO.setFirstLossPercent(firstLossPercent); + mtrVO.setHopInfos(firstHops); - // 如果丢包率超过5%,30秒后重试 - if (firstLossPercent > 5.0) { - AssertLog.info("目标IP {} 丢包率 {}% > 5%,30秒后重试探测", targetIp, firstLossPercent); + // 如果丢包率超过阈值,进行重试探测 + if (firstLossPercent > LOSS_PERCENT_THRESHOLD) { + AssertLog.info("目标IP {} 丢包率 {}% > {}%,{}秒后重试探测", + targetIp, firstLossPercent, LOSS_PERCENT_THRESHOLD, RETRY_DELAY_MS/1000); - // 等待30秒 - Thread.sleep(30000); + Thread.sleep(RETRY_DELAY_MS); // 第二次MTR探测 - double secondLossPercent = executeMtrCommand(targetIp); - mtrVO.setFinalLossPercent(secondLossPercent); - mtrVO.setHasRetry(true); + List finalHops = executeMtrCommand(targetIp); + double finalLossPercent = calculateFinalLossPercent(finalHops); + mtrVO.setFinalLossPercent(finalLossPercent); + mtrVO.setHopInfos(finalHops); - AssertLog.info("目标IP {} 重试探测完成,最终丢包率: {}%", targetIp, secondLossPercent); + AssertLog.info("目标IP {} 重试探测完成,最终丢包率: {}%", targetIp, finalLossPercent); } else { mtrVO.setFinalLossPercent(firstLossPercent); - mtrVO.setHasRetry(false); AssertLog.info("目标IP {} 丢包率 {}%,无需重试", targetIp, firstLossPercent); } - return mtrVO; } catch (Exception e) { - AssertLog.error("MTR探测异常 - IP: {}", targetIp, e); - mtrVO.setFinalLossPercent(-1.0); // -1表示探测失败 - mtrVO.setErrorMsg(e.getMessage()); - return mtrVO; + handleMtrProbeError(mtrVO, targetIp, e); } + + return mtrVO; } /** - * 执行mtr -r命令并解析结果 + * 创建MTR结果对象 */ - private double executeMtrCommand(String targetIp) { + private MtrVO createMtrVO(String targetIp, String clientId, long timestamp) { + MtrVO mtrVO = new MtrVO(); + mtrVO.setTargetIp(targetIp); + mtrVO.setClientId(clientId); + mtrVO.setTimestamp(timestamp); + return mtrVO; + } + + /** + * 处理MTR探测异常 + */ + private void handleMtrProbeError(MtrVO mtrVO, String targetIp, Exception e) { + AssertLog.error("MTR探测异常 - IP: {}", targetIp, e); + mtrVO.setFinalLossPercent(-1.0); + mtrVO.setErrorMsg(e.getMessage()); + } + + /** + * 执行mtr命令并解析结果 + * @return 跳数信息列表 + */ + private List executeMtrCommand(String targetIp) { Process process = null; try { - // 执行mtr -r -c 10命令 - ProcessBuilder processBuilder = new ProcessBuilder("mtr", "-r", "-c", "10", targetIp); + ProcessBuilder processBuilder = new ProcessBuilder("mtr", "-r", "-c", + String.valueOf(MTR_PACKET_COUNT), targetIp); process = processBuilder.start(); - // 读取命令输出 - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line; - String lastValidLine = null; - - while ((line = reader.readLine()) != null) { - // 跳过标题行,记录最后一条有效数据行 - if (!line.trim().isEmpty() && - !line.startsWith("Start:") && - !line.startsWith("HOST:") && - Character.isDigit(line.trim().charAt(0))) { - lastValidLine = line; - } - } - - // 等待命令执行完成 - boolean finished = process.waitFor(2, TimeUnit.MINUTES); - if (!finished) { - process.destroy(); - throw new RuntimeException("MTR命令执行超时"); - } - - if (lastValidLine == null) { - throw new RuntimeException("MTR命令无有效输出"); - } - - // 解析丢包率 - return parseLossPercentFromMtrOutput(lastValidLine); + return parseMtrOutput(process); } catch (Exception e) { throw new RuntimeException("MTR命令执行异常: " + e.getMessage()); @@ -197,21 +236,167 @@ public class MtrServiceImpl implements MtrService { } /** - * 从MTR输出中解析丢包率 + * 解析MTR命令输出 */ - private double parseLossPercentFromMtrOutput(String mtrOutputLine) { + private List parseMtrOutput(Process process) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + List hops = new ArrayList<>(); + + while ((line = reader.readLine()) != null) { + if (isValidHopLine(line)) { + HopInfoVO hop = parseHopLine(line); + if (hop != null) { + hops.add(hop); + } + } + } + + // 等待命令执行完成 + boolean finished = process.waitFor(MTR_TIMEOUT_MINUTES, TimeUnit.MINUTES); + if (!finished) { + process.destroy(); + throw new RuntimeException("MTR命令执行超时"); + } + + if (hops.isEmpty()) { + throw new RuntimeException("MTR命令无有效输出"); + } + + return hops; + } + + /** + * 检查是否为有效的跳数行 + */ + private boolean isValidHopLine(String line) { + return !line.trim().isEmpty() && + !line.startsWith("Start:") && + !line.startsWith("HOST:") && + Character.isDigit(line.trim().charAt(0)); + } + + /** + * 解析单跳数据行 + */ + private HopInfoVO parseHopLine(String line) { try { - // 使用正则表达式匹配Loss%列 - Pattern pattern = Pattern.compile("(\\d+\\.?\\d*)%"); - Matcher matcher = pattern.matcher(mtrOutputLine); + // 匹配格式: "1.|-- 26.10.194.38 20.0% 10 1.6 1.5 1.3 1.8 0.0" + Pattern pattern = Pattern.compile( + "^\\s*(\\d+)\\.\\|--\\s+(\\S+)\\s+(\\d+\\.?\\d*)%\\s+(\\d+)\\s+(\\d+\\.\\d+)\\s+(\\d+\\.\\d+).*" + ); + Matcher matcher = pattern.matcher(line); if (matcher.find()) { - return Double.parseDouble(matcher.group(1)); + HopInfoVO hop = new HopInfoVO(); + hop.setHopNumber(Integer.parseInt(matcher.group(1))); + + String host = matcher.group(2); + setHostInfo(hop, host); + + hop.setLossPercent(Double.parseDouble(matcher.group(3))); + hop.setAvgLatency(Double.parseDouble(matcher.group(6))); // 平均延迟 + + return hop; } - throw new RuntimeException("未找到Loss%数据: " + mtrOutputLine); + // 处理未知主机的情况(如???) + return parseUnknownHostLine(line); + } catch (Exception e) { - throw new RuntimeException("解析Loss%失败: " + e.getMessage()); + throw new RuntimeException("解析MTR跳数数据失败: " + e.getMessage() + ", 行: " + line); } } -} + + /** + * 设置主机信息(IP地址解析和地理位置查询) + */ + private void setHostInfo(HopInfoVO hop, String host) { + if (isValidIpAddress(host)) { + hop.setIpAddress(host); + // 如果是内网IP,不进行地理位置查询 + if (!isPrivateIp(host)) { + queryIpLocationInfo(hop, host); + } else { + setPrivateIpLocationInfo(hop, host); + } + } else { + hop.setHostname(host); + } + } + + /** + * 检查是否为有效的IP地址 + */ + private boolean isValidIpAddress(String host) { + return host.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}") && !host.equals("???"); + } + + /** + * 为内网IP设置特殊的地理位置信息 + */ + private void setPrivateIpLocationInfo(HopInfoVO hop, String ipAddress) { + hop.setCountry("内网"); + hop.setProvince("内网"); + hop.setCity("内网"); + hop.setIsp("内网"); + } + + /** + * 使用在线服务查询IP地理位置信息 + */ + private void queryIpLocationInfo(HopInfoVO hop, String ipAddress) { + try { + Map locationInfo = onlineIPQueryService.queryIPLocation(ipAddress); + if (locationInfo != null) { + hop.setCountry(locationInfo.get("country")); + hop.setProvince(locationInfo.get("province")); + hop.setCity(locationInfo.get("city")); + hop.setIsp(locationInfo.get("isp")); + } + } catch (Exception e) { + AssertLog.error("查询IP地理位置信息失败: {}", ipAddress, e); + // 设置默认值 + setDefaultLocationInfo(hop); + } + } + + /** + * 设置默认地理位置信息 + */ + private void setDefaultLocationInfo(HopInfoVO hop) { + hop.setCountry("未知"); + hop.setProvince("未知"); + hop.setCity("未知"); + hop.setIsp("未知"); + } + + /** + * 解析未知主机行 + */ + private HopInfoVO parseUnknownHostLine(String line) { + Pattern pattern = Pattern.compile("^\\s*(\\d+)\\.\\|--\\s+(\\S+).*"); + Matcher matcher = pattern.matcher(line); + + if (matcher.find()) { + HopInfoVO hop = new HopInfoVO(); + hop.setHopNumber(Integer.parseInt(matcher.group(1))); + hop.setHostname(matcher.group(2)); + hop.setLossPercent(0.0); + hop.setAvgLatency(0.0); + return hop; + } + + return null; + } + + /** + * 计算最终丢包率(取最后一跳的丢包率) + */ + private double calculateFinalLossPercent(List hops) { + if (CollectionUtil.isEmpty(hops)) { + return 0.0; + } + return hops.get(hops.size() - 1).getLossPercent(); + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/service/impl/OnlineIPQueryService.java b/src/main/java/com/tongran/agent/client/service/impl/OnlineIPQueryService.java new file mode 100644 index 0000000..77f3822 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/OnlineIPQueryService.java @@ -0,0 +1,200 @@ +package com.tongran.agent.client.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * 在线IP查询服务 + */ +@Component +public class OnlineIPQueryService { + + private final RestTemplate restTemplate; + + // 内网IP地址段正则表达式 + private static final Pattern PRIVATE_IP_PATTERN = Pattern.compile( + "^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.|127\\.|169\\.254\\.|0\\.|255\\.)" + ); + + public OnlineIPQueryService() { + this.restTemplate = new RestTemplate(); + } + + /** + * 查询IP地理位置信息 - 使用ip-api.com(免费无需key) + */ + public Map queryIPLocation(String ip) { + Map result = new HashMap<>(); + + // 如果是内网IP,直接返回内网标识 + if (isPrivateIp(ip)) { + return createPrivateIpResult(ip); + } + + try { + // 使用ip-api.com免费服务 + String url = "http://ip-api.com/json/" + ip + "?lang=zh-CN"; + + HttpHeaders headers = new HttpHeaders(); + headers.set("User-Agent", "Mozilla/5.0"); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, HttpMethod.GET, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { + return parseIpApiResponse(response.getBody(), ip); + } + + } catch (Exception e) { + // 如果ip-api.com失败,尝试备用服务 + return queryBackupService(ip); + } + + return createUnknownResult(); + } + + /** + * 检查是否为内网IP + */ + private boolean isPrivateIp(String ip) { + if (ip == null || ip.isEmpty()) { + return true; + } + + // 检查常见的非IP地址情况 + if (ip.equals("???") || ip.equals("*") || ip.equals("localhost")) { + return true; + } + + // 使用正则表达式匹配内网IP段 + return PRIVATE_IP_PATTERN.matcher(ip).find(); + } + + /** + * 解析ip-api.com响应 + */ + private Map parseIpApiResponse(String response, String ip) { + Map result = new HashMap<>(); + + try { + JSONObject json = JSON.parseObject(response); + + if (!"success".equals(json.getString("status"))) { + return createUnknownResult(); + } + + result.put("country", json.getString("country")); + result.put("province", json.getString("regionName")); + result.put("city", json.getString("city")); + result.put("isp", parseISP(json.getString("isp"))); + result.put("query", ip); + result.put("source", "ip-api.com"); + + return result; + + } catch (Exception e) { + return createUnknownResult(); + } + } + + /** + * 备用查询服务 + */ + private Map queryBackupService(String ip) { + try { + // 使用IP.SB作为备用服务 + String url = "https://api.ip.sb/geoip/" + ip; + + HttpHeaders headers = new HttpHeaders(); + headers.set("User-Agent", "Mozilla/5.0"); + headers.set("Accept", "application/json"); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, HttpMethod.GET, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { + return parseIpSbResponse(response.getBody(), ip); + } + + } catch (Exception e) { + // 备用服务也失败 + } + + return createUnknownResult(); + } + + /** + * 解析IP.SB响应 + */ + private Map parseIpSbResponse(String response, String ip) { + Map result = new HashMap<>(); + + try { + JSONObject json = JSON.parseObject(response); + + result.put("country", json.getString("country")); + result.put("province", json.getString("region")); + result.put("city", json.getString("city")); + result.put("isp", json.getString("isp")); + result.put("query", ip); + result.put("source", "ip.sb"); + + return result; + + } catch (Exception e) { + return createUnknownResult(); + } + } + + /** + * 创建内网IP结果 + */ + private Map createPrivateIpResult(String ip) { + Map result = new HashMap<>(); + result.put("country", "内网"); + result.put("province", "内网"); + result.put("city", "内网"); + result.put("isp", "内网"); + result.put("query", ip); + result.put("source", "private"); + return result; + } + + /** + * 标准化运营商名称 + */ + private String parseISP(String isp) { + if (isp == null) return "未知"; + + if (isp.contains("电信") || isp.contains("China Telecom")) return "中国电信"; + if (isp.contains("联通") || isp.contains("China Unicom")) return "中国联通"; + if (isp.contains("移动") || isp.contains("China Mobile")) return "中国移动"; + if (isp.contains("铁通")) return "中国铁通"; + if (isp.contains("教育网")) return "教育网"; + + return isp; + } + + private Map createUnknownResult() { + Map result = new HashMap<>(); + result.put("country", "未知"); + result.put("province", "未知"); + result.put("city", "未知"); + result.put("isp", "未知"); + result.put("source", "unknown"); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/AgentUtil.java b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java index 06d22e1..55d864f 100644 --- a/src/main/java/com/tongran/agent/client/utils/AgentUtil.java +++ b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java @@ -557,11 +557,51 @@ public class AgentUtil { } + /** + * dmidecode -s system-serial-number命令获取SN序列号 + * @return SN序列号,如果获取失败则返回空字符串 + */ + public static String getDeviceSN() { + BufferedReader reader = null; + try { + // 执行dmidecode命令获取系统序列号 + Process process = Runtime.getRuntime().exec("dmidecode -s system-serial-number"); + + // 读取命令输出 + reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String sn = reader.readLine(); + + // 等待命令执行完成 + int exitCode = process.waitFor(); + + // 检查命令是否执行成功且输出不为空 + if (exitCode == 0 && sn != null && !sn.trim().isEmpty()) { + return sn.trim(); + } else { + AssertLog.error("Failed to get SN: Command exited with code " + exitCode); + return ""; + } + + } catch (Exception e) { + // 处理其他未知异常 + AssertLog.error("Unexpected error while getting device SN: " + e.getMessage()); + return ""; + } finally { + // 确保关闭资源 + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + AssertLog.error("Error closing reader: " + e.getMessage()); + } + } + } + } /** * 获取 /etc/issue 文件的第二行内容 * @return 第二行字符串,如果不存在则返回 null */ - public static String getDeviceSN() { + public static String getDeviceSNBak() { Path issuePath = Paths.get("/etc/issue"); if (!Files.exists(issuePath)) { System.err.println("文件不存在: /etc/issue"); diff --git a/src/main/java/com/tongran/agent/client/utils/NetworkUtil.java b/src/main/java/com/tongran/agent/client/utils/NetworkUtil.java index 0ca4130..4289e53 100644 --- a/src/main/java/com/tongran/agent/client/utils/NetworkUtil.java +++ b/src/main/java/com/tongran/agent/client/utils/NetworkUtil.java @@ -1,19 +1,46 @@ package com.tongran.agent.client.utils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + public class NetworkUtil { public static boolean addRoute(String ip, String prefix, String gateway, String dev) { try { ProcessBuilder pb = new ProcessBuilder("ip", "route", "add", - ip + "/" + prefix, "via", gateway, "dev", dev); + ip + "/" + prefix, "via", gateway, "dev", dev); pb.redirectErrorStream(true); Process p = pb.start(); - return p.waitFor() == 0; + + String output = readProcessOutput(p); + int exitCode = p.waitFor(); + + if (exitCode == 0) { + return true; + } else { + // 检查是否是路由已存在的错误 + if (output != null && (output.contains("File exists") || output.contains("RTNETLINK answers: File exists"))) { + AssertLog.info("Route already exists"); + return true; + } + AssertLog.error("Failed to add route: " + output); + return false; + } } catch (Exception e) { - e.printStackTrace(); + AssertLog.error("Exception while adding route: " + e.getMessage()); return false; } } + private static String readProcessOutput(Process p) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) { + return reader.lines().collect(Collectors.joining("\n")); + } catch (IOException e) { + return "Error reading output: " + e.getMessage(); + } + } + public static boolean deleteRoute(String ip, String prefix, String gateway, String dev) { try { ProcessBuilder pb = new ProcessBuilder("ip", "route", "del",