소스 검색

update:xray proxy改造v1

lvzhiqiang 2 달 전
부모
커밋
ad87d2a8d4

+ 0 - 83
src/main/java/top/lvzhiqiang/config/MyJavJobs.java

@@ -32,9 +32,6 @@ import java.util.stream.Collectors;
 public class MyJavJobs {
 
     @Resource
-    private VideoSitePoolMapper videoSitePoolMapper;
-
-    @Resource
     private BgService bgService;
     @Resource
     private CrawlerService crawlerService;
@@ -48,86 +45,6 @@ public class MyJavJobs {
     private static final String SCHEDULED_ZONE = "Asia/Shanghai";
 
     /**
-     * 每天06:00 校验站点有效性
-     */
-    @Scheduled(cron = "0 0 6 * * ?", zone = SCHEDULED_ZONE)
-    //@Scheduled(cron = "0 10 19 * * ?",zone = SCHEDULED_ZONE)
-    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
-    public void checkVideoSite() {
-        log.warn("checkVideoSite开始==============================");
-
-        // 获取javbus官方地址
-        DicCode dicCode = WebAppConfig.dicCodeList.stream().filter(x -> 2 == x.getType() && "javbus".equals(x.getCodeKey())).findFirst().get();
-        if (dicCode == null) {
-            log.warn("javbus官方站点为Null");
-            return;
-        }
-
-        // 获取javbusUrlList
-        List<String> javbusUrlList = videoSitePoolMapper.findUrlByType(1);
-
-        // 获取javbusNewUrlList
-        Set<String> javbusNewUrlList = new HashSet<>();
-        try {
-            Document document = Jsoup.connect(dicCode.getCodeValue()).timeout(50000).ignoreContentType(true)
-                    .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36")
-                    .header("referer", "https://www.javbus.com/").get();
-
-            Elements ahrefList = document.select("strong:contains(防屏蔽地址)").next("a");
-            for (Element element : ahrefList) {
-                String text = element.text();
-                log.warn("Jsoup获取{}防屏蔽地址:{}", dicCode.getCodeValue(), text);
-                javbusNewUrlList.add(text);
-            }
-        } catch (Exception e) {
-            log.error("Jsoup获取{}防屏蔽地址异常", dicCode.getCodeValue(), e);
-        }
-
-        if (javbusNewUrlList.size() == 0) {
-            log.warn("javbusNewUrlList为空");
-        }
-        if (javbusNewUrlList.size() == 0 && javbusUrlList.size() == 0) {
-            log.warn("javbusUrlList和javbusNewUrlList为空");
-            return;
-        }
-
-        // 校验新地址
-        List<String> javbusNewUrlFinalList = javbusNewUrlList.stream().filter(e -> !javbusUrlList.contains(e)).collect(Collectors.toList());
-        List<VideoSitePool> videoSitePoolList = new ArrayList<>();
-        VideoSitePool videoSitePool;
-        for (String javbusNewUrlFinal : javbusNewUrlFinalList) {
-            try {
-                Jsoup.connect(javbusNewUrlFinal).timeout(50000);
-
-                videoSitePool = new VideoSitePool();
-                videoSitePool.setUrl(javbusNewUrlFinal);
-                videoSitePool.setType(1);
-                videoSitePoolList.add(videoSitePool);
-                log.warn("javbusNewUrlFinalList:javbus防屏蔽地址有效!javbusUrl={}", javbusNewUrlFinal);
-            } catch (Exception e) {
-                log.error("javbusNewUrlFinalList:javbus防屏蔽地址失效!javbusUrl={}", javbusNewUrlFinal, e);
-            }
-        }
-        if (videoSitePoolList.size() > 0) {
-            videoSitePoolMapper.insertList(videoSitePoolList);
-        }
-        // 校验存量地址
-        for (String javbusUrl : javbusUrlList) {
-            int deleteFlag = 1;
-            try {
-                Jsoup.connect(javbusUrl).timeout(50000);
-                log.warn("javbusUrlList:javbus防屏蔽地址有效!javbusUrl={}", javbusUrl);
-            } catch (Exception e) {
-                deleteFlag = 2;
-                log.error("javbusUrlList:javbus防屏蔽地址失效!javbusUrl={}", javbusUrl, e);
-            }
-            videoSitePoolMapper.updateDeleteFlag(javbusUrl, deleteFlag);
-        }
-
-        log.warn("checkVideoSite结束==============================");
-    }
-
-    /**
      * 每天20:00 Jsoup码池
      */
     @Scheduled(cron = "0 00 20 * * ?", zone = SCHEDULED_ZONE)

+ 7 - 2
src/main/java/top/lvzhiqiang/entity/ProxyNode.java

@@ -44,6 +44,11 @@ public class ProxyNode implements Serializable {
 
     private JSONObject extraConfig;
 
+    // MyBatis 使用这个 Setter 将 VARCHAR 内容存入
+    public void setExtraConfig(String extraConfig) {
+        this.extraConfig = JSONObject.parseObject(extraConfig);
+    }
+
     /**
      * 删除标志{1:正常,2:已删除}
      */
@@ -53,11 +58,11 @@ public class ProxyNode implements Serializable {
      * 创建时间
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime createTime;
+    private LocalDateTime createdAt;
 
     /**
      * 最后修改时间
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    private LocalDateTime modifyTime;
+    private LocalDateTime updatedAt;
 }

+ 90 - 0
src/main/java/top/lvzhiqiang/job/JavJob.java

@@ -0,0 +1,90 @@
+package top.lvzhiqiang.job;
+
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import top.lvzhiqiang.service.Crawler4JavbusService;
+import top.lvzhiqiang.service.Crawler4JavdbService;
+
+import javax.annotation.Resource;
+
+/**
+ * javdb更新任务
+ *
+ * @author lvzhiqiang
+ * 2025/10/15 17:58
+ */
+@Component
+@Slf4j
+public class JavJob {
+
+    @Resource
+    private Crawler4JavdbService crawler4JavdbService;
+
+    @Resource
+    private Crawler4JavbusService crawler4JavbusService;
+
+    /**
+     * monitorFavorites任务处理器
+     *
+     * @return com.xxl.job.core.biz.model.ReturnT<java.lang.String>
+     * @author lvzhiqiang
+     * 2025/10/15 17:58
+     */
+    @XxlJob("monitorJavdbFavoritesJobHandler")
+    public ReturnT<String> monitorJavdbFavoritesJobHandler() {
+        try {
+            crawler4JavdbService.monitorJavdbFavorites();
+        } catch (Exception e) {
+            log.error("monitorJavdbFavoritesJobHandler exception", e);
+            XxlJobHelper.log(e);
+            return ReturnT.FAIL;
+        }
+
+        return ReturnT.SUCCESS;
+    }
+
+    /**
+     * monitorActors任务处理器
+     *
+     * @return com.xxl.job.core.biz.model.ReturnT<java.lang.String>
+     * @author lvzhiqiang
+     * 2025/10/15 17:58
+     */
+    @XxlJob("monitorJavdbActorsJobHandler")
+    public ReturnT<String> monitorJavdbActorsJobHandler() {
+        try {
+            crawler4JavdbService.monitorJavdbActors();
+        } catch (Exception e) {
+            log.error("monitorJavdbActorsJobHandler exception", e);
+            XxlJobHelper.log(e);
+            return ReturnT.FAIL;
+        }
+
+        return ReturnT.SUCCESS;
+    }
+
+    /**
+     * checkJavbusVideoSite任务处理器
+     * 每天06:00 校验站点有效性
+     *
+     * @return com.xxl.job.core.biz.model.ReturnT<java.lang.String>
+     * @author lvzhiqiang
+     * 2025/10/19 17:14
+     */
+    @XxlJob("checkJavbusVideoSiteJobHandler")
+    public ReturnT<String> checkJavbusVideoSiteJobHandler() {
+        try {
+            crawler4JavbusService.checkJavbusVideoSite();
+        } catch (Exception e) {
+            log.error("checkJavbusVideoSiteJobHandler exception", e);
+            XxlJobHelper.log(e);
+            return ReturnT.FAIL;
+        }
+
+        return ReturnT.SUCCESS;
+    }
+
+}

+ 0 - 64
src/main/java/top/lvzhiqiang/job/JavdbJob.java

@@ -1,64 +0,0 @@
-package top.lvzhiqiang.job;
-
-import com.xxl.job.core.biz.model.ReturnT;
-import com.xxl.job.core.context.XxlJobHelper;
-import com.xxl.job.core.handler.annotation.XxlJob;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-import top.lvzhiqiang.service.Crawler4JavdbService;
-
-import javax.annotation.Resource;
-
-/**
- * javdb更新任务
- *
- * @author lvzhiqiang
- * 2025/10/15 17:58
- */
-@Component
-@Slf4j
-public class JavdbJob {
-
-    @Resource
-    private Crawler4JavdbService crawler4JavdbService;
-
-    /**
-     * monitorFavorites任务处理器
-     *
-     * @return com.xxl.job.core.biz.model.ReturnT<java.lang.String>
-     * @author lvzhiqiang
-     * 2025/10/15 17:58
-     */
-    @XxlJob("monitorFavoritesJobHandler")
-    public ReturnT<String> monitorFavoritesJobHandler() {
-        try {
-            crawler4JavdbService.monitorFavorites();
-        } catch (Exception e) {
-            log.error("monitorFavoritesJobHandler exception", e);
-            XxlJobHelper.log(e);
-            return ReturnT.FAIL;
-        }
-
-        return ReturnT.SUCCESS;
-    }
-
-    /**
-     * monitorActors任务处理器
-     *
-     * @return com.xxl.job.core.biz.model.ReturnT<java.lang.String>
-     * @author lvzhiqiang
-     * 2025/10/15 17:58
-     */
-    @XxlJob("monitorActorsJobHandler")
-    public ReturnT<String> monitorActorsJobHandler() {
-        try {
-            crawler4JavdbService.monitorActors();
-        } catch (Exception e) {
-            log.error("monitorActorsJobHandler exception", e);
-            XxlJobHelper.log(e);
-            return ReturnT.FAIL;
-        }
-
-        return ReturnT.SUCCESS;
-    }
-}

+ 18 - 0
src/main/java/top/lvzhiqiang/mapper/XrayProxyMapper.java

@@ -0,0 +1,18 @@
+package top.lvzhiqiang.mapper;
+
+import org.apache.ibatis.annotations.Select;
+import top.lvzhiqiang.entity.ProxyNode;
+
+import java.util.List;
+
+/**
+ * xray proxy mapper
+ *
+ * @author lvzhiqiang
+ * 2025/10/19 17:49
+ */
+public interface XrayProxyMapper {
+
+    @Select("SELECT * FROM proxy_node where delete_flag = 1")
+    List<ProxyNode> findProxyNodeList();
+}

+ 2 - 0
src/main/java/top/lvzhiqiang/service/Crawler4JavbusService.java

@@ -17,4 +17,6 @@ public interface Crawler4JavbusService {
     String findJavbusProfile(String keyword, Integer timeDay, Integer pic, String orderField, String order, Integer pageNo, Integer pageSize);
 
     Map<String,String> getJavbusCookiesMap() throws Exception;
+
+    void checkJavbusVideoSite();
 }

+ 2 - 2
src/main/java/top/lvzhiqiang/service/Crawler4JavdbService.java

@@ -11,7 +11,7 @@ import com.alibaba.fastjson.JSONObject;
 public interface Crawler4JavdbService {
     void monitorAlarm4APP_TEXT_CARD(String content, JSONObject params);
 
-    void monitorFavorites();
+    void monitorJavdbFavorites();
 
-    void monitorActors();
+    void monitorJavdbActors();
 }

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

@@ -95,7 +95,8 @@ public class BgServiceImpl implements BgService {
                 proxy2 = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 1080));
             } else {
                 proxy = Proxy.NO_PROXY;
-                proxy2 = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10808));
+                //proxy2 = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10808));
+                proxy2 = Proxy.NO_PROXY;
             }
         }
     }

