簡介
之前初級版本的博客:使用Go處理SDK返回的嵌套層級數據並將所需字段存入數據庫(一)
之前的這篇博客介紹了如何去處理HTTP請求獲取到的響應數據以及轉換成map的思路,但是前面那種方法太繁瑣了,這里給出優化版本的使用方案以及具體說明。
本優化版本大體的思路為:將HTTP返回的響應轉為json結構的string類型數據,然后將json數據轉為結構體,最后使用GORM將結構體中的數據寫入到MySQL中去!
具體的實現過程如下:首先當然是發送HTTP請求獲取響應數據,獲取到的響應數據最開始是以字節流的形式存在的,接着將這些字節流形式的數據轉換為string類型的數據,從這里開始就與上次的方式有了本質的區別了(之前的方式是轉換為map類型)!最后我們將得到的string類型的數據根據返回的格式存入跟已經定義好的與返回格式相同層級結構的結構體中即可!
Go發送HTTP請求獲取數據
首先我們需要獲取數據,使用Go給特定的URL發送請求的代碼如下(注意這里我將結果解析成了string類型):
// base func baseRqeuestString(args ...string) string { // token requestMethod url 三個參數是有順序的! token := args[0] requestMethod := args[1] url := args[2] client := &http.Client{} // get請求 req, err := http.NewRequest(requestMethod, url, nil) if err != nil { fmt.Println(err) log.Fatal(err) } // 在請求頭中加入校驗的token req.Header.Set("Authorization", "Bearer "+token) resp, err := client.Do(req)
if err != nil { fmt.Println(err) log.Fatal(err) }
defer resp.Body.Close() returnStr, err := ParseResponseString(resp) //fmt.Println("responseStr>>> " + returnStr) // HTTP字節流轉為str的結果 return returnStr } // 解析http請求返回的內容 ———— 轉換為 string func ParseResponseString(response *http.Response) (string, error) { //var result map[string]interface{} body, err := ioutil.ReadAll(response.Body) // response.Body 是一個數據流 return string(body), err // 將 io數據流轉換為string類型返回! }
得到的結果格式如下

