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

项目介绍

Base64 是开发中常用的编码格式,无论是处理 API 认证头、嵌入图片数据,还是 URL 安全传输,都离不开它。虽然在线 Base64 工具不少,但每次都要打开浏览器复制粘贴实在低效。

今天我用 OpenCode 从零写一个命令行 Base64 编解码工具,支持标准编码、URL 安全编码、文件输入和自动格式检测,全部代码约 90 行,完全用自然语言指挥 AI 完成。

技术栈: Go 1.21+,纯标准库,无第三方依赖。

最终效果:

# 编码
$ echo "Hello World" | go run main.go
SGVsbG8gV29ybGQ=

# 解码
$ echo "SGVsbG8gV29ybGQ=" | go run main.go -d
Hello World

# URL 安全编码
$ echo "Hello World" | go run main.go -u
SGVsbG8gV29ybGQ=

# 从文件编码
$ go run main.go -f logo.png
iVBORw0KGgo...

准备工作

  • Go 1.21 或更高版本
  • OpenCode(安装方法见 opencode.ai)
  • 一个终端

在项目目录下打开 OpenCode:

opencode

实践过程

第一步:初始化项目

我直接告诉 OpenCode 我想做什么,它帮我生成项目骨架。

> 我对 OpenCode 说:
>
> "用 Go 写一个命令行 Base64 编解码工具,先帮我初始化项目结构,用 main.go 单文件就行,加上 flag 解析、stdin 读取的基本框架,错误信息用中文。"

OpenCode 立刻生成了 main.go 的初始骨架,包含了 flag 定义、标准输入读取和基本的错误处理。

关键要点: OpenCode 会根据你的描述推断项目规模,如果你只说"写一个工具",它可能会创建多文件结构。明确说"单文件"和"基础框架"能让它聚焦在最小可用版本上。

第二步:实现编码功能

骨架有了,该让它实现核心的编码逻辑了。

> 我对 OpenCode 说:
>
> "把编码功能补全:从 stdin 读取文本,去掉首尾空白后,使用 base64.StdEncoding 编码,结果直接打印到 stdout。如果输入为空就提示错误。"

OpenCode 在 main 函数中补充了完整的编码逻辑:

text := strings.TrimSpace(string(input))
if text == "" {
    fmt.Fprintln(os.Stderr, "错误: 输入为空")
    os.Exit(1)
}
result := base64.StdEncoding.EncodeToString([]byte(text))
fmt.Println(result)

关键要点: 这是 OpenCode 的强项——标准库 API 调用非常准确。你不需要记 EncodeToString 的参数类型,告诉它"用 base64 标准编码",它自动补全正确的调用方式。

第三步:加上解码模式

一个工具光能编码不够,解码才是高频需求。

> 我对 OpenCode 说:
>
> "加上 -d 参数,当传了 -d 时走解码逻辑。解码时先尝试 StdEncoding,失败了试试 RawStdEncoding(兼容不带 = 填充的格式),还失败就给用户中文错误提示。"

OpenCode 帮我写出了一段带自动回退的解码逻辑:

if *decode {
    decoded, err := base64.StdEncoding.DecodeString(text)
    if err != nil {
        decoded, err = base64.RawStdEncoding.DecodeString(text)
        if err != nil {
            fmt.Fprintf(os.Stderr, "解码失败: 无法识别的 Base64 格式\n")
            os.Exit(1)
        }
    }
    fmt.Print(string(decoded))
}

关键要点: 注意我说了"先尝试...失败了试试..."这种条件分支描述。OpenCode 能理解这种级联回退逻辑,自动生成嵌套的 if-err 结构,比你手写快得多。

第四步:URL 安全编码 + 文件输入

Base64 有标准、URL 安全、Raw 几种变体,我让 OpenCode 全部支持。

> 我对 OpenCode 说:
>
> "再加两个参数:-u 启用 URL 安全编码(使用 base64.URLEncoding),-f 从文件读取输入。解码时也用 URL 安全编码优先尝试。把编码器选择抽成一个独立函数。"

OpenCode 重构了代码,把编码器选择提成了 getEncoder 函数,解码逻辑进化成了遍历 4 种编码格式自动匹配:

func getEncoder(urlSafe, raw bool) *base64.Encoding {
    if urlSafe && raw {
        return base64.RawURLEncoding
    }
    if urlSafe {
        return base64.URLEncoding
    }
    if raw {
        return base64.RawStdEncoding
    }
    return base64.StdEncoding
}