+ 90 - 0
src/main/java/top/lvzhiqiang/service/impl/Crawler4JavbusServiceImpl.java

@@ -3,6 +3,7 @@ package top.lvzhiqiang.service.impl;
 import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import com.xxl.job.core.context.XxlJobHelper;
 import lombok.extern.slf4j.Slf4j;
 import org.jsoup.Connection;
 import org.jsoup.HttpStatusException;
@@ -13,12 +14,17 @@ import org.jsoup.select.Elements;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StopWatch;
+import top.lvzhiqiang.config.WebAppConfig;
 import top.lvzhiqiang.entity.CrawlerJavbusLog;
 import top.lvzhiqiang.entity.CrawlerJavbusProfile;
 import top.lvzhiqiang.entity.DicCode;
+import top.lvzhiqiang.entity.VideoSitePool;
 import top.lvzhiqiang.mapper.CrawlerJavbusProfileMapper;
 import top.lvzhiqiang.mapper.DicCodeMapper;
+import top.lvzhiqiang.mapper.VideoSitePoolMapper;
 import top.lvzhiqiang.service.Crawler4JavbusService;
 import top.lvzhiqiang.util.DateUtils;
 import top.lvzhiqiang.util.JsoupUtil;
@@ -48,6 +54,8 @@ public class Crawler4JavbusServiceImpl implements Crawler4JavbusService {
     @Resource
     private DicCodeMapper dicCodeMapper;
     @Resource
+    private VideoSitePoolMapper videoSitePoolMapper;
+    @Resource
     private CrawlerJavbusProfileMapper crawlerJavbusProfileMapper;
     @Value("${spring.profiles.active}")
     private String env;
@@ -96,6 +104,88 @@ public class Crawler4JavbusServiceImpl implements Crawler4JavbusService {
         return javbusCookiesMap;
     }
 
+    @Override
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
+    public void checkJavbusVideoSite() {
+        XxlJobHelper.log("checkVideoSite开始==============================");
+
+        // 获取javbus官方地址
+        DicCode dicCode = WebAppConfig.dicCodeList.stream().filter(x -> 2 == x.getType() && "javbus".equals(x.getCodeKey())).findFirst().get();
+        if (dicCode == null) {
+            XxlJobHelper.log("javbus官方站点为Null");
+            return;
+        }
+
+        // 获取javbusUrlList
+        List<String> javbusUrlList = videoSitePoolMapper.findUrlByType(1);
+
+        // 获取javbusNewUrlList
+        Set<String> javbusNewUrlList = new HashSet<>();
+        try {
+            Document document = Jsoup.connect(dicCode.getCodeValue()).timeout(50000).ignoreContentType(true)
+                    .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36")
+                    .header("referer", "https://www.javbus.com/").get();
+
+            Elements ahrefList = document.select("strong:contains(防屏蔽地址)").next("a");
+            for (Element element : ahrefList) {
+                String text = element.text();
+                XxlJobHelper.log("Jsoup获取{}防屏蔽地址:{}", dicCode.getCodeValue(), text);
+                javbusNewUrlList.add(text);
+            }
+        } catch (Exception e) {
+            log.error("Jsoup获取{}防屏蔽地址异常", dicCode.getCodeValue(), e);
+            XxlJobHelper.log("Jsoup获取{}防屏蔽地址异常", dicCode.getCodeValue());
+            XxlJobHelper.log(e);
+        }
+
+        if (javbusNewUrlList.size() == 0) {
+            XxlJobHelper.log("javbusNewUrlList为空");
+        }
+        if (javbusNewUrlList.size() == 0 && javbusUrlList.size() == 0) {
+            XxlJobHelper.log("javbusUrlList和javbusNewUrlList为空");
+            return;
+        }
+
+        // 校验新地址
+        List<String> javbusNewUrlFinalList = javbusNewUrlList.stream().filter(e -> !javbusUrlList.contains(e)).collect(Collectors.toList());
+        List<VideoSitePool> videoSitePoolList = new ArrayList<>();
+        VideoSitePool videoSitePool;
+        for (String javbusNewUrlFinal : javbusNewUrlFinalList) {
+            try {
+                Jsoup.connect(javbusNewUrlFinal).timeout(50000);
+
+                videoSitePool = new VideoSitePool();
+                videoSitePool.setUrl(javbusNewUrlFinal);
+                videoSitePool.setType(1);
+                videoSitePoolList.add(videoSitePool);
+                XxlJobHelper.log("javbusNewUrlFinalList:javbus防屏蔽地址有效!javbusUrl={}", javbusNewUrlFinal);
+            } catch (Exception e) {
+                log.error("javbusNewUrlFinalList:javbus防屏蔽地址失效!javbusUrl={}", javbusNewUrlFinal, e);
+                XxlJobHelper.log("javbusNewUrlFinalList:javbus防屏蔽地址失效!javbusUrl={}", javbusNewUrlFinal);
+                XxlJobHelper.log(e);
+            }
+        }
+        if (videoSitePoolList.size() > 0) {
+            videoSitePoolMapper.insertList(videoSitePoolList);
+        }
+        // 校验存量地址
+        for (String javbusUrl : javbusUrlList) {
+            int deleteFlag = 1;
+            try {
+                Jsoup.connect(javbusUrl).timeout(50000);
+                XxlJobHelper.log("javbusUrlList:javbus防屏蔽地址有效!javbusUrl={}", javbusUrl);
+            } catch (Exception e) {
+                deleteFlag = 2;
+                log.error("javbusUrlList:javbus防屏蔽地址失效!javbusUrl={}", javbusUrl, e);
+                XxlJobHelper.log("javbusUrlList:javbus防屏蔽地址失效!javbusUrl={}", javbusUrl);
+                XxlJobHelper.log(e);
+            }
+            videoSitePoolMapper.updateDeleteFlag(javbusUrl, deleteFlag);
+        }
+
+        XxlJobHelper.log("checkVideoSite结束==============================");
+    }
+
     @Async
     @Override
     public void jsoupJavbusProfile(Long start, Integer limit) throws Exception {

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

@@ -72,13 +72,14 @@ public class Crawler4JavdbServiceImpl implements Crawler4JavdbService {
             if ("dev".equals(env)) {
                 proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 1080));
             } else {
-                proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10808));
+                //proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 10808));
+                proxy = Proxy.NO_PROXY;
             }
         }
     }
 
     @Override
