package router import ( "io/fs" "net/http" "strings" "github.com/go-chi/chi/v5" "trade/web/internal/auth" "trade/web/internal/handlers" mw "trade/web/internal/middleware" "trade/web/internal/store" ) func New(d *handlers.Deps, mgr *auth.Manager, authStore *store.AuthStore, dist fs.FS) http.Handler { r := chi.NewRouter() r.Use(mw.Recover) r.Use(mw.Logger) r.Route("/api", func(r chi.Router) { r.Post("/login", d.Login) r.Group(func(r chi.Router) { r.Use(mw.RequireUser(mgr, authStore)) r.Post("/logout", d.Logout) r.Get("/me", d.Me) r.Get("/scores", d.ListScores) r.Get("/scores/{id}", d.GetScore) r.Get("/contracts", d.ListContracts) r.Get("/candles", d.ListCandles) r.Group(func(r chi.Router) { r.Use(mw.RequireAdmin) r.Get("/admin/users", d.AdminListUsers) r.Post("/admin/users", d.AdminCreateUser) r.Patch("/admin/users/{id}", d.AdminPatchUser) r.Delete("/admin/users/{id}", d.AdminDeleteUser) }) }) }) r.Handle("/*", spa(dist)) return r } // spa 返回单文件 SPA handler:文件存在则发文件,否则发 index.html。 func spa(root fs.FS) http.Handler { fileServer := http.FileServer(http.FS(root)) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/") if path == "" { path = "index.html" } if _, err := fs.Stat(root, path); err != nil { // 找不到文件 → SPA 路由,回 index.html 让前端 router 处理 r2 := r.Clone(r.Context()) r2.URL.Path = "/" fileServer.ServeHTTP(w, r2) return } fileServer.ServeHTTP(w, r) }) }