前言
先說一下為什么要搞這個小東西?
米攸服務端前期主要是基于 Go 構建的,版本迭代過程中,業務復雜度不斷增加,再加上中員團隊有人員變動,考慮到目前團隊的技術背景,我們開始考慮把接口服務分批遷移到 Java,開發效率和可控程度更高一些。其中有一些接口服務涉及周邊模塊較多,遷移的時間成本較高,我們決定暫時繼續維護這些接口。后續接口需要升級時,如果變動較小,我們直接修改 Go 代碼;如果變動較大,我們在 Go 代碼中使用 HTTP 的方式調用 Java 接口實現,相當于給原有接口加了一個 鉤子。為了減化接口調用代碼編寫的復雜度,我們考慮在 Go 代碼中內置兩個工具函數:Get 和 Post,方便調用 Java 接口。
本文重點討論 Get 和 Post 函數實現的關鍵細節,并給出核心代碼。
Result
Java 接口的返回結果是一個 固定格式 的 Json 字符串:
-
id
請求ID,字符串。
-
code
狀態碼,整數。
-
msg
狀態信息,字符串。
-
data
數據,任意類型。
我們使用 結構體 封裝返回結果:
type Result struct {
Id string `json:"id"`
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
可以發現,結構體的字段名稱和返回結果的字段名稱是不一樣的(首字母大小寫),兩者相互轉換的時候名稱會對應不上,需要在結構體中使用類似 `json:"id"` 的聲明,把結構體中的字段名稱和返回結果的字段名稱一一對應起來。
特別注意:聲明字段名稱時標點符號的使用。
Client
Go 提供的 Http 客戶端(Client)實例是線程安全的,一個進程內只需要有一個即可:
var client = http.Client{}
Get
Get 函數的參數應該有兩個:接口路徑(url)和 接口參數(params)。接口路徑比較簡單,就是一個字符串(string),我們主要討論接口參數。
我們使用 Query String 的方式傳遞 Get 參數,如:/interface/param1=value1¶m2=value2,接口參數的類型應該是一個內部包含多個鍵值對的 字典(map[string]interface{}),鍵名稱是參數名稱,鍵值是參數值;考慮到實際使用場景,參數值的類型限制為三種:
- string
- int
- float64
因為參數的值類型是不確定的,所以使用 interface{} 表示任意類型,函數內部判斷具體類型。
因為調用接口時需要添加 請求頭 和 請求參數,所以不能直接使用 http.Get 這樣的簡化函數,實現流程:
創建請求
使用 http.NewRequest 創建請求:
req, err := http.NewRequest("GET", url, nil)
添加請求頭
設置請求響應的內容類型為 json:
req.Header.Add("Content-Type", "application/json")
設置調用接口時的 Token:
req.Header.Add("token", MEETU_API_TOKEN)
添加請求參數
創建請求參數:
query := req.URL.Query()
逐個添加請求參數:
query.Add(name, value)
注意:添加請求參數時,name(參數名稱) 和 value(參數類型) 類型都是字符串。
如前文所述,接口參數是一個字典類型的變量,我們需要遍歷這個變量中的每一個鍵值對,逐個添加參數。遍歷可以使用 Range:
for name, value := range params {
...
}
如前文所述,參數值類型是有限制的,遍歷過程中,我們需要判斷參數值類型是否符合要求。類型判斷可以使用 value.(type):
switch value.(type) {
case string:
query.Add(name, value.(string))
case int:
query.Add(name, strconv.Itoa(value.(int)))
case float64:
query.Add(name, strconv.FormatFloat(value.(float64), 'f', -1, 64))
default:
return Result{}, errors.New("params type only support string, int and float64")
}
使用 value.(string)、value.(int) 和 value.(float64) 把變量 value(類型:interface{}) 分別轉換為類型 string、int 和 float64 的變量。使用 strconv.Itoa 把 int 變量轉換為 string 變量,使用 strconv.FormatFloat 把 float64 變量轉換為 string 變量。
請求參數值可能包含特殊字符,需要轉義:
req.URL.RawQuery = query.Encode()
請求參數添加完成。
執行請求,獲取結果
resp, err := client.Do(req)
defer resp.Body.Close()
defer 表示 Get 函數執行完成之后,關閉 Http 客戶端內部的網絡連接。
解析結果
響應體(resp.Body)的數據是字節流,需要解碼并反序列化成類型為 Result 的變量 result:
json.NewDecoder(resp.Body).Decode(&result)
返回結果
return result, nil
到此,Get 函數實現完成,代碼如下:
func Get(url string, params map[string]interface{}) (Result, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return Result{}, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("token", MEETU_API_TOKEN)
if params != nil {
query := req.URL.Query()
for name, value := range params {
switch value.(type) {
case string:
query.Add(name, value.(string))
case int:
query.Add(name, strconv.Itoa(value.(int)))
case float64:
query.Add(name, strconv.FormatFloat(value.(float64), 'f', -1, 64))
default:
return Result{}, errors.New("params type only support string, int and float64")
}
}
req.URL.RawQuery = query.Encode()
}
resp, err := client.Do(req)
if err != nil {
return Result{}, err
}
defer resp.Body.Close()
var result Result
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return Result{}, err
}
return result, nil
}
Post
Post 函數的實現過程整體和 Get 是類似的,唯一不同的就是請求參數的處理。我們使用 Body 傳遞 Post 參數。
其余內容請參考:Go Http Get 和 Post 工具函數。
浙公網安備 33010602011771號