golang获取结构体不同内容

917 阅读1分钟
Hello 第一次写文章,大家将就看。
本次分享的是一个结构体处理的小技巧。

需求背景

产品需求要修改用户信息,还得取到修改前内容和修改后内容。

实现

type User struct {
   UserId    string `json:"userId"`
   UserName  string `json:"userName"`
   UserClass string `json:"userClass"`
}

笨方法

刚开始没仔细想,就几个字段随手就写了以下的实现。
func DiffMap(old, new *User) (before, after map[string]interface{}) {
   before, after = make(map[string]interface{}), make(map[string]interface{})
   if old.UserName != new.UserName {
      before["userName"] = old.UserName
      after["userName"] = new.UserName
   }
   if old.UserId != new.UserId {
      before["userId"] = old.UserId
      after["userId"] = new.UserId
   }
   if old.UserClass != new.UserClass {
      before["userClass"] = old.UserId
      after["userClass"] = new.UserId
   }
   return
}
功能实现了,然后结构体越来越大,字段越来越多,if 越来越多,这代码看着就很扯了。
随后还有很多信息修改,都要这样的结构。。。然后就考虑优化了。

优化

这种结构体操作肯定就开始考虑反射了,然后就有以下实现:
func DiffMap(old, new interface{}, excludes ...string) (before, after map[string]interface{}) {
   before, after = make(map[string]interface{}), make(map[string]interface{})
   valOld := reflect.ValueOf(old).Elem()
   valNew := reflect.ValueOf(new).Elem()

   for i := 0; i < valOld.NumField(); i++ {
      key := valOld.Type().Field(i).Name
      if valOld.Field(i).Type() != valNew.FieldByName(key).Type() {
         continue
      }
      // 假如结构体字段包含指针,需要特殊处理
      if valOld.Field(i).Type().Name() == "Decimal" {
         vo, oOk := valOld.Field(i).Interface().(decimal.Decimal)
         vn, nOk := valNew.FieldByName(key).Interface().(decimal.Decimal)
         if oOk && nOk && vo.Equal(vn) {
            continue
         }
      } else {
         if valOld.Field(i).Interface() == valNew.FieldByName(key).Interface() {
            continue
         }
         if valOld.Field(i).IsZero() && valNew.FieldByName(key).IsZero() {
            continue
         }
      }
      isExclude := false
      for _, col := range excludes {
         if key == col {
            isExclude = true
            break
         }
      }
      if isExclude {
         continue
      }
      before[key] = valOld.Field(i)
      after[key] = valNew.FieldByName(key)
   }
   return before, after
}
优化之后有再多的这种需求都不怕了,直接就可以取到结果了,代码看着舒服多了。

总结

代码重复的东西,能封装就尽量封装,不要怕问题多,方法总比问题多的。