利用递归的方式获取restful风格有nextUrl接口返回的数据

概述

  最近做业务获取到的API中的数据格式如下:

{
  "data": [
    {
      "account_id": "xxx",
      "campaign_id": "xxxxxxx",
      "id": "x32xx284026xxx9"
    },
    {
      "account_id": "xxx",
      "campaign_id": "xxxxxxxxx",
      "id": "2xx713x22xx"
    },
    ...
    ],
  "paging": {
    "cursors": {
      "before": "xxxerrr",
      "after": "sdfsdf"
    },
    "next": "https://graph.facebook.com/v7.0/xx/adsets?access_token=xxx&pretty=0&fields=xx%2Ccampaign_id&limit=4&after=sdfsdf"
  }
}

  我们可以看到:这种格式返回的数据我们只知道下一页应该请求的URL,但是并不知道数据的总量!

  这跟我们在以往处理数据库中数据的思路是不一样的。在数据库中处理的数据我们大概知道数据的量级以便做预处理。但是由于这些数据我们是请求三方API获取的,事先我们是不知道数据的量级的,只有不断去请求指定的next对应的URL获取新的数据去处理。

解决思路

  我的解决方案是:由于我们并不确定数据的量级,在不知到数据总量的情况下我们可以使用递归的方式,判断请求到的数据只要有next并且next对应的数据不为空,那么就继续请求接口,直到没有next为止,然后“从里往外”将数据塞到事先准备好的容器类型即可。

  这里要特别注意的是:递归函数的结束条件是没有next数据!

Go代码

用于解析数据的结构体如下

// 通用的分页
type Paging struct {
    //Cursors interface{} `json:"cursors"`
    Next string `json:"next"`
}

type CampaignFB struct {
    Id          string  `gorm:"column:id;size:255;primaryKey; comment:'CampaignID'"`
    AccountId   string  `json:"account_id" gorm:"column:account_id;size:255; not null; comment:'所属广告账户Id'"`
    Name        string  `gorm:"column:name; size:255; comment:'Campaign名字'"`
    
    
}
// json解析为struct的时候用到
type CampaignFBResponse struct {
    Data   []*CampaignFB `json:"data"`
    Paging *Paging       `json:"paging"`
}

递归的代码V1版

// 获取某个账户下的所有Ad 递归获取下一页数据
func GetAccountAds(f func(requestMethod, url string) string, requestMethod, url string, a *[]*model.AdFB){
    strRet := f(requestMethod, url)
    //fmt.Println("strRet>>> ",strRet)
    var response model.AdFBResponse
    err := json.NewDecoder(strings.NewReader(strRet)).Decode(&response)
    if err != nil{
        fmt.Println("AdFB json转结构体出错>>> ",err)
    }else{
        //fmt.Println("response>>> ",response)
        paging := response.Paging
        data := response.Data
        for _, adObj := range data{
            *a = append(*a, adObj)
        }
        // 如果有下一页则递归获取数据 递归结束的条件
        if paging != nil && paging.Next != ""{
            nextStrUrl := (*paging).Next
            nextStrUrlNew := strings.Replace(nextStrUrl, "https://graph.facebook.com/v8.0/", BaseURL, 1)
            // 递归获取下一页数据
            GetAccountAds(f, requestMethod,nextStrUrlNew,a)
        }
    }
}

相关完整代码

