// Package router 负责 HTTP 路由注册,将请求分发到 Controller。 package router import ( "fmt" "net/http" "path/filepath" "strings" "stock-analysis/internal/controller" ) // New 构建应用路由处理器。 // webDir 为前端静态资源目录(View 层)。 func New(mc *controller.MarketController, ac *controller.AuthController, webDir string) http.Handler { mux := http.NewServeMux() // 公开接口:无需登录。 mux.HandleFunc("POST /api/auth/send-code", ac.SendCode) mux.HandleFunc("POST /api/auth/register", ac.Register) mux.HandleFunc("POST /api/auth/login", ac.Login) // 受保护接口:需要登录。 mux.HandleFunc("GET /api/auth/me", ac.Me) mux.HandleFunc("POST /api/auth/logout", ac.Logout) mux.HandleFunc("GET /api/bonds", requireAuth(ac, mc.ListBonds)) mux.HandleFunc("GET /api/bonds/{code}/kline", requireAuth(ac, mc.GetKline)) mux.HandleFunc("POST /api/backtest", requireAuth(ac, mc.RunBacktest)) mux.HandleFunc("GET /api/status", requireAuth(ac, mc.GetDataStatus)) mux.HandleFunc("GET /api/db/bonds", requireAuth(ac, mc.ListDBBonds)) // 静态资源:登录页及样式脚本公开,首页受保护。 fs := noCache(http.FileServer(http.Dir(webDir))) mux.Handle("/auth.html", fs) mux.Handle("/style.css", fs) mux.HandleFunc("/", requireAuth(ac, func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" && r.URL.Path != "/index.html" { http.NotFound(w, r) return } http.ServeFile(w, r, filepath.Join(webDir, "index.html")) })) return mux } // requireAuth 中间件:未登录的 API 请求返回 401,页面请求重定向到登录页。 func requireAuth(ac *controller.AuthController, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if _, err := ac.CurrentUser(r); err != nil { if strings.HasPrefix(r.URL.Path, "/api/") { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) fmt.Fprintf(w, `{"error":"%s"}`, err.Error()) } else { http.Redirect(w, r, "/auth.html", http.StatusFound) } return } next.ServeHTTP(w, r) } } // noCache 中间件:禁止浏览器缓存静态资源,确保每次加载最新版本。 func noCache(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") next.ServeHTTP(w, r) }) }