Initial commit: 可转债轮动分析系统

- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换)
- 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制)
- 增量同步:支持 -incr/-since 参数按存档日期增量入库
- 策略回测:净值曲线/10指标/基准对比,含基本面过滤
- 用户认证:用户名密码 + 邮箱验证码双登录方式
- 前端:三Tab SPA(实时行情/策略回测/数据管理)
This commit is contained in:
2026-08-17 20:23:22 +08:00
commit e28bfec957
45 changed files with 7889 additions and 0 deletions

71
internal/router/router.go Normal file
View File

@@ -0,0 +1,71 @@
// 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)
})
}