{ "request_status": "SUCCESS", "request_id": "xxx", "paging": {}, "adsquads": [ { "sub_request_status": "SUCCESS", "adsquad": { "id": "xx-6b5b-4exx82-a3c2-xx", "updated_at": "2020-10-05T23:14:44.884Z", "created_at": "2019-09-16T10:21:34.662Z", "name": "x Ad, om, x, 23-35+", "status": "ACTIVE", "campaign_id": "xxx-xx-44ba-xx-xxx", "type": "SNAP_ADS", "targeting": { "regulated_content": false, "geos": [ { "country_code": "om" } ], }, "targeting_reach_status": "VALID", "placement": "UNSUPPORTED", "billing_event": "IMPRESSION", } }, { "sub_request_status": "SUCCESS", "adsquad": { "id": "xx-d717-49f2-x-xx", "updated_at": "2020-10-05T23:15:47.741Z", "created_at": "2019-09-18T07:19:26.661Z", "name": "App Install, kw, Male, 18-35+", "status": "ACTIVE", "campaign_id": "xx-d4fe-4a85-878b-xx", "type": "SNAP_ADS", "targeting": { "regulated_content": false, "geos": [ { "country_code": "kw" } ], }, "targeting_reach_status": "VALID", } }, ...... ] }
注意理解這個結果的格式十分重要!
定義結構體
我們接下來的工作就是將這個json字符串轉換為結構體,然后將數據存入數據庫中!
定義結構體主要是為了2個方面的工作:一個是獲取json轉換結構體的結果,另外就是根據結構體中存的字段將我們需要的數據寫入到數據庫中。
定義的結構體以及其對應的成員函數如下:
// 存數據庫 type AdSetSub struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` CampaignId string `json:"campaign_id"` CreateTime int64 `json:"-"` UpdateTime int64 `json:"-"` AdType string `json:"type"` AccountId string } // json解析 比存入數據庫的字段多,因此做組合處理 type AdSet struct { AdSetSub CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } // json解析嵌套格式用到下面2個結構體 ———— 跟HTTP返回的結構保持一致 type AdSetSnapResponse struct { RequestStatus string `json:"request_status"` RequestId string `json:"request_id"` Paging interface{} `json:"paging"` Adsquads []*AdsquadsSubResponse `json:"adsquads"` // 注意這里的字段要與返回的字段對應上! } type AdsquadsSubResponse struct { Adsquad *AdSet `json:"adsquad"` // 注意這里的字段要與返回的字段對應上! } // 根據GORM設置表名 注意用的是AdSetSub func (a AdSetSub) TableName() string{ return "adset" } // AdSet結構體中數據處理 func (a *AdSet) Format(accountId string){ // 時間字符串轉時間戳 createTimeStamp, err1 := Timestr2Timestamp(a.CreatedAt) updateTimeStamp, err2 := Timestr2Timestamp(a.UpdatedAt) if err1 != nil{ fmt.Println("AdSet轉換時間戳異常") }else{ a.CreateTime = createTimeStamp } if err2 != nil{ fmt.Println("AdSet轉換時間戳異常") }else{ a.UpdateTime = updateTimeStamp } // 賬號ID a.AccountId = accountId }
json數據解析為結構體的函數
// 獲取某一個account下的adsquad的信息 // 處理賬戶下的AdSet數據寫入數據庫中 )———— 主體函數中調用它傳入對應參數即可,注意db是gorm的鏈接對象 func handleAdSetToMySQL(accountId, token string, db *gorm.DB) (insertAdSet int64){ // 得到存放着adset地址的切片 adsets := getAccountAdsquads(baseRqeuestString,accountId,token) fmt.Println("len_adsets>>> ",len(adsets)) insertAdSet = int64(len(adsets)) for _, adsetObj := range adsets{ // 注意這里的 adsetObj 其實是指針,需要用 *號 獲取到具體結構體! // First方法中要的就是指針,所以不用 &符號獲取指針了 err := db.Where("id=?",(*adsetObj).ID).First(adsetObj).Error if err != nil{
// 寫入數據庫前做一下字段的格式化或者某些字段的單獨處理 (*adsetObj).Format(accountId)
// 寫入數據庫 注意這里用的是 AdSetSub結構體 db.Create(adsetObj.AdSetSub) } } return } func getAccountAdsquads(f func(...string) string, accountID string, token string) []*AdSet { requestMethod := "GET" url := "https://adsapi.snapchat.com/v1/adaccounts/" + accountID + "/adsquads" ret := f(token, requestMethod, url) //fmt.Println("Adsquads 返回的字符串響應>>> ",ret) result := NewAdSetFromJsonString(ret) return result } // json 轉結構體 AdSet func NewAdSetFromJsonString(str string) []*AdSet{
// 注意 json解析成struct的時候 用這兩個結構體解析! var a []*AdSet var response AdSetSnapResponse err := json.NewDecoder(strings.NewReader(str)).Decode(&response) if err != nil{ fmt.Println("AdSet json轉結構體出錯!err>>> ",err) } // 遍歷結果,根據上面的結構將一個個結構體數據存入切片中 for _, AdsetSub := range response.Adsquads{ a = append(a, AdsetSub.Adsquad) } return a // 最終返回的是一個切片,里面的元素是json轉換為結構體對應的指針 }
主體函數中GORM的簡單使用
// 初始化db db, err := gorm.Open("mysql", "root:123@(localhost)/gorm1?charset=utf8mb4&parseTime=True&loc=Local") //db, err := gorm.Open("mysql", "aduser:adpm@(39.98.79.195:3309)/ads_server_db?charset=utf8") //fmt.Printf("db>>> %T, %v \n",db, db) if err != nil { fmt.Println("err>>> ", err) // panic panic(err) } // defer defer db.Close() // 1 GORM 獲取單獨的數據 // 從ownership表中獲取token及其他的數據 var ownerObj OwnerShip ownerRet := db.Where("name=?","snap_chat_test").Find(&ownerObj) if ownerRet.Error != nil{ panic(ownerRet.Error) } // 注意 結果放在了 ownerObj 這個結構體里面了! fmt.Println("refresh_token>>> ",ownerObj.RefreshToken)
// 2 GORM 獲取多條數據 // 獲取所有賬號的ID var accountsList []AdAccountSub accRet := db.Find(&accountsList) if accRet.Error != nil{ panic(accRet.Error) } // 注意結果都放在了 accountsList這個切片里面了! fmt.Println("accountsList>>> ",accountsList) 。。。。。。
時間字符串轉時間戳的方法

// 時間字符串轉時間戳 func Timestr2Timestamp(str string) (int64, error) { return Timestr2TimestampBasic(str, "", nil) } func Timestr2TimestampBasic(str string, format string, loc *time.Location) (int64, error) { t, err := Timestr2TimeBasic(str, format, loc) if err != nil { return -1., err } return (int64(t.UnixNano()) * 1) / 1e6, nil // 時間為毫秒級別,這里是1e6 } func Timestr2TimeBasic(value string, resultFormat string, resultLoc *time.Location) (time.Time, error) { /** - params value: 轉換內容字符串 resultFormat: 結果時間格式 resultLoc: 結果時區 */ resultLoc = getLocationDefault(resultLoc) useFormat := []string{ // 可能的轉換格式 BINano, BIMicro, BIMil, BISec, BICST, BIUTC, BIDate, BITime, time.RFC3339, time.RFC3339Nano, } var t time.Time for _, usef := range useFormat { tt, error := time.ParseInLocation(usef, value, resultLoc) t = tt if error != nil { continue } break } if t == getTimeDefault(resultLoc) { return t, errors.New("時間字符串格式錯誤") } if resultFormat == "" { // 時間為毫秒級別 resultFormat = "2006-01-02 15:04:05.000" } st := t.Format(resultFormat) fixedt, _ := time.ParseInLocation(resultFormat, st, resultLoc) return fixedt, nil } // 獲取time默認值, 造一個錯誤 func getTimeDefault(loc *time.Location) time.Time { loc = getLocationDefault(loc) t, _ := time.ParseInLocation("2006-01-02 15:04:05", "", loc) return t } func getLocationDefault(loc *time.Location) *time.Location { if loc == nil { loc, _ = time.LoadLocation("Local") } return loc }
~~~