diff --git a/go/controllers/tenant_site.go b/go/controllers/tenant_site.go index 13decae..aee1d70 100644 --- a/go/controllers/tenant_site.go +++ b/go/controllers/tenant_site.go @@ -5,6 +5,7 @@ import ( "net/url" "os" "path/filepath" + "regexp" "strconv" "strings" @@ -147,6 +148,67 @@ func (c *TenantSiteController) forceHTTPSStorageURLs(html string) string { return strings.ReplaceAll(html, "http://"+host, "https://"+host) } +// rewriteThemeAssets 将模板内书写的相对资源路径(href/src/url() 等) +// 统一改写为 /themes/{code}/ 下的绝对路径,保证任意页面深度(/、/news、 +// /news/:id、/page/:path)下静态资源都能命中 beego 的 /themes 静态映射。 +// 第三方模板通常直接写相对路径(如 assets/css/xxx.css),不改写会 404。 +// 注:Go regexp(RE2)不支持 \1/\2 反向引用,闭合引号用 ["'] 宽松匹配; +// 值部分 [^"']* 不含引号,正常 HTML 下首尾引号必然一致,行为等价。 +var ( + reThemeAssetAttr = regexp.MustCompile(`(?i)\b(href|src|poster|data-src|srcset)\s*=\s*(["'])([^"']*)["']`) + reThemeAssetURL = regexp.MustCompile(`(?i)url\(\s*(["']?)([^"')]+)["']?\s*\)`) +) + +func rewriteThemeAssets(html, code string) string { + prefix := "/themes/" + code + "/" + fix := func(u string) string { + u = strings.TrimSpace(u) + if u == "" { + return u + } + lower := strings.ToLower(u) + // 绝对路径/协议链接/锚点等一律不动(含引擎输出的 /uploads、存储 CDN 地址) + if strings.HasPrefix(u, "/") || strings.HasPrefix(u, "#") || strings.HasPrefix(u, "?") || + strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") || + strings.HasPrefix(lower, "//") || strings.HasPrefix(lower, "data:") || + strings.HasPrefix(lower, "javascript:") || strings.HasPrefix(lower, "mailto:") || + strings.HasPrefix(lower, "tel:") { + return u + } + u = strings.TrimPrefix(u, "./") + for strings.HasPrefix(u, "../") { + u = strings.TrimPrefix(u, "../") + } + return prefix + u + } + + html = reThemeAssetAttr.ReplaceAllStringFunc(html, func(m string) string { + sub := reThemeAssetAttr.FindStringSubmatch(m) + attr, quote, val := sub[1], sub[2], sub[3] + if strings.EqualFold(attr, "srcset") { + parts := strings.Split(val, ",") + for i, p := range parts { + seg := strings.Fields(strings.TrimSpace(p)) + if len(seg) > 0 { + seg[0] = fix(seg[0]) + parts[i] = strings.Join(seg, " ") + } + } + val = strings.Join(parts, ", ") + } else { + val = fix(val) + } + return attr + "=" + quote + val + quote + }) + + // 行内 style /