Skip to content

🎓 查询单个字段:用 GetField 只取 country_code

当你只需要一个字段(比如国家代码)时,不必拉取完整的 JSON 响应。本教程带你用 GetField 精准取值,并与完整查询做对比。

🎯 你将学到

  • 📥 用 GetField 查询指定 IP 的单个字段
  • 🌍 只取 country_code 这一字段
  • ⚖️ 对比 GetFieldGetIPInfo 完整查询的差异
  • 🧹 处理字段返回值中的尾随换行符
  • 🛡️ 用 errors.Is 判断字段无效等错误

📋 前置条件

  • ✅ 已安装 Go 1.21+(本教程基于 Go 1.23)
  • ✅ 已完成 快速入门安装指南,能跑通第一个查询
  • ✅ 了解 Clientcontext 的基本用法(参见 客户端概念Context
  • 💡 可选:拥有一个 ipapi.co API Key,用于提升速率限制额度(免费层亦可运行本教程示例)

🎨 一图抵千言 — 单字段查询的决策与对比流程

本教程围绕「只要一个字段时该用 GetField 还是 GetIPInfo」展开,下图给出完整决策路径:

🚀 步骤 1:初始化项目与客户端

先建一个可运行的项目,并引入 SDK。

bash
mkdir single-field-demo && cd single-field-demo
go mod init single-field-demo
go get github.com/cyberspacesec/ipapi.co-skills/pkg/ipapi

接着写一个最小程序,创建客户端并打印一行确认信息:

go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/cyberspacesec/ipapi.co-skills/pkg/ipapi"
)

func main() {
	// 创建客户端;如有 API Key 可传入 ipapi.WithAPIKey("xxx")
	client := ipapi.NewClient()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	_ = ctx
	fmt.Println("✅ 客户端就绪,客户端指针:", client != nil)
	if client == nil {
		log.Fatal("客户端初始化失败")
	}
}

运行:

bash
go run main.go

预期输出:

✅ 客户端就绪,客户端指针: true

🌍 步骤 2:用 GetField 只取 country_code

GetField(ctx, ip, field) 对应 GET https://ipapi.co/{ip}/{field}/,只返回单个字段的原始字符串,不解析为结构体,省带宽、省内存

下面查询 8.8.8.8country_code

go
package main

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/cyberspacesec/ipapi.co-skills/pkg/ipapi"
)

func main() {
	client := ipapi.NewClient()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 只取 country_code 这一个字段
	code, err := client.GetField(ctx, "8.8.8.8", "country_code")
	if err != nil {
		log.Fatalf("查询 country_code 失败: %v", err)
	}

	// API 返回的原始字符串通常带一个尾随换行符,按需裁剪
	code = strings.TrimSpace(code)

	fmt.Printf("8.8.8.8 的国家代码: %s\n", code)
}

运行:

bash
go run main.go

预期输出:

8.8.8.8 的国家代码: US

为什么返回的是 string?

GetField 返回的是 API 原始响应体(string),而非结构体字段。这样既能取字符串型字段(如 country_code),也能取数值型字段(如 latitude),调用方按需自行类型转换。

⚖️ 步骤 3:对比完整查询 GetIPInfo

作为对照,用 GetIPInfo(ctx, ip, "json") 拉取完整 JSON 并解析为 IPInfo 结构体,再从中读取 CountryCode

go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/cyberspacesec/ipapi.co-skills/pkg/ipapi"
)

func main() {
	client := ipapi.NewClient()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 完整查询:一次性返回所有字段
	info, err := client.GetIPInfo(ctx, "8.8.8.8", "json")
	if err != nil {
		log.Fatalf("完整查询失败: %v", err)
	}

	fmt.Printf("8.8.8.8 的国家代码: %s\n", info.CountryCode)
	fmt.Printf("顺便看到的其它信息 -> 城市: %s, ASN: %s\n", info.City, info.ASN)
}

运行:

bash
go run main.go

预期输出:

8.8.8.8 的国家代码: US
顺便看到的其它信息 -> 城市: Mountain View, ASN: AS15169

📊 步骤 4:两种方式放在一起对比

把两种查询写进同一个程序,直观对比响应体大小与结果一致性:

go
package main

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/cyberspacesec/ipapi.co-skills/pkg/ipapi"
)

func main() {
	client := ipapi.NewClient()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 方式 A:只取单字段
	single, err := client.GetField(ctx, "8.8.8.8", "country_code")
	if err != nil {
		log.Fatalf("GetField 失败: %v", err)
	}
	single = strings.TrimSpace(single)

	// 方式 B:完整查询后取字段
	full, err := client.GetIPInfo(ctx, "8.8.8.8", "json")
	if err != nil {
		log.Fatalf("GetIPInfo 失败: %v", err)
	}

	fmt.Println("=== 对比结果 ===")
	fmt.Printf("GetField  -> country_code = %q\n", single)
	fmt.Printf("GetIPInfo -> country_code = %q\n", full.CountryCode)
	fmt.Println("两者是否一致:", single == full.CountryCode)
}

运行:

bash
go run main.go

预期输出:

=== 对比结果 ===
GetField  -> country_code = "US"
GetIPInfo -> country_code = "US"
两者是否一致: true

选型建议

  • 只需要 1 个字段 且不在乎其它信息 → 用 GetField,响应体仅几个字节,最快最省。
  • 需要 多个字段 或要做关联分析(如“城市 + ASN”一起判)→ 用 GetIPInfo 一次拉全,比多次调 GetField 更划算。
  • 想要 非 JSON 格式(csv/yaml/xml)的原始字节 → 用 GetIPInfoRaw
