• 利用递归的方式获取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
    }

    ~~~

  • 相关阅读:
    Java 日期字符串与日期类型转换
    Android 开发笔记“关闭默认键盘”
    MySql 日期转字符串
    Android 开发笔记“调用.net webservice遇到的问题”
    远程连接MySQL 不允许
    未能启用约束。一行或多行中包含违反非空、唯一或外键约束的值。
    Android 开发笔记“浅谈DDMS视图”
    Android 开发笔记“Eclipse 调试和快捷键”
    Android 开发笔记“程序安装包APK的制作”
    第四周进度条
  • 原文地址:https://www.cnblogs.com/paulwhw/p/13941520.html
Copyright © 2020-2023  润新知