fix: 新增 NetBusinessServiceImpl 实现类,修复 Spring Bean 注入失败

问题:BusinessTasks依赖注入NetBusinessService,但缺少@Service实现类
导致启动报错 NoSuchBeanDefinitionException

解决:从1.20反编译代码恢复 NetBusinessServiceImpl
- 多进程 cgroup + iptables 网络流量监控
- System.out改为 SLF4J Logger
- @Service注解确保Spring自动装配
This commit is contained in:
lee
2026-07-22 17:32:55 +08:00
parent f1fa6dca7c
commit 5ab9a52628
@@ -0,0 +1,370 @@
package com.tongran.agent.client.service.impl;
import com.tongran.agent.client.core.config.ApplicationProperties;
import com.tongran.agent.client.core.vo.NetBusinessVO;
import com.tongran.agent.client.service.NetBusinessService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
public class NetBusinessServiceImpl implements NetBusinessService {
private static final String CGROUP_BASE = "/sys/fs/cgroup/net_cls";
private static final String CGROUP_PREFIX = "agent_";
private final ConcurrentHashMap<String, Long> processClassIdMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, String> processCgroupPathMap = new ConcurrentHashMap<>();
private final Set<String> activeProcesses = ConcurrentHashMap.newKeySet();
private List<String> processList = new ArrayList<>();
@Resource
private ApplicationProperties properties;
@PostConstruct
public void init() {
try {
this.loadProcessListFromFile();
for (String processName : this.processList) {
List<Integer> pids = this.getProcessPids(processName);
if (!pids.isEmpty()) {
this.createProcessCgroup(processName);
this.initProcessChains("iptables", processName);
this.initProcessChains("ip6tables", processName);
this.addProcessToCgroup(processName, pids);
this.activeProcesses.add(processName);
log.info("进程 {} (PID: {}) 初始化完成", processName, pids);
} else {
log.info("进程 {} 当前无PID运行,跳过初始化", processName);
}
}
log.info("=== cgroup多进程监控初始化完成, 监控进程列表: {} ===", this.processList);
} catch (Exception e) {
log.error("初始化失败: {}", e.getMessage(), e);
}
}
@PreDestroy
public void cleanup() {
try {
for (String processName : this.activeProcesses) {
this.cleanupProcessRules("iptables", processName);
this.cleanupProcessRules("ip6tables", processName);
this.removeProcessCgroup(processName);
}
log.info("已清理所有 iptables 规则和 cgroup");
} catch (Exception e) {
log.error("清理失败: {}", e.getMessage(), e);
}
}
@Override
public List<NetBusinessVO> netList(long timestamp) {
List<NetBusinessVO> resultList = new ArrayList<>();
try {
List<String> oldProcessList = new ArrayList<>(this.processList);
this.loadProcessListFromFile();
Set<String> removedProcesses = new HashSet<>(oldProcessList);
removedProcesses.removeAll(this.processList);
if (!removedProcesses.isEmpty()) {
log.info("检测到配置文件中已删除的进程: {},开始清理...", removedProcesses);
for (String processName : removedProcesses) {
this.cleanupProcessRules("iptables", processName);
this.cleanupProcessRules("ip6tables", processName);
this.removeProcessCgroup(processName);
this.activeProcesses.remove(processName);
log.info("已清理进程 {} 的所有规则", processName);
}
}
Set<String> addedProcesses = new HashSet<>(this.processList);
addedProcesses.removeAll(oldProcessList);
if (!addedProcesses.isEmpty()) {
log.info("检测到新增的进程: {}", addedProcesses);
for (String processName : addedProcesses) {
List<Integer> pids = this.getProcessPids(processName);
if (!pids.isEmpty()) {
this.createProcessCgroup(processName);
this.initProcessChains("iptables", processName);
this.initProcessChains("ip6tables", processName);
this.addProcessToCgroup(processName, pids);
this.activeProcesses.add(processName);
log.info("已为进程 {} (PID: {}) 创建规则", processName, pids);
} else {
log.info("进程 {} 当前无PID,暂不创建规则", processName);
}
}
}
for (String processName : this.processList) {
List<Integer> pids = this.getProcessPids(processName);
if (!pids.isEmpty()) {
if (!this.activeProcesses.contains(processName)) {
log.info("进程 {} 已有PID运行但无规则,开始创建规则...", processName);
this.createProcessCgroup(processName);
this.initProcessChains("iptables", processName);
this.initProcessChains("ip6tables", processName);
this.activeProcesses.add(processName);
}
this.addProcessToCgroup(processName, pids);
this.ensureProcessChainsExist("iptables", processName);
this.ensureProcessChainsExist("ip6tables", processName);
long[] v4Stats = this.getTrafficStatsForProcess("iptables", processName);
long[] v6Stats = this.getTrafficStatsForProcess("ip6tables", processName);
NetBusinessVO vo = new NetBusinessVO();
vo.setIpv4InSpeed(v4Stats[0]);
vo.setIpv4OutSpeed(v4Stats[1]);
vo.setIpv6InSpeed(v6Stats[0]);
vo.setIpv6OutSpeed(v6Stats[1]);
vo.setInSpeed(v4Stats[0] + v6Stats[0]);
vo.setOutSpeed(v4Stats[1] + v6Stats[1]);
vo.setTimestamp(timestamp);
vo.setName("total");
vo.setProcessName(processName);
resultList.add(vo);
if (v4Stats[0] > 0 || v4Stats[1] > 0 || v6Stats[0] > 0 || v6Stats[1] > 0) {
log.debug("进程 {} (PID: {}): IPv4收={} 发={}, IPv6收={} 发={}", processName, pids, v4Stats[0], v4Stats[1], v6Stats[0], v6Stats[1]);
}
} else {
log.debug("进程 {} 当前无PID运行,保留已有规则等待进程启动", processName);
}
}
} catch (Exception e) {
log.error("获取网络统计失败: {}", e.getMessage(), e);
}
return resultList;
}
private long generateClassId(String processName) {
int hash = Math.abs(processName.hashCode()) & 0xFFFFF;
if (hash == 0) hash = 1;
return (long) hash & 0xFFFFFFFFL;
}
private void createProcessCgroup(String processName) {
try {
long classId = this.generateClassId(processName);
String cgroupPath = CGROUP_BASE + "/" + CGROUP_PREFIX + processName;
this.executeCommand("mkdir -p " + cgroupPath);
this.executeCommand("echo " + classId + " > " + cgroupPath + "/net_cls.classid");
this.processClassIdMap.put(processName, classId);
this.processCgroupPathMap.put(processName, cgroupPath);
log.info("进程 {} cgroup 创建完成, classid={}, path={}", processName, classId, cgroupPath);
} catch (Exception e) {
log.error("创建进程 cgroup 失败 {}: {}", processName, e.getMessage());
}
}
private void removeProcessCgroup(String processName) {
try {
String cgroupPath = this.processCgroupPathMap.get(processName);
if (cgroupPath != null) {
this.executeCommand("cat " + cgroupPath + "/tasks | while read pid; do echo $pid > " + CGROUP_BASE + "/tasks 2>/dev/null; done");
this.executeCommand("rmdir " + cgroupPath + " 2>/dev/null");
this.processClassIdMap.remove(processName);
this.processCgroupPathMap.remove(processName);
log.info("已删除进程 {} 的 cgroup", processName);
}
} catch (Exception e) {
log.error("删除进程 cgroup 失败 {}: {}", processName, e.getMessage());
}
}
private synchronized void loadProcessListFromFile() {
List<String> newProcessList = new ArrayList<>();
String path = properties.getConfPath() + "/process.conf";
File file = new File(path);
if (file.exists()) {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
if ((line = line.trim()).isEmpty() || line.startsWith("#")) continue;
newProcessList.add(line);
}
log.info("从 {} 加载进程列表: {}", path, newProcessList);
} catch (Exception e) {
log.error("读取配置文件失败 {}: {}", path, e.getMessage());
}
}
this.processList.clear();
this.processList.addAll(newProcessList);
}
private List<Integer> getProcessPids(String processName) {
List<Integer> pids = new ArrayList<>();
try {
Process p = new ProcessBuilder("sh", "-c", "pgrep -x " + processName + " 2>/dev/null").start();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
if (!line.trim().isEmpty()) pids.add(Integer.parseInt(line));
}
}
} catch (Exception ignored) {
}
return pids;
}
private void addProcessToCgroup(String processName, List<Integer> pids) {
String cgroupPath = this.processCgroupPathMap.get(processName);
if (cgroupPath == null) return;
for (Integer pid : pids) {
try {
Process check = new ProcessBuilder("sh", "-c", "cat " + cgroupPath + "/tasks 2>/dev/null | grep -q " + pid).start();
if (check.waitFor() == 0) continue;
this.executeCommand("echo " + pid + " > " + cgroupPath + "/tasks");
log.debug("进程 {} ({}) 已加入 cgroup", pid, processName);
} catch (Exception ignored) {
}
}
}
private String generateSafeChainName(String processName) {
String safe = processName.replaceAll("[^a-zA-Z0-9]", "_");
String hash = Integer.toHexString(processName.hashCode());
while (hash.length() < 8) hash = "0" + hash;
if (hash.length() > 8) hash = hash.substring(0, 8);
String prefix = safe.length() > 12 ? safe.substring(0, 12) : safe;
return prefix + "_" + hash;
}
private String buildChainName(String safeName, String suffix, String direction) {
String baseName = "am_" + safeName;
int maxNeeded = Math.max(baseName.length() + 6, baseName.length() + 7);
if (maxNeeded > 28) {
int baseMaxLen = 21;
if (baseName.length() > baseMaxLen) {
String prefix = baseName.substring(0, 8);
String suffixPart = baseName.substring(baseName.length() - (baseMaxLen - 9));
baseName = prefix + "_" + suffixPart;
}
}
return baseName + "_" + suffix + "_" + direction;
}
private long getProcessClassId(String processName) {
Long classId = this.processClassIdMap.get(processName);
if (classId == null) {
classId = this.generateClassId(processName);
this.processClassIdMap.put(processName, classId);
}
return classId;
}
private void initProcessChains(String command, String processName) {
long classId = this.getProcessClassId(processName);
String safeName = this.generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = this.buildChainName(safeName, suffix, "in");
String outChain = this.buildChainName(safeName, suffix, "out");
this.initChain(command, inChain, "INPUT", classId);
this.initChain(command, outChain, "OUTPUT", classId);
}
private void ensureProcessChainsExist(String command, String processName) {
long classId = this.getProcessClassId(processName);
String safeName = this.generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = this.buildChainName(safeName, suffix, "in");
String outChain = this.buildChainName(safeName, suffix, "out");
this.ensureChainExists(command, inChain, "INPUT", classId);
this.ensureChainExists(command, outChain, "OUTPUT", classId);
}
private void ensureChainExists(String command, String chainName, String hookChain, long classId) {
try {
Process check = new ProcessBuilder("sh", "-c", command + " -L " + chainName + " -n >/dev/null 2>&1").start();
if (check.waitFor() != 0) {
this.executeCommand(command + " -N " + chainName);
this.executeCommand(command + " -A " + chainName + " -m cgroup --cgroup " + classId + " -j RETURN");
this.executeCommand(command + " -A " + chainName + " -j RETURN");
this.executeCommand(command + " -I " + hookChain + " 1 -m cgroup --cgroup " + classId + " -j " + chainName);
log.info("{} 链 {} 初始化完成 (classid={})", command, chainName, classId);
}
} catch (Exception e) {
log.error("确保链存在失败 {}: {}", chainName, e.getMessage());
}
}
private void initChain(String command, String chainName, String hookChain, long classId) {
try {
this.executeCommand(command + " -N " + chainName + " 2>/dev/null");
this.executeCommand(command + " -F " + chainName);
this.executeCommand(command + " -A " + chainName + " -m cgroup --cgroup " + classId + " -j RETURN");
this.executeCommand(command + " -A " + chainName + " -j RETURN");
Process check = new ProcessBuilder("sh", "-c", command + " -C " + hookChain + " -m cgroup --cgroup " + classId + " -j " + chainName + " 2>/dev/null").start();
if (check.waitFor() != 0) {
this.executeCommand(command + " -I " + hookChain + " 1 -m cgroup --cgroup " + classId + " -j " + chainName);
log.debug("{} 插入规则到 {} 成功", command, hookChain);
} else {
log.debug("{} 规则已存在于 {},跳过插入", command, hookChain);
}
log.debug("{} 链 {} 初始化完成 (classid={})", command, chainName, classId);
} catch (Exception e) {
log.error("初始化链 {} 失败: {}", chainName, e.getMessage());
}
}
private long[] getTrafficStatsForProcess(String command, String processName) {
long classId = this.getProcessClassId(processName);
String safeName = this.generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = this.buildChainName(safeName, suffix, "in");
String outChain = this.buildChainName(safeName, suffix, "out");
return new long[]{this.getChainBytes(command, inChain), this.getChainBytes(command, outChain)};
}
private void cleanupProcessRules(String command, String processName) {
try {
Long classId = this.processClassIdMap.get(processName);
if (classId == null) return;
String safeName = this.generateSafeChainName(processName);
String suffix = command.equals("iptables") ? "v4" : "v6";
String inChain = this.buildChainName(safeName, suffix, "in");
String outChain = this.buildChainName(safeName, suffix, "out");
this.executeCommand(command + " -D INPUT -m cgroup --cgroup " + classId + " -j " + inChain + " 2>/dev/null");
this.executeCommand(command + " -D OUTPUT -m cgroup --cgroup " + classId + " -j " + outChain + " 2>/dev/null");
this.executeCommand(command + " -F " + inChain + " 2>/dev/null");
this.executeCommand(command + " -F " + outChain + " 2>/dev/null");
this.executeCommand(command + " -X " + inChain + " 2>/dev/null");
this.executeCommand(command + " -X " + outChain + " 2>/dev/null");
log.info("已清理 {} 中进程 {} 的规则", command, processName);
} catch (Exception e) {
log.error("清理进程规则失败 {}: {}", processName, e.getMessage());
}
}
private long getChainBytes(String command, String chainName) {
try {
Process p = new ProcessBuilder("sh", "-c", command + " -L " + chainName + " -v -n -x | grep RETURN | head -1").start();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line = br.readLine();
if (line == null || line.isEmpty()) return 0L;
String[] parts = line.trim().split("\\s+");
if (parts.length < 2) return 0L;
return Long.parseLong(parts[1]);
}
} catch (Exception e) {
log.error("获取链 {} 统计失败: {}", chainName, e.getMessage());
}
return 0L;
}
private void executeCommand(String command) throws Exception {
Process p = new ProcessBuilder("sh", "-c", command).start();
int exitCode = p.waitFor();
if (exitCode != 0) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()))) {
String error = br.lines().reduce("", (a, b) -> a + "\n" + b);
if (!error.isEmpty() && !error.contains("No such file") && !error.contains("File exists")) {
log.warn("命令执行警告: {} -> {}", command, error);
}
}
}
}
}