diff --git a/src/main/java/com/yunhai/cms/utils/PortalUrlHelper.java b/src/main/java/com/yunhai/cms/utils/PortalUrlHelper.java index 6cb088a..d34f0ee 100644 --- a/src/main/java/com/yunhai/cms/utils/PortalUrlHelper.java +++ b/src/main/java/com/yunhai/cms/utils/PortalUrlHelper.java @@ -57,8 +57,12 @@ public class PortalUrlHelper { */ public static PortalUrlHelper forPreview(HttpServletRequest request, String siteCode, String theme) { PortalUrlHelper base = fromRequest(request, siteCode, theme); - // 显式走 5 参构造:第 4 参是 base-origin,代理前缀必须落第 5 参(避免误绑 4 参重载) - return new PortalUrlHelper(base.baseUrl, siteCode, theme, null, "/api/theme/" + theme + "/asset/"); + // 显式走 5 参构造:第 4 参是 base-origin,代理前缀必须落第 5 参(避免误绑 4 参重载)。 + // W3(方案 §7.4):前缀拼 request.getContextPath()——base/prod 上下文 /yschcms 下 + // 不拼会导致 iframe 内 CSS/JS 链接打到网关根 404(预览裸 HTML); + // 空上下文(dev)退化为根相对 /api/theme/…,dev 形态不回归(T-W3-02)。 + return new PortalUrlHelper(base.baseUrl, siteCode, theme, null, + request.getContextPath() + "/api/theme/" + theme + "/asset/"); } /** diff --git a/src/test/java/com/yunhai/cms/theme/PreviewApiIT.java b/src/test/java/com/yunhai/cms/theme/PreviewApiIT.java index c24184d..b5816a8 100644 --- a/src/test/java/com/yunhai/cms/theme/PreviewApiIT.java +++ b/src/test/java/com/yunhai/cms/theme/PreviewApiIT.java @@ -137,6 +137,17 @@ class PreviewApiIT extends CmsIntegrationTestBase { assertThat(body(result)).contains("/api/theme/t-demo-red/asset/css/main.css"); } + @Test + @DisplayName("T-W3-04: 端到端回归:contextPath=/yschcms 时预览资产 href 带上下文前缀") + void previewAssetHrefCarriesContextPathEndToEnd() throws Exception { + MvcResult result = standalone.perform(get("/yschcms/api/theme/preview") + .contextPath("/yschcms") + .param("siteCode", "demo").param("theme", "t-demo-red")) + .andExpect(status().isOk()).andReturn(); + assertThat(body(result)) + .contains("href=\"/yschcms/api/theme/t-demo-red/asset/css/main.css\""); + } + @Test @DisplayName("T26: 预览响应 no-store(不进缓存/CDN)") void previewNoStore() throws Exception { diff --git a/src/test/java/com/yunhai/cms/utils/PortalUrlHelperTest.java b/src/test/java/com/yunhai/cms/utils/PortalUrlHelperTest.java index 0c3e9c3..30f1e25 100644 --- a/src/test/java/com/yunhai/cms/utils/PortalUrlHelperTest.java +++ b/src/test/java/com/yunhai/cms/utils/PortalUrlHelperTest.java @@ -149,6 +149,37 @@ class PortalUrlHelperTest { assertThat(url.rewriteUploadUrls(null)).isNull(); } + // ===== forPreview 预览资产前缀(W3:context-path 修复,方案 §7.3)===== + + @Test + @DisplayName("T-W3-01: forPreview 带上下文:资产前缀含 contextPath") + void forPreview_assetPrefixCarriesContextPath() { + PortalUrlHelper url = PortalUrlHelper.forPreview( + request("http", "base.yunhaixingtong.com", 80, "/yschcms"), "demo", "scholar-blue"); + assertThat(url.asset("css/main.css")) + .isEqualTo("/yschcms/api/theme/scholar-blue/asset/css/main.css"); + } + + @Test + @DisplayName("T-W3-02: forPreview 空上下文退化:前缀 /api/theme/…(dev 形态不回归)") + void forPreview_emptyContextFallsBackToRootPrefix() { + PortalUrlHelper url = PortalUrlHelper.forPreview( + request("http", "localhost", 8072, ""), "demo", "t-demo-red"); + assertThat(url.asset("css/main.css")) + .isEqualTo("/api/theme/t-demo-red/asset/css/main.css"); + } + + @Test + @DisplayName("T-W3-03: forPreview 站内链接不受资产前缀串味(home/category 仍指真实门户)") + void forPreview_homeStaysRealPortal() { + PortalUrlHelper url = PortalUrlHelper.forPreview( + request("http", "base.yunhaixingtong.com", 80, "/yschcms"), "demo", "scholar-blue"); + assertThat(url.home()).isEqualTo("http://base.yunhaixingtong.com/yschcms/portal/demo/"); + assertThat(url.category(3)).isEqualTo("http://base.yunhaixingtong.com/yschcms/portal/demo/c/3"); + assertThat(url.search("x", null)) + .isEqualTo("http://base.yunhaixingtong.com/yschcms/portal/demo/search?q=x"); + } + // ===== siteHome 分支(B-4:学校域形态下未配域子站不再退化为 "/")===== @Test