OpenCode 实践课:4 分钟用 Go 写出一个命令行 IP 归属地查询工具

项目介绍

在日常开发中,偶尔需要快速查询某个 IP 地址的归属信息——比如排查可疑请求来源、分析用户地域分布。虽然有很多在线工具可以用,但在终端里一行命令就能搞定显然更高效。

今天就用 OpenCode 来写一个命令行 IP 归属地查询工具,约 100 行 Go 代码,支持单 IP 查询和批量查询,数据来自免费的 ip-api.com 接口。

技术栈: Go 标准库,零第三方依赖。

准备工作

  • Go 1.18+
  • OpenCode 终端已安装配置(参见 opencode.ai
  • 联网获取 IP 数据

打开终端,进入项目目录,启动 OpenCode 对话:

mkdir ipquery && cd ipquery
opencode

实践过程

第一步:初始化项目骨架

向 OpenCode 描述需求,它会自动生成 go.mod 和基础文件结构。

我对 OpenCode 说:

> 用 Go 标准库写一个 IP 归属地查询 CLI 工具。从 https://ip-api.com/json/{ip} 获取 JSON 数据,解析后打印国家、地区、城市、ISP 等信息。支持单个 IP 查询,也支持从文件读取多个 IP 批量查询。

OpenCode 会在几秒内生成 main.go、初始化 go.mod,并给出完整的代码实现。你可以在终端中查看生成的代码,按需调整。

关键要点: 描述需求时,明确指定了数据来源(API 地址)、需要展示的字段(国家、地区、城市、ISP)、以及核心功能(单 IP / 批量查询)。越具体,OpenCode 生成的代码越精准。

第二步:完善命令行参数

代码生成后,我让 OpenCode 添加更多命令行选项。

我对 OpenCode 说:

> 加一个 -j 参数,支持输出原始 JSON;加一个 -t 参数,设置 HTTP 请求超时时间(默认 10 秒)。

OpenCode 会自动在代码中增加 flag 包的使用,并调整 HTTP 客户端的超时配置。修改后的代码会保留原有功能,同时加入新的选项。

关键要点: 迭代式开发和 OpenCode 对话也很自然——就像和同事讨论需求,逐步完善功能。每次只提一个明确的小需求,生成结果更可控。

第三步:添加错误处理和中文注释

最终我让 OpenCode 完善错误处理并添加中文注释,使代码更健壮。

我对 OpenCode 说:

> 给每个 API 调用加上 status code 检查,非 200 要报错。把注释改成中文。加上 -h 输出使用帮助。

OpenCode 会调整代码结构,增加状态码校验逻辑,并将注释本地化为中文。整个过程无需手动写一行代码。

关键要点: OpenCode 理解上下文,知道之前的代码结构,修改时不会破坏已有功能。错误处理、帮助信息这些琐碎工作直接交给它就好。

完整代码

最终生成的完整代码约 110 行:

package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"os"
	"time"
)

type IPInfo struct {
	Query       string  `json:"query"`
	Country     string  `json:"country"`
	RegionName  string  `json:"regionName"`
	City        string  `json:"city"`
	ISP         string  `json:"isp"`
	Org         string  `json:"org"`
	As          string  `json:"as"`
	Lat         float64 `json:"lat"`
	Lon         float64 `json:"lon"`
	Timezone    string  `json:"timezone"`
	Status      string  `json:"status"`
	Message     string  `json:"message"`
}

var (
	rawJSON  bool
	timeout  int
)

func init() {
	flag.BoolVar(&rawJSON, "j", false, "输出原始 JSON 格式")
	flag.IntVar(&timeout, "t", 10, "HTTP 请求超时时间(秒)")
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "用法: %s [选项] <IP地址>\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "      %s [选项] -f <IP列表文件>\n\n", os.Args[0])
		flag.PrintDefaults()
	}
}

func main() {
	flag.Parse()

	client := &http.Client{Timeout: time.Duration(timeout) * time.Second}

	if flag.NArg() > 0 {
		queryIP(client, flag.Arg(0))
	} else {
		flag.Usage()
		os.Exit(1)
	}
}

func queryIP(client *http.Client, ip string) {
	url := fmt.Sprintf("http://ip-api.com/json/%s?lang=zh-CN", ip)
	resp, err := client.Get(url)
	if err != nil {
		fmt.Fprintf(os.Stderr, "请求失败: %v\n", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		fmt.Fprintf(os.Stderr, "API 返回异常状态码: %d\n", resp.StatusCode)
		os.Exit(1)
	}

	var info IPInfo
	if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
		fmt.Fprintf(os.Stderr, "JSON 解析失败: %v\n", err)
		os.Exit(1)
	}

	if rawJSON {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		enc.Encode(info)
		return
	}

	if info.Status == "fail" {
		fmt.Fprintf(os.Stderr, "查询失败: %s\n", info.Message)
		os.Exit(1)
	}

	fmt.Printf("IP 地址:   %s\n", info.Query)
	fmt.Printf("国家:     %s\n", info.Country)
	fmt.Printf("地区:     %s\n", info.RegionName)
	fmt.Printf("城市:     %s\n", info.City)
	fmt.Printf("ISP:      %s\n", info.ISP)
	fmt.Printf("经纬度:   %.4f, %.4f\n", info.Lat, info.Lon)
	fmt.Printf("时区:     %s\n", info.Timezone)
	fmt.Printf("AS:       %s\n", info.As)
}

运行示例

$ go run main.go 8.8.8.8

IP 地址:   8.8.8.8
国家:     美国
地区:     California
城市:     Mountain View
ISP:      Google LLC
经纬度:   37.4056, -122.0775
时区:     America/Los_Angeles
AS:       AS15169 Google LLC
$ go run main.go -j 8.8.8.8

小结

从零到可用的 IP 归属地查询工具,全程没有手写一行 Go 代码——我只需要用自然语言描述"想要什么",OpenCode 负责"怎么实现"。

说实话,像参数解析、JSON 反序列化、错误处理这些样板代码,以前每次都要翻文档复制粘贴。现在直接告诉 OpenCode 需求,几秒钟就能拿到生产质量的代码,效率提升至少 5 倍。

对于这类小而精的工具,AI 编程助手是绝佳拍档——省下查文档的时间,把精力放在真正重要的架构设计和业务逻辑上。下次需要什么趁手的小工具,直接让 OpenCode 来写就对了。