插件系统模块原理说明

基于1.31 本文档深入解析插件系统模块的设计原理和核心实现,涵盖扩展机制、插件开发、集成模式等核心内容。

目录


一、设计原理

1.1 模块定位

在整体架构中的位置

插件系统是 GORM 的扩展机制,位于应用层和核心层之间,允许在不修改核心代码的情况下添加功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
┌─────────────────────────────────────────────────────────────────────┐
│ 应用层 (用户代码) │
│ db.Use(&ReadWritePlugin{}) │
│ db.Use(&PrometheusPlugin{}) │
│ db.Use(&CachePlugin{}) │
└──────────────────────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ 插件系统 (本模块) ★ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ DB.Use() 入口 │ │
│ │ │ │
│ │ func (db *DB) Use(plugin Plugin) error { │ │
│ │ name := plugin.Name() │ │
│ │ db.Config.Plugins[name] = plugin │ │
│ │ return plugin.Initialize(db) │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Plugin 接口定义 │ │
│ │ │ │
│ │ type Plugin interface { │ │
│ │ Name() string │ │
│ │ Initialize(*DB) error │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 插件存储与管理 │ │
│ │ │ │
│ │ type Config struct { │ │
│ │ Plugins map[string]Plugin // 插件注册表 │ │
│ │ ... │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ 核心回调系统 │
│ (callbacks/callbacks.go) │
│ │
│ 插件通过注册回调实现功能扩展: │
│ - db.Callback().Create().Before("gorm:create").Register(...) │
│ - db.Callback().Query().After("gorm:query").Register(...) │
└─────────────────────────────────────────────────────────────────────┘

与其他模块的逻辑关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────┐
│ Config 模块 │ ← 存储 Plugins map
│ 配置管理 │
└────────┬────────┘


┌─────────────────┐
│ Plugin 模块 │ ← 提供扩展接口
│ 插件系统 │
└────────┬────────┘

├──────────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Callback 模块 │ │ Statement 模块 │
│ 回调钩子 │ │ 语句构建 │
└─────────────────┘ └─────────────────┘
│ │
└──────────┬───────┘

┌────────────────────┐
│ 插件扩展功能 │
│ - 读写分离 │
│ - 查询缓存 │
│ - 性能监控 │
│ - 数据脱敏 │
└────────────────────┘

1.2 设计目的

核心问题: 如何在不修改核心代码的情况下扩展 GORM 功能?

问题分析:

  1. 封闭性问题: GORM 核心代码需要保持稳定,不能频繁修改
  2. 扩展性问题: 不同用户有不同的功能需求
  3. 集成性问题: 扩展功能需要与核心系统无缝集成
  4. 维护性问题: 扩展代码应该易于开发和维护

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
┌─────────────────────────────────────────────────────────────────┐
│ 问题 1: 封闭性 │
├─────────────────────────────────────────────────────────────────┤
│ 解决方案: 定义稳定的 Plugin 接口 │
│ │
│ type Plugin interface { │
│ Name() string // 插件标识 │
│ Initialize(*DB) error // 初始化入口 │
│ } │
│ │
│ 优势: │
│ • 核心代码只依赖接口,不依赖具体实现 │
│ • 接口定义稳定,向后兼容 │
│ • 插件开发遵循统一规范 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 问题 2: 扩展性 │
├─────────────────────────────────────────────────────────────────┤
│ 解决方案: 支持注册多个插件 │
│ │
│ db.Use(&ReadWritePlugin{}) │
│ db.Use(&CachePlugin{}) │
│ db.Use(&PrometheusPlugin{}) │
│ │
│ 优势: │
│ • 可以同时使用多个插件 │
│ • 插件之间相互独立 │
│ • 插件可以灵活组合 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 问题 3: 集成性 │
├─────────────────────────────────────────────────────────────────┤
│ 解决方案: 通过回调系统集成 │
│ │
│ func (p *MyPlugin) Initialize(db *gorm.DB) error { │
│ db.Callback().Query().Before("gorm:query") │
│ .Register("myplugin:cache", p.cacheHandler) │
│ return nil │
│ } │
│ │
│ 优势: │
│ • 无缝集成到 GORM 执行流程 │
│ • 可以拦截和修改任何操作 │
│ • 支持before/after/replace 等多种集成方式 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 问题 4: 维护性 │
├─────────────────────────────────────────────────────────────────┤
│ 解决方案: 提供清晰的开发模式和最佳实践 │
│ │
│ 1. 明确的插件生命周期 │
│ 2. 可靠的初始化机制 │
│ 3. 灵活的配置方式 │
│ 4. 完善的错误处理 │
└─────────────────────────────────────────────────────────────────┘

1.3 结构安排依据

模块职责划分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
插件系统的职责边界:

┌────────────────────────────────────────────────────────────┐
│ 插件系统负责 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. 插件接口定义 (Plugin interface) │ │
│ │ 2. 插件注册机制 (DB.Use) │ │
│ │ 3. 插件存储管理 (Config.Plugins) │ │
│ │ 4. 插件初始化流程 (Initialize) │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ 回调系统负责 (不归插件系统) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. 回调注册 (Callback.Register) │ │
│ │ 2. 回调执行 (Processor.Execute) │ │
│ │ 3. 回调排序 (before/after/replace) │ │
│ │ 4. 回调链管理 │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ 具体插件负责 (不归插件系统) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. 具体功能实现 │ │
│ │ 2. 业务逻辑处理 │ │
│ │ 3. 数据存储管理 │ │
│ │ 4. 错误处理恢复 │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘

关键数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 插件接口定义 (gorm.go)
type Plugin interface {
Name() string // 插件唯一标识
Initialize(*DB) error // 插件初始化
}

// 配置结构 (gorm.go)
type Config struct {
// ... 其他配置

Plugins map[string]Plugin // 插件注册表
}

// DB 结构 (gorm.go)
type DB struct {
Config *Config // 嵌入配置

// ... 其他字段
}

// 插件注册方法 (finisher_api.go)
func (db *DB) Use(plugin Plugin) error {
name := plugin.Name()
if db.Config.Plugins == nil {
db.Config.Plugins = make(map[string]Plugin)
}
db.Config.Plugins[name] = plugin
return plugin.Initialize(db)
}

依赖关系分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
插件系统的依赖:

┌──────────┐
│ Config │ ← 被依赖
└────┬─────┘

┌────────────┴────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Plugin │ │ DB │
│ (interface) │────────▶│ (struct) │
└───────────────┘ └───────┬───────┘


┌───────────────┐
│ DB.Use() │
│ 方法 │
└───────┬───────┘


┌───────────────┐
│ Initialize │
│ 调用 │
└───────────────┘

插件系统被依赖:

┌──────────┐
│ Plugin │ ← 接口被实现
└────┬─────┘


┌────────────────────────────┐
│ 各种具体插件实现 │
│ - ReadWritePlugin │
│ - CachePlugin │
│ - PrometheusPlugin │
│ - ... │
└────────────────────────────┘

1.4 与其他模块的逻辑关系

与 Callback 模块的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────┐
│ 关系: 协作集成 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Plugin 模块 Callback 模块 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Plugin │ │ Callback │ │
│ │ 接口定义 │ │ 系统实现 │ │
│ └──────┬───────┘ └──────▲───────┘ │
│ │ │ │
│ │ Initialize() │ │
│ │ 中 │ │
│ │ 注册回调 ───────────────┘ │
│ │ │ │
│ │ ┌───────┴────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 插件 A │ │ Create回调 │ │ Query回调 │ │
│ │ (功能实现) │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 协作模式: Plugin 通过 Callback 实现 │
│ 1. Plugin.Initialize() 获取 DB 实例 │
│ 2. 通过 db.Callback() 注册回调 │
│ 3. 回调执行时调用 Plugin 的处理逻辑 │
└─────────────────────────────────────────────────────────────┘

与 Statement 模块的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────┐
│ 关系: 数据访问 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Plugin 模块 Statement 模块 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Plugin │ │ Statement │ │
│ │ (扩展逻辑) │ │ (查询上下文) │ │
│ └──────┬───────┘ └──────▲───────┘ │
│ │ │ │
│ │ 回调中访问 │ │
│ │ ◀──────────────────────┘ │
│ │ │ │
│ │ ┌───────┴────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 读取 Statement│ │ SQL.Builder │ │ Vars │ │
│ │ 修改查询参数 │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 访问模式: │
│ func (p *MyPlugin) handler(db *gorm.DB) { │
│ stmt := db.Statement │
│ // 访问 stmt.Table, stmt.Model, stmt.Clauses 等 │
│ } │
└─────────────────────────────────────────────────────────────┘

与 Logger 模块的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────┐
│ 关系: 辅助调试 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Plugin 模块 Logger 模块 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Plugin │ │ Logger │ │
│ │ (业务逻辑) │ │ (日志记录) │ │
│ └──────┬───────┘ └──────▲───────┘ │
│ │ │ │
│ │ 调用 logger │ │
│ │ ──────────────────────┘ │
│ │ │ │
│ │ ┌───────┴────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 记录插件状态 │ │ Info/Error │ │ Slow Query │ │
│ │ 调试插件行为 │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 使用模式: │
│ func (p *MyPlugin) Initialize(db *gorm.DB) error { │
│ logger := db.Logger │
│ logger.Info(context.Background(), "plugin initialized") │
│ return nil │
│ } │
└─────────────────────────────────────────────────────────────┘

1.5 插件类型分类

按功能分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
┌─────────────────────────────────────────────────────────────┐
│ 功能增强型插件 │
├─────────────────────────────────────────────────────────────┤
│ 目的: 添加 GORM 核心没有的功能 │
│ │
│ 示例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 读写分离插件 (ReadWritePlugin) │ │
│ │ • 自动路由读操作到从库 │ │
│ │ • 路由写操作到主库 │ │
│ │ • 支持负载均衡 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 查询缓存插件 (CachePlugin) │ │
│ │ • 缓存查询结果 │ │
│ │ • 自动失效管理 │ │
│ │ • 支持分布式缓存 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 软删除插件 (SoftDeletePlugin) │ │
│ │ • 自动过滤已删除记录 │ │
│ │ • 支持恢复操作 │ │
│ │ • 记录删除时间 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 行为修改型插件 │
├─────────────────────────────────────────────────────────────┤
│ 目的: 修改 GORM 的默认行为 │
│ │
│ 示例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SQL 重写插件 (SQLRewritePlugin) │ │
│ │ • 修改生成的 SQL │ │
│ │ • 添加查询提示 │ │
│ │ • 优化执行计划 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 数据脱敏插件 (DataMaskingPlugin) │ │
│ │ • 自动脱敏敏感字段 │ │
│ │ • 基于角色的脱敏 │ │
│ │ • 审计日志记录 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 字段加密插件 (FieldEncryptionPlugin) │ │
│ │ • 自动加密敏感字段 │ │
│ │ • 透明解密读取 │ │
│ │ • 支持多种加密算法 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 监控审计型插件 │
├─────────────────────────────────────────────────────────────┤
│ 目的: 记录和分析数据库操作 │
│ │
│ 示例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 性能监控插件 (PrometheusPlugin) │ │
│ │ • 记录查询耗时 │ │
│ │ • 统计慢查询 │ │
│ │ • 导出监控指标 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 审计日志插件 (AuditLogPlugin) │ │
│ │ • 记录所有操作 │ │
│ │ • 追踪数据变更 │ │
│ │ • 支持合规要求 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 慢查询分析插件 (SlowQueryPlugin) │ │
│ │ • 检测慢查询 │ │
│ │ • 分析执行计划 │ │
│ │ • 提供优化建议 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 性能优化型插件 │
├─────────────────────────────────────────────────────────────┤
│ 目的: 提升数据库操作性能 │
│ │
│ 示例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 批量操作插件 (BatchPlugin) │ │
│ │ • 自动批量插入 │ │
│ │ • 批量更新优化 │ │
│ │ • 减少网络往返 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 连接池优化插件 (ConnPoolPlugin) │ │
│ │ • 动态调整连接池大小 │ │
│ │ • 连接预热 │ │
│ │ • 连接健康检查 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 查询优化插件 (QueryOptimizerPlugin) │ │
│ │ • 自动添加索引提示 │ │
│ │ • 优化 JOIN 顺序 │ │
│ │ • 减少子查询 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

按集成方式分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
┌─────────────────────────────────────────────────────────────┐
│ Before 回调插件 │
├─────────────────────────────────────────────────────────────┤
│ 在 GORM 操作之前执行 │
│ │
│ func (p *Plugin) Initialize(db *gorm.DB) error { │
│ db.Callback().Create().Before("gorm:create") │
│ .Register("plugin:before", p.beforeCreate) │
│ return nil │
│ } │
│ │
│ 用途: 参数验证、数据预处理、权限检查 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ After 回调插件 │
├─────────────────────────────────────────────────────────────┤
│ 在 GORM 操作之后执行 │
│ │
│ func (p *Plugin) Initialize(db *gorm.DB) error { │
│ db.Callback().Query().After("gorm:query") │
│ .Register("plugin:after", p.afterQuery) │
│ return nil │
│ } │
│ │
│ 用途: 结果处理、缓存更新、日志记录 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Replace 回调插件 │
├─────────────────────────────────────────────────────────────┤
│ 替换 GORM 的默认操作 │
│ │
│ func (p *Plugin) Initialize(db *gorm.DB) error { │
│ db.Callback().Create().Replace("gorm:create") │
│ .Register("plugin:replace", p.replaceCreate) │
│ return nil │
│ } │
│ │
│ 用途: 自定义实现、功能替换、性能优化 │
└─────────────────────────────────────────────────────────────┘

二、核心数据结构

2.1 Plugin 接口

源码定义 (interfaces.go:23-27)

1
2
3
4
5
6
// Plugin GORM plugin interface
// Plugin 是 GORM 插件的接口定义
type Plugin interface {
Name() string // 返回插件唯一标识
Initialize(*DB) error // 插件初始化
}

接口设计原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
┌─────────────────────────────────────────────────────────────┐
│ Name() 方法 │
├─────────────────────────────────────────────────────────────┤
│ 目的: 提供插件的唯一标识 │
│ │
│ 设计要求: │
│ 1. 必须唯一 - 同名插件会被覆盖 │
│ 2. 应当有意义 - 反映插件功能 │
│ 3. 使用命名空间 - 避免冲突 (如 "cache:redis") │
│ │
│ 示例: │
│ func (p *CachePlugin) Name() string { │
│ return "cache:redis" │
│ } │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Initialize() 方法 │
├─────────────────────────────────────────────────────────────┤
│ 目的: 初始化插件并与 GORM 集成 │
│ │
│ 设计要求: │
│ 1. 接收 *DB 参数 - 访问 GORM 实例 │
│ 2. 返回 error - 初始化失败时报告错误 │
│ 3. 幂等性 - 多次调用不会产生副作用 │
│ │
│ 常见操作: │
│ • 注册回调 │
│ • 初始化插件状态 │
│ • 配置插件参数 │
│ • 建立外部连接 │
│ │
│ 示例: │
│ func (p *CachePlugin) Initialize(db *gorm.DB) error { │
│ // 注册 Query 回调 │
│ db.Callback().Query().Before("gorm:query") │
│ .Register("cache:query", p.queryHandler) │
│ │
│ // 注册 Create 回调 │
│ db.Callback().Create().After("gorm:create") │
│ .Register("cache:invalidate", p.invalidateHandler) │
│ │
│ // 初始化缓存客户端 │
│ p.client = redis.NewClient(p.redisConfig) │
│ │
│ return nil │
│ } │
└─────────────────────────────────────────────────────────────┘

概念 2: 插件注册

注册流程 (finisher_api.go:799-806)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Use 注册插件到 GORM
func (db *DB) Use(plugin Plugin) error {
// 1. 获取插件名称
name := plugin.Name()

// 2. 初始化插件存储
if db.Config.Plugins == nil {
db.Config.Plugins = make(map[string]Plugin)
}

// 3. 存储插件
db.Config.Plugins[name] = plugin

// 4. 初始化插件
return plugin.Initialize(db)
}

详细流程图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
                用户代码


┌────────────────┐
│ db.Use(&...) │
└────────┬───────┘


┌────────────────────────────┐
│ 1. 获取插件名称 │
│ name := plugin.Name() │
└─────────────┬──────────────┘


┌────────────────────────────┐
│ 2. 检查/创建存储 │
│ if Plugins == nil { │
│ Plugins = make(...) │
│ } │
└─────────────┬──────────────┘


┌────────────────────────────┐
│ 3. 存储插件实例 │
│ Plugins[name] = plugin │
└─────────────┬──────────────┘


┌────────────────────────────┐
│ 4. 调用 Initialize │
│ return plugin.Initialize │
└─────────────┬──────────────┘

┌───────┴────────┐
│ │
▼ ▼
成功 失败
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 插件可用 │ │ 返回 error │
└──────────────┘ └──────────────┘

注册时机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌─────────────────────────────────────────────────────────────┐
│ 推荐时机 │
├─────────────────────────────────────────────────────────────┤
│ 1. 数据库打开之后立即注册 │
│ │
│ db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) │
│ if err != nil { │
│ panic(err) │
│ } │
│ │
│ // 立即注册插件 │
│ db.Use(&ReadWritePlugin{}) │
│ db.Use(&CachePlugin{}) │
│ │
│ 优势: │
│ • 确保所有操作都受插件影响 │
│ • 插件状态在整个生命周期中一致 │
│ • 避免遗漏某些操作 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 需要注意 │
├─────────────────────────────────────────────────────────────┤
│ 1. 插件注册顺序 │
│ • 后注册的插件可能覆盖先注册的回调 │
│ • 需要考虑插件之间的依赖关系 │
│ │
│ 2. Session 和 Clone │
│ • 插件注册会影响整个 DB 实例 │
│ • db.Session() 创建的新 session 继承插件 │
│ • db.Clone() 保持插件配置 │
│ │
│ 3. 插件冲突 │
│ • 相同名称的插件会被覆盖 │
│ • 回调名称冲突需要谨慎处理 │
└─────────────────────────────────────────────────────────────┘

概念 3: 回调集成

集成方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 方式 1: Before 回调 - 在操作前执行
db.Callback().Create().Before("gorm:create")
.Register("plugin:before", func(db *gorm.DB) {
// 在创建前执行
})

// 方式 2: After 回调 - 在操作后执行
db.Callback().Query().After("gorm:query")
.Register("plugin:after", func(db *gorm.DB) {
// 在查询后执行
})

// 方式 3: Replace 回调 - 替换默认操作
db.Callback().Update().Replace("gorm:update")
.Register("plugin:replace", func(db *gorm.DB) {
// 自定义更新逻辑
})

集成流程图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
┌─────────────────────────────────────────────────────────────┐
│ 插件回调集成流程 │
└─────────────────────────────────────────────────────────────┘

用户代码 GORM 核心 插件实现
│ │ │
│ db.Use(&Plugin{}) │ │
├────────────────────────►│ │
│ │ │
│ Initialize() │
│ ├──────────────────────────►│
│ │ │
│ │ db.Callback().Query() │
│ │ .Before("gorm:query") │
│ │ .Register("plugin", fn) │
│ │◄───────────────────────────┤
│ │ │
│ 返回 success │ │
│◄─────────────────────────┤ │
│ │ │
│ │ │
查询执行时: │ │
│ │ │
│ db.Find(...) │ │
├────────────────────────►│ │
│ │ │
│ 开始执行 Query │
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ Before 回调 GORM 操作 │
│ │ │ │
│ ┌───────┴───────┐ │ │
│ ▼ ▼ │ │
│ gorm:query plugin:callback │ │
│ │ │ │ │
│ └───────┬───────┘ │ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ After 回调 │
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ gorm:query plugin:callback │
│ │ │
│ ▼ │
│ 返回结果 │
│◄──────────────────────┤ │

回调集成示例 - 查询缓存插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
type CachePlugin struct {
cache CacheClient
}

func (p *CachePlugin) Name() string {
return "cache:redis"
}

func (p *CachePlugin) Initialize(db *gorm.DB) error {
// 注册查询前回调 - 尝试从缓存获取
db.Callback().Query().Before("gorm:query")
.Register("cache:before", p.beforeQuery)

// 注册查询后回调 - 更新缓存
db.Callback().Query().After("gorm:query")
.Register("cache:after", p.afterQuery)

// 注册创建后回调 - 失效相关缓存
db.Callback().Create().After("gorm:create")
.Register("cache:invalidate", p.invalidateCache)

return nil
}

// 查询前 - 尝试从缓存获取
func (p *CachePlugin) beforeQuery(db *gorm.DB) {
// 生成缓存键
key := p.generateCacheKey(db)

// 尝试从缓存获取
if data, found := p.cache.Get(key); found {
// 设置到 Statement 中
db.Statement.Dest = data

// 跳过数据库查询
db.SkipRemaining()
}
}

// 查询后 - 写入缓存
func (p *CachePlugin) afterQuery(db *gorm.DB) {
if db.Error == nil {
key := p.generateCacheKey(db)
p.cache.Set(key, db.Statement.Dest, 5*time.Minute)
}
}

// 创建后 - 失效缓存
func (p *CachePlugin) invalidateCache(db *gorm.DB) {
pattern := fmt.Sprintf("table:%s:*", db.Statement.Table)
p.cache.DelPattern(pattern)
}

概念 4: 插件状态管理

状态存储模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 方式 1: 插件内部存储
type CachePlugin struct {
cache *redis.Client
ttl time.Duration
keyFunc func(*gorm.DB) string
}

func (p *CachePlugin) Initialize(db *gorm.DB) error {
p.cache = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
p.ttl = 5 * time.Minute
p.keyFunc = defaultKeyFunc
return nil
}

// 方式 2: 利用 Context
type ContextKey string

const PluginStateKey ContextKey = "plugin:state"

func (p *Plugin) beforeQuery(db *gorm.DB) {
state := &PluginState{
StartTime: time.Now(),
QueryHash: hashQuery(db),
}
context.WithValue(db.Statement.Context, PluginStateKey, state)
}

func (p *Plugin) afterQuery(db *gorm.DB) {
state := db.Statement.Context.Value(PluginStateKey).(*PluginState)
duration := time.Since(state.StartTime)
// 记录执行时间...
}

// 方式 3: 利用 Statement 中的 Dest
func (p *Plugin) beforeQuery(db *gorm.DB) {
// 保存原始 Dest
originalDest := db.Statement.Dest

// 设置回调函数在之后恢复
defer func() {
db.Statement.Dest = originalDest
}()

// 插件逻辑...
}

2.2 理论基础

理论 1: 开闭原则 (Open-Closed Principle)

定义: 软件实体应该对扩展开放,对修改封闭。

在 GORM 插件系统中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
┌─────────────────────────────────────────────────────────────┐
│ 传统修改方式 (不推荐) │
├─────────────────────────────────────────────────────────────┤
│ │
│ // 直接修改 GORM 核心代码 │
│ func (db *DB) Find(dest interface{}, conds ...interface{}) { │
│ // ... 原有代码 ... │
│ │
│ // 添加缓存逻辑 (修改核心代码!) │
│ if cacheData := getFromCache(key); cacheData != nil { │
│ return cacheData │
│ } │
│ } │
│ │
│ 问题: │
│ 1. 核心代码变得复杂 │
│ 2. 每个功能都要修改核心 │
│ 3. 难以维护和升级 │
│ 4. 无法选择性启用功能 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 插件扩展方式 (推荐) │
├─────────────────────────────────────────────────────────────┤
│ │
│ // GORM 核心代码保持不变 │
│ func (db *DB) Find(dest interface{}, conds ...interface{}) { │
│ // ... 原有代码 ... │
│ } │
│ │
│ // 通过插件扩展 │
│ type CachePlugin struct {} │
│ │
│ func (p *CachePlugin) Initialize(db *gorm.DB) error { │
│ db.Callback().Query().Before("gorm:query") │
│ .Register("cache", p.cacheHandler) │
│ return nil │
│ } │
│ │
│ // 使用时注册插件 │
│ db.Use(&CachePlugin{}) │
│ │
│ 优势: │
│ 1. 核心代码保持稳定 │
│ 2. 功能独立开发 │
│ 3. 易于维护和升级 │
│ 4. 可选择性启用 │
└─────────────────────────────────────────────────────────────┘

架构对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
违反开闭原则的架构:

┌──────────────────────────────────────┐
│ 应用程序 │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 功能 A │ │ 功能 B │ │ 功能 C │ │
│ └────┬───┘ └────┬───┘ └────┬───┘ │
└───────┼────────────┼────────────┼─────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────┐
│ GORM 核心 (需要修改) │
│ ┌──────────────────────────────┐ │
│ │ 混合了各种功能的代码 │ │
│ │ 难以维护和测试 │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘

符合开闭原则的架构:

┌──────────────────────────────────────┐
│ 应用程序 │
└───────────────┬──────────────────────┘


┌──────────────────────────────────────┐
│ GORM 核心 (稳定不变) │
│ ┌──────────────────────────────┐ │
│ │ Plugin 接口定义 │ │
│ └───────────┬──────────────────┘ │
└──────────────┼──────────────────────┘

┌────────┼────────┐
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│插件 A│ │插件 B│ │插件 C│
└──────┘ └──────┘ └──────┘

理论 2: 依赖注入 (Dependency Injection)

定义: 将依赖关系的创建和使用分离,通过接口注入依赖。

在 GORM 插件中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 示例: 读写分离插件

// 定义数据源接口
type DataSource interface {
Master() gorm.ConnPool
Slave() gorm.ConnPool
}

// 插件实现
type ReadWritePlugin struct {
dataSource DataSource // 依赖接口,不依赖具体实现
selector *Selector // 内部依赖
}

// 通过构造函数注入依赖
func NewReadWritePlugin(ds DataSource) *ReadWritePlugin {
return &ReadWritePlugin{
dataSource: ds,
}
}

// Initialize 接收 DB 实例
func (p *ReadWritePlugin) Initialize(db *gorm.DB) error {
// 注入到回调中
db.Callback().Query().Before("gorm:query")
.Register("rw:select", p.selectSlave)

db.Callback().Create().Before("gorm:create")
.Register("rw:select", p.selectMaster)

return nil
}

func (p *ReadWritePlugin) selectSlave(db *gorm.DB) {
db.Statement.ConnPool = p.dataSource.Slave()
}

func (p *ReadWritePlugin) selectMaster(db *gorm.DB) {
db.Statement.ConnPool = p.dataSource.Master()
}

依赖注入的好处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
┌─────────────────────────────────────────────────────────────┐
│ 没有依赖注入 │
├─────────────────────────────────────────────────────────────┤
│ │
│ type Plugin struct { │
│ db *gorm.DB // 直接依赖具体的 DB │
│ } │
│ │
│ func (p *Plugin) Initialize(db *gorm.DB) error { │
│ p.db = db // 存储 DB 引用 │
│ // 问题: 难以测试,依赖具体实例 │
│ } │
│ │
│ 测试困难: │
│ • 需要创建真实的 DB 连接 │
│ • 难以模拟不同的场景 │
│ • 测试速度慢 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 有依赖注入 │
├─────────────────────────────────────────────────────────────┤
│ │
│ // 定义接口 │
│ type Database interface { │
│ Query(query string, args ...interface{}) │
│ Exec(query string, args ...interface{}) │
│ } │
│ │
│ type Plugin struct { │
│ db Database // 依赖接口 │
│ } │
│ │
│ func (p *Plugin) Initialize(db *gorm.DB) error { │
│ // 使用接口包装 │
│ p.db = &DatabaseWrapper{db: db} │
│ } │
│ │
│ 测试容易: │
│ • 可以轻松创建 Mock 实现 │
│ • 可以控制测试场景 │
│ • 测试速度快 │
└─────────────────────────────────────────────────────────────┘

理论 3: 责任链模式 (Chain of Responsibility)

定义: 将多个处理器连成一条链,沿着链传递请求,直到有处理器处理它。

在 GORM 回调中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
回调执行的责任链:

┌────────────────────────────────────────────────────────────┐
│ Query 操作 │
└────────────────────┬───────────────────────────────────────┘


┌──────────────────────────────────┐
│ Before: Query 回调链 │
└────────────┬─────────────────────┘

┌────────────┴────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ plugin:cache │ │ plugin:auth │
│ │ │ │
│ 1. 检查缓存 │ │ 1. 验证权限 │
│ 2. 命中则跳过 │ │ 2. 通过则继续│
└──────┬───────┘ └──────┬───────┘
│ │
│ [命中] │
▼ │
跳过后续 │
│ │
│ [未命中/通过] │
│ │
└────────────┬────────────┘


┌──────────────────────────────────┐
│ gorm:query (核心查询) │
└────────────┬─────────────────────┘


┌──────────────────────────────────┐
│ After: Query 回调链 │
└────────────┬─────────────────────┘

┌────────────┴────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│plugin:logger │ │plugin:metric │
│ │ │ │
│ 1. 记录日志 │ │ 1. 收集指标 │
│ 2. 继续传递 │ │ 2. 继续传递 │
└──────┬───────┘ └──────┬───────┘
│ │
└────────────┬────────────┘


返回结果

插件协作示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 插件 1: 缓存插件
func (p *CachePlugin) Initialize(db *gorm.DB) error {
db.Callback().Query().Before("gorm:query")
.Register("cache:check", p.checkCache)
db.Callback().Query().After("gorm:query")
.Register("cache:update", p.updateCache)
return nil
}

// 插件 2: 权限插件
func (p *AuthPlugin) Initialize(db *gorm.DB) error {
db.Callback().Query().Before("gorm:query")
.Register("auth:check", "cache:check") // 在缓存检查之后
return nil
}

// 插件 3: 日志插件
func (p *LogPlugin) Initialize(db *gorm.DB) error {
db.Callback().Query().After("gorm:query")
.Register("log:query", p.logQuery)
return nil
}

// 执行顺序:
// 1. cache:check (检查缓存)
// 2. auth:check (检查权限,缓存未命中时)
// 3. gorm:query (执行查询,权限通过且缓存未命中)
// 4. cache:update (更新缓存,查询成功后)
// 5. log:query (记录日志)

2.3 学习方法

方法 1: 分析现有插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
推荐学习路径:

1. GORM 官方插件
├─ gorm-prometheus (性能监控)
├─ gorm-redis (缓存集成)
├─ gorm-encrypt (字段加密)
└─ gorm-soft-delete (软删除)

2. 社区插件
├─ 读写分离插件
├─ 分库分表插件
├─ 审计日志插件
└─ 数据脱敏插件

3. 学习重点
├─ 插件接口实现
├─ 回调注册策略
├─ 状态管理方式
└─ 错误处理机制

方法 2: 实践插件开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
渐进式开发路径:

阶段 1: 简单观察者插件
├─ 目标: 记录所有 SQL 查询
├─ 技术: After 回调
├─ 难度: ★☆☆
└─ 验收: 能够正确记录所有查询

阶段 2: 功能增强插件
├─ 目标: 实现查询缓存
├─ 技术: Before + After 回调
├─ 难度: ★★☆
└─ 验收: 缓存命中率和正确性

阶段 3: 行为修改插件
├─ 目标: 实现数据脱敏
├─ 技术: Before + After 回调
├─ 难度: ★★★
└─ 验收: 敏感字段自动脱敏

阶段 4: 复杂功能插件
├─ 目标: 实现读写分离
├─ 技术: Replace 回调 + 连接池管理
├─ 难度: ★★★
└─ 验收: 读写正确路由和负载均衡

方法 3: 调试和测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
调试技巧:

1. 使用 Logger 跟踪
type DebugPlugin struct {
name string
}

func (p *DebugPlugin) Initialize(db *gorm.DB) error {
logger := db.Logger
logger.Info(context.Background(),
fmt.Sprintf("plugin %s initializing", p.name))

db.Callback().Query().Before("gorm:query")
.Register("debug:before", func(db *gorm.DB) {
logger.Info(context.Background(),
fmt.Sprintf("plugin %s: before query", p.name))
})
return nil
}

2. 打印回调注册顺序
func printCallbacks(db *gorm.DB, op string) {
processor := db.Callback().Query()
// 打印所有注册的回调和它们的顺序
}

3. 使用断言和日志
func (p *Plugin) handler(db *gorm.DB) {
fmt.Printf("Plugin called, SQL: %s\n", db.Statement.SQL.String())
// ... 处理逻辑
}

测试策略:

1. 单元测试
func TestCachePlugin(t *testing.T) {
db := setupTestDB()
plugin := &CachePlugin{}

err := plugin.Initialize(db)
assert.Nil(t, err)

// 测试缓存功能...
}

2. 集成测试
func TestPluginIntegration(t *testing.T) {
db := setupTestDB()
db.Use(&CachePlugin{})
db.Use(&LogPlugin{})

// 测试插件协作...
}

3. 性能测试
func BenchmarkPlugin(b *testing.B) {
db := setupTestDB()
db.Use(&CachePlugin{})

b.ResetTimer()
for i := 0; i < b.N; i++ {
var results []Model
db.Find(&results)
}
}

2.4 实施策略

策略 1: 插件开发检查清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
开发前:
□ 确定插件功能和范围
□ 分析需要的回调点
□ 设计插件状态管理
□ 规划测试策略

开发中:
□ 实现 Name() 方法
□ 实现 Initialize() 方法
□ 注册必要的回调
□ 实现核心逻辑
□ 添加错误处理

开发后:
□ 单元测试
□ 集成测试
□ 性能测试
□ 文档编写
□ 示例代码

策略 2: 回调选择指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
选择合适的回调点:

┌────────────────────────────────────────────────────────────┐
│ Create 操作 │
├────────────────────────────────────────────────────────────┤
│ Before "gorm:create" │
│ • 数据验证 │
│ • 默认值设置 │
│ • 字段自动填充 │
│ • 权限检查 │
│ │
│ After "gorm:create" │
│ • 缓存失效 │
│ • 日志记录 │
│ • 事件通知 │
│ • 关联数据创建 │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ Query 操作 │
├────────────────────────────────────────────────────────────┤
│ Before "gorm:query" │
│ • 缓存检查 │
│ • 查询权限验证 │
│ • 查询条件修改 │
│ • 分页参数处理 │
│ │
│ After "gorm:query" │
│ • 缓存更新 │
│ • 结果脱敏 │
│ • 性能指标收集 │
│ • 日志记录 │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ Update 操作 │
├────────────────────────────────────────────────────────────┤
│ Before "gorm:update" │
│ • 版本号检查 │
│ • 更新权限验证 │
│ • 变更记录准备 │
│ • 字段过滤 │
│ │
│ After "gorm:update" │
│ • 缓存失效 │
│ • 变更日志记录 │
│ • 事件触发 │
│ • 关联数据更新 │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│ Delete 操作 │
├────────────────────────────────────────────────────────────┤
│ Before "gorm:delete" │
│ • 删除权限检查 │
│ • 关联数据检查 │
│ • 软删除处理 │
│ • 级联删除验证 │
│ │
│ After "gorm:delete" │
│ • 缓存失效 │
│ • 删除日志记录 │
│ • 关联数据清理 │
│ • 存储空间回收 │
└────────────────────────────────────────────────────────────┘

策略 3: 错误处理最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 1. Initialize 错误处理
func (p *Plugin) Initialize(db *gorm.DB) error {
// 验证配置
if p.config == nil {
return fmt.Errorf("plugin: config is required")
}

// 初始化外部依赖
client, err := NewClient(p.config.Endpoint)
if err != nil {
return fmt.Errorf("plugin: failed to create client: %w", err)
}
p.client = client

// 注册回调
db.Callback().Query().Before("gorm:query")
.Register("plugin", p.handler)

return nil
}

// 2. 回调错误处理
func (p *Plugin) handler(db *gorm.DB) {
// 不修改 db.Error,避免覆盖原有错误
if err := p.process(db); err != nil {
// 记录错误但继续执行
db.Logger.Error(context.Background(),
"plugin handler failed",
"error", err)
}
}

// 3. 恢复机制
func (p *Plugin) handler(db *gorm.DB) {
defer func() {
if r := recover(); r != nil {
db.Logger.Error(context.Background(),
"plugin panic recovered",
"panic", r)
}
}()

// 插件逻辑...
}

策略 4: 性能优化建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 1. 避免重复初始化
type Plugin struct {
initialized bool
mu sync.RWMutex
}

func (p *Plugin) Initialize(db *gorm.DB) error {
p.mu.Lock()
defer p.mu.Unlock()

if p.initialized {
return nil
}

// 初始化逻辑...

p.initialized = true
return nil
}

// 2. 缓存频繁访问的数据
type Plugin struct {
tableCache map[string]*TableInfo
cacheMu sync.RWMutex
}

func (p *Plugin) getTableInfo(table string) *TableInfo {
p.cacheMu.RLock()
info, ok := p.tableCache[table]
p.cacheMu.RUnlock()

if ok {
return info
}

p.cacheMu.Lock()
defer p.cacheMu.Unlock()

// 双重检查
if info, ok := p.tableCache[table]; ok {
return info
}

info = p.fetchTableInfo(table)
p.tableCache[table] = info
return info
}

// 3. 使用对象池
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}

func (p *Plugin) process(db *gorm.DB) {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()

// 使用 buf 处理数据...
}

三、核心实现

3.1 插件注册机制

Use() 方法完整实现 (gorm.go:575-585)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Use use plugin
// Use 方法用于注册插件到 GORM
func (db *DB) Use(plugin Plugin) error {
// === 第 1 步: 获取插件名称 ===
name := plugin.Name()

// === 第 2 步: 检查插件是否已注册 ===
if _, ok := db.Plugins[name]; ok {
// 插件已存在,返回错误
return ErrRegistered
}

// === 第 3 步: 调用 Initialize 方法初始化插件 ===
// 注意:先调用 Initialize,成功后才存储插件
// 这样如果初始化失败,不会留下不完整的插件
if err := plugin.Initialize(db); err != nil {
return err
}

// === 第 4 步: 存储插件到 Plugins map ===
db.Plugins[name] = plugin

return nil
}

3.2 插件初始化流程

Initialize() 方法职责

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Initialize 是插件初始化的入口点
// 插件开发者在此方法中完成以下工作:
func (p *MyPlugin) Initialize(db *gorm.DB) error {
// === 职责 1: 验证配置 ===
if p.config == nil {
return fmt.Errorf("plugin: config is required")
}

// === 职责 2: 初始化内部状态 ===
if err := p.setup(); err != nil {
return fmt.Errorf("plugin: setup failed: %w", err)
}

// === 职责 3: 注册回调 ===
db.Callback().Query().Before("gorm:query")
.Register("plugin:before", p.beforeQuery)

db.Callback().Query().After("gorm:query")
.Register("plugin:after", p.afterQuery)

return nil
}

3.3 回调系统集成

回调注册方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 方式 1: Before 回调 - 在操作前执行
db.Callback().Create().Before("gorm:create")
.Register("plugin:before", func(db *gorm.DB) {
// 参数验证、数据预处理
})

// 方式 2: After 回调 - 在操作后执行
db.Callback().Query().After("gorm:query")
.Register("plugin:after", func(db *gorm.DB) {
// 结果处理、缓存更新、日志记录
})

// 方式 3: Replace 回调 - 替换默认操作
db.Callback().Update().Replace("gorm:update")
.Register("plugin:replace", func(db *gorm.DB) {
// 完全自定义的更新逻辑
})

3.4 插件状态管理

状态存储模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 模式 1: 插件内部存储
type CachePlugin struct {
mu sync.RWMutex
cache *redis.Client
ttl time.Duration
}

// 模式 2: 利用 Context
type contextKey string
const pluginStateKey contextKey = "plugin:state"

func (p *Plugin) beforeQuery(db *gorm.DB) {
state := &PluginState{StartTime: time.Now()}
ctx := context.WithValue(db.Statement.Context, pluginStateKey, state)
db.Statement.Context = ctx
}

// 模式 3: 利用 Statement.Settings
func (p *Plugin) beforeQuery(db *gorm.DB) {
db.Statement.Settings.Store("key", "value")
}

四、实战代码示例

4.1 基础插件开发

查询日志插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package plugin

import (
"context"
"fmt"
"gorm.io/gorm"
)

type QueryLoggerPlugin struct{}

func (p *QueryLoggerPlugin) Name() string {
return "query:logger"
}

func (p *QueryLoggerPlugin) Initialize(db *gorm.DB) error {
db.Callback().Query().After("gorm:query")
.Register("query:logger", p.logQuery)
return nil
}

func (p *QueryLoggerPlugin) logQuery(db *gorm.DB) {
db.Logger.Info(
db.Statement.Context,
fmt.Sprintf("[QUERY] %s - Rows: %d",
db.Statement.SQL.String(),
db.RowsAffected),
)
}

4.2 读写分离插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package plugin

import (
"gorm.io/gorm"
)

type DataSource interface {
Master() gorm.ConnPool
Slave() gorm.ConnPool
}

type ReadWritePlugin struct {
dataSource DataSource
}

func NewReadWritePlugin(ds DataSource) *ReadWritePlugin {
return &ReadWritePlugin{dataSource: ds}
}

func (p *ReadWritePlugin) Name() string {
return "read:write:separation"
}

func (p *ReadWritePlugin) Initialize(db *gorm.DB) error {
// 读操作路由到从库
db.Callback().Query().Before("gorm:query")
.Register("rw:slave", p.selectSlave)

// 写操作路由到主库
db.Callback().Create().Before("gorm:create")
.Register("rw:master", p.selectMaster)

db.Callback().Update().Before("gorm:update")
.Register("rw:master", p.selectMaster)

db.Callback().Delete().Before("gorm:delete")
.Register("rw:master", p.selectMaster)

return nil
}

func (p *ReadWritePlugin) selectSlave(db *gorm.DB) {
if slave := p.dataSource.Slave(); slave != nil {
db.Statement.ConnPool = slave
}
}

func (p *ReadWritePlugin) selectMaster(db *gorm.DB) {
db.Statement.ConnPool = p.dataSource.Master()
}

4.3 查询缓存插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package plugin

import (
"context"
"encoding/json"
"fmt"
"time"
"gorm.io/gorm"
)

type CachePlugin struct {
cache CacheClient
ttl time.Duration
}

type CacheClient interface {
Get(ctx context.Context, key string, dest interface{}) (bool, error)
Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error
Del(ctx context.Context, key string) error
}

func NewCachePlugin(client CacheClient, ttl time.Duration) *CachePlugin {
return &CachePlugin{cache: client, ttl: ttl}
}

func (p *CachePlugin) Name() string {
return "cache:redis"
}

func (p *CachePlugin) Initialize(db *gorm.DB) error {
db.Callback().Query().Before("gorm:query")
.Register("cache:before", p.beforeQuery)

db.Callback().Query().After("gorm:query")
.Register("cache:after", p.afterQuery)

db.Callback().Create().After("gorm:create")
.Register("cache:invalidate", p.invalidate)

return nil
}

func (p *CachePlugin) beforeQuery(db *gorm.DB) {
key := p.cacheKey(db)
var data interface{}
if found, _ := p.cache.Get(db.Statement.Context, key, &data); found {
json.Unmarshal(data.([]byte), db.Statement.Dest)
db.SkipRemaining()
}
}

func (p *CachePlugin) afterQuery(db *gorm.DB) {
if db.Error != nil {
return
}
data, _ := json.Marshal(db.Statement.Dest)
p.cache.Set(db.Statement.Context, p.cacheKey(db), data, p.ttl)
}

func (p *CachePlugin) invalidate(db *gorm.DB) {
pattern := fmt.Sprintf("%s:*", db.Statement.Table)
p.cache.Del(db.Statement.Context, pattern)
}

func (p *CachePlugin) cacheKey(db *gorm.DB) string {
return fmt.Sprintf("%v:%v", db.Statement.SQL.String(), db.Statement.Vars)
}

4.4 性能监控插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package plugin

import (
"sync"
"time"
"gorm.io/gorm"
)

type MetricsPlugin struct {
metrics *MetricsCollector
}

type MetricsCollector struct {
mu sync.RWMutex
queryCount map[string]int64
queryDuration map[string]time.Duration
}

func NewMetricsPlugin() *MetricsPlugin {
return &MetricsPlugin{
metrics: &MetricsCollector{
queryCount: make(map[string]int64),
queryDuration: make(map[string]time.Duration),
},
}
}

func (p *MetricsPlugin) Name() string {
return "metrics:collector"
}

func (p *MetricsPlugin) Initialize(db *gorm.DB) error {
ops := []struct {
name string
prepare func(gorm.Callback) *gorm.Callback
}{
{"query", func(c gorm.Callback) *gorm.Callback { return c.Query() }},
{"create", func(c gorm.Callback) *gorm.Callback { return c.Create() }},
{"update", func(c gorm.Callback) *gorm.Callback { return c.Update() }},
{"delete", func(c gorm.Callback) *gorm.Callback { return c.Delete() }},
}

for _, op := range ops {
opName := op.name
prepare := op.prepare

prepare(db.Callback()).Before("gorm:"+opName)
.Register("metrics:before:"+opName, func(db *gorm.DB) {
db.Statement.Settings.Store("metrics:start", time.Now())
})

prepare(db.Callback()).After("gorm:"+opName)
.Register("metrics:after:"+opName, func(db *gorm.DB) {
if start, ok := db.Statement.Settings.Load("metrics:start"); ok {
duration := time.Since(start.(time.Time))
p.metrics.mu.Lock()
p.metrics.queryCount[opName]++
p.metrics.queryDuration[opName] += duration
p.metrics.mu.Unlock()
}
})
}

return nil
}

func (p *MetricsPlugin) GetMetrics() *MetricsCollector {
return p.metrics
}

4.5 数据脱敏插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package plugin

import (
"strings"
"gorm.io/gorm"
)

type DataMaskingPlugin struct {
rules map[string]func(string) string
}

func NewDataMaskingPlugin() *DataMaskingPlugin {
return &DataMaskingPlugin{
rules: make(map[string]func(string) string),
}
}

func (p *DataMaskingPlugin) Name() string {
return "data:masking"
}

func (p *DataMaskingPlugin) Initialize(db *gorm.DB) error {
db.Callback().Query().After("gorm:query")
.Register("masking:after", p.maskResult)
return nil
}

func (p *DataMaskingPlugin) AddRule(field string, fn func(string) string) {
p.rules[field] = fn
}

func (p *DataMaskingPlugin) maskResult(db *gorm.DB) {
// 实现脱敏逻辑...
}

// 脱敏函数
var MaskPhone = func(s string) string {
if len(s) != 11 {
return s
}
return s[:3] + "****" + s[7:]
}

var MaskEmail = func(s string) string {
parts := strings.Split(s, "@")
if len(parts) != 2 {
return s
}
return string(parts[0][0]) + "***@" + parts[1]
}

五、可视化流程图

5.1 插件注册流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    用户代码

│ db.Use(plugin)


┌──────────────────┐
│ 获取插件名称 │
│ name=plugin.Name()│
└────────┬─────────┘


┌──────────────────┐ ┌──────────────┐
│ 检查是否已注册 │────▶│ 返回错误 │
│ Plugins[name] │ │ ErrRegistered│
└────────┬─────────┘ └──────────────┘
│ No

┌──────────────────┐
│ 调用 Initialize │
└────────┬─────────┘

┌────┴────┐
│ │
▼ ▼
成功 失败
│ │
│ ▼
│ ┌──────────────┐
│ │ 返回错误 │
│ └──────────────┘

┌──────────────────┐
│ 存储插件 │
│ Plugins[name]=p │
└────────┬─────────┘


┌──────────────────┐
│ 返回成功 │
└──────────────────┘

5.2 插件初始化流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    plugin.Initialize(db)


┌───────────────────────┐
│ 1. 验证配置 │
└───────────┬───────────┘


┌───────────────────────┐
│ 2. 初始化状态 │
└───────────┬───────────┘


┌───────────────────────┐
│ 3. 注册回调 │
│ ├─ Query.Before │
│ ├─ Query.After │
│ ├─ Create.Before │
│ └─ ... │
└───────────┬───────────┘


┌───────────────────────┐
│ 4. 建立外部连接 │
└───────────┬───────────┘


┌───────────────────────┐
│ 5. 返回成功 │
└───────────────────────┘

5.3 回调集成流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
db.Use(plugin)


Initialize(db)

├─ db.Callback().Query()
│ .Before("gorm:query")
│ .Register("plugin", fn)


执行查询时:


┌──────────────┐
│ Before 回调链 │
│ │
│ gorm:query │
│ plugin:fn │
└──────┬───────┘


┌──────────────┐
│ 执行查询 │
└──────┬───────┘


┌──────────────┐
│ After 回调链 │
│ │
│ gorm:query │
│ plugin:fn │
└──────┬───────┘


返回结果

5.4 插件系统架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
┌─────────────────────────────────┐
│ 应用层 │
│ db.Use(&Plugin{}) │
└────────────┬────────────────────┘


┌─────────────────────────────────┐
│ 插件注册层 │
│ DB.Use(plugin) │
│ - plugin.Name() │
│ - plugin.Initialize(db) │
│ - Plugins[name]=plugin │
└────────────┬────────────────────┘


┌─────────────────────────────────┐
│ 插件接口层 │
│ type Plugin interface { │
│ Name() string │
│ Initialize(*DB) error │
│ } │
└────────────┬────────────────────┘


┌─────────────────────────────────┐
│ 插件实现层 │
│ CachePlugin RWPlugin Log... │
└────────────┬────────────────────┘


┌─────────────────────────────────┐
│ 回调集成层 │
│ db.Callback().Query() │
│ .Before("gorm:query") │
│ .Register("plugin", fn) │
└────────────┬────────────────────┘


┌─────────────────────────────────┐
│ 核心执行层 │
│ Query Create Update Delete │
└─────────────────────────────────┘

六、最佳实践与故障排查

6.1 开发最佳实践

插件开发检查清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
开发前:
□ 明确插件功能和边界
□ 分析需要拦截的回调点
□ 设计插件状态管理
□ 规划测试策略

开发中:
□ 实现 Name() 方法
□ 实现 Initialize() 方法
□ 注册必要的回调
□ 添加错误处理
□ 确保幂等性

开发后:
□ 单元测试
□ 集成测试
□ 性能测试
□ 文档完整

回调选择指南

1
2
3
4
5
6
7
8
9
10
11
// Create.Before: 数据验证、默认值、权限检查
// Create.After: 缓存失效、日志记录、事件通知

// Query.Before: 缓存检查、权限验证、条件修改
// Query.After: 缓存更新、结果脱敏、指标收集

// Update.Before: 版本检查、权限验证、变更记录
// Update.After: 缓存失效、变更日志

// Delete.Before: 权限检查、关联检查、软删除
// Delete.After: 缓存失效、删除日志

错误处理最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Initialize 错误处理
func (p *Plugin) Initialize(db *gorm.DB) error {
if p.config == nil {
return fmt.Errorf("plugin: config required")
}

client, err := NewClient(p.config.Endpoint)
if err != nil {
return fmt.Errorf("plugin: client: %w", err)
}
p.client = client

return nil
}

// 回调错误处理
func (p *Plugin) handler(db *gorm.DB) {
if err := p.process(db); err != nil {
// 不覆盖 db.Error
db.Logger.Error(context.Background(), "plugin: %v", err)
}
}

// Panic 恢复
func (p *Plugin) handler(db *gorm.DB) {
defer func() {
if r := recover(); r != nil {
db.Logger.Error(context.Background(), "panic: %v", r)
}
}()
// 插件逻辑...
}

6.2 常见问题与解决方案

问题 1: 插件初始化失败

1
2
3
4
5
6
7
8
9
10
11
12
13
// 错误
func (p *Plugin) Initialize(db *gorm.DB) error {
db.Callback().Query().Before("gorm:query")
.Register("plugin", p.handler)
// 忘记 return nil
}

// 正确
func (p *Plugin) Initialize(db *gorm.DB) error {
db.Callback().Query().Before("gorm:query")
.Register("plugin", p.handler)
return nil // 必须返回
}

问题 2: 插件冲突

1
2
3
4
5
6
// 使用命名空间避免冲突
db.Callback().Query().Before("gorm:query")
.Register("cache:plugin", handlerA)

db.Callback().Query().Before("gorm:query")
.Register("log:plugin", handlerB)

问题 3: 并发安全

1
2
3
4
5
6
7
8
9
10
11
// 使用互斥锁
type Plugin struct {
mu sync.RWMutex
data map[string]int
}

func (p *Plugin) handler(db *gorm.DB) {
p.mu.Lock()
p.data["key"]++
p.mu.Unlock()
}

6.3 性能优化建议

避免重复初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Plugin struct {
initialized bool
mu sync.Mutex
}

func (p *Plugin) Initialize(db *gorm.DB) error {
p.mu.Lock()
defer p.mu.Unlock()

if p.initialized {
return nil
}

// 初始化逻辑...
p.initialized = true
return nil
}

使用对象池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}

