diff --git a/src/main/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImpl.java b/src/main/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImpl.java index b508c08..a22b882 100644 --- a/src/main/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImpl.java +++ b/src/main/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImpl.java @@ -65,7 +65,7 @@ public class CmsSiteDirectoryImpl implements SiteDirectory { // 由 CmsSiteDirectoryImplTest 冻结)。仅用于 siteCode 反解,权限判定仍走 requireSchoolMember。 sites = fetchSites(null); } catch (Exception e) { - log.warn("[svc-hall] CMS 站点目录不可达:{}", e.getMessage()); + log.warn("[svc-hall] CMS 站点目录不可达:{}{}", e.getMessage(), rootCauseBrief(e)); return null; } if (sites != null) { @@ -93,7 +93,8 @@ public class CmsSiteDirectoryImpl implements SiteDirectory { try { return fetchSites(schId); } catch (Exception e) { - log.warn("[svc-hall] CMS 站点列表获取失败(schId={}):{}", schId, e.getMessage()); + log.warn("[svc-hall] CMS 站点列表获取失败(schId={}):{}{}", + schId, e.getMessage(), rootCauseBrief(e)); throw new BizException(ErrorCode.PARAM_ERROR, "站点服务不可达,请稍后重试"); } }); @@ -124,6 +125,20 @@ public class CmsSiteDirectoryImpl implements SiteDirectory { return s; } + /** + * 最内层原因摘要。SDK 的 "OpenAPI request failed: ioError" 只说明请求没拿到响应, + * 真正的原因是 cause 链末端的 ConnectException/SocketTimeoutException/SSLException; + * 不补这一段,日志里永远分不清"对端没起"和"读超时"。 + * 包可见:契约测试直接断言摘要形态。 + */ + static String rootCauseBrief(Throwable e) { + Throwable cur = e; + while (cur.getCause() != null && cur.getCause() != cur) { + cur = cur.getCause(); + } + return cur == e ? "" : "(根因 " + cur.getClass().getSimpleName() + ": " + cur.getMessage() + ")"; + } + /** resolveSite 的全量缓存键(与真实 schId 不冲突的哨兵) */ private static final String ALL_SITES_KEY = "-1"; } diff --git a/src/test/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImplTest.java b/src/test/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImplTest.java index c58df60..c0caab8 100644 --- a/src/test/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImplTest.java +++ b/src/test/java/com/yunhai/svchall/service/impl/CmsSiteDirectoryImplTest.java @@ -123,4 +123,20 @@ class CmsSiteDirectoryImplTest { void listSites_rejectsNullSchId() { assertThrows(BizException.class, () -> directory.listSites(null)); } + + @Test + @DisplayName("rootCauseBrief:ioError 包装层要透出最内层原因(区分\"对端没起\"与\"读超时\")") + void rootCauseBrief_exposesInnermostCause() { + // 无 cause:不追加,避免与 e.getMessage() 重复 + assertEquals("", CmsSiteDirectoryImpl.rootCauseBrief( + new RuntimeException("OpenAPI request failed: ioError"))); + + // 实际形态:OpenApiException(ioError) ← ResourceAccessException ← ConnectException + java.net.ConnectException refused = new java.net.ConnectException("Connection refused: connect"); + Exception wrapped = new RuntimeException("OpenAPI request failed: ioError", + new RuntimeException("I/O error on GET request", refused)); + String brief = CmsSiteDirectoryImpl.rootCauseBrief(wrapped); + assertTrue(brief.contains("ConnectException"), brief); + assertTrue(brief.contains("Connection refused: connect"), brief); + } }