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
