zhiqiang.lv vor 2 Monaten
Ursprung
Commit
4755d4c0e2

+ 65 - 0
src/main/java/top/lvzhiqiang/controller/TestController.java

@@ -0,0 +1,65 @@
+package top.lvzhiqiang.controller;
+
+import com.jcraft.jsch.JSchException;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import top.lvzhiqiang.dto.R;
+import top.lvzhiqiang.util.SSHDynamicProxy;
+import top.lvzhiqiang.util.StringUtils;
+
+@RestController
+@RequestMapping("/test")
+public class TestController {
+
+    @GetMapping("/test1")
+    public Object test1() {
+        SSHDynamicProxy proxy = new SSHDynamicProxy();
+        String title = "空";
+        try {
+            // SSH 服务器信息
+            String SSH_HOST = "204.13.154.148";
+            int SSH_PORT = 22;
+            String SSH_USER = "crawler";
+            String SSH_PASSWORD = "xxxx";
+
+            // 启动 SSH 动态代理
+            proxy.startDynamicProxy(SSH_HOST, SSH_PORT, SSH_USER, SSH_PASSWORD);
+
+            title = proxy.verifySSH("https://www.google.com");
+
+            return title;
+        } catch (JSchException e) {
+            e.printStackTrace();
+            System.err.println("SSH Error (check host/user/pass or SSH service): " + e.getMessage());
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println("Jsoup Error (check connectivity/target site): " + e.getMessage());
+        } finally {
+            // 4. 关闭 SSH 会话
+            proxy.stop();
+        }
+
+        return R.ok().message(title);
+    }
+
+    @GetMapping("/test2")
+    public Object test2(String targetUrl) {
+        SSHDynamicProxy proxy = new SSHDynamicProxy();
+        String title = "空";
+        try {
+            title = proxy.verifySSH(StringUtils.isNotEmpty(targetUrl) ? targetUrl : "https://www.google.com");
+        } catch (JSchException e) {
+            e.printStackTrace();
+            System.err.println("SSH Error (check host/user/pass or SSH service): " + e.getMessage());
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println("Jsoup Error (check connectivity/target site): " + e.getMessage());
+        } finally {
+            // 4. 关闭 SSH 会话
+            proxy.stop();
+        }
+
+        return R.ok().message(title);
+    }
+}

+ 63 - 0
src/main/java/top/lvzhiqiang/entity/ProxyNode.java

