5. Hook
...大约 2 分钟
1. Hook
Hook(钩子),主要思想是提前在可能增加功能的地方埋好(预设)一个钩子,当需要重新修改或者增加这个地方的逻辑的时候,把扩展的类或者方法挂载到这个点即可。
例如:
- Github 支持的 travis 持续集成服务,当有
git push
事件发生时,会触发 travis 拉取新的代码进行构建。 - 在IDE 中当按下
Ctrl + s
后,自动格式化代码。 - 前端常用的
hot reload
机制,前端代码发生变更时,自动编译打包,通知浏览器自动刷新页面,实现所写即所得
钩子机制设计的好坏,取决于扩展点选择的是否合适。例如对于持续集成来说,代码如果不发生变更,反复构建是没有意义的,因此钩子应设计在代码可能发生变更的地方,比如 MR、PR 合并前后。
对于 ORM 框架来说,Hook 适合放在 CRUD 操作的前后。例如:查询的数据中包含敏感数据,可以在查询后对其进行脱敏后再返回。
2. 实现
Hook 需要和结构体绑定,交给用户实现,ORM 框架则在 CRUD 操作中插入这些 hook。
session/hooks.go
:
package session
import (
"geeorm/log"
"reflect"
)
// Hooks
const (
BeforeQuery = "BeforeQuery"
AfterQuery = "AfterQuery"
BeforeUpdate = "BeforeUpdate"
AfterUpdate = "AfterUpdate"
BeforeDelete = "BeforeDelete"
AfterDelete = "AfterDelete"
BeforeInsert = "BeforeInsert"
AfterInsert = "AfterInsert"
)
// CallMethod calls the registered hooks
func (s *Session) CallMethod(method string, value any) {
var fm reflect.Value
if value == nil {
fm = reflect.ValueOf(s.RefTable().Model).MethodByName(method)
} else {
fm = reflect.ValueOf(value).MethodByName(method)
}
param := []reflect.Value{reflect.ValueOf(s)}
if fm.IsValid() {
if v := fm.Call(param); len(v) > 0 {
if err, ok := v[0].Interface().(error); ok {
log.Error(err)
}
}
}
return
}
- 判断当前操作对象
value
,若为空则默认设置为Session.refTable.Model
- 获取 hook 方法
- 获取 hook 方法的参数,类型为
*Session
- 调用 hook
2.1 插入 Hook
在 CRUD 操作中插入 Hook,例如Find:
func (s *Session) Find(vals any) error {
s.CallMethod(BeforeQuery, nil)
...
for rows.Next() {
...
s.CallMethod(AfterQuery, dst.Addr().Interface())
dstSlice.Set(reflect.Append(dstSlice, dst))
}
return rows.Close()
}
3. 单元测试
package session
import (
"geeorm/log"
"testing"
)
type Account struct {
ID int `geeorm:"PRIMARY KEY"`
Password string
}
const (
pass = "******"
)
func (a *Account) BeforeInsert(s *Session) error {
log.Info("before insert", a)
a.ID += 1000
return nil
}
func (a *Account) AfterQuery(s *Session) error {
log.Info("after query", a)
a.Password = pass
return nil
}
func TestSession_CallMethod(t *testing.T) {
s := newSession().Model(&Account{})
_ = s.DropTable()
_ = s.CreateTable()
_, _ = s.Insert(&Account{1, "123"}, &Account{2, "abc"})
u := &Account{}
err := s.First(u)
if err != nil || u.ID != 1001 || u.Password != pass {
t.Fatal("failed to call after query hooks")
}
}
Reference
Powered by Waline v2.15.2