mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-06 18:58:22 +08:00
eb16882282
* fix(bootstrap module): add bootstrap module to meet future function * Fix readme * Fix panic because log.logger is nil * fix import --------- Co-authored-by: zbhan <zbhan@freewheel.tv>
456 lines
10 KiB
Markdown
456 lines
10 KiB
Markdown
# Bootstrap 模块初始化框架
|
||
|
||
## 概述
|
||
|
||
Bootstrap 是一个模块化的初始化框架,允许各个模块通过注册钩子的方式自动完成初始化,支持优先级控制、条件初始化、错误策略等高级特性。
|
||
|
||
## 核心特性
|
||
|
||
- ✅ **优先级排序** - 保证模块按正确的顺序初始化
|
||
- ✅ **钩子命名** - 每个钩子都有清晰的名称,便于日志追踪和错误定位
|
||
- ✅ **上下文传递** - 模块之间可以共享数据(如数据库实例)
|
||
- ✅ **条件初始化** - 根据配置动态决定是否初始化某个模块
|
||
- ✅ **灵活的错误处理** - 支持快速失败、继续执行、警告三种策略
|
||
- ✅ **详细日志** - 显示初始化进度、耗时统计
|
||
- ✅ **线程安全** - 使用互斥锁保护全局状态
|
||
- ✅ **测试友好** - 提供 Clear() 方法清除钩子
|
||
|
||
## 快速开始
|
||
|
||
### 1. 在模块中注册初始化钩子
|
||
|
||
在你的模块包中创建 `init.go` 文件:
|
||
|
||
```go
|
||
// proxy/init.go
|
||
package proxy
|
||
|
||
import (
|
||
"nofx/bootstrap"
|
||
"nofx/config"
|
||
)
|
||
|
||
func init() {
|
||
// 注册初始化钩子
|
||
bootstrap.Register("Proxy模块", bootstrap.PriorityCore, initProxyModule)
|
||
}
|
||
|
||
func initProxyModule(ctx *bootstrap.Context) error {
|
||
// 从配置中读取 proxy 配置
|
||
proxyConfig := ctx.Config.Proxy
|
||
|
||
// 初始化代理管理器
|
||
if err := InitGlobalProxyManager(proxyConfig); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 将实例存储到上下文,供其他模块使用
|
||
ctx.Set("proxy_manager", GetGlobalProxyManager())
|
||
|
||
return nil
|
||
}
|
||
```
|
||
|
||
### 2. 在 main.go 中运行初始化
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"log"
|
||
"nofx/bootstrap"
|
||
"nofx/config"
|
||
|
||
// 导入需要初始化的模块(触发 init() 注册)
|
||
_ "nofx/proxy"
|
||
_ "nofx/market"
|
||
_ "nofx/trader"
|
||
)
|
||
|
||
func main() {
|
||
// 加载配置
|
||
cfg, err := config.LoadConfig("config.json")
|
||
if err != nil {
|
||
log.Fatalf("加载配置失败: %v", err)
|
||
}
|
||
|
||
// 创建初始化上下文
|
||
ctx := bootstrap.NewContext(cfg)
|
||
|
||
// 执行所有初始化钩子
|
||
if err := bootstrap.Run(ctx); err != nil {
|
||
log.Fatalf("初始化失败: %v", err)
|
||
}
|
||
|
||
// 启动业务逻辑...
|
||
}
|
||
```
|
||
|
||
### 3. 运行效果
|
||
|
||
```
|
||
🔄 开始初始化 3 个模块...
|
||
[1/3] 初始化: Database模块 (优先级: 20)
|
||
✓ 完成: Database模块 (耗时: 120ms)
|
||
[2/3] 初始化: Proxy模块 (优先级: 50)
|
||
↳ 代理自动刷新已启动 (间隔: 30m0s)
|
||
↳ 代理池状态: 总计=5, 黑名单=0, 可用=5
|
||
✓ 完成: Proxy模块 (耗时: 35ms)
|
||
[3/3] 初始化: Market模块 (优先级: 100)
|
||
✓ 完成: Market模块 (耗时: 200ms)
|
||
✅ 所有模块初始化完成 (总耗时: 355ms)
|
||
📊 统计: 成功=3, 跳过=0
|
||
```
|
||
|
||
## 优先级常量
|
||
|
||
系统预定义了以下优先级常量(数值越小越先执行):
|
||
|
||
| 常量 | 值 | 用途 | 示例 |
|
||
|------|-----|------|------|
|
||
| `PriorityInfrastructure` | 10 | 基础设施 | 日志系统、配置加载 |
|
||
| `PriorityDatabase` | 20 | 数据库连接 | SQLite、Redis |
|
||
| `PriorityCore` | 50 | 核心模块 | Proxy、Market Monitor |
|
||
| `PriorityBusiness` | 100 | 业务模块 | Trader、API Server |
|
||
| `PriorityBackground` | 200 | 后台任务 | 定时任务、监控 |
|
||
|
||
### 使用示例
|
||
|
||
```go
|
||
// 数据库模块(最先初始化)
|
||
bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase)
|
||
|
||
// 代理模块(核心模块)
|
||
bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy)
|
||
|
||
// Trader模块(依赖数据库和代理)
|
||
bootstrap.Register("Trader", bootstrap.PriorityBusiness, initTrader)
|
||
```
|
||
|
||
## 高级特性
|
||
|
||
### 1. 条件初始化
|
||
|
||
某些模块只在特定条件下才需要初始化:
|
||
|
||
```go
|
||
bootstrap.Register("Proxy模块", bootstrap.PriorityCore, initProxy).
|
||
EnabledIf(func(ctx *bootstrap.Context) bool {
|
||
// 只在配置中启用 proxy 时才初始化
|
||
return ctx.Config.Proxy != nil && ctx.Config.Proxy.Enabled
|
||
})
|
||
```
|
||
|
||
**输出**:
|
||
```
|
||
[2/5] 跳过: Proxy模块 (条件未满足)
|
||
```
|
||
|
||
### 2. 错误处理策略
|
||
|
||
支持三种错误处理策略:
|
||
|
||
#### FailFast(默认)- 遇到错误立即停止
|
||
|
||
```go
|
||
bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase)
|
||
// 默认就是 FailFast,无需显式设置
|
||
```
|
||
|
||
**效果**:Database 初始化失败,整个系统停止启动
|
||
|
||
#### ContinueOnError - 继续执行,收集所有错误
|
||
|
||
```go
|
||
bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy).
|
||
OnError(bootstrap.ContinueOnError)
|
||
```
|
||
|
||
**效果**:Proxy 失败不影响其他模块,最后汇总所有错误
|
||
|
||
#### WarnOnError - 继续执行,只打印警告
|
||
|
||
```go
|
||
bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy).
|
||
OnError(bootstrap.WarnOnError)
|
||
```
|
||
|
||
**效果**:Proxy 失败只打印警告,不影响系统运行
|
||
|
||
**输出**:
|
||
```
|
||
[2/5] 初始化: Proxy模块 (优先级: 50)
|
||
⚠️ 警告: Proxy模块 (耗时: 15ms) - 连接代理服务器超时
|
||
```
|
||
|
||
### 3. 上下文数据共享
|
||
|
||
模块之间可以通过 Context 共享数据:
|
||
|
||
```go
|
||
// database/init.go - 存储数据库实例
|
||
func initDatabase(ctx *bootstrap.Context) error {
|
||
db, err := sql.Open("sqlite", "config.db")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 存储到上下文
|
||
ctx.Set("database", db)
|
||
return nil
|
||
}
|
||
|
||
// trader/init.go - 获取数据库实例
|
||
func initTrader(ctx *bootstrap.Context) error {
|
||
// 从上下文获取数据库实例
|
||
db, ok := ctx.Get("database")
|
||
if !ok {
|
||
return fmt.Errorf("database 未初始化")
|
||
}
|
||
|
||
database := db.(*sql.DB)
|
||
// 使用 database 初始化 trader...
|
||
return nil
|
||
}
|
||
```
|
||
|
||
**安全获取**:
|
||
```go
|
||
// 使用 MustGet,不存在会 panic(适合必需的依赖)
|
||
db := ctx.MustGet("database").(*sql.DB)
|
||
```
|
||
|
||
### 4. 链式调用
|
||
|
||
支持流畅的链式调用:
|
||
|
||
```go
|
||
bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy).
|
||
EnabledIf(func(ctx *bootstrap.Context) bool {
|
||
return ctx.Config.Proxy != nil && ctx.Config.Proxy.Enabled
|
||
}).
|
||
OnError(bootstrap.WarnOnError)
|
||
```
|
||
|
||
### 5. 自定义错误策略
|
||
|
||
在 Run 时可以指定全局默认错误策略:
|
||
|
||
```go
|
||
// 所有钩子默认使用 ContinueOnError,除非钩子自己指定了 FailFast
|
||
err := bootstrap.RunWithPolicy(ctx, bootstrap.ContinueOnError)
|
||
```
|
||
|
||
## 完整示例
|
||
|
||
### 示例1:Database 模块
|
||
|
||
```go
|
||
// database/init.go
|
||
package database
|
||
|
||
import (
|
||
"database/sql"
|
||
"nofx/bootstrap"
|
||
)
|
||
|
||
func init() {
|
||
bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase)
|
||
}
|
||
|
||
func initDatabase(ctx *bootstrap.Context) error {
|
||
db, err := sql.Open("sqlite", "config.db")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 测试连接
|
||
if err := db.Ping(); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 存储到上下文
|
||
ctx.Set("database", db)
|
||
return nil
|
||
}
|
||
```
|
||
|
||
### 示例2:Proxy 模块(条件初始化 + 警告策略)
|
||
|
||
```go
|
||
// proxy/init.go
|
||
package proxy
|
||
|
||
import (
|
||
"nofx/bootstrap"
|
||
"nofx/config"
|
||
)
|
||
|
||
func init() {
|
||
bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy).
|
||
EnabledIf(func(ctx *bootstrap.Context) bool {
|
||
return ctx.Config.Proxy != nil && ctx.Config.Proxy.Enabled
|
||
}).
|
||
OnError(bootstrap.WarnOnError) // Proxy 失败不影响系统
|
||
}
|
||
|
||
func initProxy(ctx *bootstrap.Context) error {
|
||
proxyConfig := convertConfig(ctx.Config.Proxy)
|
||
|
||
if err := InitGlobalProxyManager(proxyConfig); err != nil {
|
||
return err
|
||
}
|
||
|
||
ctx.Set("proxy_manager", GetGlobalProxyManager())
|
||
return nil
|
||
}
|
||
```
|
||
|
||
### 示例3:Trader 模块(依赖其他模块)
|
||
|
||
```go
|
||
// trader/init.go
|
||
package trader
|
||
|
||
import (
|
||
"nofx/bootstrap"
|
||
)
|
||
|
||
func init() {
|
||
bootstrap.Register("Trader", bootstrap.PriorityBusiness, initTrader)
|
||
}
|
||
|
||
func initTrader(ctx *bootstrap.Context) error {
|
||
// 获取依赖
|
||
db := ctx.MustGet("database").(*sql.DB)
|
||
|
||
// 可选依赖
|
||
var proxyMgr *proxy.ProxyManager
|
||
if pm, ok := ctx.Get("proxy_manager"); ok {
|
||
proxyMgr = pm.(*proxy.ProxyManager)
|
||
}
|
||
|
||
// 使用依赖初始化 trader...
|
||
return nil
|
||
}
|
||
```
|
||
|
||
## 调试和测试
|
||
|
||
### 查看已注册的钩子
|
||
|
||
```go
|
||
hooks := bootstrap.GetRegistered()
|
||
for _, hook := range hooks {
|
||
fmt.Printf("钩子: %s, 优先级: %d\n", hook.Name, hook.Priority)
|
||
}
|
||
```
|
||
|
||
### 清除钩子(用于测试)
|
||
|
||
```go
|
||
func TestMyModule(t *testing.T) {
|
||
// 清除之前注册的钩子
|
||
bootstrap.Clear()
|
||
|
||
// 注册测试钩子
|
||
bootstrap.Register("Test", 10, func(ctx *bootstrap.Context) error {
|
||
return nil
|
||
})
|
||
|
||
// 运行测试...
|
||
}
|
||
```
|
||
|
||
### 统计钩子数量
|
||
|
||
```go
|
||
count := bootstrap.Count()
|
||
fmt.Printf("已注册 %d 个初始化钩子\n", count)
|
||
```
|
||
|
||
## 错误处理最佳实践
|
||
|
||
### 1. 关键模块使用 FailFast
|
||
|
||
```go
|
||
// 数据库是关键依赖,失败必须停止
|
||
bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase)
|
||
// 默认是 FailFast,无需显式设置
|
||
```
|
||
|
||
### 2. 可选模块使用 WarnOnError
|
||
|
||
```go
|
||
// Proxy 是可选的,失败可以使用直连
|
||
bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy).
|
||
OnError(bootstrap.WarnOnError)
|
||
```
|
||
|
||
### 3. 批量初始化使用 ContinueOnError
|
||
|
||
```go
|
||
// 批量加载插件,希望看到所有失败的插件
|
||
for _, plugin := range plugins {
|
||
bootstrap.Register(plugin.Name, 150, plugin.Init).
|
||
OnError(bootstrap.ContinueOnError)
|
||
}
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### Q1: 如何保证模块A在模块B之前初始化?
|
||
|
||
使用优先级控制:
|
||
```go
|
||
bootstrap.Register("ModuleA", 50, initA) // 先执行
|
||
bootstrap.Register("ModuleB", 100, initB) // 后执行
|
||
```
|
||
|
||
### Q2: 如何在初始化失败时获取详细信息?
|
||
|
||
钩子名称会自动包含在错误信息中:
|
||
```
|
||
Error: [Proxy模块] 初始化失败: 连接代理服务器超时
|
||
```
|
||
|
||
### Q3: 可以动态注册钩子吗?
|
||
|
||
可以,但建议在 `init()` 函数中注册:
|
||
```go
|
||
// 推荐:在 init() 中注册(包加载时自动执行)
|
||
func init() {
|
||
bootstrap.Register("MyModule", 100, initModule)
|
||
}
|
||
|
||
// 不推荐:在运行时注册(可能导致顺序问题)
|
||
func main() {
|
||
bootstrap.Register("MyModule", 100, initModule)
|
||
}
|
||
```
|
||
|
||
### Q4: 如何在钩子中访问命令行参数?
|
||
|
||
通过 Context 的 Data 字段传递:
|
||
```go
|
||
// main.go
|
||
ctx := bootstrap.NewContext(cfg)
|
||
ctx.Set("args", os.Args)
|
||
|
||
// module/init.go
|
||
func initModule(ctx *bootstrap.Context) error {
|
||
args := ctx.MustGet("args").([]string)
|
||
// 使用 args...
|
||
}
|
||
```
|
||
## 性能考虑
|
||
|
||
- 钩子注册是线程安全的,但注册本身有轻微的锁开销
|
||
- 建议在 `init()` 函数中注册,避免运行时动态注册
|
||
- 钩子执行是顺序的,不会并发执行
|
||
- 每个钩子的耗时会被记录并显示
|
||
|
||
## 许可证
|
||
|
||
本模块为 NOFX 项目内部模块,遵循项目整体许可证。
|