diff --git a/backend/Makefile b/backend/Makefile new file mode 100644 index 00000000..9aa0c965 --- /dev/null +++ b/backend/Makefile @@ -0,0 +1,6 @@ +.PHONY: wire + +wire: + @echo "生成 Wire 代码..." + @cd cmd/server && go generate + @echo "Wire 代码生成完成" \ No newline at end of file diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index a391a031..9358775a 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -1,8 +1,11 @@ package main +//go:generate go run github.com/google/wire/cmd/wire + import ( "context" _ "embed" + "errors" "flag" "log" "net/http" @@ -110,78 +113,25 @@ func runSetupServer() { } func runMainServer() { - // 加载配置 - cfg, err := config.Load() - if err != nil { - log.Fatalf("Failed to load config: %v", err) - } - - // 初始化时区(类似 PHP 的 date_default_timezone_set) - if err := timezone.Init(cfg.Timezone); err != nil { - log.Fatalf("Failed to initialize timezone: %v", err) - } - - // 初始化数据库 - db, err := initDB(cfg) - if err != nil { - log.Fatalf("Failed to connect to database: %v", err) - } - - // 初始化Redis - rdb := initRedis(cfg) - - // 初始化Repository - repos := repository.NewRepositories(db) - - // 初始化Service - services := service.NewServices(repos, rdb, cfg) - - // 初始化Handler buildInfo := handler.BuildInfo{ Version: Version, BuildType: BuildType, } - handlers := handler.NewHandlers(services, repos, rdb, buildInfo) - // 设置Gin模式 - if cfg.Server.Mode == "release" { - gin.SetMode(gin.ReleaseMode) - } - - // 创建路由 - r := gin.New() - r.Use(gin.Recovery()) - r.Use(middleware.Logger()) - r.Use(middleware.CORS()) - - // 注册路由 - registerRoutes(r, handlers, services, repos) - - // Serve embedded frontend if available - if web.HasEmbeddedFrontend() { - r.Use(web.ServeEmbeddedFrontend()) + app, err := initializeApplication(buildInfo) + if err != nil { + log.Fatalf("Failed to initialize application: %v", err) } + defer app.Cleanup() // 启动服务器 - srv := &http.Server{ - Addr: cfg.Server.Address(), - Handler: r, - // ReadHeaderTimeout: 读取请求头的超时时间,防止慢速请求头攻击 - ReadHeaderTimeout: time.Duration(cfg.Server.ReadHeaderTimeout) * time.Second, - // IdleTimeout: 空闲连接超时时间,释放不活跃的连接资源 - IdleTimeout: time.Duration(cfg.Server.IdleTimeout) * time.Second, - // 注意:不设置 WriteTimeout,因为流式响应可能持续十几分钟 - // 不设置 ReadTimeout,因为大请求体可能需要较长时间读取 - } - - // 优雅关闭 go func() { - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := app.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("Failed to start server: %v", err) } }() - log.Printf("Server started on %s", cfg.Server.Address()) + log.Printf("Server started on %s", app.Server.Addr) // 等待中断信号 quit := make(chan os.Signal, 1) @@ -193,7 +143,7 @@ func runMainServer() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := srv.Shutdown(ctx); err != nil { + if err := app.Server.Shutdown(ctx); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } @@ -201,6 +151,11 @@ func runMainServer() { } func initDB(cfg *config.Config) (*gorm.DB, error) { + // 初始化时区(在数据库连接之前,确保时区设置正确) + if err := timezone.Init(cfg.Timezone); err != nil { + return nil, err + } + gormConfig := &gorm.Config{} if cfg.Server.Mode == "debug" { gormConfig.Logger = logger.Default.LogMode(logger.Info) @@ -479,3 +434,34 @@ func registerRoutes(r *gin.Engine, h *handler.Handlers, s *service.Services, rep gateway.GET("/usage", h.Gateway.Usage) } } + +// setupRouter 配置路由器中间件和路由 +func setupRouter(r *gin.Engine, cfg *config.Config, handlers *handler.Handlers, services *service.Services, repos *repository.Repositories) *gin.Engine { + // 应用中间件 + r.Use(middleware.Logger()) + r.Use(middleware.CORS()) + + // 注册路由 + registerRoutes(r, handlers, services, repos) + + // Serve embedded frontend if available + if web.HasEmbeddedFrontend() { + r.Use(web.ServeEmbeddedFrontend()) + } + + return r +} + +// createHTTPServer 创建HTTP服务器 +func createHTTPServer(cfg *config.Config, router *gin.Engine) *http.Server { + return &http.Server{ + Addr: cfg.Server.Address(), + Handler: router, + // ReadHeaderTimeout: 读取请求头的超时时间,防止慢速请求头攻击 + ReadHeaderTimeout: time.Duration(cfg.Server.ReadHeaderTimeout) * time.Second, + // IdleTimeout: 空闲连接超时时间,释放不活跃的连接资源 + IdleTimeout: time.Duration(cfg.Server.IdleTimeout) * time.Second, + // 注意:不设置 WriteTimeout,因为流式响应可能持续十几分钟 + // 不设置 ReadTimeout,因为大请求体可能需要较长时间读取 + } +} diff --git a/backend/cmd/server/wire.go b/backend/cmd/server/wire.go new file mode 100644 index 00000000..91cc131a --- /dev/null +++ b/backend/cmd/server/wire.go @@ -0,0 +1,103 @@ +//go:build wireinject +// +build wireinject + +package main + +import ( + "sub2api/internal/config" + "sub2api/internal/handler" + "sub2api/internal/repository" + "sub2api/internal/service" + + "net/http" + + "github.com/gin-gonic/gin" + "github.com/google/wire" + "github.com/redis/go-redis/v9" + "gorm.io/gorm" +) + +type Application struct { + Server *http.Server + Cleanup func() +} + +func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { + wire.Build( + // Config provider + provideConfig, + + // Database provider + provideDB, + + // Redis provider + provideRedis, + + // Repository provider + provideRepositories, + + // Service provider + provideServices, + + // Handler provider + provideHandlers, + + // Router provider + provideRouter, + + // HTTP Server provider + provideHTTPServer, + + // Cleanup provider + provideCleanup, + + // Application provider + wire.Struct(new(Application), "Server", "Cleanup"), + ) + return nil, nil +} + +func provideConfig() (*config.Config, error) { + return config.Load() +} + +func provideDB(cfg *config.Config) (*gorm.DB, error) { + return initDB(cfg) +} + +func provideRedis(cfg *config.Config) *redis.Client { + return initRedis(cfg) +} + +func provideRepositories(db *gorm.DB) *repository.Repositories { + return repository.NewRepositories(db) +} + +func provideServices(repos *repository.Repositories, rdb *redis.Client, cfg *config.Config) *service.Services { + return service.NewServices(repos, rdb, cfg) +} + +func provideHandlers(services *service.Services, repos *repository.Repositories, rdb *redis.Client, buildInfo handler.BuildInfo) *handler.Handlers { + return handler.NewHandlers(services, repos, rdb, buildInfo) +} + +func provideRouter(cfg *config.Config, handlers *handler.Handlers, services *service.Services, repos *repository.Repositories) *gin.Engine { + if cfg.Server.Mode == "release" { + gin.SetMode(gin.ReleaseMode) + } + + r := gin.New() + r.Use(gin.Recovery()) + + return setupRouter(r, cfg, handlers, services, repos) +} + +func provideHTTPServer(cfg *config.Config, router *gin.Engine) *http.Server { + return createHTTPServer(cfg, router) +} + +func provideCleanup() func() { + return func() { + // @todo + } +} diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go new file mode 100644 index 00000000..e0d46e59 --- /dev/null +++ b/backend/cmd/server/wire_gen.go @@ -0,0 +1,99 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package main + +import ( + "github.com/gin-gonic/gin" + "github.com/redis/go-redis/v9" + "gorm.io/gorm" + "net/http" + "sub2api/internal/config" + "sub2api/internal/handler" + "sub2api/internal/repository" + "sub2api/internal/service" +) + +import ( + _ "embed" +) + +// Injectors from wire.go: + +func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { + config, err := provideConfig() + if err != nil { + return nil, err + } + db, err := provideDB(config) + if err != nil { + return nil, err + } + repositories := provideRepositories(db) + client := provideRedis(config) + services := provideServices(repositories, client, config) + handlers := provideHandlers(services, repositories, client, buildInfo) + engine := provideRouter(config, handlers, services, repositories) + server := provideHTTPServer(config, engine) + v := provideCleanup() + application := &Application{ + Server: server, + Cleanup: v, + } + return application, nil +} + +// wire.go: + +type Application struct { + Server *http.Server + Cleanup func() +} + +func provideConfig() (*config.Config, error) { + return config.Load() +} + +func provideDB(cfg *config.Config) (*gorm.DB, error) { + return initDB(cfg) +} + +func provideRedis(cfg *config.Config) *redis.Client { + return initRedis(cfg) +} + +func provideRepositories(db *gorm.DB) *repository.Repositories { + return repository.NewRepositories(db) +} + +func provideServices(repos *repository.Repositories, rdb *redis.Client, cfg *config.Config) *service.Services { + return service.NewServices(repos, rdb, cfg) +} + +func provideHandlers(services *service.Services, repos *repository.Repositories, rdb *redis.Client, buildInfo handler.BuildInfo) *handler.Handlers { + return handler.NewHandlers(services, repos, rdb, buildInfo) +} + +func provideRouter(cfg *config.Config, handlers *handler.Handlers, services *service.Services, repos *repository.Repositories) *gin.Engine { + if cfg.Server.Mode == "release" { + gin.SetMode(gin.ReleaseMode) + } + + r := gin.New() + r.Use(gin.Recovery()) + + return setupRouter(r, cfg, handlers, services, repos) +} + +func provideHTTPServer(cfg *config.Config, router *gin.Engine) *http.Server { + return createHTTPServer(cfg, router) +} + +func provideCleanup() func() { + return func() { + + } +} diff --git a/backend/go.mod b/backend/go.mod index b17beedf..1c83ffeb 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,6 +13,7 @@ require ( github.com/redis/go-redis/v9 v9.3.0 github.com/spf13/viper v1.18.2 golang.org/x/crypto v0.44.0 + golang.org/x/net v0.47.0 golang.org/x/term v0.37.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.5.4 @@ -33,6 +34,8 @@ require ( github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/subcommands v1.2.0 // indirect + github.com/google/wire v0.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/icholy/digest v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -50,6 +53,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.56.0 // indirect github.com/refraction-networking/utls v1.8.1 // indirect @@ -66,9 +70,11 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.47.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index b3febba6..0198ecfc 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -48,8 +48,12 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= +github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4= @@ -154,8 +158,12 @@ golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= @@ -166,6 +174,8 @@ golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= diff --git a/backend/tools.go b/backend/tools.go new file mode 100644 index 00000000..fc19d5ce --- /dev/null +++ b/backend/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/google/wire/cmd/wire" +)