Golang实现分页GET请求的方法与技巧

Golang实现分页GET请求的方法与技巧 我在使用 http.Get() 处理分页 API 请求时遇到了困难。

Go 似乎发生了恐慌,但我无法确定原因。 “panic: runtime error: invalid memory address or nil pointer dereference [signal 0xc0000005 code=0x0 addr=0x40 pc=0x64cdc3]”

我知道第一个 GET 请求是成功发出的。问题似乎出在循环过程中。

有没有更简单的方法来实现这个功能?

Orders := OrdersResponse{}
var NextLink string
OrdersCall, _ := http.Get("https://" + APIKey + ":" + APIPass + "@example.myshopify.com/admin/api/2020-04/orders.json?attribution_app_id=123456&limit=250")

Paginate := 1
for Paginate == 1 {
	// If we got a NextLink call it.
	if strings.Contains(OrdersCall.Header["Link"][0], "next") {
		OrdersCall, _ = http.Get(NextLink)
	}
	OrdResio, _ := ioutil.ReadAll(OrdersCall.Body)
	OrdersCall.Body.Close()
	OrdJSON := string(OrdResio)
	OrdersResp := OrdersResponse{}
	json.Unmarshal([]byte(OrdJSON), &OrdersResp)

	Orders.Orders = append(Orders.Orders, OrdersResp.Orders...)

	if strings.Contains(OrdersCall.Header["Link"][0], "next") {
		i := strings.Index(OrdersCall.Header["Link"][0], ">")
		NextLink = OrdersCall.Header["Link"][0][1:i]
	} else {
        Paginate = 0
        break
	}

}

fmt.Println(len(Orders.Orders))

更多关于Golang实现分页GET请求的方法与技巧的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

事实证明,第二次调用时身份验证失败了。这可以归咎于我这边糟糕的错误处理。

更多关于Golang实现分页GET请求的方法与技巧的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


实际上这非常有帮助。我认为 OrdersCall 使用的是从第一个 API 响应返回的任何结构体,但第二个响应不同,因为它包含了额外的头部信息。我猜测第一个和最后一个响应分别没有“上一页”或“下一页”的链接。

我该如何找出 OrdersCall 初始化时 HTTP 客户端正在使用的结构体?或者有没有更简单的方法来处理这个问题?

@Sivan,仅凭这些信息无法确定问题所在。你能提供完整的堆栈跟踪吗?根据那个 panic 中的 addr 值,看起来代码在某个地方访问了一个结构体中的字段,而该字段是 nil 的。要缩小范围,我们需要获取更多上下文信息。

你的代码存在几个关键问题导致了空指针解引用。主要问题在于没有正确处理HTTP响应和错误处理。以下是修复后的实现:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "strings"
    "time"
)

type OrdersResponse struct {
    Orders []Order `json:"orders"`
}

type Order struct {
    ID    int64  `json:"id"`
    Name  string `json:"name"`
    // 根据实际API响应添加其他字段
}

func fetchPaginatedOrders(apiKey, apiPass string) ([]Order, error) {
    client := &http.Client{
        Timeout: 30 * time.Second,
    }
    
    baseURL := fmt.Sprintf("https://%s:%s@example.myshopify.com/admin/api/2020-04/orders.json", 
        apiKey, apiPass)
    
    var allOrders []Order
    nextLink := baseURL + "?attribution_app_id=123456&limit=250"
    
    for nextLink != "" {
        // 发送请求
        req, err := http.NewRequest("GET", nextLink, nil)
        if err != nil {
            return nil, fmt.Errorf("创建请求失败: %w", err)
        }
        
        resp, err := client.Do(req)
        if err != nil {
            return nil, fmt.Errorf("请求失败: %w", err)
        }
        
        // 检查响应状态
        if resp.StatusCode != http.StatusOK {
            resp.Body.Close()
            return nil, fmt.Errorf("API返回错误状态: %s", resp.Status)
        }
        
        // 读取响应体
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            resp.Body.Close()
            return nil, fmt.Errorf("读取响应失败: %w", err)
        }
        resp.Body.Close()
        
        // 解析JSON
        var pageResp OrdersResponse
        if err := json.Unmarshal(body, &pageResp); err != nil {
            return nil, fmt.Errorf("解析JSON失败: %w", err)
        }
        
        // 添加订单到结果集
        allOrders = append(allOrders, pageResp.Orders...)
        
        // 检查是否有下一页
        nextLink = extractNextLink(resp.Header)
    }
    
    return allOrders, nil
}

func extractNextLink(headers http.Header) string {
    linkHeader := headers.Get("Link")
    if linkHeader == "" {
        return ""
    }
    
    // Shopify API的Link头格式: <https://...>; rel="next", <https://...>; rel="previous"
    links := strings.Split(linkHeader, ",")
    for _, link := range links {
        parts := strings.Split(strings.TrimSpace(link), ";")
        if len(parts) >= 2 && strings.Contains(parts[1], `rel="next"`) {
            nextURL := strings.Trim(parts[0], " <>")
            return nextURL
        }
    }
    
    return ""
}

func main() {
    apiKey := "your_api_key"
    apiPass := "your_api_password"
    
    orders, err := fetchPaginatedOrders(apiKey, apiPass)
    if err != nil {
        fmt.Printf("获取订单失败: %v\n", err)
        return
    }
    
    fmt.Printf("总共获取了 %d 个订单\n", len(orders))
}

更简洁的版本使用net/http的自动重定向处理:

func fetchOrdersSimple(apiKey, apiPass string) ([]Order, error) {
    client := &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            // 保持认证头信息
            req.SetBasicAuth(apiKey, apiPass)
            return nil
        },
    }
    
    url := fmt.Sprintf("https://example.myshopify.com/admin/api/2020-04/orders.json?attribution_app_id=123456&limit=250")
    
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }
    req.SetBasicAuth(apiKey, apiPass)
    
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var result OrdersResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }
    
    return result.Orders, nil
}

关键修复点:

  1. 使用http.Client替代http.Get()以便更好地控制请求
  2. 添加完整的错误处理
  3. 正确关闭响应体(使用defer
  4. 修复Link头的解析逻辑
  5. 使用json.NewDecoder直接解析响应体,避免额外的字符串转换
回到顶部