- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换) - 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制) - 增量同步:支持 -incr/-since 参数按存档日期增量入库 - 策略回测:净值曲线/10指标/基准对比,含基本面过滤 - 用户认证:用户名密码 + 邮箱验证码双登录方式 - 前端:三Tab SPA(实时行情/策略回测/数据管理)
72 lines
2.5 KiB
Go
72 lines
2.5 KiB
Go
// 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)
|
||
})
|
||
}
|