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 := strings.TrimSpace(os.Getenv(SPADirEnv)) if dist == "" { dist = "dist" } index := filepath.Join(dist, "index.html") if _, err := os.Stat(index); err != nil { 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) }) } // 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() }