diff --git a/src/main/java/i18nupdatemod/util/AssetUtil.java b/src/main/java/i18nupdatemod/util/AssetUtil.java index d75c840..eae3569 100644 --- a/src/main/java/i18nupdatemod/util/AssetUtil.java +++ b/src/main/java/i18nupdatemod/util/AssetUtil.java @@ -20,15 +20,16 @@ import java.util.List; import java.util.Map; import java.util.concurrent.*; +import java.util.stream.Collectors; public class AssetUtil { private static final String CFPA_ASSET_ROOT = "http://downloader1.meitangdehulu.com:22943/"; + private static final String GITHUB = "https://raw.githubusercontent.com/"; private static final List MIRRORS; static { // 镜像地址可以改成服务器下发 MIRRORS = new ArrayList<>(); - MIRRORS.add("https://raw.githubusercontent.com/"); // 此镜像源维护者:502y MIRRORS.add("http://8.137.167.65:64684/"); } @@ -45,48 +46,30 @@ public static String getString(String url) throws IOException, URISyntaxExceptio } public static String getFastestUrl() { + // 海外用户直接用 GitHub,不测速 + if (!LocationDetectUtil.isMainlandChina()) { + Log.info("Outside mainland China: Using GitHub source"); + return GITHUB; + } + + // 中国大陆:测速选择最快的国内源 + Log.info("Inside mainland China: Testing mirrors..."); List urls = new ArrayList<>(MIRRORS); urls.add(CFPA_ASSET_ROOT); - ExecutorService executor = Executors.newFixedThreadPool(Math.max(urls.size(), 10)); + ExecutorService executor = Executors.newCachedThreadPool(); try { - List> futures = new ArrayList<>(); - for (String url : urls) { - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - return testUrlConnection(url); - } catch (IOException e) { - return null; // 表示失败 - } - }, executor); - futures.add(future); - } - - // 阻塞等待最快完成且成功的任务 - String fastest = null; - while (!futures.isEmpty()) { - CompletableFuture first = CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0])); - fastest = (String) first.join(); - - // 移除已完成的 future - futures.removeIf(CompletableFuture::isDone); - - if (fastest != null) { - // 成功,取消其他任务 - for (CompletableFuture f : futures) { - f.cancel(true); - } - Log.info("Using fastest url: %s", fastest); - return fastest; - } - } - - // 全部失败,返回默认 URL - Log.info("All urls are unreachable, using CFPA_ASSET_ROOT"); - return CFPA_ASSET_ROOT; - - } finally { + String fastest = executor.invokeAny( + urls.stream().map(url -> (Callable) () -> testUrlConnection(url)).collect(Collectors.toList()), + 10, TimeUnit.SECONDS + ); + executor.shutdownNow(); + Log.info("Using fastest url: %s", fastest); + return fastest; + } catch (Exception e) { executor.shutdownNow(); + Log.info("All sources unreachable, using CFPA_ASSET_ROOT as fallback"); + return CFPA_ASSET_ROOT; } } diff --git a/src/main/java/i18nupdatemod/util/LocationDetectUtil.java b/src/main/java/i18nupdatemod/util/LocationDetectUtil.java new file mode 100644 index 0000000..30063c0 --- /dev/null +++ b/src/main/java/i18nupdatemod/util/LocationDetectUtil.java @@ -0,0 +1,57 @@ +package i18nupdatemod.util; + +import org.apache.commons.io.IOUtils; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +public class LocationDetectUtil { + private static Boolean cached = null; + + private static final String[][] GEO_APIS = { + {"Kugou", "https://mips.kugou.com/check/iscn?&format=json"}, + {"IP.SB", "https://api.ip.sb/geoip"} + }; + + public static boolean isMainlandChina() { + if (cached != null) { + return cached; + } + + for (String[] api : GEO_APIS) { + Boolean result = tryApi(api[0], api[1]); + if (result != null) { + cached = result; + return cached; + } + } + + cached = false; + return false; + } + + private static Boolean tryApi(String name, String url) { + try { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + if (conn.getResponseCode() == 200) { + String response = IOUtils.toString(conn.getInputStream(), StandardCharsets.UTF_8); + boolean isChina = parseResponse(response); + Log.info("Location Detected (" + name + " API): " + (isChina ? "Inside mainland China" : "Outside mainland China")); + return isChina; + } + } catch (Exception e) { + Log.debug(name + " API detection failed: " + e.getMessage()); + } + return null; + } + + private static boolean parseResponse(String response) { + response = response.trim(); + // Kugou: {"flag":1/true} | IP.SB: {"country_code":"CN"} + return response.matches(".*(\"flag\".*[\":] *(1|true)|\"country_code\".*\"CN\").*"); + } +}