关键要点: "抽成一个独立函数"这个指令非常重要。OpenCode 默认倾向于把所有逻辑写在一起,你主动要求重构,它会立刻把代码模块化——这正是结对编程的感觉。

第五步:完善帮助信息

一个好的 CLI 工具离不开清晰的帮助输出。

> 我对 OpenCode 说:
>
> "给 flag.Usage 加一个自定义的帮助信息,包含工具简介、用法示例和参数说明,全部用中文。另外加一个 -r 参数控制是否使用 Raw 编码(去掉结尾的 =)。"

OpenCode 生成了一段带有使用示例的帮助函数,并且无缝集成了 -r 参数到已有的编码选择逻辑中。最终的 --help 输出非常友好。

完整代码

package main

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

func main() {
    decode := flag.Bool("d", false, "解码模式")
    urlSafe := flag.Bool("u", false, "使用 URL 安全编码")
    filePath := flag.String("f", "", "从文件读取输入")
    raw := flag.Bool("r", false, "使用 Raw 编码(无填充字符)")
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "Base64 编解码工具\n\n")
        fmt.Fprintf(os.Stderr, "用法:\n")
        fmt.Fprintf(os.Stderr, "  echo <文本> | %s              # 编码\n", os.Args[0])
        fmt.Fprintf(os.Stderr, "  echo <编码> | %s -d           # 解码\n", os.Args[0])
        fmt.Fprintf(os.Stderr, "  %s -f <文件>                  # 从文件编码\n\n", os.Args[0])
        fmt.Fprintf(os.Stderr, "参数:\n")
        flag.PrintDefaults()
    }
    flag.Parse()

    var input []byte
    var err error

    if *filePath != "" {
        input, err = os.ReadFile(*filePath)
    } else {
        input, err = io.ReadAll(os.Stdin)
    }
    if err != nil {
        fmt.Fprintf(os.Stderr, "读取输入失败: %v\n", err)
        os.Exit(1)
    }

    text := strings.TrimSpace(string(input))
    if text == "" {
        fmt.Fprintln(os.Stderr, "错误: 输入为空")
        os.Exit(1)
    }

    if *decode {
        output := tryDecode(text, *urlSafe)
        fmt.Print(string(output))
    } else {
        enc := getEncoder(*urlSafe, *raw)
        result := enc.EncodeToString([]byte(text))
        fmt.Println(result)
    }
}

func getEncoder(urlSafe, raw bool) *base64.Encoding {
    if urlSafe && raw {
        return base64.RawURLEncoding
    }
    if urlSafe {
        return base64.URLEncoding
    }
    if raw {
        return base64.RawStdEncoding
    }
    return base64.StdEncoding
}

func tryDecode(text string, urlSafe bool) []byte {
    encodings := []*base64.Encoding{
        base64.StdEncoding,
        base64.URLEncoding,
        base64.RawStdEncoding,
        base64.RawURLEncoding,
    }
    if urlSafe {
        encodings = []*base64.Encoding{
            base64.URLEncoding,
            base64.RawURLEncoding,
            base64.StdEncoding,
            base64.RawStdEncoding,
        }
    }
    for _, enc := range encodings {
        decoded, err := enc.DecodeString(text)
        if err == nil {
            return decoded
        }
    }
    fmt.Fprintln(os.Stderr, "解码失败: 无法识别的 Base64 格式")
    os.Exit(1)
    return nil
}

小结

从零到完整可用的 Base64 工具,整个过程我没有写过一行代码,都是通过自然语言让 OpenCode 完成:

| 环节 | 传统开发 | 用 OpenCode |
|------|---------|------------|
| 编码 API 查文档 | 2-3 分钟 | 0(AI 直接写对) |
| 错误处理 | 5 分钟 | 30 秒(一句"给出中文错误提示") |
| 重构抽函数 | 3 分钟 | 10 秒(一句"抽成独立函数") |
| 写帮助信息 | 5 分钟 | 20 秒 |

效率提升大约 5-10 倍。 更重要的是,你不需要在编码-文档-StackOverflow 之间来回切换,所有对话都在终端里完成,思维的连贯性得到了最大程度的保留。

如果你还没试过 OpenCode,强烈建议从这样一个小工具开始体验。