Files
xlgo-core/docs/plans/Version_Update_Plan_v1.1.0.md
2026-07-14 10:24:10 +08:00

12 KiB
Raw Permalink Blame History

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.3bug fix)与 v1.0.4DX & 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/GracefulShutdownJWTConfig.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 statusbody 仍带业务码。可在 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/MaxHeaderBytesTLS{Enabled,CertFile,KeyFile}UnixSocket stringResponseMode stringapp.go:408-414http.Server 改为读这些字段(缺省回退到当前硬编码值)。TLS 开启时用 ListenAndServeTLSUnixSocket 非空时优先 unix socket。Shutdown 超时读 ShutdownTimeout

#14 JWTConfig.Expire time.Duration + 删 TokenExpireconfig/config.go:44, 209-213 JWTConfig.Expiretime.Durationmapstructure + viper string 解析 "24h"),新增 RefreshExpire time.DurationIssuerAlgorithmAppConfig.TokenExpirebreaking)。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.Loadconfig/manager.go:340)解析后自动调用,错误包裹返回,把"运行时第一次请求才暴露"提前到启动。

#15 response Mode 开关response/response.go 新增 type Mode intModeBusiness/ModeREST、包级 currentMode + SetMode(m)Mode()Fail/Unauthorized/NotFound/ServerError/RateLimit/FailWithCode 内:ModeREST 时按错误码/错误类型映射 HTTP statusErrUnauthorized→401ErrNotFound→404ErrServer→500ErrRateLimit→429、参数类→400),c.JSON(status, Response{...})ModeBusiness 维持 200。App.Init 末尾按 ServerConfig.ResponseModeresponse.SetMode。新增 response.Custom(c, httpStatus, code, data) 显式 API。

组 2:组件 Manager 化(#105 组件,照 database.Manager 蓝本)

蓝本参考 database/manager.go:180-192DefaultManager 包级实例 + 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

  1. redisdatabase/redis.go:新增 type RedisManager struct{ cfg; client *redis.Client; mu } + DefaultRedisInitRedis/CloseRedis/GetRedis/HealthCheckRedis 代理。下游 5 处直接读 database.RedisClientjwt/jwt.gomiddleware/ratelimit.gocache/cache.gocache/lock.go 20+ 处、app.go)全部改为 database.GetRedis()cache/lock.go 改为接受 *redis.Client 参数或内部 GetRedis()

  2. cachecache/cache.gotype CacheManager struct{ client *redis.Client; svc CacheService; mu } + DefaultCacheredisCache.client 从硬编码 database.RedisClient 改为构造时注入。Init/GetCache 代理。cache/lock.go 的分布式锁函数改走 DefaultCache 或显式传 client。

  3. jwtjwt/jwt.gotype Manager struct{ blacklist *TokenBlacklist; cfg *config.JWTConfig; mu } + DefaultJWTTokenBlacklist.Add/IsBlacklisted 内部 database.RedisClientdatabase.GetRedis()GenerateToken/ParseToken/InvalidateToken/RefreshToken/... 代理到 DefaultJWT

  4. storagestorage/storage.gotype Manager struct{ cfg; current Storage; mu } + DefaultStorageInit/GetStorage/SetStorage/Upload/... 代理到 DefaultStorage.current。最干净,无外部下游。

  5. loggerlogger/logger.gotype 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 structapp.go:41-62)加字段:redisMgr *database.RedisManagercacheMgr *cache.CacheManagerjwtMgr *jwt.ManagerstorageMgr *storage.ManagerloggerMgr *logger.Manager

组 3App 生命周期

#12 Lifecycle Hooksapp.go 新增 type Hook struct{ Name string; OnInit func(*App) error; OnStart func(*App) error; OnReady func(*App); OnStop func(*App) error } + WithHook(Hook) OptionApphooks []HookInit() 内组件初始化完成后按序调 OnInitStartServer 监听前调 OnStart,端口就绪后调 OnReadyShutdown 开头调 OnStop。各 hook 错误中断流程并返回。

#22 App.Go + in-flight goroutineapp.go Appwg sync.WaitGroup + ctx context.Context(根 ctx+ cancel。新增 App.Go(fn func(ctx context.Context))wg.Add(1); go func(){ defer wg.Done(); fn(ctx) }()ShutdownOnStop 后、关 HTTP 前调 cancel()wg.Wait()(带 ShutdownTimeout 超时)。

#11 删 wire 包 — 删 wire/wire.go(整包)。清理 app.goWithWire/WithoutWire/enableWireInit()wire.InitServices() 调用。cache.Init() 原由 wire 触发,改由 WithRedis/WithCache 触发(或 App.Init 显式调)。

清理双轨app.go:494-537:删 StartServerWithPortGracefulShutdown(与 App.StartServer/App.Shutdown 重复)。breaking,升级说明列出。

组 4:中间件与路由

#24 RequestID 默认装入app.go:~348 App.Init 中间件链改为无条件 a.router.Use(middleware.RequestID())(在 Recovery 之前),让每个响应/panic 日志都带 request_id。核对 #23middleware/recover.go:20GetRequestID(c),配合 #24 后 panic 日志 request_id 非空。移除 gin.Recovery() 双重保险(保留 middleware.Recover() 一个即可)。

#19 请求级 Timeout 中间件 — 新增 middleware/timeout.go func Timeout(d time.Duration) gin.HandlerFuncctx, 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.gomiddleware.Metrics() 记录 HTTP latency / status code / in-flighthistogram + counter + gauge)。新增 router/metrics.goRegisterMetricsRoute(r, path...) 默认 /metricspromhttp.Handler()。新增 WithMetricsRoute(path...) Option。App.Init 装入 Metrics() 中间件 + 路由。

#17 livez/readyzrouter/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.Managerhealthy bool + consecutiveFailures int + healthMuPool.SetConnMaxIdleTime 配置化(DatabaseConfigConnMaxIdleTime)。
  • 探活:App.Init 末尾用 App.Go 起后台 goroutine,每 30s HealthCheck(ctx),连续 N 次失败标记 healthy=falsereadyz//health 读此标记返回 503。
  • ReplicaPicker 接口加健康度:replica ping 失败剔除轮询,恢复后重新纳入。RoundRobinPicker/RandomPicker 实现 health-aware 选取。

组 6:收尾与发版

  • app.go:27 const 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 发版

验证

  1. go mod tidy && go build ./... — 编译通过。
  2. go test ./... — 全绿,含新增单测。
  3. go run ./examplefull 栈)— 启动无错,/livez→200、/readyz→200(依赖在时)、/metrics→prometheus 文本、/health 兼容;配置错误时 Validate 在启动期拦截。
  4. 手动验证 breaking:删 wire 后 example 不再 import wireAppConfig.TokenExpire 删除后 grep 无残留;response.SetMode(ModeREST)Unauthorized 返回 401。
  5. App.Go 起一个长任务 goroutine,发 SIGTERM,确认 Shutdown 等其退出(日志可见)。
  6. 主库探活:手动关 mysql,30s 后 /readyz→503、/health→503;恢复后自动转 200。
  7. 发版:commit + annotated tag v1.1.0git push xlgo-core main && git push xlgo-core v1.1.0release 内容写本地 gitHub_release_v1.1.0.md.gitignore 忽略),用户网页创建。