package binlog_test
import("fmt""github.com/json-iterator/go""github.com/siddontang/go-mysql/canal""github.com/siddontang/go-mysql/schema""reflect""runtime/debug""strings""testing""time")funcTestBinLog(t *testing.T){
gobinLogListener()// placeholder for your handsome code
time.Sleep(5* time.Minute)
fmt.Print("Thx for watching")}type User struct{
Id int`gorm:"column:id"`
Name string`gorm:"column:name"`
Status string`gorm:"column:status"`
Created time.Time `gorm:"column:created"`}// 表名,大小写不敏感func(User)TableName()string{
return"User"}// 数据库名称,大小写不敏感func(User)SchemaName()string{
return"Test"}funcbinLogListener(){
c, err :=getDefaultCanal()if err ==nil{
coords, err := c.GetMasterPos()if err ==nil{
c.SetEventHandler(&binlogHandler{
})
c.RunFrom(coords)}}}funcgetDefaultCanal()(*canal.Canal,error){
cfg := canal.NewDefaultConfig()
cfg.Addr = fmt.Sprintf("%s:%d","127.0.0.1",3306)
cfg.User ="root"
cfg.Password ="root"
cfg.Flavor ="mysql"
cfg.Dump.ExecutionPath =""return canal.NewCanal(cfg)}type binlogHandler struct{
canal.DummyEventHandler // Dummy handler from external lib
BinlogParser // Our custom helper}func(h *binlogHandler)OnRow(e *canal.RowsEvent)error{
deferfunc(){
if r :=recover(); r !=nil{
fmt.Print(r," ",string(debug.Stack()))}}()// base value for canal.DeleteAction or canal.InsertActionvar n =0var k =1if e.Action == canal.UpdateAction {
n =1
k =2}for i := n; i <len(e.Rows); i += k {
key := strings.ToLower(e.Table.Schema +"."+ e.Table.Name)
key2 := strings.ToLower(User{
}.SchemaName()+"."+ User{
}.TableName())switch key {
case key2:
user := User{
}
h.GetBinLogData(&user, e, i)switch e.Action {
case canal.UpdateAction:
oldUser := User{
}
h.GetBinLogData(&oldUser, e, i-1)
fmt.Printf("User %d name changed from %s to %s\n", user.Id, oldUser.Name, user.Name)case canal.InsertAction:
fmt.Printf("User %d is created with name %s\n", user.Id, user.Name)case canal.DeleteAction:
fmt.Printf("User %d is deleted with name %s\n", user.Id, user.Name)default:
fmt.Printf("Unknown action")}}}returnnil}func(h *binlogHandler)String()string{
return"binlogHandler"}type BinlogParser struct{
}func(m *BinlogParser)GetBinLogData(element interface{
}, e *canal.RowsEvent, n int)error{
var columnName stringvar ok bool
v := reflect.ValueOf(element)
s := reflect.Indirect(v)
t := s.Type()
num := t.NumField()for k :=0; k < num; k++{
parsedTag :=parseTagSetting(t.Field(k).Tag)
name := s.Field(k).Type().Name()if columnName, ok = parsedTag["COLUMN"];!ok || columnName =="COLUMN"{
continue}switch name {
case"bool":
s.Field(k).SetBool(m.boolHelper(e, n, columnName))case"int":
s.Field(k).SetInt(m.intHelper(e, n, columnName))case"string":
s.Field(k).SetString(m.stringHelper(e, n, columnName))case"Time":
timeVal := m.dateTimeHelper(e, n, columnName)
s.Field(k).Set(reflect.ValueOf(timeVal))case"float64":
s.Field(k).SetFloat(m.floatHelper(e, n, columnName))default:if_, ok := parsedTag["FROMJSON"]; ok {
newObject := reflect.New(s.Field(k).Type()).Interface()
json := m.stringHelper(e, n, columnName)
jsoniter.Unmarshal([]byte(json),&newObject)
s.Field(k).Set(reflect.ValueOf(newObject).Elem().Convert(s.Field(k).Type()))}}}returnnil}func(m *BinlogParser)dateTimeHelper(e *canal.RowsEvent, n int, columnName string) time.Time {
columnId := m.getBinlogIdByName(e, columnName)if e.Table.Columns[columnId].Type != schema.TYPE_TIMESTAMP {
panic("Not dateTime type")}
t,_:= time.Parse("2006-01-02 15:04:05", e.Rows[n][columnId].(string))return t
}func(m *BinlogParser)intHelper(e *canal.RowsEvent, n int, columnName string)int64{
columnId := m.getBinlogIdByName(e, columnName)if e.Table.Columns[columnId].Type != schema.TYPE_NUMBER {
return0}switch e.Rows[n][columnId].(type){
caseint8:returnint64(e.Rows[n][columnId].(int8))caseint32:returnint64(e.Rows[n][columnId].(int32))caseint64:return e.Rows[n][columnId].(int64)caseint:returnint64(e.Rows[n][columnId].(int))caseuint8:returnint64(e.Rows[n][columnId].(uint8))caseuint16:returnint64(e.Rows[n][columnId].(uint16))caseuint32:returnint64(e.Rows[n][columnId].(uint32))caseuint64:returnint64(e.Rows[n][columnId].(uint64))caseuint:returnint64(e.Rows[n][columnId].(uint))}return0}func(m *BinlogParser)floatHelper(e *canal.RowsEvent, n int, columnName string)float64{
columnId := m.getBinlogIdByName(e, columnName)if e.Table.Columns[columnId].Type != schema.TYPE_FLOAT {
panic("Not float type")}switch e.Rows[n][columnId].(type){
casefloat32:returnfloat64(e.Rows[n][columnId].(float32))casefloat64:returnfloat64(e.Rows[n][columnId].(float64))}returnfloat64(0)}func(m *BinlogParser)boolHelper(e *canal.RowsEvent, n int, columnName string)bool{
val := m.intHelper(e, n, columnName)if val ==1{
returntrue}returnfalse}func(m *BinlogParser)stringHelper(e *canal.RowsEvent, n int, columnName string)string{
columnId := m.getBinlogIdByName(e, columnName)if e.Table.Columns[columnId].Type == schema.TYPE_ENUM {
values := e.Table.Columns[columnId].EnumValues
iflen(values)==0{
return""}if e.Rows[n][columnId]==nil{
//Если в енум лежит нуул ставим пустую строкуreturn""}return values[e.Rows[n][columnId].(int64)-1]}
value := e.Rows[n][columnId]switch value := value.(type){
case[]byte:returnstring(value)casestring:return value
}return""}func(m *BinlogParser)getBinlogIdByName(e *canal.RowsEvent, name string)int{
for id, value :=range e.Table.Columns {
if value.Name == name {
return id
}}panic(fmt.Sprintf("There is no column %s in table %s.%s", name, e.Table.Schema, e.Table.Name))}funcparseTagSetting(tags reflect.StructTag)map[string]string{
settings :=map[string]string{
}for_, str :=range[]string{
tags.Get("sql"), tags.Get("gorm")}{
tags := strings.Split(str,";")for_, value :=range tags {
v := strings.Split(value,":")
k := strings.TrimSpace(strings.ToUpper(v[0]))iflen(v)>=2{
settings[k]= strings.Join(v[1:],":")}else{
settings[k]= k
}}}return settings
}