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

87
cmd/server/main.go Normal file
View File

@@ -0,0 +1,87 @@
// server 是可转债轮动分析系统的 B/S 服务入口。
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"stock-analysis/internal/controller"
"stock-analysis/internal/datasource/eastmoney"
"stock-analysis/internal/datasource/fallback"
"stock-analysis/internal/datasource/tencent"
"stock-analysis/internal/router"
"stock-analysis/internal/service"
"stock-analysis/internal/storage/sqlite"
)
func main() {
addr := ":8080"
if p := os.Getenv("PORT"); p != "" {
addr = ":" + p
}
webDir := "web"
dbPath := defaultDBPath()
// 东财为主数据源(实时行情+历史K线),腾讯为历史K线备用源(东财失败时自动切换)
ds := fallback.New(eastmoney.NewClient(), tencent.NewClient())
svc := service.NewMarketService(ds)
mc := controller.NewMarketController(svc, dbPath)
store, err := sqlite.Open(dbPath)
if err != nil {
log.Fatalf("open sqlite: %v", err)
}
defer store.Close()
authSvc := service.NewAuthService(store)
if err := authSvc.EnsureAdmin(context.Background()); err != nil {
log.Printf("ensure admin: %v", err)
}
ac := controller.NewAuthController(authSvc)
handler := router.New(mc, ac, webDir)
server := &http.Server{
Addr: addr,
Handler: handler,
ReadHeaderTimeout: 5 * time.Second,
}
// 优雅退出:收到信号后关闭服务。
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go func() {
log.Printf("stock-analysis server listening on http://localhost%s", addr)
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("server: %v", err)
}
}()
<-ctx.Done()
log.Println("shutting down server...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Printf("shutdown: %v", err)
}
}
// defaultDBPath 返回默认数据库路径。
// 默认使用项目目录下的 stock.db,避免 OneDrive/AppData 的 I/O 限制。
func defaultDBPath() string {
if env := os.Getenv("STOCK_DB_PATH"); env != "" {
return env
}
exe, err := os.Executable()
if err != nil {
return "stock.db"
}
return filepath.Join(filepath.Dir(exe), "stock.db")
}