@@ -0,0 +1,63 @@
+package top.lvzhiqiang.entity;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+
+/**
+ * 代理节点表
+ *
+ * @author lvzhiqiang
+ * 2025/10/17 17:16
+ */
+@Data
+public class ProxyNode implements Serializable {
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    private String protocol;
+
+    private String address;
+
+    private Integer port;
+
+    private String password;
+
+    private String status;
+
+    private Integer priority;
+
+    private String countryCode;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime lastTestedAt;
+
+    private JSONObject extraConfig;
+
+    /**
+     * 删除标志{1:正常,2:已删除}
+     */
+    private Integer deleteFlag;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    /**
+     * 最后修改时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime modifyTime;
+}

+ 1 - 1
src/main/java/top/lvzhiqiang/service/impl/Crawler4JavdbServiceImpl.java

@@ -72,7 +72,7 @@ public class Crawler4JavdbServiceImpl implements Crawler4JavdbService {
             if ("dev".equals(env)) {
                 proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 1080));
             } else {
-                proxy = Proxy.NO_PROXY;
+                proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 10808));
             }
         }
     }

+ 0 - 72
src/main/java/top/lvzhiqiang/util/JsoupWithSshTunnel.java

@@ -1,72 +0,0 @@
-package top.lvzhiqiang.util;
-
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.Session;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-
-public class JsoupWithSshTunnel {
-    private static final String REMOTE_HOST = "144.34.207.84";  // 远程Trojan服务器IP/域名
-    private static final int REMOTE_PORT = 26376;  // SSH端口,通常22
-    private static final String USERNAME = "root";
-    private static final String PASSWORD = "SnS2fm42wWWc";  // 或用私钥
-    private static final int LOCAL_SOCKS_PORT = 1235;  // 本地SOCKS5端口
-
-    private Session sshSession;
-
-    // 初始化SSH隧道
-    public void initTunnel() throws Exception {
-        JSch jsch = new JSch();
-        // 如果用私钥:jsch.addIdentity("/path/to/private-key");
-        sshSession = jsch.getSession(USERNAME, REMOTE_HOST, REMOTE_PORT);
-        sshSession.setPassword(PASSWORD);
-        sshSession.setConfig("StrictHostKeyChecking", "no");  // 跳过主机密钥检查(生产环境慎用)
-        sshSession.connect(30000);  // 连接超时30秒
-
-        // 设置动态端口转发(SOCKS5)
-        // 注意:这里"127.0.0.1", 0 表示动态转发到远程的任意端口
-        sshSession.setPortForwardingL(LOCAL_SOCKS_PORT, "127.0.0.1", 0);  // 简化版动态转发
-        System.out.println("SSH隧道建立成功,本地SOCKS5代理: 127.0.0.1:" + LOCAL_SOCKS_PORT);
-    }
-
-    // 使用Jsoup爬取(通过隧道代理)
-    public Document fetchWithProxy(String url) throws Exception {
-        Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", LOCAL_SOCKS_PORT));
-        proxy = Proxy.NO_PROXY;
-
-        return Jsoup.connect(url)
-                .proxy(proxy)
-                .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
-                .timeout(10000)
-                .get();
-    }
-
-    // 关闭隧道
-    public void closeTunnel() {
-        if (sshSession != null && sshSession.isConnected()) {
-            sshSession.disconnect();
-            System.out.println("SSH隧道已关闭");
-        }
-    }
-
-    public static void main(String[] args) {
-        JsoupWithSshTunnel app = new JsoupWithSshTunnel();
-        try {
-            app.initTunnel();  // 先建隧道
-
-            // 爬取示例
-            Document doc = app.fetchWithProxy("https://www.baidu.com");
-            System.out.println("Title: " + doc.title());
-
-            // 你的业务逻辑...
-
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            app.closeTunnel();  // 结束后关闭
-        }
-    }
-}

+ 66 - 26
src/main/java/top/lvzhiqiang/util/SSHDynamicProxy.java

@@ -1,59 +1,99 @@
 package top.lvzhiqiang.util;
 
 import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
 import com.jcraft.jsch.Session;
-import org.jsoup.Connection;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
+import org.jsoup.select.Elements;
 
 import java.net.InetSocketAddress;
 import java.net.Proxy;
+import java.util.HashMap;
+import java.util.Map;
 
 public class SSHDynamicProxy {
     private JSch jsch;
     private Session session;
-    private final int localPort = 1111;  // 本地 SOCKS5 端口
+
+    // SSH 服务器信息
+    private static final String SSH_HOST = "204.13.154.148"; // Trojan/SSH 服务器 IP
+    private static final int SSH_PORT = 22;
+    private static final String SSH_USER = "crawler";
+    private static final String SSH_PASSWORD = "xxxxx";
+
+    // 本地 SOCKS 代理端口
+    private static final int LOCAL_SOCKS_PORT = 10808;
 
     /**
      * SSH 服务器参数:host/user/password 是 SSH 登录凭据
      */
-    public void startDynamicProxy(String sshHost, int sshPort, String sshUser, String sshPassword) throws Exception {
+    public void startDynamicProxy(String sshHost, int sshPort, String sshUser, String sshPassword) throws JSchException {
+        // 1. 创建 SSH 会话
         jsch = new JSch();
-
-        // 创建 SSH 会话
         session = jsch.getSession(sshUser, sshHost, sshPort);  // sshPort 默认为 22
         session.setPassword(sshPassword);  // SSH 登录密码,不是 Trojan 密码
 
-        // 配置:跳过主机密钥检查(生产环境不推荐)
+        // 跳过主机密钥检查 (生产环境不推荐,应使用 known_hosts)
         java.util.Properties config = new java.util.Properties();
         config.put("StrictHostKeyChecking", "no");
+
+        // 启用客户端发送心跳包 (Client Alive),告诉远程服务器,每隔 5 秒发送一个心跳包
+        config.put("ServerAliveInterval", "5");
+        config.put("ServerAliveCountMax", "3"); // 如果 3 次心跳失败,断开连接
+
+        // 增加隧道稳定性配置:
+        // 降低窗口大小,以适应复杂的网络环境和MTU不匹配
+        config.put("session.connect.timeout", "30000"); // 增加连接超时
+        config.put("fsacp-streamlocal-window-size", "32768"); // 降低窗口大小
+        config.put("fsacp-remote-window-size", "32768");     // 降低窗口大小
+
         session.setConfig(config);
 
         // 建立连接
         session.connect();
         System.out.println("SSH 连接成功: " + sshHost);
 
-        // 建立动态端口转发(SOCKS5 代理)
-        int assignedPort = session.setPortForwardingL(localPort, "0.0.0.0", 0);
+        // 2. 启动动态端口转发 (创建本地 SOCKS 代理)
+        // L: Dynamic Forwarding (动态转发)
+        int assignedPort = session.setPortForwardingL(LOCAL_SOCKS_PORT, "127.0.0.1", 0);
+        // 这里的 "127.0.0.1" 和 "0" 是动态转发的标准设置
         System.out.println("SOCKS5 代理启动在: 127.0.0.1:" + assignedPort);
     }
 
     /**
      * 通过 SSH 隧道爬取网页
      */
-    public Document fetchViaSSH(String targetUrl) throws Exception {
+    public void fetchViaSSH(String targetUrl) throws Exception {
         // 创建 SOCKS5 代理,指向本地端口
-        Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", localPort));
+        Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", LOCAL_SOCKS_PORT));
 
-        Connection connection = Jsoup.connect(targetUrl)
-                .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
-                .proxy(proxy)  // 使用本地 SOCKS5 代理
-                .timeout(30000)
-                .followRedirects(true);
+        Map<String, String> headerMap = new HashMap<>();
+        headerMap.put("referer", targetUrl);
+        Document document = JsoupUtil.requestDocument(targetUrl, JsoupUtil.HTTP_GET, proxy, null, headerMap, null);
+        Elements itembSelects = document.select("div.movie-list").select("div.item");
 
-        Document doc = connection.get();
-        System.out.println("页面标题: " + doc.title());
-        return doc;
+        System.out.println("页面标题: " + itembSelects.text());
+    }
+
+    public String verifySSH(String targetUrl) throws Exception {
+        // 创建 SOCKS 代理对象
+        Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", LOCAL_SOCKS_PORT));
+
+        System.out.println("Attempting to crawl via SOCKS5 proxy...");
+
+       /* Map<String, String> headerMap = new HashMap<>();
+        Document doc = JsoupUtil.requestDocument(targetUrl, JsoupUtil.HTTP_GET, proxy, null, headerMap, null);*/
+
+        org.jsoup.nodes.Document doc = Jsoup.connect(targetUrl)
+                .proxy(proxy)
+                .timeout(10000)
+                .get();
+
+        String title = doc.title();
+        System.out.println("Crawl successful! Title: " + title);
+
+        return title;
     }
 
     public void stop() {
@@ -66,21 +106,21 @@ public class SSHDynamicProxy {
     public static void main(String[] args) {
         SSHDynamicProxy proxy = new SSHDynamicProxy();
         try {
-            // SSH 服务器配置(不是 Trojan 配置)
-            String sshHost = "144.34.207.84";  // SSH 服务器 IP/域名
-            String sshUser = "root";                 // SSH 用户名
-            String sshPassword = "SnS2fm42wWWc"; // SSH 登录密码
-
             // 启动 SSH 动态代理
-            proxy.startDynamicProxy(sshHost, 26376, sshUser, sshPassword);
+            proxy.startDynamicProxy(SSH_HOST, SSH_PORT, SSH_USER, SSH_PASSWORD);
 
             // 测试访问(通过 SSH 隧道)
-            Document doc = proxy.fetchViaSSH("https://www.google.com");
-            System.out.println("IP 检查: " + doc.select("pre").text()); // 应显示 SSH 服务器 IP
+            // proxy.fetchViaSSH("https://javdb.com/lists/Nw7OzB?lst=0");
 
+            proxy.verifySSH("https://www.google.com");
+        } catch (JSchException e) {
+            e.printStackTrace();
+            System.err.println("SSH Error (check host/user/pass or SSH service): " + e.getMessage());
         } catch (Exception e) {
             e.printStackTrace();
+            System.err.println("Jsoup Error (check connectivity/target site): " + e.getMessage());
         } finally {
+            // 4. 关闭 SSH 会话
             proxy.stop();
         }
     }

+ 100 - 0
src/main/java/top/lvzhiqiang/util/XrayConfigGenerator.java

@@ -0,0 +1,100 @@
+package top.lvzhiqiang.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import top.lvzhiqiang.entity.ProxyNode;
+
+public class XrayConfigGenerator {
+    private static final int LOCAL_SOCKS_PORT = 10808;
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    // 完整的 JSON 配置生成 (返回字符串)
+    public String generateFullConfig(ProxyNode node) {
+        ObjectNode fullConfig = MAPPER.createObjectNode();
+
+        // 1. Log 配置
+        fullConfig.putObject("log").put("loglevel", "warning");
+
+        // 2. Inbound (本地 SOCKS5 代理)
+        ArrayNode inbounds = fullConfig.putArray("inbounds");
+        ObjectNode inbound = inbounds.addObject();
+        inbound.put("port", LOCAL_SOCKS_PORT);
+        inbound.put("listen", "127.0.0.1");
+        inbound.put("protocol", "socks");
+        inbound.putObject("settings").put("auth", "no").put("udp", false);
+
+        // 3. Outbound (协议转换)
+        ArrayNode outbounds = fullConfig.putArray("outbounds");
+        outbounds.add(generateOutbound(node));
+
+        try {
+            return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(fullConfig);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to serialize Xray config", e);
+        }
+    }
+
+    // 生成特定协议的 Outbound 配置
+    private ObjectNode generateOutbound(ProxyNode node) {
+        ObjectNode outbound = MAPPER.createObjectNode();
+        outbound.put("protocol", node.getProtocol());
+
+        // Settings 结构
+        ObjectNode settings = outbound.putObject("settings");
+        ArrayNode servers = settings.putArray("servers");
+        ObjectNode server = servers.addObject();
+        server.put("address", node.getAddress());
+        server.put("port", node.getPort());
+
+        // Stream Settings (TLS, Network)
+        ObjectNode streamSettings = outbound.putObject("streamSettings");
+        streamSettings.put("network", "tcp"); // 默认
+
+        // 根据协议类型,添加额外的协议配置
+        switch (node.getProtocol().toLowerCase()) {
+            case "trojan":
+                configureTrojan(server, streamSettings, node);
+                break;
+            case "vless":
+                //configureVless(server, streamSettings, node);
+                break;
+            case "vmess":
+                //configureVmess(server, streamSettings, node);
+                break;
+            case "ssr":
+                //configureShadowsocksR(server, streamSettings, node);
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported protocol: " + node.getProtocol());
+        }
+        return outbound;
+    }
+
+    // --- 协议配置细节(仅展示 Trojan 示例)---
+    private void configureTrojan(ObjectNode server, ObjectNode streamSettings, ProxyNode node) {
+        server.put("password", node.getPassword());
+
+        // 检查 extraConfig 中的 XTLS/Flow
+        if (node.getExtraConfig() != null && node.getExtraConfig().containsKey("flow")) {
+            server.put("flow", (String) node.getExtraConfig().get("flow"));
+        }
+
+        // TLS 配置
+        streamSettings.put("security", "tls");
+        ObjectNode tlsSettings = streamSettings.putObject("tlsSettings");
+
+        // 确保 SNI 字段存在
+        String sni = (String) node.getExtraConfig().getOrDefault("sni", node.getAddress());
+        tlsSettings.put("serverName", sni);
+
+        // TLS 版本兼容性修复
+        String maxVersion = (String) node.getExtraConfig().getOrDefault("max_tls_version", "1.3");
+        tlsSettings.put("maxVersion", maxVersion);
+
+        tlsSettings.put("allowInsecure", false);
+    }
+
+    // TODO: 实现 configureVless, configureVmess, configureShadowsocksR 方法...
+    // 这些方法将从 node.getExtraConfig() 中读取 network, path, uuid, encryption 等字段并配置 settings/streamSettings
+}