OpenCode 实践课:4 分钟用 Go 写出一个命令行 HTTP Mock 服务器

项目介绍

前端开发时,后端接口还没写好怎么办?今天我们用 OpenCode 在 4 分钟内做一个 HTTP Mock 服务器——通过 JSON 配置文件定义接口的路径、方法、状态码和返回数据,启动后就能为前端提供模拟 API。

技术栈:Go 语言标准库(net/httpencoding/json),零第三方依赖。

最终效果:写好一份 mock.json,一行命令 go run main.go,前端就能调起来了。

准备工作

  • 安装 Go 1.20+
  • 安装 OpenCode(参考 opencode.ai)
  • 准备 VS Code 或任意编辑器

打开 OpenCode,在项目目录下就可以开始对话开发了。

实践过程

第一步:创建项目骨架

我对 OpenCode 说:

> 帮我创建一个 Go 项目,目录名 mock-server,用 go mod init,main.go 先写个基础的 HTTP 服务框架,监听端口从命令行参数读取。

OpenCode 自动创建了项目目录,运行 go mod init mock-server,并生成了初始的 main.go

package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
)

func main() {
    port := flag.Int("p", 8080, "监听端口")
    flag.Parse()

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Mock Server Running")
    })

    addr := fmt.Sprintf(":%d", *port)
    log.Printf("Mock 服务启动: http://localhost%s", addr)
    log.Fatal(http.ListenAndServe(addr, nil))
}

要点:我只描述了想要什么功能,OpenCode 自动处理了 Go 项目初始化、模块管理、代码框架。不需要手敲 go mod init,不需要查阅 flag 包用法。

第二步:定义配置结构

我对 OpenCode 说:

> 我需要通过 JSON 文件配置 Mock 接口。在 main.go 里定义配置结构体,包含端口 port、接口列表 endpoints。每个 endpoint 要有路径 path、方法 method、状态码 status、响应体 body(支持任意 JSON)、可选 headers 和延迟 delay(毫秒)。再写一个加载 JSON 配置文件的函数。

OpenCode 在原有代码基础上进行了修改,关键代码:

type Endpoint struct {
    Path    string            `json:"path"`
    Method  string            `json:"method"`
    Status  int               `json:"status"`
    Body    json.RawMessage   `json:"body"`
    Headers map[string]string `json:"headers,omitempty"`
    Delay   int               `json:"delay,omitempty"`
}

type Config struct {
    Port      int        `json:"port"`
    Endpoints []Endpoint `json:"endpoints"`
}

OpenCode 还自动添加了 os.ReadFilejson.Unmarshal 来加载配置,并且把 -p 参数改成了 -c 指向配置文件,因为端口可以直接从 JSON 里读取。

要点:我只需要描述字段含义,OpenCode 就完成了结构体定义、JSON tag 标注、文件读取逻辑。json.RawMessage 这个类型也是它选的——保证响应体可以是任意 JSON 结构,不会被二次序列化。

第三步:实现核心 Mock 逻辑

我对 OpenCode 说:

> 实现 HTTP 路由注册:遍历 endpoints,用 http.HandleFunc 注册每个路径。请求进来时,先检查 HTTP 方法是否匹配,不匹配返回 405。匹配的话,如果有 delay 就先 Sleep,然后设置自定义 headers,返回对应的状态码和 body。默认 Content-Type 设为 application/json。

OpenCode 生成了完整的请求处理逻辑:

for _, ep := range cfg.Endpoints {
    e := ep
    mux.HandleFunc(e.Path, func(w http.ResponseWriter, r *http.Request) {
        if e.Method != "" && r.Method != e.Method {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusMethodNotAllowed)
            w.Write([]byte(`{"error":"method not allowed"}`))
            return
        }
        if e.Delay > 0 {
            time.Sleep(time.Duration(e.Delay) * time.Millisecond)
        }
        for k, v := range e.Headers {
            w.Header().Set(k, v)
        }
        if _, ok := e.Headers["Content-Type"]; !ok {
            w.Header().Set("Content-Type", "application/json")
        }
        w.WriteHeader(e.Status)
        w.Write(e.Body)
        log.Printf("[%s] %s → %d (%dms)", r.Method, r.URL.Path, e.Status, e.Delay)
    })
}

要点:我描述了业务逻辑(方法校验、延迟、响应),OpenCode 把 Go 的 HTTP 细节处理得很好——比如 e := ep 避免闭包变量捕获问题,WriteHeader 必须在 Write 之前调用等。这些细节如果不熟悉 Go HTTP 的话很容易踩坑。

第四步:完善体验

我对 OpenCode 说:

> 再加几个功能:启动时打印所有已注册的接口列表(格式化输出)、未匹配路径返回统一 404 JSON、支持 Ctrl+C 优雅退出。