func (p *Plugin) process(db *gorm.DB) {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
// 使用 buf...
}

6.4 安全建议

敏感数据过滤

1
2
3
4
5
func (p *Plugin) logQuery(db *gorm.DB) {
vars := p.filterSensitive(db.Statement.Vars)
db.Logger.Info(context.Background(),
db.Statement.SQL.String(), vars...)
}

权限验证

1
2
3
4
5
6
7
func (p *Plugin) beforeQuery(db *gorm.DB) {
user := getCurrentUser(db.Statement.Context)
if !p.hasPermission(user, db.Statement.Table) {
db.Error = ErrPermissionDenied
db.SkipRemaining()
}
}

七、学习验证

7.1 知识自测

基础题

  1. Plugin 接口包含哪两个方法?

    • A. Name() 和 Initialize()
    • B. Name() 和 Setup()
    • C. Init() 和 Start()
  2. 插件注册使用哪个方法?

    • A. db.Register()
    • B. db.Use()
    • C. db.Add()
  3. Before、After 和 Replace 回调的主要区别是什么?

    • A. 执行时机和行为不同
    • B. 参数不同
    • C. 返回值不同
  4. 如何避免插件名称冲突?

    • A. 使用命名空间
    • B. 使用随机名称
    • C. 不需要避免
  5. 插件初始化失败会怎样?

    • A. 忽略错误继续
    • B. 返回错误,插件不注册
    • C. 自动重试

