OpenCode 实践课:5 分钟用 Go 写出一个命令行 Base64 编解码工具

项目介绍

Base64 是开发者日常高频使用的编码格式——JWT 解析、配置文件编码、API 数据传输都离不开它。本文展示如何用 OpenCode 从零构建一个功能齐全的命令行 Base64 编解码工具,支持标准/URL-safe Base64、文件输入、行宽控制和去填充模式。

  • 技术栈:Go 标准库(encoding/base64flagos
  • 最终代码:约 90 行,零外部依赖
  • 核心功能:编码/解码、URL-safe 模式、管道输入、文件输入、76 字行宽、Raw 无填充

准备工作

确保已安装 Go(1.18+)和 OpenCode:

go version      # go version go1.21.0 ...
opencode --version

新建项目目录,在 OpenCode 中打开即可开始:

mkdir base64-cli && cd base64-cli
opencode .

实践过程

第一步:创建基础编解码程序

我对 OpenCode 说:

> 帮我用 Go 写一个命令行 Base64 编解码工具,支持 -d 参数切换解码模式,默认从标准输入读取,结果输出到标准输出。只使用标准库。

OpenCode 生成:

// main.go
package main

import (
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"os"
)

func main() {
	decode := flag.Bool("d", false, "decode mode")
	flag.Parse()

	input, _ := io.ReadAll(os.Stdin)

	if *decode {
		result, err := base64.StdEncoding.DecodeString(string(input))
		if err != nil {
			fmt.Fprintf(os.Stderr, "decode error: %v\n", err)
			os.Exit(1)
		}
		os.Stdout.Write(result)
	} else {
		output := base64.StdEncoding.EncodeToString(input)
		fmt.Print(output)
	}
}

测试一下:

echo "Hello OpenCode" | go run main.go
# SGVsbG8gT3BlbkNvZGU=

echo "SGVsbG8gT3BlbkNvZGU=" | go run main.go -d
# Hello OpenCode

关键要点: 一句话描述需求,OpenCode 就能生成可直接运行的基础版本。不需要手动查 encoding/base64 的 API 文档。

第二步:添加 URL-safe Base64 支持

我对 OpenCode 说:

> 加一个 -u 参数支持 URL-safe Base64(用 -_ 替代 +/),编解码都要支持。

OpenCode 修改后的核心逻辑:

urlsafe := flag.Bool("u", false, "use URL-safe Base64")

// 编解码时根据 -u 选择编码集
enc := base64.StdEncoding
if *urlsafe {
	enc = base64.URLEncoding
}

测试验证:

echo "a>b?c" | go run main.go -u
# YT5iP2M=

echo "YT5iP2M=" | go run main.go -d -u
# a>b?c

关键要点: 功能叠加只需追加自然语言描述,OpenCode 理解上下文后精准定位要修改的位置,不会重写整个文件。

第三步:支持文件输入和行宽控制

我对 OpenCode 说:

> 支持从文件读取输入(go run main.go file.txt)。编码输出时默认每 76 个字符换行,加 -w 参数关闭换行。

OpenCode 生成的关键改动:

var input []byte
if flag.NArg() > 0 {
	input, _ = os.ReadFile(flag.Arg(0))
} else {
	input, _ = io.ReadAll(os.Stdin)
}

// 行宽控制
noWrap := flag.Bool("w", false, "disable line wrapping")
if !*noWrap {
	for i := 0; i < len(output); i += 76 {
		end := i + 76
		if end > len(output) {
			end = len(output)
		}
		fmt.Println(output[i:end])
	}
} else {
	fmt.Print(output)
}

测试文件输入:

go run main.go go.mod
# bW9kdWxlIGJhc2U2NC1jbGkKCmdvIDEuMjE=

关键要点: OpenCode 自动处理了管道输入与文件输入的优先级——有文件名就优先读文件,否则从 stdin 读取。这种细节不需要手动指定。

第四步:添加 Raw 模式(无填充)

我对 OpenCode 说:

> 再加一个 -p 参数,去掉 Base64 的 = 填充。需要和 -u 参数组合使用也正确。

OpenCode 修改:

noPad := flag.Bool("p", false, "omit padding")

// 四种编码组合
switch {
case *urlsafe && *noPad:
	enc = base64.RawURLEncoding
case *urlsafe:
	enc = base64.URLEncoding
case *noPad:
	enc = base64.RawStdEncoding
default:
	enc = base64.StdEncoding
}

测试组合参数:

echo "hello" | go run main.go -u -p
# aGVsbG8

echo "aGVsbG8" | go run main.go -d -u -p
# hello

关键要点: OpenCode 正确理解了 urlsafe + noPad 需要 RawURLEncoding 而非简单的逻辑叠加,说明它能理解 API 之间的语义关系。

完整代码

package main

import (
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"os"
)

func main() {
	decode := flag.Bool("d", false, "decode mode")
	urlsafe := flag.Bool("u", false, "use URL-safe Base64")
	noWrap := flag.Bool("w", false, "disable line wrapping (76 chars)")
	noPad := flag.Bool("p", false, "omit padding characters")
	help := flag.Bool("h", false, "show help")
	flag.Parse()

	if *help {
		fmt.Println("base64-cli - Base64 encode/decode utility")
		fmt.Println("Usage: base64-cli [flags] [file]")
		fmt.Println("Flags:")
		flag.PrintDefaults()
		fmt.Println("Examples:")
		fmt.Println("  echo 'hello' | base64-cli")
		fmt.Println("  echo 'aGVsbG8=' | base64-cli -d")
		fmt.Println("  echo 'a>b' | base64-cli -u")
		fmt.Println("  base64-cli -u -p -w file.txt")
		return
	}

	var input []byte
	var err error

	if flag.NArg() > 0 {
		input, err = os.ReadFile(flag.Arg(0))
	} else {
		input, err = io.ReadAll(os.Stdin)
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "read error: %v\n", err)
		os.Exit(1)
	}

	if *decode {
		var enc *base64.Encoding
		switch {
		case *urlsafe:
			enc = base64.URLEncoding
		case *noPad:
			enc = base64.RawStdEncoding
		default:
			enc = base64.StdEncoding
		}

		result, err := enc.DecodeString(string(input))
		if err != nil {
			fmt.Fprintf(os.Stderr, "decode error: %v\n", err)
			os.Exit(1)
		}
		os.Stdout.Write(result)
	} else {
		var enc *base64.Encoding
		switch {
		case *urlsafe && *noPad:
			enc = base64.RawURLEncoding
		case *urlsafe:
			enc = base64.URLEncoding
		case *noPad:
			enc = base64.RawStdEncoding
		default:
			enc = base64.StdEncoding
		}

		output := enc.EncodeToString(input)
		if *noWrap {
			fmt.Print(output)
		} else {
			for i := 0; i < len(output); i += 76 {
				end := i + 76
				if end > len(output) {
					end = len(output)
				}
				fmt.Println(output[i:end])
			}
		}
	}
}

小结

从零到功能完备的命令行工具,共用 4 轮对话、实际耗时约 5 分钟。按传统方式开发,需要用 go doc encoding/base64 查阅 API、手写 flag 解析逻辑、处理各种边界条件,至少需要 20-30 分钟。

OpenCode 的核心价值不在"生成代码",而在于"消除上下文切换"——不需要在编辑器、文档、搜索引擎之间往返。描述需求 → 得到可运行代码 → 验证 → 继续迭代,所有步骤都在终端内完成,思考不中断。

这次实践用到的 OpenCode 技巧:

精确描述输入输出——"从标准输入读取,结果输出到标准输出"让 OpenCode 直接生成管道友好的代码

逐层叠加功能——不要一次性描述所有需求,先跑通基础版再迭代,每轮验证更快

信任上下文理解——OpenCode 修改代码时不重写整个文件,只改相关部分,这和 ChatGPT 的"每次重来"体验完全不同