細數在用golang&beego做api server過程中的坑(二)
在介紹之前先說明一下,標題中帶有【beego】標籤的,是beego框架使用中遇到的坑。如果沒有,那就是golang本身的坑。當然,此坑並非人家程式碼有問題,有幾個地方反而是出於效能等各方面的考量有意而為之。但這些地方卻是一門語言或框架的初學者大概率會遇到的困惑。
傳送門:ofollow,noindex">細數在用golang&beego做api server過程中的坑(一)
【beego】4、orm查詢時沒有對應結果集: now row found (error)
java程式員對於持久層框架可能都用過mybatis,對查詢操作也會比較熟悉。當一行程式碼下去,資料庫給你咔咔一頓找,發現沒有結果時,會返回null 或 一個空的collection。beego中也有持久層框架,就叫orm。對於查詢方面的處理,如果資料庫查詢後沒有找到任何結果,orm不會返回空,而是會返回一個error!!
舉例:
var container po.OnlineAccumulateRecord err = o.QueryTable(new(po.OnlineAccumulateRecord)).Filter("User", user.Id).One(&container) if err != nil && !models.IsNoRowFoundError(err) { return 0, err }
這段程式碼是我從現有工程裡面隨便摘出來的,大意就是通過使用者id篩選出積分表裡的一條記錄。這個時候如果查不到對應的記錄,err != nil
便會成立。這時候單純的通過err !=nil
就無法判斷到底是真的出現數據庫查詢錯誤,還是沒有找到對應記錄。因此就有了models.IsNoRowFoundError(err)
這一判斷。對於是否是NoRowFoundError
,可行的方法之一是判斷err是否相等:
if err == orm.ErrNoRows { }
之二是判斷error message:
//判斷一個錯誤是不是因為資料庫中沒有符合條件的記錄,而丟擲的錯誤。 //這種情況是一種正常的情況,不應該作為錯誤丟擲 func IsNoRowFoundError(err error) bool { if err == nil { return false } if strings.Contains(err.Error(), "no row found") { return true } return false }
當然對於其他的查詢方式,也會有這個問題,例如:
o := orm.NewOrm() err = o.Read(&orderInfo, "OuterOrderId")
在此就不一一列舉。
我個人認為astaxie大神在設計beego orm時不可能沒考慮到這一點。之所以這樣設計,可能的一個原因是,對於剛才的查詢程式碼:
var container po.OnlineAccumulateRecord err = o.QueryTable(new(po.OnlineAccumulateRecord)).Filter("User", user.Id).One(&container)
不能夠通過判斷container是不是nil
來斷定有沒有查詢到對應的記錄。因為在golang中,一個變數由兩部分組成:type & value
,只要任何一部分是明確的,則該變數就不是nil。在上例中,container的type
已經明確宣告是po.OnlineAccumulateRecord
, 此時value是零值。因此它永遠都不會是nil。
【beego】5、orm插入記錄,如果pk是非整型(e.g. string):no LastInsertId available error
orm設計時對於整型pk做了特別的支援:
當 Field 型別為 int, int32, int64, uint, uint32, uint64 時,可以設定欄位為自增健
當模型定義裡沒有主鍵時,符合上述型別且名稱為 Id 的 Field 將被視為自增健。
但如果主鍵是string,通過以下程式碼做insert操作時會返回"no LastInsertId available" error。因此需要增加IsNoLastInsertIdError 判斷以免影響程式執行的分支走向。
o := orm.NewOrm() _, err = o.Insert(wechatNotify) if models.IsNoLastInsertIdError(err) { return nil }
同樣的,IsNoLastInsertIdError 判斷的方法之一是根據error message判斷:
//這種情況是一種正常的情況,不應該作為錯誤丟擲 func IsNoLastInsertIdError(err error) bool { if err == nil { return false } if strings.Contains(err.Error(), "no LastInsertId available") { return true } return false }
【beego】6、報錯誤導:<Ormer> table: '.' not found,
好不容易把程式碼擼完了,興沖沖的執行debug,結果發現如下報錯資訊:
<Ormer> table: `.` not found, make sure it was registered with `RegisterModel()`
根據字面意思是說正在操作的這個名字為“.”的table沒有進行regist操作。於是排查,但很快發現:
1、根本就沒有一個叫"."的table
2、正在操作的表也已經在beego啟動的時候完成了regist操作
然後通過原始碼進行定位,報錯位置:
func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) { val := reflect.ValueOf(md) ind = reflect.Indirect(val) typ := ind.Type() if needPtr && val.Kind() != reflect.Ptr { panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ))) } name := getFullName(typ) if mi, ok := modelCache.getByFullName(name); ok { return mi, ind } panic(fmt.Errorf("<Ormer> table: `%s` not found, make sure it was registered with `RegisterModel()`", name)) }
那這個name是怎麼得出來的呢?
// get reflect.Type name with package path. func getFullName(typ reflect.Type) string { return typ.PkgPath() + "." + typ.Name() }
也就是說,type.PkgPath() 和 type.Name()都是empty string 返回值。那什麼時候他們返回的是empty string 呢?
// Name returns the type's name within its package for a defined type. // For other (non-defined) types it returns the empty string. Name() string // PkgPath returns a defined type's package path, that is, the import path // that uniquely identifies the package, such as "encoding/base64". // If the type was predeclared (string, error) or not defined (*T, struct{}, // []int, or A where A is an alias for a non-defined type), the package path // will be the empty string. PkgPath() string
從上述程式碼可以看出,當o.Insert()
接收到引數是not defined
的話,就會導致該項錯誤,而並不是說這個玩意沒有regist(就這些玩意咋去regist嘛)。註釋部分也說了not defined包括哪幾種:*T, struct{}, []int
,以及這幾類的別名。
最後舉個能出現這個問題的例子:
var _struct structExample var _pointer &_struct o := orm.NewOrm() o.Insert(&_pointer)
【beego】7、報錯誤導:panic: reflect: call of reflect.Value.Interface on zero Value
再一次好不容易把程式碼擼完了,興沖沖的執行debug,結果發現如下報錯資訊:
panic: reflect: call of reflect.Value.Interface on zero Value goroutine 1 [running]: reflect.valueInterface(0x0, 0x0, 0x0, 0x1, 0x0, 0x0) /usr/local/Cellar/go/1.11/libexec/src/reflect/value.go:953 +0x2ce reflect.Value.Interface(0x0, 0x0, 0x0, 0x0, 0x0) /usr/local/Cellar/go/1.11/libexec/src/reflect/value.go:948 +0x4c github.com/astaxie/beego/orm.getFieldType(0x1af8900, 0xc0001e19e0, 0x196, 0x0, 0x0, 0x0) /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_utils.go:181 +0xd00 github.com/astaxie/beego/orm.newFieldInfo(0xc0001f9040, 0x1af8900, 0xc0001e19e0, 0x196, 0x1a873ee, 0x4, 0x0, 0x0, 0x1e2bba0, 0x1af8900, ...) /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_info_f.go:244 +0x372e github.com/astaxie/beego/orm.addModelFields(0xc0001f9040, 0x1bf8820, 0xc0001e1980, 0x199, 0x0, 0x0, 0xc0000dd828, 0x0, 0x0) /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_info_m.go:70 +0x3f4 github.com/astaxie/beego/orm.newModelInfo(0x1af8340, 0xc0001e1980, 0x16, 0xc0001f9040) /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_info_m.go:45 +0x302 github.com/astaxie/beego/orm.registerModel(0x1c4b231, 0x4, 0x1af8340, 0xc0001e1980, 0x201) /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_boot.go:62 +0x7b0 github.com/astaxie/beego/orm.RegisterModelWithPrefix(0x1c4b231, 0x4, 0xc0000ddd38, 0x7, 0x7) /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_boot.go:320 +0x16d rrs.com/rrsservice/models/po.init.28() /Users/tangxqa/develop/code/go/src/rrs.com/rrsservice/models/po/payment_wx.go:147 +0x2aa
可以通過堆疊資訊看出這一報錯發生在regist model時,regist部分的程式碼為:
orm.RegisterModelWithPrefix(prefix, new(PaymentInfo))
PaymentInfo:
type PaymentInfo struct { Idstring`orm:"size(32);pk;column(id)"` Statusstring OrderIdstring OrderSourcestring PayTypestring OrderPriceint UnifiedOrderRes *UnifiedOrderResponse `orm:"rel(fk);null"` User*User Tstime.Time }
好像程式碼寫的沒毛病。只好再根據堆疊資訊找到beego原始碼中:github.com/astaxie/beego/orm.getFieldType 位於 astaxie/beego/orm/models_utils.go:181
,getFieldType
方法中是一堆型別判斷程式碼:
// return field type as type constant from reflect.Value func getFieldType(val reflect.Value) (ft int, err error) { switch val.Type() { case reflect.TypeOf(new(int8)): ft = TypeBitField case reflect.TypeOf(new(int16)): ft = TypeSmallIntegerField case reflect.TypeOf(new(int32)), reflect.TypeOf(new(int)): ft = TypeIntegerField case reflect.TypeOf(new(int64)): ft = TypeBigIntegerField ··· ···
什麼時候會走到這?那就看看github.com/astaxie/beego/orm.newFieldInfo 位於 gthub.com/astaxie/beego/orm/models_info_f.go:244
的程式碼:
這些程式碼都是一些對欄位tag 的分析處理。等等,是不是忘記新增tag了!!
tag = "rel" tagValue = tags[tag] if tagValue != "" { switch tagValue { case "fk": fieldType = RelForeignKey break checkType case "one": fieldType = RelOneToOne break checkType case "m2m": fieldType = RelManyToMany if tv := tags["rel_table"]; tv != "" { fi.relTable = tv } else if tv := tags["rel_through"]; tv != "" { fi.relThrough = tv } break checkType default: err = fmt.Errorf("rel only allow these value: fk, one, m2m") goto wrongTag } } tag = "reverse" tagValue = tags[tag] if tagValue != "" { switch tagValue { case "one": fieldType = RelReverseOne break checkType case "many": fieldType = RelReverseMany if tv := tags["rel_table"]; tv != "" { fi.relTable = tv } else if tv := tags["rel_through"]; tv != "" { fi.relThrough = tv } break checkType default: err = fmt.Errorf("reverse only allow these value: one, many") goto wrongTag } } fieldType, err = getFieldType(addrField)
新增tag,搞定!!
User*User`orm:"rel(fk)"`
那是不是所有的欄位在orm時都需要新增標籤?不是。當欄位是指標型別時,如果沒有用orm:"-"
進行orm忽略,必須要新增標籤來進行表關係設定。
相關資料:https://beego.me/docs/mvc/model/models.md 參照其中的表關係設定章節。