12 KiB
xlgo v1.1.0 实施计划 — HA & Manager 化
本文件为 v1.1.0 版本的逐项实施计划,对应体检报告(
docs/plans/Version_v1.0.2_report.md)第三~四章 #10-#24 架构/HA 改进。 范围:13 项(#20 RedisRateLimiter、#23 Recover 带 request_id 已实现,本次核对配合)。
Context
v1.0.3(bug fix)与 v1.0.4(DX & Docs)已发布推送,GitHub Release 已创建,工作区干净。v1.0.2 体检报告规划的 v1.1.0 阶段共 13 项架构/HA 改进,目标是把 xlgo 从"一半组件可注入、一半是单例"的撕裂状态,贯彻到"通用 / 高可用 / 易上手"。
本次一次性推完 13 项,发 v1.1.0。已确认的边界决策:
- 版本策略:v1.1.0 接受少量 breaking(#11 删 wire 包、#14 删
AppConfig.TokenExpire、删StartServerWithPort/GracefulShutdown、JWTConfig.Expire类型变更),在 CHANGELOG「升级说明」章节列出;其余(#15 response)用全局开关默认兼容存量。 - #10 Manager 化:storage/cache/redis/jwt/logger 5 个全做(含 logger),照
database.Manager+ 包级 facade 蓝本。 - #15 response:全局
SetMode(ModeBusiness|ModeREST),默认ModeBusiness(现状全 200 + 业务码)兼容;ModeREST下 401/404/500 返回对应 HTTP status,body 仍带业务码。可在ServerConfig.ResponseMode配置。 - #11 wire:删掉整个
wire包(其事 App Option 已覆盖)。 - #18:接受
prometheus/client_golang新依赖。 - #12:只提供
WithHook(Hook{...})机制,不引入 etcd 依赖、不提供内置示例。
实现方案(按依赖顺序分 6 组)
组 1:配置层(其余项的基础)
#13 Server 参数配置化 — config/config.go:84-88
ServerConfig 增字段:ReadTimeout/WriteTimeout/IdleTimeout/ShutdownTimeout/MaxHeaderBytes、TLS{Enabled,CertFile,KeyFile}、UnixSocket string、ResponseMode string。app.go:408-414 的 http.Server 改为读这些字段(缺省回退到当前硬编码值)。TLS 开启时用 ListenAndServeTLS;UnixSocket 非空时优先 unix socket。Shutdown 超时读 ShutdownTimeout。
#14 JWTConfig.Expire time.Duration + 删 TokenExpire — config/config.go:44, 209-213
JWTConfig.Expire 改 time.Duration(mapstructure + viper string 解析 "24h"),新增 RefreshExpire time.Duration、Issuer、Algorithm。删 AppConfig.TokenExpire(breaking)。jwt/jwt.go 内过期取值改读 JWTConfig.Expire。Duration 解析依赖 mapstructure decode hook。
#16 Config Validate — 新增 config/config.go (*Config).Validate() error
校验:Server.Port 范围、JWT.Secret 非空且 ≥32 字符(启用 jwt 时)、启用 mysql 时关键字段、Duration 非负等。config.Manager.Load(config/manager.go:340)解析后自动调用,错误包裹返回,把"运行时第一次请求才暴露"提前到启动。
#15 response Mode 开关 — response/response.go
新增 type Mode int、ModeBusiness/ModeREST、包级 currentMode + SetMode(m)、Mode()。Fail/Unauthorized/NotFound/ServerError/RateLimit/FailWithCode 内:ModeREST 时按错误码/错误类型映射 HTTP status(ErrUnauthorized→401、ErrNotFound→404、ErrServer→500、ErrRateLimit→429、参数类→400),c.JSON(status, Response{...});ModeBusiness 维持 200。App.Init 末尾按 ServerConfig.ResponseMode 调 response.SetMode。新增 response.Custom(c, httpStatus, code, data) 显式 API。
组 2:组件 Manager 化(#10,5 组件,照 database.Manager 蓝本)
蓝本参考 database/manager.go:180-192(DefaultManager 包级实例 + SetDefaultManager + 包级 facade 代理 + 实例方法)。每个组件统一模式:
- 新增
type Manager struct{ ...; mu sync.Mutex }+var DefaultXxx = &Manager{} SetDefaultManager(m)提升用户实例到全局- 包级
Init/Get/操作函数代理到DefaultXxx(保留,兼容存量) App持有各 Manager 实例(App加字段),WithXxx时初始化 App 自己的实例并SetDefaultXxx
顺序(按依赖):redis → cache → jwt → storage → logger
-
redis —
database/redis.go:新增type RedisManager struct{ cfg; client *redis.Client; mu }+DefaultRedis。InitRedis/CloseRedis/GetRedis/HealthCheckRedis代理。下游 5 处直接读database.RedisClient(jwt/jwt.go、middleware/ratelimit.go、cache/cache.go、cache/lock.go20+ 处、app.go)全部改为database.GetRedis()。cache/lock.go改为接受*redis.Client参数或内部GetRedis()。 -
cache —
cache/cache.go:type CacheManager struct{ client *redis.Client; svc CacheService; mu }+DefaultCache。redisCache.client从硬编码database.RedisClient改为构造时注入。Init/GetCache代理。cache/lock.go的分布式锁函数改走DefaultCache或显式传 client。 -
jwt —
jwt/jwt.go:type Manager struct{ blacklist *TokenBlacklist; cfg *config.JWTConfig; mu }+DefaultJWT。TokenBlacklist.Add/IsBlacklisted内部database.RedisClient改database.GetRedis()。GenerateToken/ParseToken/InvalidateToken/RefreshToken/...代理到DefaultJWT。 -
storage —
storage/storage.go:type Manager struct{ cfg; current Storage; mu }+DefaultStorage。Init/GetStorage/SetStorage/Upload/...代理到DefaultStorage.current。最干净,无外部下游。 -
logger —
logger/logger.go:type Manager struct{ cfg; logger/apiLog/dbLog *zap.Logger; fileWriters; mu }+DefaultLogger。包级Init/Sync/Close/Info/.../APILog/DBLog代理。特殊:logger 是App.Init最先初始化的组件,DefaultLogger初始化前包级函数须安全降级到 Nop(现状Close()已重置为 Nop,沿用)。下游 8 包的logger.Info(...)调用点无需改(仍走包级 facade)。
App struct(app.go:41-62)加字段:redisMgr *database.RedisManager、cacheMgr *cache.CacheManager、jwtMgr *jwt.Manager、storageMgr *storage.Manager、loggerMgr *logger.Manager。
组 3:App 生命周期
#12 Lifecycle Hooks — app.go
新增 type Hook struct{ Name string; OnInit func(*App) error; OnStart func(*App) error; OnReady func(*App); OnStop func(*App) error } + WithHook(Hook) Option。App 加 hooks []Hook。Init() 内组件初始化完成后按序调 OnInit;StartServer 监听前调 OnStart,端口就绪后调 OnReady;Shutdown 开头调 OnStop。各 hook 错误中断流程并返回。
#22 App.Go + in-flight goroutine — app.go
App 加 wg sync.WaitGroup + ctx context.Context(根 ctx)+ cancel。新增 App.Go(fn func(ctx context.Context)):wg.Add(1); go func(){ defer wg.Done(); fn(ctx) }()。Shutdown 在 OnStop 后、关 HTTP 前调 cancel() 并 wg.Wait()(带 ShutdownTimeout 超时)。
#11 删 wire 包 — 删 wire/wire.go(整包)。清理 app.go 的 WithWire/WithoutWire/enableWire 及 Init() 中 wire.InitServices() 调用。cache.Init() 原由 wire 触发,改由 WithRedis/WithCache 触发(或 App.Init 显式调)。
清理双轨 — app.go:494-537:删 StartServerWithPort、GracefulShutdown(与 App.StartServer/App.Shutdown 重复)。breaking,升级说明列出。
组 4:中间件与路由
#24 RequestID 默认装入 — app.go:~348
App.Init 中间件链改为无条件 a.router.Use(middleware.RequestID())(在 Recovery 之前),让每个响应/panic 日志都带 request_id。核对 #23:middleware/recover.go:20 已 GetRequestID(c),配合 #24 后 panic 日志 request_id 非空。移除 gin.Recovery() 双重保险(保留 middleware.Recover() 一个即可)。
#19 请求级 Timeout 中间件 — 新增 middleware/timeout.go
func Timeout(d time.Duration) gin.HandlerFunc:ctx, cancel := context.WithTimeout(c.Request.Context(), d); defer cancel(); c.Request = c.Request.WithContext(ctx); c.Next()。可通过 WithRequestTimeout(d) Option 装入全局,下游 GORM/Redis 走 c.Request.Context() 级联取消。
#18 Prometheus metrics — 新依赖 prometheus/client_golang
新增 middleware/metrics.go:middleware.Metrics() 记录 HTTP latency / status code / in-flight(histogram + counter + gauge)。新增 router/metrics.go:RegisterMetricsRoute(r, path...) 默认 /metrics 挂 promhttp.Handler()。新增 WithMetricsRoute(path...) Option。App.Init 装入 Metrics() 中间件 + 路由。
#17 livez/readyz — router/router.go
新增 RegisterLivenessRoute(r) → GET /livez 永不依赖外部(进程存活即 200)。新增 RegisterReadinessRoute(r, checks...) → GET /readyz 复用 HealthCheck,失败 503。新增 WithLivenessRoute()/WithReadinessRoute() Option。/health 保留兼容。livez 不查依赖、readyz 查依赖,对 K8s probe 友好。
组 5:依赖健康自愈(#21)
#21 主库探活 + replica 健康剔除 — database/manager.go
database.Manager加healthy bool+consecutiveFailures int+healthMu。Pool.SetConnMaxIdleTime配置化(DatabaseConfig加ConnMaxIdleTime)。- 探活:
App.Init末尾用App.Go起后台 goroutine,每 30sHealthCheck(ctx),连续 N 次失败标记healthy=false;readyz//health读此标记返回 503。 ReplicaPicker接口加健康度:replica ping 失败剔除轮询,恢复后重新纳入。RoundRobinPicker/RandomPicker实现 health-aware 选取。
组 6:收尾与发版
app.go:27const Version = "1.1.0"。CHANGELOG.md加[1.1.0]章节(Added/Changed/Fixed + 升级说明 breaking 列表)。README 更新日志 + 底部链接。examples/(full/minimal)同步:full 例子的配置文件加server.read_timeout等新字段、jwt.expire: 24h,验证 5 组件 Manager 化后仍可跑。- 测试:
go test ./...全绿;为 Manager 化、Validate、response Mode、livez/readyz、metrics、timeout、App.Go 补单测。 go mod tidy。
关键文件清单
| 文件 | 涉及 issue |
|---|---|
app.go |
#10 App 字段、#12 hooks、#13 server、#19/#18/#17/#24 路由中间件、#21 探活、#22 App.Go、#11 删 wire、删双轨、Version |
config/config.go |
#13 ServerConfig、#14 JWTConfig+删TokenExpire、#16 Validate |
config/manager.go |
#16 Load 调 Validate、Duration decode hook |
response/response.go |
#15 Mode 开关 + status 映射 |
database/redis.go |
#10 redis Manager |
database/manager.go |
#21 探活 + replica 健康剔除 |
cache/cache.go cache/lock.go |
#10 cache Manager + 解耦 RedisClient |
jwt/jwt.go |
#10 jwt Manager + #14 Expire |
storage/storage.go |
#10 storage Manager |
logger/logger.go |
#10 logger Manager |
middleware/{requestid,recover}.go |
#24 装入 + #23 核对 |
middleware/timeout.go(新) |
#19 |
middleware/metrics.go(新) |
#18 |
router/router.go router/metrics.go(新) |
#17 livez/readyz + #18 /metrics |
wire/wire.go |
#11 删除 |
go.mod |
#18 prometheus 依赖 |
CHANGELOG.md README.md |
发版 |
验证
go mod tidy && go build ./...— 编译通过。go test ./...— 全绿,含新增单测。go run ./example(full 栈)— 启动无错,/livez→200、/readyz→200(依赖在时)、/metrics→prometheus 文本、/health兼容;配置错误时Validate在启动期拦截。- 手动验证 breaking:删 wire 后
example不再 import wire;AppConfig.TokenExpire删除后 grep 无残留;response.SetMode(ModeREST)下Unauthorized返回 401。 App.Go起一个长任务 goroutine,发 SIGTERM,确认Shutdown等其退出(日志可见)。- 主库探活:手动关 mysql,30s 后
/readyz→503、/health→503;恢复后自动转 200。 - 发版:commit + annotated tag
v1.1.0→git push xlgo-core main && git push xlgo-core v1.1.0;release 内容写本地gitHub_release_v1.1.0.md(.gitignore 忽略),用户网页创建。