Go實戰記錄總結

一、http請求中第二次讀取body時無數據問題

在go的網絡編程中,不管是request中的body,仍是response中的body都是io.ReadCloser類型,意味着一旦所有讀取完成,就沒法進行第二次讀取,由於在io.ReadCloser的內部會有一個標記,記錄讀取到什麼位置,所以一旦讀到尾,就不能再從頭讀取了。web

因爲ReadCloser不能Seek,所以通常的解決方法就是把body讀出來以後,再從新包裝成io.ReadColser,而後再綁定回body上。以gin中的中間件讀取爲例,先在中間件中讀取body,而後在控制器裏讀取body,sql

func printBody(ctx *gin.Context) {
	defer ctx.Request.Body.Close()
	body, _ := ioutil.ReadAll(ctx.Request.Body)
	ctx.Request.Body = ioutil.NopCloser(bytes.NewReader(body)) //關鍵一步,把已經讀取出來的body數據,使用NopCloser從新包裝成io.ReadCloser
	fmt.Println(string(body))
}

func main() {
	r := gin.Default()
	r.Use(printBody)
	r.POST("/test", func(ctx *gin.Context) {
		defer ctx.Request.Body.Close()
		body, _ := ioutil.ReadAll(ctx.Request.Body)
		fmt.Println("router:", string(body))
	})
	r.Run(":9999")
}
複製代碼

運行結果: 數據庫

若是把關鍵的那步代碼去掉,則讀取不到,變成了如下結果:

固然,若是不須要二次讀取body數據,那麼也就不必這麼操做了。

二、使用sql語句時,字段名大小寫問題

以前一直用的是mongo,後來在一次開發中使用postgresql,在建表時,設置了兩個字段名,appKey和appSecret,而後在go中拼接sql語句時,一開始是這麼寫的編程

db.Where("appKey = ? AND appSecret = ?", appKey,appSecret).First(&user)
複製代碼

程序會報 pq: column "appkey" does not exist 錯誤,查看執行的sql的語句:json

SELECT * FROM "secrets"  WHERE "secrets"."deletedAt" IS NULL AND ((appKey = '123' AND appSecret = '123'))
複製代碼

查閱資料以後發現,sql語句對大小寫是不敏感的,它會把appKey和appSecret當作appkey,appsecret,所以就會出錯api

解決的方法是對須要大寫的字符串用雙引號引發來,如如下代碼網絡

db.Where("\"appKey\" = ? AND \"appSecret\" = ?", appKey,appSecret).First(&user) // 使用 \ 對雙引號進行轉義
複製代碼

查看執行的sql語句:app

SELECT * FROM "secrets"  WHERE "secrets"."deletedAt" IS NULL AND (("appKey" = '123' AND "appSecret" = '123'))
複製代碼

這個時候,大小寫就被區分開了,程序也就正常了。框架

可是這樣會在代碼裏出現過多的反斜杆,不是特別的舒服,所以建議使用下劃線來從新命名字段,即appKey,appSecret改成app_key,app_secretpost

三、map與json

在go中,若是發送了一個http請求,返回一個json,而後要對這個json進行操做,可使用json.Unmarshal(),來將獲取到的json轉爲map,而後再進行處理。

func main() {
	url := "http://localhost:9900/test"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	var data map[string]interface{}
	json.Unmarshal(body, &data) // 使用Unmarshal,將返回的json轉爲map
	fmt.Println(data) // map[a:1 b:2]
}
複製代碼

若是一個接口要返回一個json數據響應,則可使用json.Marshal(),來將map轉爲json字符串

func main() {
	http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
		data := map[string]int{
			"a": 1,
			"b": 4,
		}
		resp, _ := json.Marshal(data) // 此時的resp是[]byte,使用string(resp),能夠獲得{"a":1,"b":4}的json字符串
		writer.Header().Add("Content-Type", "application/json")
		writer.WriteHeader(200)
		writer.Write(resp)
	})
	http.ListenAndServe(":6666", nil)
}
複製代碼

這兩種互相轉化的方式,在一些web框架中,被大量的使用,好比在gin中,經常使用的context.JSON()方法,最終調用的是一個WriteJSON方法,而這個方法也是很簡潔

func WriteJSON(w http.ResponseWriter, obj interface{}) error {
	writeContentType(w, jsonContentType)
	jsonBytes, err := json.Marshal(obj) // 就是這裏,將傳入的interface{}轉爲[]byte形式的json字符串
	if err != nil {
		return err
	}
	_, err = w.Write(jsonBytes)
	return err
}
複製代碼

四、使用gorm時,postgresql外鍵約束問題

使用關係型數據庫,設計表的時候,有時會設計外鍵,這個時候也會給外鍵添加約束,也就是參照完整性約束。然而在使用gorm,編寫數據模型時,發現沒法創建外鍵約束,外鍵的關聯是創建起來了,可是約束沒有創建起來。gorm使用ForeignKey,AssociationForeignKey都沒法自動創建約束,即使使用sql tag也創建不了,嘗試了好幾回。最終使用了gorm的api,用api創建外鍵約束

Client.CreateTable(&model.User{})
Client.CreateTable(&model.Profile{})
Client.Model(&model.User{}).AddForeignKey("profileId", "profile(id)", "SET NULL", "CASCADE")
// 爲user表添加profileId外鍵,並設置外鍵約束,刪除時SET NULL, 更新時CASCADE
複製代碼

注意這個AddForeignKey方法,若是已經創建了約束,再次執行程序會報錯,因此要作適當的判斷。

結語

第一次使用go開發系統,多多少少在使用上有些不習慣,以上是在開發中遇到的一些問題,再接再礪!

Thanks!

相關文章
相關標籤/搜索