进阶题

  1. 如何实现查询缓存插件?需要注册哪些回调?

    • Query.Before (检查缓存)
    • Query.After (更新缓存)
    • Write.After (失效缓存)
  2. 读写分离插件如何区分读写操作?

    • Query → 从库
    • Create/Update/Delete → 主库
  3. 如何处理插件间的依赖关系?

    • 控制注册顺序
    • 使用回调排序
    • 使用共享状态
  4. 如何实现并发安全?

    • 使用 sync.Mutex
    • 使用 sync.RWMutex
    • 使用 atomic
  5. 如何调试插件?

    • 使用 Logger
    • 打印回调顺序
    • 使用断点

实战题

  1. 实现慢查询日志插件
  2. 实现审计日志插件
  3. 实现查询结果缓存插件

7.2 实践练习

练习 1: 查询计数插件

统计每种操作的执行次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type CountPlugin struct {
mu sync.RWMutex
counts map[string]int64
}

func (p *CountPlugin) Name() string {
return "query:counter"
}

func (p *CountPlugin) Initialize(db *gorm.DB) error {
// 实现...
return nil
}

func (p *CountPlugin) GetStats() map[string]int64 {
return p.counts
}

练习 2: 查询超时插件

为查询设置超时时间。

练习 3: 数据加密插件

自动加密敏感字段。