fix: CMS 站点不可达日志补最内层根因,不再被 SDK 的 ioError 文案吞掉
build / build-push (push) Successful in 2m20s

现象:CmsSiteDirectoryImpl 两处 catch 只打 e.getMessage(),而平台 SDK 统一把
传输层失败包成 "OpenAPI request failed: ioError",底层 ConnectException /
SocketTimeoutException / SSLException 全部丢失,日志分不清"对端没起"还是"读超时"。

改动:
- 新增 rootCauseBrief():沿 cause 链取最内层原因追加为"(根因 X: msg)";
  无 cause 时不追加,避免与 e.getMessage() 重复。
- resolveSite / listSites 两处 catch 日志接入该方法。

契约不变:路径 /oapi/site/list、per-target basePath /cms、fail-closed 语义均未动。
测试:CmsSiteDirectoryImplTest 新增 1 例断言摘要形态;mvn test 82/82 绿。
This commit is contained in:
chenxing
2026-09-15 20:52:59 +08:00
parent d47951abd7
commit 000d0591fa
2 changed files with 33 additions and 2 deletions
@@ -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";
}
@@ -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);
}
}