package top.lvzhiqiang.util; import com.xxl.job.core.context.XxlJobHelper; import org.jsoup.Jsoup; import org.springframework.stereotype.Component; import top.lvzhiqiang.entity.ProxyNode; import top.lvzhiqiang.mapper.XrayProxyMapper; import javax.annotation.Resource; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.concurrent.TimeUnit; /** * 管理 Xray 的配置和进程 */ @Component public class XrayManager { private static final String XRAY_PATH = "/usr/program/xray/xray"; private static final String CONFIG_PATH = "/usr/program/xray/config.json"; private static Process xrayProcess; private static final int LOCAL_SOCKS_PORT = 10808; @Resource private XrayProxyMapper xrayProxyMapper; // 1. 生成新的 Xray 配置 private String generateXrayConfig(ProxyNode node) { return XrayConfigGenerator.generateFullConfig(node, LOCAL_SOCKS_PORT); } // 2. 停止旧的 Xray 进程 private void stopXray() { if (xrayProcess != null && xrayProcess.isAlive()) { XxlJobHelper.log("Stopping current Xray process..."); xrayProcess.destroy(); try { // 等待进程优雅关闭 if (!xrayProcess.waitFor(5, TimeUnit.SECONDS)) { xrayProcess.destroyForcibly(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); xrayProcess.destroyForcibly(); } } } // 3. 启动新的 Xray 进程 private void startXray() throws IOException { XxlJobHelper.log("Starting new Xray process..."); ProcessBuilder pb = new ProcessBuilder(XRAY_PATH, "run", "-c", CONFIG_PATH); // 将输出重定向到文件或父进程,以便调试 pb.redirectErrorStream(true); xrayProcess = pb.start(); // 确保进程在 JVM 退出时被销毁 Runtime.getRuntime().addShutdownHook(new Thread(this::stopXray)); // 启动后等待几秒确保端口监听 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } XxlJobHelper.log("New Xray process started."); } // 4. 切换节点的主逻辑 public void switchNode() { boolean flag = false; Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", LOCAL_SOCKS_PORT)); try { verifySSH("https://www.google.com", Proxy.NO_PROXY); String javdbTitle = verifySSH("https://www.javdb.com", proxy); if (!javdbTitle.contains("JavDB")) { flag = true; } } catch (IOException e) { flag = true; } if (flag) { List nodes = xrayProxyMapper.findProxyNodeList(); for (ProxyNode node : nodes) { try { // A. 生成新配置并写入文件 String newConfig = generateXrayConfig(node); Files.write(Paths.get(CONFIG_PATH), newConfig.getBytes(StandardCharsets.UTF_8)); // B. 停止旧进程 stopXray(); // C. 启动新进程 startXray(); // D. 验证连接 (可选但推荐) String javdbTitle = verifySSH("https://www.javdb.com", proxy); if (javdbTitle.contains("JavDB")) { break; } } catch (Exception e) { System.err.println("Node " + node.getId() + " failed: " + e.getMessage()); // 标记节点失效 db.updateNodeStatus(node.getId(), "dead"); // 继续循环尝试下一个节点 } } } } // 5. 连接测试(必须用原生 Java 网络库) public String verifySSH(String targetUrl, Proxy proxy) throws IOException { // 创建 SOCKS 代理对象 XxlJobHelper.log("Attempting to crawl via SOCKS5 proxy..."); org.jsoup.nodes.Document doc = Jsoup.connect(targetUrl) .proxy(proxy) .timeout(10000) .get(); String title = doc.title(); XxlJobHelper.log("Crawl successful! Title: " + title); return title; } }