v1.4.0 - instance+facade 一致性收尾
把 cron/storage/cache/ratelimit 四个"纯全局"包按
database.Manager/jwt.Manager模式实例化,trace 纳入 App 生命周期,清理WithoutWire技术债。单进程多 App 部署成为一等公民。
为什么发这个版本
database.Manager/logger.LogManager/config.Manager 早已是"实例 + 全局默认 facade"模式,但 cron/storage/cache/ratelimit 仍是纯全局--App 不持 per-App 实例,只绑包级全局 facade。后果:
- 多 App 互相污染:App A
Shutdown会停掉 App B 的 cron 调度器与限流器;App BInit覆盖 App A 的 storage driver;cache/ratelimit 硬编码database.GetRedis(),多 App 下 A 的缓存读到 B 的 Redis。 - trace 未纳生命周期:
trace.Close从未被调用,OTel exporter 后台 goroutine/连接泄漏。 WithoutWire空函数技术债:v1.1.0 删 wire 包后遗留的 no-op,违反框架"不保留 deprecated wrapper"纪律。
本版本收尾这四包的实例化,补齐 trace 生命周期,并修复连带的多 App 跨 App 误杀问题。
⚠️ 破坏性变更(升级前必读)
1. xlgo.WithoutWire() 删除
v1.1.0 删 wire 包后遗留的空 no-op,全仓无引用。直接删除调用即可,无行为变化。
2. cron.AddTask(...) 在 App.Init 之前注册不再生效
App 现持专属调度器,Init 时创建并提升为全局默认。pre-Init 调包级 cron.AddTask 会注册到 init 默认调度器、被 App swap 丢弃。
// ❌ 旧(pre-Run 全局注册,v1.4.0 失效)
cron.AddTask("cleanup", cron.Every(5*time.Minute), cleanup)
app := xlgo.New(xlgo.WithConfig(cfg), xlgo.WithCron())
// ✅ 新(WithCronTask 注册到 App 调度器,蕴含启用 cron)
app := xlgo.New(xlgo.WithConfig(cfg),
xlgo.WithCronTask("cleanup", cron.Every(5*time.Minute), cleanup),
)post-Init 的包级 cron.AddTask 仍可用(代理到 App 调度器);standalone(无 App)用法不变。
3. middleware.StopRateLimiters() 语义收窄
仅停默认 Registry 的限流器。App Shutdown 改调自己的 registry.Stop(),不再误停其他 App。
4. commitReplacedResources 不再关闭 previousDB/previousRedis/loggerSnapshot
多 App 下 "previous" 可能是另一个 App 的活跃资源,关闭致 redis: client is closed 或 logger writer 误杀。单 App 无影响(init 默认无资源)。手动 database.InitRedis 后再 App.Init 的旧 client 关闭由用户负责。
5. cache/ratelimit redis 注入(签名扩展)
cache:新增NewRedisCacheWithRedis(client)/CacheManager.InitWithRedis(client)/SwapDefaultCacheManager(m);NewRedisCache()仍存在(standalone)。middleware:RedisRateLimiter新增WithRedisClient(client)选项。
✨ 新增
WithTrace - trace 纳入 App 生命周期
app := xlgo.New(xlgo.WithConfigPath("config.yaml"), xlgo.WithTrace())# config.yaml
trace:
enabled: true
service_name: my-service
endpoint: localhost:4318
exporter_type: otlp-http # otlp-http / otlp-grpc / stdout
sample_ratio: 1.0
# insecure: true # 明文 collector 需开启
# propagator: w3c # w3c(默认) / b3 / jaegerInit 调 trace.Init + 装 trace.Middleware;Shutdown 调 trace.Close(此前从未调用,致 exporter 泄漏)。不做实例隔离--OTel TracerProvider 是进程级全局单例,多 App 不要同时开 WithTrace。
App-bound 限流中间件(多 App per-App 计数隔离)
// 包级 LoginRateLimit() 多 App 下仅绑最后 Init 的 App;
// 用 app-bound 实现真正的 per-App 计数隔离:
app.GetRouter().GET("/login", app.RateLimitRegistry().LoginRateLimit(), handler)新增 App getter(绕过全局 facade,直取 App 实例)
app.Scheduler()- cron 调度器app.Cache()- 缓存服务app.RedisClient()- Redis 客户端(注入RedisRateLimiter等)app.RateLimitRegistry()- 限流器 Registry
其它
cron/storage/cache/middleware各SwapDefault*(App Init 失败回滚用)config.TraceConfig+Config.Trace字段storage.StorageManager.Close()- 闭合 Init/Close 生命周期(io.Closer断言,当前 no-op,为未来驱动预留收口点)
🐛 修复
- 多 App cron 隔离:App A
Shutdown不再停掉 App B 的调度器。 - 多 App storage/cache 隔离:App B
Init不再覆盖 App A 的 driver / cache redis;cache 不再硬编码全局 Redis。 - 多 App 限流器:App A
Shutdown不再重置 App B 的 loginLimiter 计数(原StopRateLimiters全局置 nil 致 B 下次请求懒创建新 limiter、计数清零,稳态客户端借 A 的 Shutdown 窗口绕过限流)。 - trace exporter 泄漏:
App.Shutdown现调trace.Close。 - 多 App logger/redis 跨 App 误杀:
commitReplacedResources不再关闭非己所有的 previous 资源。
多 App 边界(使用前知悉)
| 项 | 说明 |
|---|---|
| 包级限流 facade | LoginRateLimit 等多 App 下仅绑最后 Init 的 App;per-App 计数隔离用 app.RateLimitRegistry() |
CustomRateLimit(包级) |
首请求须在 Init 后(否则 cleanup goroutine 泄漏);app-bound 路径无此问题 |
| trace | 进程全局共享,多 App 任一 Shutdown 关全局导出 |
StorageManager.Close |
当前 no-op(oss.Client 无 Close),未来驱动实现 io.Closer 即自动收口 |
✅ 验证
go build+go vet+go test -race ./...全绿。- 新增契约测试:trace lifecycle、storage/cron/cache/ratelimit 实例隔离 + 多 App 不串 + rollback 还原、cache 多 Redis 隔离、ratelimit 多 App Shutdown 不重置计数 + per-App 计数隔离、storage Close 各路径。
- 独立对抗性复审:无 CRITICAL/HIGH,发现项全部处置。
升级
go get github.com/EthanCodeCraft/xlgo-core@v1.4.0