-    public void monitorActors() {
+    public void monitorJavdbActors() {
         XxlJobHelper.log("monitorActors开始==============================");
         beforeJavbus();
 
@@ -153,7 +154,7 @@ public class Crawler4JavdbServiceImpl implements Crawler4JavdbService {
     }
 
     @Override
-    public void monitorFavorites() {
+    public void monitorJavdbFavorites() {
         XxlJobHelper.log("monitorFavorites开始==============================");
         beforeJavbus();
 

+ 4 - 4
src/main/java/top/lvzhiqiang/util/XrayConfigGenerator.java

@@ -6,11 +6,11 @@ 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) {
+    public static String generateFullConfig(ProxyNode node, int LOCAL_SOCKS_PORT) {
         ObjectNode fullConfig = MAPPER.createObjectNode();
 
         // 1. Log 配置
@@ -36,7 +36,7 @@ public class XrayConfigGenerator {
     }
 
     // 生成特定协议的 Outbound 配置
-    private ObjectNode generateOutbound(ProxyNode node) {
+    private static ObjectNode generateOutbound(ProxyNode node) {
         ObjectNode outbound = MAPPER.createObjectNode();
         outbound.put("protocol", node.getProtocol());
 
@@ -72,7 +72,7 @@ public class XrayConfigGenerator {
     }
 
     // --- 协议配置细节(仅展示 Trojan 示例)---
-    private void configureTrojan(ObjectNode server, ObjectNode streamSettings, ProxyNode node) {
+    private static void configureTrojan(ObjectNode server, ObjectNode streamSettings, ProxyNode node) {
         server.put("password", node.getPassword());
 
         // 检查 extraConfig 中的 XTLS/Flow

+ 134 - 0
src/main/java/top/lvzhiqiang/util/XrayManager.java

@@ -0,0 +1,134 @@
+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<ProxyNode> 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;
+    }
+}

+ 42 - 0
src/test/java/top/lvzhiqiang/TestXrayProxy.java

@@ -0,0 +1,42 @@
+package top.lvzhiqiang;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import top.lvzhiqiang.entity.ProxyNode;
+import top.lvzhiqiang.mapper.XrayProxyMapper;
+import top.lvzhiqiang.util.XrayManager;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 单元测试类
+ *
+ * @author lvzhiqiang
+ * @since 11:19 2022/5/2
+ */
+@Slf4j
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringBootTest(properties = {
+        "spring.profiles.active=dev",
+        "logging.level.top.lvzhiqiang=DEBUG"
+}
+)
+public class TestXrayProxy {
+
+    @Resource
+    private XrayManager xrayManager;
+    @Resource
+    private XrayProxyMapper xrayProxyMapper;
+
+
+    @Test
+    public void test1() {
+        List<ProxyNode> proxyNodeList = xrayProxyMapper.findProxyNodeList();
+        System.out.println(proxyNodeList);
+    }
+
+}