OpenCode 实践课:4 分钟用 Go 写出一个命令行网站健康监测工具

项目介绍

你是否曾经需要快速检查一批网站是否正常运行?今天我们用 OpenCode 写一个命令行网站健康监测工具——输入一组 URL,一键获取每个站点的 HTTP 状态码和响应时间,支持并发检测和文件批量导入。

  • 技术栈:Go 标准库(net/httpsynctimebufio
  • 核心功能:并发 HTTP 请求、超时控制、文件读取、结果汇总报告
  • 代码量:约 90 行

准备工作

  • Go 1.18+
  • OpenCode 终端 AI 编程助手

安装 OpenCode 后,在终端执行 opencode 即可进入交互式编程环境。

实践过程

步骤 1:初始化项目

打开 OpenCode,用自然语言让它创建项目骨架:

> 我对 OpenCode 说:
>
> 帮我创建一个 Go 项目,项目名叫 urlchecker,初始化 go mod

OpenCode 随即执行:

mkdir urlchecker && cd urlchecker && go mod init urlchecker

并在工作区打开了 main.go 等待下一步指令。无需手动敲命令,全程一句话搞定。

步骤 2:实现核心检测逻辑

> 我对 OpenCode 说:
>
> 在 main.go 里实现一个函数 checkURL,接收一个 URL 字符串和超时时长,使用 http.Client 发送 GET 请求,返回一个结构体包含 URL、状态码、耗时和错误信息

OpenCode 分析需求后,生成了带结构体定义和 HTTP 客户端的核心代码:

type Result struct {
	URL        string
	StatusCode int
	Duration   time.Duration
	Error      error
}

func checkURL(url string, timeout time.Duration) Result {
	client := &http.Client{Timeout: timeout}
	start := time.Now()
	resp, err := client.Get(url)
	duration := time.Since(start)
	if err != nil {
		return Result{URL: url, Error: err, Duration: duration}
	}
	defer resp.Body.Close()
	return Result{URL: url, StatusCode: resp.StatusCode, Duration: duration}
}

不需要自己查文档——OpenCode 自动补全了 http.Client 的超时设置、defer resp.Body.Close() 等最佳实践。

步骤 3:添加并发支持和文件读取

单线程检测太慢,我们告诉 OpenCode 加上并发和文件导入:

> 我对 OpenCode 说:
>
> 在 main 函数里实现以下功能:
> 1. 支持从命令行参数直接传入 URL 列表
> 2. 支持 -f 参数指定一个文本文件,逐行读取 URL
> 3. 用 goroutine + WaitGroup + channel 实现并发检测所有 URL
> 4. 打印汇总报告,显示每个 URL 的状态码、耗时,最后统计正常/异常数量

OpenCode 一次性生成了 main() 函数和 readURLsFromFile() 辅助函数,并发逻辑用 sync.WaitGroupchan Result 配合实现,结构清晰:

var wg sync.WaitGroup
results := make(chan Result, len(urls))

for _, url := range urls {
    wg.Add(1)
    go func(u string) {
        defer wg.Done()
        results <- checkURL(u, 10*time.Second)
    }(url)
}

go func() {
    wg.Wait()
    close(results)
}()

这大概是我最喜欢的 OpenCode 使用方式——你描述业务需求,它处理并发细节。

步骤 4:测试运行

> 我对 OpenCode 说:
>
> 编译运行,用几个真实网站测试一下

$ go build -o urlchecker.exe . && ./urlchecker.exe www.baidu.com github.com google.com

========== 网站健康监测报告 ==========
检查时间: 2026-08-07 10:30:00
URL 数量: 3
======================================
✓  https://www.baidu.com                          200  156ms
✓  https://github.com                             200  312ms
❌ https://google.com                              错误: context deadline exceeded
======================================
总计: 3  |  正常: 2  |  异常: 1

工具完美运行。整个过程从零到可用的命令行工具,只花了 4 分钟。

完整代码

package main

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
)

type Result struct {
	URL        string
	StatusCode int
	Duration   time.Duration
	Error      error
}

func checkURL(url string, timeout time.Duration) Result {
	client := &http.Client{Timeout: timeout}
	start := time.Now()
	resp, err := client.Get(url)
	duration := time.Since(start)
	if err != nil {
		return Result{URL: url, Error: err, Duration: duration}
	}
	defer resp.Body.Close()
	return Result{URL: url, StatusCode: resp.StatusCode, Duration: duration}
}

func readURLsFromFile(filename string) ([]string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var urls []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" && !strings.HasPrefix(line, "#") {
			if !strings.HasPrefix(line, "http") {
				line = "https://" + line
			}
			urls = append(urls, line)
		}
	}
	return urls, scanner.Err()
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("网站健康监测工具")
		fmt.Println("用法: urlchecker <url> [url2 url3 ...]")
		fmt.Println("      urlchecker -f urls.txt")
		os.Exit(1)
	}

	var urls []string
	if os.Args[1] == "-f" && len(os.Args) > 2 {
		var err error
		urls, err = readURLsFromFile(os.Args[2])
		if err != nil {
			fmt.Printf("读取文件失败: %v\n", err)
			os.Exit(1)
		}
	} else {
		urls = os.Args[1:]
		for i, u := range urls {
			if !strings.HasPrefix(u, "http") {
				urls[i] = "https://" + u
			}
		}
	}

	fmt.Println("========== 网站健康监测报告 ==========")
	fmt.Printf("检查时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
	fmt.Printf("URL 数量: %d\n", len(urls))
	fmt.Println("======================================")

	var wg sync.WaitGroup
	results := make(chan Result, len(urls))

	for _, url := range urls {
		wg.Add(1)
		go func(u string) {
			defer wg.Done()
			results <- checkURL(u, 10*time.Second)
		}(url)
	}

	go func() {
		wg.Wait()
		close(results)
	}()

	okCount, failCount := 0, 0
	for r := range results {
		if r.Error != nil {
			failCount++
			fmt.Printf("❌ %-50s 错误: %v\n", r.URL, r.Error)
		} else {
			okCount++
			status := "✓"
			if r.StatusCode >= 400 {
				status = "⚠"
			}
			fmt.Printf("%s %-50s %d  %v\n", status, r.URL, r.StatusCode, r.Duration.Round(time.Millisecond))
		}
	}

	fmt.Println("======================================")
	fmt.Printf("总计: %d  |  正常: %d  |  异常: %d\n", len(urls), okCount, failCount)
}

小结

用 OpenCode 开发这个工具,体会最深的是从想法到代码的转换速度。传统开发流程:构思 → 查文档 → 写代码 → 查文档 → 调试;而 OpenCode 的流程是:描述需求 → 审阅生成代码 → 运行验证。

4 分钟的时间里,你只需要做两件事:描述清楚需求确认代码正确性。至于 Go 的http.Client超时怎么设、WaitGroup怎么用、bufio.Scanner怎么读文件——这些细节 OpenCode 都能帮你处理,你专注在"想要什么"上就好。