// 获取账户下的campaign数据 ———— 调用的地方
func handleAccountCampaigns(token, accountId string, app *app.App) {
    requestMethod := "GET"
    url := BaseURL + accountId + "/campaigns?fields=account_id,id,name,objective,status,created_time,updated_time,daily_budget,lifetime_budget&access_token=" + token
    camLst := []*model.CampaignFB{}
    // TODO 直接传入ret的地址!可以直接在ret中操作,将账户的Campaign数据写入camLst中!!!
    GetAccountCampaigns(FBBaseRequestString, requestMethod, url, &camLst)
    // fmt.Println("len_ret>>> ", len(camLst))
    // 存入数据库
    for _, camObj := range camLst {
        // Format一下
        camObj.Format()
        // 直接用Save方法 实现 insert或update
        err := app.SrvStore.Campaign().FBSave(camObj)
        if err != nil {
            mlog.Error("创建Campaign失败:" + (*camObj).Id)
        }
    }
}
// 获取某个账户下的所有AdSet 递归获取下一页数据
func GetAccountCampaigns(f func(requestMethod, url string) string, requestMethod, url string, c *[]*model.CampaignFB) {
    strRet := f(requestMethod, url)
    //fmt.Println("strRet>>> ", strRet)
    // json转结构体 Campaign
    var response model.CampaignFBResponse
    err := json.NewDecoder(strings.NewReader(strRet)).Decode(&response)
    if err != nil {
        fmt.Println("CampaignFB json转结构体出错>>> ", err)
    } else {
        //fmt.Println("response>>> ",response)
        paging := response.Paging
        data := response.Data
        for _, camObj := range data {
            // 往成员变量中写入数据
            *c = append(*c, camObj)
        }
        // 有下一页就递归获取 注意这里要加双重判断!!!
        if paging != nil && paging.Next != "" {
            nextStrUrl := (*paging).Next
            nextStrUrlNew := strings.Replace(nextStrUrl, "https://graph.facebook.com/v8.0/", BaseURL, 1)
            // 递归获取下一页数据
            GetAccountCampaigns(f, requestMethod, nextStrUrlNew, c)
        }
    }
}

// FB base
// 发HTTP请求并解析数据
func FBBaseRequestString(requestMethod, url string) string {
    client := &http.Client{}
    req, err := http.NewRequest(requestMethod, url, nil)
    if err != nil {
        fmt.Println("err1 >>>>> ", err)
    }
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("err2 >>>>>", err)
    }
    returnStr, err := parseResponseString(resp)
    //fmt.Println("returnStr>>> " + returnStr) // HTTP字节流转为str的结果
    return returnStr
}

// 解析http请求返回的结果 ———— 转换为string
func parseResponseString(response *http.Response) (string, error) {
    body, err := ioutil.ReadAll(response.Body)
    return string(body), err // 将io数据流转换为string
}
View Code

简单说明

  这里需要注意函数的最后一个参数:一定要是一个指针类型(引用)!这个函数并不返回任何数值,将外部事先定义好存储对应结构的切片后,将这个切片的地址传入即可:

// 事先定义好接收的切片
camLst := []*model.AdFB{}
// 将camLst的地址传入,函数执行后将数据会塞进camLst中
GetAccountAds(FBBaseRequestString, requestMethod, url, &camLst)

递归V2版 ***

  上面的写法还是有点问题,在实际中自己优化了一下写法:

// 获取某个账户下的所有Ad 递归获取下一页数据
func GetAccountAds(f func(requestMethod, url string) string, requestMethod, url string) ([]*model.AdFB, *model.Apperror){
    strRet := f(requestMethod, url)
    //fmt.Println("strRet>>> ",strRet)
    var a []*model.AdFB
    var response model.AdFBResponse
    err := json.NewDecoder(strings.NewReader(strRet)).Decode(&response)
    if err != nil{
        fmt.Println("AdFB json转结构体出错>>> ",err)
        return nil, model.NewApperror("xxx")
    }
    // 先将数据保存一下再分页
    //fmt.Println("response>>> ",response)
    data := response.Data
    for _, adObj := range data{
        a = append(a, adObj)
    }
    paging := response.Paging
    // 如果有下一页则递归获取数据 递归结束的条件
    if paging != nil && paging.Next != ""{
        nextStrUrl := (*paging).Next
        nextStrUrlNew := strings.Replace(nextStrUrl, "https://graph.facebook.com/v8.0/", BaseURL, 1)
        // 递归获取下一页数据并返回结果
        if nextData, err := GetAccountAds(f, requestMethod,nextStrUrlNew); err != nil{
            return a, nil
        }else{
            // 注意这里的写法
            return append(a,nextData...),nil
        }
    }
    // 没有分页数据直接返回即可    
    return a, nil
}

~~~

posted on 2020-11-07 16:54  江湖乄夜雨  阅读(321)  评论(0编辑  收藏  举报