前言
對接Apple渠道的API接口需要在請求中加上證書認證,源證書的格式為.p12,需要轉成.pem格式存儲Cert與PrivateKey信息,然后利用.pem文件中的信息發送請求。
將p12文件轉換為pem文件
直接在mac終端使用命令即可完成:
openssl pkcs12 -in xxx.p12 -out xxx.pem -nodes -clcerts
獲取pem文件數據並發送請求的示例 ***

package main import ( "crypto/tls" //"crypto/x509" "encoding/pem" "fmt" "io/ioutil" "net/http" //"testing" ) // func TestPemRequest(t *testing.T) { func PemRequest() { b, _ := ioutil.ReadFile("/Users/Wanghongwei/Downloads/xxx.pem") pem.Decode(b) var pemBlocks []*pem.Block var v *pem.Block var pkey []byte for { v, b = pem.Decode(b) if v == nil { break } if v.Type == "PRIVATE KEY" { pkey = pem.EncodeToMemory(v) } else { pemBlocks = append(pemBlocks, v) } } bytes := pem.EncodeToMemory(pemBlocks[0]) keyString := string(pkey) CertString := string(bytes) fmt.Printf("Cert :\n %s \n Key:\n %s \n ", CertString, keyString) //pool := x509.NewCertPool() c, _ := tls.X509KeyPair(bytes, pkey) //pool.AppendCertsFromPEM(b) cfg := &tls.Config{ Certificates:[]tls.Certificate{c}, } tr := &http.Transport{ TLSClientConfig: cfg, } client := &http.Client{Transport: tr} // request, _ := http.NewRequest("GET", "https://api.searchads.apple.com/api/v3/acls", nil) request.Header.Set("Content-Type", "application/json") resp, err := client.Do(request) // require.Nil(t, err) if err != nil{ fmt.Println("err>>> ",err) }else{ data, _ := ioutil.ReadAll(resp.Body) fmt.Printf(string(data)) } } func main() { PemRequest() }
實踐經驗
將pem文件中的信息存入配置項
在實際使用中,賬號的認證證書一般是不會改的,所以pem文件中的數據我們可以在讀取出來后存在本地(數據庫或者放在map中),程序用到的時候直接拿對應的參數即可,這里我是直接做成了配置文件:

var NoPassMap = map[string]string{ "certString": "xxx", "keyString": "xxx", "orgId": "x", } var RNoPassMap = map[string]string{ "certString": "xxx", "keyString": "xxx", "orgId": "x", } var AppStareMap = map[string]string{ "certString": "xxx", "keyString": "zzz", "orgId": "17zz", } // 賬號字典 var pemMap = map[string]map[string]string{ "Nopass": NoPassMap, "RNoPass": RNoPassMap, "Appstare": AppStareMap, }
使用Go發送POST請求
與Get請求不一樣,POST請求的數據是放在請求體中的,我們需要將所需要的請求數據構建成想要的格式,然后轉成bytes.Reader格式的數據才能發送請求。
請求體中的樣例數據格式如下:

POST https://api.searchads.apple.com/api/v3/reports/campaigns { "startTime": "2020-08-04", "endTime": "2020-08-14", "selector": { "orderBy": [ { "field": "countryOrRegion", "sortOrder": "ASCENDING" } ], "conditions": [ { "field": "countriesOrRegions", "operator": "CONTAINS_ANY", "values": [ "US", "GB" ] }, { "field": "countryOrRegion", "operator": "IN", "values": [ "US" ] } ], "pagination": { "offset": 0, "limit": 1000 } }, "groupBy": [ "countryOrRegion" ], "timeZone": "UTC", "returnRecordsWithNoMetrics": true, "returnRowTotals": true, "returnGrandTotals": true }
使用go進行構建與轉換的代碼如下:

func (a *appleTask) getPostBody(startDate, endDate string) (*bytes.Reader, *model.AppError) { // POST請求的請求體構建 info := make(map[string]interface{}) // 1、構建 orderBy 的篩選 orderDic := map[string]string{ "field": "localSpend", "sortOrder": "ASCENDING", } orderLst := []interface{}{orderDic} // 2、構建 conditions 的篩選 values1 := []interface{}{"false", "true"} conditionDic1 := map[string]interface{}{ "field": "deleted", "operator": "IN", "values": values1, } conditionLst := []interface{}{ conditionDic1, } // 3、構建分頁的篩選 paginationMap := map[string]interface{}{ "offset": 0, "limit": 1000, } // 4、selector的數據 SelectorObj := map[string]interface{}{ "pagination": paginationMap, "orderBy": orderLst, "conditions": conditionLst, } // selector info["selector"] = SelectorObj info["startTime"] = startDate info["endTime"] = endDate info["timeZone"] = "UTC" // info["returnRecordsWithNoMetrics"] = true //info["returnRowTotals"] = true //info["returnGrandTotals"] = true info["granularity"] = "DAILY" bytesData, err := json.Marshal(info) if err != nil { return nil, model.NewAppError("getPostBody", "Marshal.error", err.Error(), nil) } reader := bytes.NewReader(bytesData) return reader, nil }
處理HTTP請求並將結果轉為string類型的json數據的代碼如下:
// base func (a *App) AppleBaseRequestString(pemObj *model.PemMsg, requestMethod, url string, requestBody *bytes.Reader) (string, *model.AppError) { // 注意得將上面得到的cert與privateKey這兩個string類型的數據轉換為bytes類型!
certBytes := []byte(pemObj.CertString) ketBytes := []byte(pemObj.KeyString) // 處理 c, _ := tls.X509KeyPair(certBytes, ketBytes) cfg := &tls.Config{ Certificates: []tls.Certificate{c}, } tr := &http.Transport{ TLSClientConfig: cfg, } client := &http.Client{Transport: tr} var request *http.Request var err error // get請求傳nil,post請求傳如上面代碼中構建好的請求體數據 if requestBody == nil { request, err = http.NewRequest(requestMethod, url, nil) } else { request, err = http.NewRequest(requestMethod, url, requestBody) } if err != nil { return "", model.NewAppError("appleBaseRequestString", err.Error(), url, nil) } // 設置請求頭 —— Apple的Ad專屬認證的一個請求頭 request.Header.Set("Authorization", fmt.Sprintf("orgId=%s", pemObj.OrgId)) // 注意POST請求必須加這個請求頭 request.Header.Set("Content-Type", "application/json") resp, err1 := client.Do(request) if err1 != nil { return "", model.NewAppError("appleBaseRequestString", err1.Error(), url, nil) } data, err2 := ioutil.ReadAll(resp.Body) if err2 != nil { return "", model.NewAppError("appleBaseRequestString", err2.Error(), url, nil) } return string(data), nil }
最后將得到的json數據轉換為結構體參考我之前的這篇文章:
使用Go解析HTTP返回數據為struct並存入數據庫的操作