OpenCode 快速补上了这些功能:

  • 启动时遍历 endpoints 打印表格
  • 注册了 mux.HandleFunc("/", ...) 作为 fallback 404
  • signal.Notify 监听 SIGINT/SIGTERM 实现优雅退出

启动效果:

已注册接口:
  GET    /api/users           → 200
  POST   /api/users           → 201
  GET    /api/health          → 200

2026/08/05 Mock 服务已启动 → http://localhost:8080

要点:这三项功能我一句话说完,OpenCode 自动补充了 os/signal 导入、信号通道创建等样板代码。这就是 AI 编程助手的价值——把琐碎的实现细节交给它,我们专注于"想要什么"。

完整代码

一个 mock.json 配置文件:

{
  "port": 8080,
  "endpoints": [
    {
      "path": "/api/users",
      "method": "GET",
      "status": 200,
      "body": [{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}],
      "delay": 200
    },
    {
      "path": "/api/users",
      "method": "POST",
      "status": 201,
      "body": {"id":3,"name":"Charlie"}
    },
    {
      "path": "/api/health",
      "method": "GET",
      "status": 200,
      "body": {"status":"ok"}
    }
  ]
}

main.go 完整代码(约 85 行):

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

type Endpoint struct {
	Path    string            `json:"path"`
	Method  string            `json:"method"`
	Status  int               `json:"status"`
	Body    json.RawMessage   `json:"body"`
	Headers map[string]string `json:"headers,omitempty"`
	Delay   int               `json:"delay,omitempty"`
}

type Config struct {
	Port      int        `json:"port"`
	Endpoints []Endpoint `json:"endpoints"`
}

func main() {
	cfgPath := flag.String("c", "mock.json", "配置文件路径")
	flag.Parse()

	data, err := os.ReadFile(*cfgPath)
	if err != nil {
		log.Fatalf("读取配置失败: %v", err)
	}

	var cfg Config
	if err := json.Unmarshal(data, &cfg); err != nil {
		log.Fatalf("解析配置失败: %v", err)
	}

	mux := http.NewServeMux()
	for _, ep := range cfg.Endpoints {
		e := ep
		mux.HandleFunc(e.Path, func(w http.ResponseWriter, r *http.Request) {
			if e.Method != "" && r.Method != e.Method {
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusMethodNotAllowed)
				w.Write([]byte(`{"error":"method not allowed"}`))
				return
			}
			if e.Delay > 0 {
				time.Sleep(time.Duration(e.Delay) * time.Millisecond)
			}
			for k, v := range e.Headers {
				w.Header().Set(k, v)
			}
			if _, ok := e.Headers["Content-Type"]; !ok {
				w.Header().Set("Content-Type", "application/json")
			}
			w.WriteHeader(e.Status)
			w.Write(e.Body)
			log.Printf("[%s] %s → %d (%dms)", r.Method, r.URL.Path, e.Status, e.Delay)
		})
	}

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{"error":"endpoint not found"}`))
	})

	fmt.Println("\n已注册接口:")
	for _, ep := range cfg.Endpoints {
		method := ep.Method
		if method == "" {
			method = "ALL"
		}
		fmt.Printf("  %-6s %-24s → %d\n", method, ep.Path, ep.Status)
	}
	fmt.Println()

	addr := fmt.Sprintf(":%d", cfg.Port)
	log.Printf("Mock 服务已启动 → http://localhost%s", addr)

	go func() {
		ch := make(chan os.Signal, 1)
		signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
		<-ch
		log.Println("服务已停止")
		os.Exit(0)
	}()

	log.Fatal(http.ListenAndServe(addr, mux))
}

运行方式:

go run main.go -c mock.json

测试:

curl http://localhost:8080/api/users        # 返回用户列表,延迟 200ms
curl -X POST http://localhost:8080/api/users # 返回新建用户
curl http://localhost:8080/api/health       # 健康检查

小结

整个开发过程大约 4 分钟,四个步骤、四次对话,一个完整可用的 HTTP Mock 服务器就出来了。

对比传统开发方式:建项目、写结构体、写路由、处理边界情况、写错误处理、加日志……同样的功能手写至少要 20-30 分钟,还得不时查文档确认 API 用法。用 OpenCode 的话,思路→描述→生成→验证,几乎零切换成本。

这个 Mock 服务器虽然简单,但已经能覆盖日常前端联调的常见场景:GET/POST 模拟、延迟模拟、自定义状态码。如果需要更多功能(比如通配符路径、请求体校验),也只需要再对 OpenCode 说一句话就行。

用 AI 编程的关键不是让它替你写代码,而是你负责设计、审查和决策,它负责实现细节和样板代码。 人的精力花在"做什么"和"对不对"上,OpenCode 花在"怎么写"上——这才是效率提升的核心。