go-admin registered its welcome page on GET / in every non-prod mode, so a reverse proxy that forwarded / to the server showed 「GO-ADMIN欢迎您」 instead of the Admin (happened after the 2026-09-28 server migration). When dist/index.html exists, / now returns it; without a dist (vite development) the previous welcome/prod behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
package router
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// SPADirEnv overrides where the built frontend lives. Without it the server
|
|
// looks for ./dist next to the working directory.
|
|
const SPADirEnv = "GOAUTO_WEB_DIST"
|
|
|
|
// InitSPARouter serves the built Vue app from the same origin as the API.
|
|
//
|
|
// Same-origin is what web/.env.production already assumes: VUE_APP_BASE_API is
|
|
// empty there, so the bundle issues relative requests. Serving dist from this
|
|
// binary makes that assumption true without a second web server and without
|
|
// CORS.
|
|
//
|
|
// `[必须]` Registering this is conditional on dist actually existing. In
|
|
// development the frontend runs under vite on its own port and there is no
|
|
// dist; a NoRoute handler installed anyway would turn every genuine 404 into
|
|
// an HTML page, which is far more confusing than a plain 404.
|
|
func InitSPARouter(engine *gin.Engine) {
|
|
dist, index, ok := spaIndex()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
engine.Static("/assets", filepath.Join(dist, "assets"))
|
|
for _, name := range []string{"favicon.ico", "logo.png"} {
|
|
if path := filepath.Join(dist, name); fileExists(path) {
|
|
engine.StaticFile("/"+name, path)
|
|
}
|
|
}
|
|
|
|
// History-mode routing means any unmatched non-API GET is a client route
|
|
// and must return index.html so the app can boot and route it itself.
|
|
//
|
|
// `[必须]` API paths are excluded. Answering a mistyped API call with 200
|
|
// and an HTML body would surface in the client as a JSON parse error
|
|
// instead of the 404 it actually is.
|
|
engine.NoRoute(func(c *gin.Context) {
|
|
if c.Request.Method != http.MethodGet || isAPIPath(c.Request.URL.Path) {
|
|
c.Status(http.StatusNotFound)
|
|
return
|
|
}
|
|
c.File(index)
|
|
})
|
|
}
|
|
|
|
// spaIndex resolves the built frontend directory and reports whether its
|
|
// index.html exists.
|
|
func spaIndex() (dist, index string, ok bool) {
|
|
dist = strings.TrimSpace(os.Getenv(SPADirEnv))
|
|
if dist == "" {
|
|
dist = "dist"
|
|
}
|
|
index = filepath.Join(dist, "index.html")
|
|
if _, err := os.Stat(index); err != nil {
|
|
return dist, index, false
|
|
}
|
|
return dist, index, true
|
|
}
|
|
|
|
// registerRootRoute decides what `GET /` returns (#346).
|
|
//
|
|
// `[必须]` When the built frontend exists, `/` must be the Admin SPA. go-admin's
|
|
// welcome page used to own `/` in every non-prod mode, so any reverse proxy
|
|
// that forwarded `/` to this server (instead of serving dist itself) showed
|
|
// "GO-ADMIN欢迎您" instead of the Admin — which is exactly what happened after
|
|
// the 2026-09-28 server migration. The welcome page is kept only for
|
|
// development without a dist, where the frontend runs under vite.
|
|
func registerRootRoute(r gin.IRoutes, mode string, welcome gin.HandlerFunc) {
|
|
if _, index, ok := spaIndex(); ok {
|
|
r.GET("/", func(c *gin.Context) { c.File(index) })
|
|
return
|
|
}
|
|
if mode != "prod" {
|
|
r.GET("/", welcome)
|
|
}
|
|
}
|
|
|
|
// isAPIPath reports whether a path belongs to the server rather than the SPA.
|
|
func isAPIPath(path string) bool {
|
|
for _, prefix := range []string{"/api/", "/swagger/", "/static/", "/form-generator/", "/ws/", "/wslogout/", "/info"} {
|
|
if strings.HasPrefix(path, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func fileExists(path string) bool {
|
|
info, err := os.Stat(path)
|
|
return err == nil && !info.IsDir()
|
|
}
|