Skip to content
35 changes: 19 additions & 16 deletions src/main/java/i18nupdatemod/util/AssetUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@

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<String> MIRRORS;

static {
// 镜像地址可以改成服务器下发
MIRRORS = new ArrayList<>();
MIRRORS.add("https://raw.githubusercontent.com/");
// 此镜像源维护者:502y
MIRRORS.add("http://8.137.167.65:64684/");
}
Expand All @@ -45,34 +45,38 @@ public static String getString(String url) throws IOException, URISyntaxExceptio
}

public static String getFastestUrl() {
List<String> urls = new ArrayList<>(MIRRORS);
urls.add(CFPA_ASSET_ROOT);
List<String> urls = new ArrayList<>();

// 根据地理位置选择源列表
if (LocationDetectUtil.isMainlandChina()) {
// 中国大陆:测速选择最快的国内源
urls.addAll(MIRRORS);
urls.add(CFPA_ASSET_ROOT);
Log.info("Inside mainland China: Testing mirrors...");
} else {
// 海外用户:直接使用 GitHub 源
urls.add(GITHUB);
Log.info("Outside mainland China: Using GitHub source...");
}

ExecutorService executor = Executors.newFixedThreadPool(Math.max(urls.size(), 10));
try {
List<CompletableFuture<String>> futures = new ArrayList<>();
for (String url : urls) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
futures.add(CompletableFuture.supplyAsync(() -> {
try {
return testUrlConnection(url);
} catch (IOException e) {
return null; // 表示失败
return null;
}
}, executor);
futures.add(future);
}, executor));
}

// 阻塞等待最快完成且成功的任务
String fastest = null;
while (!futures.isEmpty()) {
CompletableFuture<Object> first = CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]));
fastest = (String) first.join();

// 移除已完成的 future
fastest = (String) CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0])).join();
futures.removeIf(CompletableFuture::isDone);

if (fastest != null) {
// 成功,取消其他任务
for (CompletableFuture<String> f : futures) {
f.cancel(true);
}
Expand All @@ -81,8 +85,7 @@ public static String getFastestUrl() {
}
}

// 全部失败,返回默认 URL
Log.info("All urls are unreachable, using CFPA_ASSET_ROOT");
Log.info("All sources unreachable, using CFPA_ASSET_ROOT as fallback");
return CFPA_ASSET_ROOT;

} finally {
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/i18nupdatemod/util/LocationDetectUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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 API JSON: {"flag": 1} 或 {"flag": true}
if (response.contains("\"flag\":1") || response.contains("\"flag\": 1") ||response.contains("\"flag\":true") || response.contains("\"flag\": true")) {
return true;
}
// IP.SB API JSON: {"country_code": "CN"}
if (response.contains("\"country_code\":\"CN\"") || response.contains("\"country_code\": \"CN\"")) {
return true;
}
return false;
}

public static void resetCache() {
cached = null;
}
}