From 078340da0b5c422a0cdcc89bc709e84b2174eb4f Mon Sep 17 00:00:00 2001 From: Francis-Xavier-code <203426472+Francis-Xavier-code@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:18:30 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(client):=20=E4=BF=AE=E5=A4=8D=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=9B=BE=E7=89=87=E5=9F=9F=E5=90=8D=E4=BB=8E=E6=9C=AA?= =?UTF-8?q?=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JmConstants.DEFAULT_IMAGE_DOMAINS 由不可变列表改为 CopyOnWriteArrayList, 修复 updateSetting() 对不可变列表调用 add() 抛出 UnsupportedOperationException - updateSetting() 增加去重,避免重复客户端初始化时同一域名被反复追加 - AbstractJmClient 后台初始化任务增加异常日志,避免异常被 Future 静默吞掉 Closes #17 --- .../jmcomic/core/client/AbstractJmClient.java | 16 ++++++++++------ .../jmcomic/core/client/impl/JmApiClient.java | 3 ++- .../jmcomic/core/constant/JmConstants.java | 3 ++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java index 90f2bd3..53d4700 100644 --- a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java +++ b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java @@ -104,12 +104,16 @@ protected AbstractJmClient(JmConfiguration config, OkHttpClient httpClient, Cook * 后台异步初始化:更新域名列表 -> 域名探活排掉死域名 -> 启动定期复探 -> 调子类初始化 */ this.internalExecutor.submit(() -> { - this.updateDomains(); - DomainProbe probe = createDomainProbe(); - this.domainManager.probeAllDomains(probe); - this.domainManager.startPeriodicProbe(probe, config.getDomainProbeIntervalMs()); - this.domainManager.setInitialized(true); - this.initialize(); + try { + this.updateDomains(); + DomainProbe probe = createDomainProbe(); + this.domainManager.probeAllDomains(probe); + this.domainManager.startPeriodicProbe(probe, config.getDomainProbeIntervalMs()); + this.domainManager.setInitialized(true); + this.initialize(); + } catch (Throwable e) { + logger.error("后台初始化任务执行失败", e); + } }); // 生成一个 128位的 AES 随机密钥 try { diff --git a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java index 67801e1..346cee7 100644 --- a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java +++ b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java @@ -111,8 +111,9 @@ private void updateSetting() { logger.info("当前API客户端版本[{}],更新API客户端版本[{}] -> [{}]", JmConstants.APP_VERSION, JmConstants.APP_VERSION, JmConstants.APP_VERSION); } - if (imgHost != null) { + if (imgHost != null && !JmConstants.DEFAULT_IMAGE_DOMAINS.contains(imgHost)) { JmConstants.DEFAULT_IMAGE_DOMAINS.add(imgHost); + logger.info("添加动态图片域名[{}],当前图片域名列表: {}", imgHost, JmConstants.DEFAULT_IMAGE_DOMAINS); } } diff --git a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/constant/JmConstants.java b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/constant/JmConstants.java index 1a2fa5b..9eb9252 100644 --- a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/constant/JmConstants.java +++ b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/constant/JmConstants.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; /** * @author JUKOMU @@ -126,7 +127,7 @@ private JmConstants() { "www.cdnplaystation6.cc" )); - public static List DEFAULT_IMAGE_DOMAINS = Collections.unmodifiableList(List.of( + public static List DEFAULT_IMAGE_DOMAINS = new CopyOnWriteArrayList<>(List.of( "cdn-msp.jmapiproxy1.cc", "cdn-msp.jmapiproxy2.cc", "cdn-msp2.jmapiproxy2.cc", From d69e0785eda9852d5658aa3e5e94453d841182a1 Mon Sep 17 00:00:00 2001 From: JUKOMU <3037738647@qq.com> Date: Fri, 14 Aug 2026 23:33:41 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(client):=20=E5=AE=8C=E5=96=84=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=9B=BE=E7=89=87=E5=9F=9F=E5=90=8D=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=92=8C=E5=88=9D=E5=A7=8B=E5=8C=96=E5=BC=82=E5=B8=B8=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 规范化服务端返回的图片域名,兼容完整 URL 和主机名 - 保证并发初始化时图片域名去重追加的原子性 - 使用 execute 执行后台初始化,避免异常被 Future 静默保存 --- .../jmcomic/core/client/AbstractJmClient.java | 5 +-- .../jmcomic/core/client/impl/JmApiClient.java | 36 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java index 53d4700..3937eb1 100644 --- a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java +++ b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/AbstractJmClient.java @@ -103,7 +103,7 @@ protected AbstractJmClient(JmConfiguration config, OkHttpClient httpClient, Cook /* * 后台异步初始化:更新域名列表 -> 域名探活排掉死域名 -> 启动定期复探 -> 调子类初始化 */ - this.internalExecutor.submit(() -> { + this.internalExecutor.execute(() -> { try { this.updateDomains(); DomainProbe probe = createDomainProbe(); @@ -111,8 +111,9 @@ protected AbstractJmClient(JmConfiguration config, OkHttpClient httpClient, Cook this.domainManager.startPeriodicProbe(probe, config.getDomainProbeIntervalMs()); this.domainManager.setInitialized(true); this.initialize(); - } catch (Throwable e) { + } catch (RuntimeException e) { logger.error("后台初始化任务执行失败", e); + throw e; } }); // 生成一个 128位的 AES 随机密钥 diff --git a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java index 346cee7..fc0579f 100644 --- a/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java +++ b/jmcomic-core/src/main/java/io/github/jukomu/jmcomic/core/client/impl/JmApiClient.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.net.CookieManager; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.time.Instant; import java.util.List; @@ -111,9 +112,38 @@ private void updateSetting() { logger.info("当前API客户端版本[{}],更新API客户端版本[{}] -> [{}]", JmConstants.APP_VERSION, JmConstants.APP_VERSION, JmConstants.APP_VERSION); } - if (imgHost != null && !JmConstants.DEFAULT_IMAGE_DOMAINS.contains(imgHost)) { - JmConstants.DEFAULT_IMAGE_DOMAINS.add(imgHost); - logger.info("添加动态图片域名[{}],当前图片域名列表: {}", imgHost, JmConstants.DEFAULT_IMAGE_DOMAINS); + String imageHost = normalizeImageHost(imgHost); + if (imageHost != null) { + boolean added; + synchronized (JmConstants.DEFAULT_IMAGE_DOMAINS) { + added = !JmConstants.DEFAULT_IMAGE_DOMAINS.contains(imageHost) && JmConstants.DEFAULT_IMAGE_DOMAINS.add(imageHost); + } + if (added) { + logger.info("添加动态图片域名[{}],当前图片域名列表: {}", imageHost, JmConstants.DEFAULT_IMAGE_DOMAINS); + } + } + } + + /** + * 标准化图片域名,提取并返回主机名(Host)。 + * + * @param imgHost 图片URL或主机名,可为任意格式 + * @return 标准化后的主机名 + */ + private static String normalizeImageHost(String imgHost) { + if (StringUtils.isBlank(imgHost)) { + return null; + } + + String value = imgHost.trim(); + try { + URI uri = URI.create(value); + if (uri.getScheme() == null) { + uri = URI.create(JmConstants.PROTOCOL_HTTPS + value); + } + return uri.getHost(); + } catch (IllegalArgumentException e) { + return null; } }