diff --git a/go/conf/app.conf b/go/conf/app.conf index bd96477..27f059e 100644 --- a/go/conf/app.conf +++ b/go/conf/app.conf @@ -2,6 +2,16 @@ httpport = 9000 runmode = dev +# ==================== 平台后台 SPA 静态托管 ==================== +# 之前 platform.yunzer.cn 的 SPA 静态文件依赖 nginx 在对应 server 块里 +# 配 location ~ ^/(platform|backend|auth|api|app)/ { proxy_pass ... } 反代到 Go, +# 而 nginx 默认 server 块经常只有 root + static,缺这条就会让所有 API 请求 404。 +# 现在由 Go 自己吐这些 host 的 SPA 静态;/platform|/backend|/auth|/api|/app +# 仍走 beego Router;/uploads|/themes 走 main.go 注册的 SetStaticPath。 +# 多 host 用英文逗号分隔;目录留空则关闭该功能,回落到由 nginx 自行处理。 +platform_spa_dir = /www/wwwroot/platform.yunzer.cn/output +platform_spa_hosts = platform.yunzer.cn + # 启用请求体复制(允许多次读取请求体) copyrequestbody = true diff --git a/go/main.exe b/go/main.exe deleted file mode 100644 index 3520e6f..0000000 Binary files a/go/main.exe and /dev/null differ diff --git a/go/routers/router.go b/go/routers/router.go index 93c16c8..999e6c2 100644 --- a/go/routers/router.go +++ b/go/routers/router.go @@ -2,6 +2,7 @@ package routers import ( "os" + "strings" "server/controllers" "server/middleware" @@ -87,4 +88,18 @@ func init() { platform.Register() api.Register() } + + // 平台后台 SPA 静态托管(详见 routers/spa.go 注释)。 + // 由 app.conf 的 platform_spa_dir / platform_spa_hosts 控制;任一为空则不启用。 + if spaDir, _ := beego.AppConfig.String("platform_spa_dir"); strings.TrimSpace(spaDir) != "" { + var hosts []string + if raw, _ := beego.AppConfig.String("platform_spa_hosts"); raw != "" { + for _, h := range strings.Split(raw, ",") { + if t := strings.TrimSpace(h); t != "" { + hosts = append(hosts, t) + } + } + } + RegisterPlatformSPA(spaDir, hosts...) + } } diff --git a/go/routers/spa.go b/go/routers/spa.go new file mode 100644 index 0000000..355cc43 --- /dev/null +++ b/go/routers/spa.go @@ -0,0 +1,81 @@ +package routers + +import ( + "net/http" + "os" + "strings" + + beego "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/context" +) + +// RegisterPlatformSPA 让 Go 自己吐指定 hosts 的平台后台 SPA 静态资源, +// 替代 nginx 在对应 server 块里的 +// +// location ~ ^/(platform|backend|auth|api|app)/ { proxy_pass http://127.0.0.1:8081; ... } +// +// 那段反代规则(曾因宝塔重生成 vhost 被冲掉而出现 "页面 404")。 +// +// 规则: +// - 仅对 hosts 列表里的 host 生效; +// - 这些前缀直接放行交给 beego Router / Static 处理,不接管: +// /platform/、/backend/、/auth/、/api/、/app/ → 业务 API +// /uploads/、/themes/ → main.go SetStaticPath 注册的静态目录 +// /.well-known/ → ACME 证书验证目录 +// - 其余请求从 spaDir 直接 ServeFile; +// - 文件不存在时回 index.html(SPA hash 路由 fallback)。 +// +// 调用方在 router.go init() 里读 app.conf 的 platform_spa_dir / platform_spa_hosts。 +// spaDir 为空或 hosts 为空则视为关闭功能,行为不变。 +func RegisterPlatformSPA(spaDir string, hosts ...string) { + spaDir = strings.TrimSpace(spaDir) + if spaDir == "" || len(hosts) == 0 { + return + } + + hostSet := make(map[string]struct{}, len(hosts)) + for _, h := range hosts { + h = strings.ToLower(strings.TrimSpace(h)) + if h != "" { + hostSet[h] = struct{}{} + } + } + + passThrough := []string{ + "/platform/", "/backend/", "/auth/", "/api/", "/app/", + "/uploads/", "/themes/", + "/.well-known/", + } + + beego.InsertFilter("*", beego.BeforeStatic, func(ctx *context.Context) { + host := strings.ToLower(ctx.Request.Host) + if i := strings.IndexByte(host, ':'); i > 0 { + host = host[:i] + } + if _, ok := hostSet[host]; !ok { + return + } + + path := ctx.Request.URL.Path + for _, p := range passThrough { + if strings.HasPrefix(path, p) { + return + } + } + + // 静态资源或入口 + rel := path + if rel == "" || rel == "/" { + rel = "/index.html" + } + target := spaDir + rel + + if f, err := os.Stat(target); err == nil && !f.IsDir() { + http.ServeFile(ctx.ResponseWriter, ctx.Request, target) + return + } + + // SPA fallback:未知路径(hash 路由直接访问 /xxx/yyy)回 index.html + http.ServeFile(ctx.ResponseWriter, ctx.Request, spaDir+"/index.html") + }) +} \ No newline at end of file