From 87a85ef7ca182af82577be06d6997489d1a52724 Mon Sep 17 00:00:00 2001 From: kevin Date: Thu, 20 Aug 2026 01:46:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E9=9D=99=E6=80=81=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E5=86=85=E5=B5=8C=E4=BA=8C=E8=BF=9B=E5=88=B6=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20install.sh=20=E9=83=A8=E7=BD=B2=E5=90=8E?= =?UTF-8?q?=20/static=20404=20=E5=AF=BC=E8=87=B4=E5=86=99=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E9=A1=B5=E6=97=A0=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 线上经 install.sh 部署时只复制二进制 + templates,internal/web/static 未被复制,/static/vendor/quill/* 全部 404,Quill 加载失败正文不可输入 - 改为 go:embed 把 Quill css/js 编译进二进制,engine.StaticFS 提供, 运行期不再依赖部署目录/工作目录,杜绝同类部署遗漏 - 已验证:移除磁盘 static 目录后静态资源仍可正常提供 --- internal/web/server.go | 17 ++++++++++++++--- internal/web/static_embed.go | 13 +++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 internal/web/static_embed.go diff --git a/internal/web/server.go b/internal/web/server.go index b54b35e..3d23c3f 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -4,13 +4,14 @@ import ( "encoding/json" "fmt" "html/template" + "io/fs" "math" "net" "net/http" "net/url" "os" - "strconv" "path/filepath" + "strconv" "strings" "time" "unicode/utf8" @@ -56,6 +57,8 @@ type WebServer struct { caddyDataDir string outbound *outbound.Manager hub *connhub.Hub + // staticFS 内嵌的静态资源(/static 路由),二进制自包含 + staticFS http.FileSystem // pusher 邮件状态变化推送(IMAP 客户端实时同步),可空 pusher imap_server.Pusher } @@ -291,6 +294,12 @@ func NewWebServer(cfg config.WebConfig, stores *store.Stores, attStorage *storag template.Must(tmpl.ParseGlob("internal/web/templates/admin/*.html")) engine.SetHTMLTemplate(tmpl) + // 内嵌静态资源根目录(/static 路由数据源) + staticSub, err := fs.Sub(staticFS, "static") + if err != nil { + return nil, fmt.Errorf("加载内嵌静态资源失败: %w", err) + } + ws := &WebServer{ engine: engine, stores: stores, @@ -302,6 +311,7 @@ func NewWebServer(cfg config.WebConfig, stores *store.Stores, attStorage *storag caddyDataDir: caddyCfg.DataDir, outbound: ob, hub: hub, + staticFS: http.FS(staticSub), pusher: pusher, } @@ -321,8 +331,9 @@ func (ws *WebServer) registerRoutes() { ws.engine.Use(middleware.SecurityHeaders()) // 静态资源(本地化的 Quill 编辑器等第三方前端库)。 - // 同源加载以满足 CSP script-src/style-src 'self',且服务器离线可用。 - ws.engine.Static("/static", "internal/web/static") + // 内嵌于二进制:同源加载以满足 CSP script-src/style-src 'self', + // 且不依赖部署目录(install.sh 只复制二进制 + templates)。 + ws.engine.StaticFS("/static", ws.staticFS) // Public routes (no auth required) ws.engine.GET("/login", authHandler.ShowLogin) diff --git a/internal/web/static_embed.go b/internal/web/static_embed.go new file mode 100644 index 0000000..6ca1794 --- /dev/null +++ b/internal/web/static_embed.go @@ -0,0 +1,13 @@ +package web + +// 静态资源内嵌进二进制:install.sh 只部署二进制 + templates, +// 此前 internal/web/static/ 未被复制导致线上 /static 全部 404。 +// go:embed 使前端资源自包含,不再依赖部署目录与工作目录。 + +import "embed" + +// staticFS 包含 internal/web/static/ 下的全部静态资源 +// (目前为本地化的 Quill 编辑器 css/js)。 +// +//go:embed all:static +var staticFS embed.FS