Files
lijiaoqiao/supply-api/internal/app/bootstrap.go

171 lines
5.2 KiB
Go

package app
import (
"context"
"errors"
"net/http"
"strings"
"time"
"lijiaoqiao/supply-api/internal/config"
"lijiaoqiao/supply-api/internal/httpapi"
"lijiaoqiao/supply-api/internal/middleware"
"lijiaoqiao/supply-api/internal/pkg/logging"
)
// BuildServerOptions 定义 HTTP 服务装配所需的最小输入。
type BuildServerOptions struct {
Env string
ServerConfig config.ServerConfig
Logger logging.Logger
SupplyAPI *httpapi.SupplyAPI
AlertAPI *httpapi.AlertAPI
AuthMiddleware *middleware.AuthMiddleware
RateLimitConfig *middleware.RateLimitConfig
DBHealthCheck func(context.Context) error
RedisHealthCheck func(context.Context) error
}
type buildRouteMuxOptions struct {
SupplyAPI *httpapi.SupplyAPI
AlertAPI *httpapi.AlertAPI
DBHealthCheck func(context.Context) error
RedisHealthCheck func(context.Context) error
}
type middlewareChainOptions struct {
Env string
Logger logging.Logger
AuthMiddleware *middleware.AuthMiddleware
RateLimitConfig *middleware.RateLimitConfig
}
type resolvedBuildServerOptions struct {
Env string
ServerConfig config.ServerConfig
Logger logging.Logger
SupplyAPI *httpapi.SupplyAPI
AlertAPI *httpapi.AlertAPI
AuthMiddleware *middleware.AuthMiddleware
RateLimitConfig *middleware.RateLimitConfig
DBHealthCheck func(context.Context) error
RedisHealthCheck func(context.Context) error
}
// BuildServer 构建可复用的 HTTP server 与 handler 装配。
func BuildServer(opts BuildServerOptions) (*http.Server, error) {
resolved, err := resolveBuildServerOptions(opts)
if err != nil {
return nil, err
}
mux := buildRouteMux(buildRouteMuxOptions{
SupplyAPI: resolved.SupplyAPI,
AlertAPI: resolved.AlertAPI,
DBHealthCheck: resolved.DBHealthCheck,
RedisHealthCheck: resolved.RedisHealthCheck,
})
handler := buildMiddlewareChain(middlewareChainOptions{
Env: resolved.Env,
Logger: resolved.Logger,
AuthMiddleware: resolved.AuthMiddleware,
RateLimitConfig: resolved.RateLimitConfig,
}, mux)
return &http.Server{
Addr: resolved.ServerConfig.Addr,
Handler: handler,
ReadHeaderTimeout: resolved.ServerConfig.ReadTimeout,
ReadTimeout: resolved.ServerConfig.ReadTimeout,
WriteTimeout: resolved.ServerConfig.WriteTimeout,
IdleTimeout: resolved.ServerConfig.IdleTimeout,
}, nil
}
func resolveBuildServerOptions(opts BuildServerOptions) (resolvedBuildServerOptions, error) {
if opts.SupplyAPI == nil {
return resolvedBuildServerOptions{}, errors.New("supply api is required")
}
if opts.AlertAPI == nil {
return resolvedBuildServerOptions{}, errors.New("alert api is required")
}
if opts.Logger == nil {
return resolvedBuildServerOptions{}, errors.New("logger is required")
}
env, err := ResolveEnv(opts.Env)
if err != nil {
return resolvedBuildServerOptions{}, err
}
if env != "dev" && opts.AuthMiddleware == nil {
return resolvedBuildServerOptions{}, errors.New("auth middleware is required outside dev")
}
return resolvedBuildServerOptions{
Env: env,
ServerConfig: normalizeServerConfig(opts.ServerConfig),
Logger: opts.Logger,
SupplyAPI: opts.SupplyAPI,
AlertAPI: opts.AlertAPI,
AuthMiddleware: opts.AuthMiddleware,
RateLimitConfig: resolveRateLimitConfig(env, opts.RateLimitConfig),
DBHealthCheck: opts.DBHealthCheck,
RedisHealthCheck: opts.RedisHealthCheck,
}, nil
}
func normalizeServerConfig(serverConfig config.ServerConfig) config.ServerConfig {
if strings.TrimSpace(serverConfig.Addr) == "" {
serverConfig.Addr = ":18082"
}
if serverConfig.ReadTimeout == 0 {
serverConfig.ReadTimeout = 10 * time.Second
}
if serverConfig.WriteTimeout == 0 {
serverConfig.WriteTimeout = 15 * time.Second
}
if serverConfig.IdleTimeout == 0 {
serverConfig.IdleTimeout = 30 * time.Second
}
if serverConfig.ShutdownTimeout == 0 {
serverConfig.ShutdownTimeout = 5 * time.Second
}
return serverConfig
}
func resolveRateLimitConfig(env string, rateLimitConfig *middleware.RateLimitConfig) *middleware.RateLimitConfig {
if rateLimitConfig != nil {
return rateLimitConfig
}
rateLimitConfig = middleware.DefaultRateLimitConfig()
rateLimitConfig.Enabled = env != "dev"
return rateLimitConfig
}
func buildRouteMux(opts buildRouteMuxOptions) *http.ServeMux {
mux := http.NewServeMux()
healthHandler := httpapi.NewHealthHandlerWithDefaults(opts.DBHealthCheck, opts.RedisHealthCheck)
healthHandler.RegisterRoutes(mux)
opts.SupplyAPI.Register(mux)
opts.AlertAPI.Register(mux)
return mux
}
func buildMiddlewareChain(opts middlewareChainOptions, next http.Handler) http.Handler {
var handler http.Handler = next
handler = middleware.RequestID(handler)
handler = middleware.Recovery(handler)
handler = middleware.Logging(handler, opts.Logger)
handler = middleware.TracingMiddleware(handler)
if opts.Env != "dev" {
handler = middleware.NewRateLimitHandler(opts.RateLimitConfig, handler)
handler = opts.AuthMiddleware.TokenVerifyMiddleware(handler)
handler = opts.AuthMiddleware.BearerExtractMiddleware(handler)
handler = opts.AuthMiddleware.QueryKeyRejectMiddleware(handler)
}
return handler
}