⚖️ GetField vs GetIPInfo 全维度对比
维度GetFieldGetIPInfo
端点GET /{ip}/{field}/GET /{ip}/json/
返回类型原始 string*IPInfo 结构体
响应体大小几字节~几十字节数百字节~1KB
是否解析❌ 不解析,原样返回✅ JSON 反序列化
字段数1 个28 个
尾随换行有,需 TrimSpace无,结构体已解析
错误校验ErrInvalidField 白名单ErrInvalidIP 格式校验
典型场景只取 country_code / ip关联分析多字段

带宽敏感场景优先 GetField;需要上下文关联优先 GetIPInfo

🛡️ 步骤 5:处理无效字段与错误

GetField 会对字段名做白名单校验(见源码 api.go 中的 validFields)。传入非法字段会返回 ErrInvalidField。用 errors.Is 精准判别:

🎨 一图抵千言 — GetField 调用的状态机视角

把上图中的「决策分支」换成「状态流转」,能更直观地看到一次 GetField 调用从入参校验到拿到结果(或落到某类错误)经历哪些状态:

go
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	"github.com/cyberspacesec/ipapi.co-skills/pkg/ipapi"
)

func main() {
	client := ipapi.NewClient()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 故意传一个不存在的字段
	_, err := client.GetField(ctx, "8.8.8.8", "not_a_field")
	if err != nil {
		switch {
		case errors.Is(err, ipapi.ErrInvalidField):
			fmt.Println("⛔ 字段名非法,请查阅可用字段列表")
		case errors.Is(err, ipapi.ErrRateLimited):
			log.Fatalf("触发速率限制: %v", err)
		case errors.Is(err, ipapi.ErrInvalidIP):
			log.Fatalf("IP 地址非法: %v", err)
		default:
			log.Fatalf("其它错误: %v", err)
		}
		return
	}

	fmt.Println("不会执行到这里")
}

运行:

bash
go run main.go

预期输出:

⛔ 字段名非法,请查阅可用字段列表

合法字段清单

country_code 只是众多可用字段之一。完整列表见 validFields,文档化版本见 字段参考 - country_code字段总览

📋 常用合法字段速查(部分)
字段名含义返回示例
ipIP 地址8.8.8.8
city城市Mountain View
region州/省California
country国家代码US
country_name国家全名United States
country_codeISO alpha-2US
asn自治域号AS15169
org归属组织Google LLC
timezoneIANA 时区America/Los_Angeles
latitude纬度37.4056
longitude经度-122.0775
currency货币代码USD

注意:continent 不在白名单(合法的是 continent_code),传错会触发 ErrInvalidField

📦 完整代码

下面是整合后的可运行示例,包含 GetFieldGetIPInfo 对比与错误处理:

go
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/cyberspacesec/ipapi.co-skills/pkg/ipapi"
)

func main() {
	client := ipapi.NewClient()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	const ip = "8.8.8.8"

	// 1) GetField:只取 country_code
	single, err := client.GetField(ctx, ip, "country_code")
	if err != nil {
		if errors.Is(err, ipapi.ErrInvalidField) {
			log.Fatalf("字段名非法: %v", err)
		}
		log.Fatalf("GetField 失败: %v", err)
	}
	single = strings.TrimSpace(single)

	// 2) GetIPInfo:完整查询后取同名字段
	full, err := client.GetIPInfo(ctx, ip, "json")
	if err != nil {
		log.Fatalf("GetIPInfo 失败: %v", err)
	}

	// 3) 对比输出
	fmt.Println("=== 单字段 vs 完整查询 ===")
	fmt.Printf("GetField  -> country_code = %q\n", single)
	fmt.Printf("GetIPInfo -> country_code = %q\n", full.CountryCode)
	fmt.Printf("结果一致: %v\n", single == full.CountryCode)
	fmt.Printf("完整查询额外信息: 城市=%s, ASN=%s, 时区=%s\n",
		full.City, full.ASN, full.Timezone)

	// 4) 演示非法字段错误处理
	if _, err := client.GetField(ctx, ip, "not_a_field"); err != nil {
		fmt.Printf("非法字段错误演示: errors.Is(ErrInvalidField) = %v\n",
			errors.Is(err, ipapi.ErrInvalidField))
	}
}

🖥️ 运行结果

bash
go run main.go
=== 单字段 vs 完整查询 ===
GetField  -> country_code = "US"
GetIPInfo -> country_code = "US"
结果一致: true
完整查询额外信息: 城市=Mountain View, ASN=AS15169, 时区=America/Los_Angeles
非法字段错误演示: errors.Is(ErrInvalidField) = true

🧠 小结

  • 🎯 GetField(ctx, ip, field) 对应 GET /{ip}/{field}/只返回单个字段的原始字符串,最适合“只要一个值”的场景。
  • 🌍 取 country_code 时记得 strings.TrimSpace 去掉尾随换行。
  • ⚖️ 对比 GetIPInfo:单字段更省带宽、更快;完整查询一次拿全、便于关联分析。
  • 🛡️ 非法字段返回 ErrInvalidField,用 errors.Is 精准判别,配合 错误处理策略 更稳健。
  • 📚 GetField 的 API 细节见 GetField 方法参考;查本机 IP 的单字段用 GetClientField

➡️ 下一步

基于 MIT 许可证发布