如何正确使用 Go 调用 ip-api.com 获取 IP 地理位置信息

本文详解 go 程序调用 ip-api.com json api 时响应体为空、结构体解析失败的两大核心问题:未正确读取响应 body 及结构体字段未导出+缺少 json 标签映射,并提供完整可运行示例。

本文详解 go 程序调用 ip-api.com json api 时响应体为空、结构体解析失败的两大核心问题:未正确读取响应 body 及结构体字段未导出+缺少 json 标签映射,并提供完整可运行示例。

在 Go 中调用外部 HTTP API(如 https://www.php.cn/link/434f2774582010201d7784fa906e546a)时,看似简单的 http.Get 却常因两个隐蔽但关键的错误导致“响应状态 200 正常,但 body 为空、解析失败”。你遇到的问题正是典型代表:resp.Body 在 json.NewDecoder(resp.Body).Decode() 后已 EOF,再调用 ioutil.ReadAll(resp.Body) 必然返回空字节;同时,结构体字段全为小写(未导出),JSON 解码器无法赋值——二者叠加,最终得到一个零值结构体和空响应体输出。

✅ 正确做法:顺序 + 导出 + 标签

首先,必须先完整读取响应 Body,再解码。json.Decoder 会流式消费 io.Reader,一旦读完,resp.Body 就不可重用。因此不能在 Decode() 后再 ReadAll() —— 应该统一用 ioutil.ReadAll(Go 1.16+ 推荐 io.ReadAll)读取原始字节,再用 json.Unmarshal 解析

body, err := io.ReadAll(resp.Body)
if err != nil {
    return IpResponseSuccess{}, err
}
defer resp.Body.Close() // 注意:Close 仍需调用,即使已读完

err = json.Unmarshal(body, &ip_response)
if err != nil {
    return IpResponseSuccess{}, err
}

其次,结构体字段必须导出(首字母大写)且通过 json tag 显式映射。Go 的 encoding/json 只能序列化/反序列化导出字段(即首字母大写)。原代码中 as, city 等均为小写私有字段,解码器完全忽略它们,导致结构体始终为零值。

修正后的结构体如下(同时将 lat/lon 改为 float64,匹配 API 实际返回数值类型):

type IpResponseSuccess struct {
    As          string  `json:"as"`
    City        string  `json:"city"`
    Country     string  `json:"country"`
    CountryCode string  `json:"countryCode"`
    Isp         string  `json:"isp"`
    Lat         float64 `json:"lat"`
    Lon         float64 `json:"lon"`
    Org         string  `json:"org"`
    Query       string  `json:"query"`
    Region      string  `json:"region"`
    RegionName  string  `json:"regionName"`
    Status      string  `json:"status"`
    Timezone    string  `json:"timezone"`
    Zip         string  `json:"zip"`
}

⚠️ 注意:ioutil 已在 Go 1.16 中弃用,请使用 io/ioutil 的替代方案 io 包。完整示例基于 Go 1.19+:

package main

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

type IpResponseSuccess struct {
    As          string  `json:"as"`
    City        string  `json:"city"`
    Country     string  `json:"country"`
    CountryCode string  `json:"countryCode"`
    Isp         string  `json:"isp"`
    Lat         float64 `json:"lat"`
    Lon         float64 `json:"lon"`
    Org         string  `json:"org"`
    Query       string  `json:"query"`
    Region      string  `json:"region"`
    RegionName  string  `json:"regionName"`
    Status      string  `json:"status"`
    Timezone    string  `json:"timezone"`
    Zip         string  `json:"zip"`
}

func Query(url string) (IpResponseSuccess, error) {
    resp, err := http.Get(url)
    if err != nil {
        return IpResponseSuccess{}, err
    }
    defer resp.Body.Close() // 始终确保关闭

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return IpResponseSuccess{}, err
    }

    var ipResp IpResponseSuccess
    if err := json.Unmarshal(body, &ipResp); err != nil {
        return IpResponseSuccess{}, fmt.Errorf("failed to parse JSON: %w", err)
    }

    // 可选:调试输出原始响应
    // fmt.Printf("Raw response: %s\n", body)

    return ipResp, nil
}

func main() {
    ip, err := Query("http://ip-api.com/json")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Location: %s, %s — Lat: %.4f, Lon: %.4f\n", ip.City, ip.Country, ip.Lat, ip.Lon)
}

? 额外建议与注意事项

  • 不要依赖 Content-Length 判断 body 是否为空:HTTP/1.1 持久连接下,resp.Body 是惰性读取流,不读就不会触发传输。
  • 检查响应状态码:虽然本例是 200,但生产环境务必添加 if resp.StatusCode != http.StatusOK 校验。
  • 考虑超时与上下文:使用 http.Client 配置 Timeout 或 context.WithTimeout,避免请求无限挂起。
  • API 限频提醒:ip-api.com 免费版有请求频率限制(约 45 次/分钟),高频调用需加缓存或升级计划。
  • HTTPS 优先:官方推荐使用 https://www.php.cn/link/434f2774582010201d7784fa906e546a,更安全可靠。

遵循以上两点核心修正(读取顺序 + 结构体导出+tag),你的 Go 程序即可稳定、准确地获取 IP 地理位置数据。

文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/shoujipingce/125072.html

上一篇 2026-07-08 21:13
红米Note11T Pro忘记密码怎么办?锁屏解锁和清除数据说明
下一篇 2026-07-08 21:13

相关推荐