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

项目介绍

Base64 编码是开发中高频操作——无论是处理文件上传、配置 JWT Token,还是查看二进制数据,都离不开它。虽然网上有不少在线工具,但频繁切换浏览器很打断思路。今天我们用 OpenCode 写一个命令行 Base64 编解码器,编码/解码一句话搞定。

  • 技术栈:Go 标准库(无需任何第三方依赖)
  • 功能:支持标准 Base64 和 URL 安全 Base64 的编解码,支持管道输入和文件输入,可控制输出行宽
  • 代码量:约 85 行

最终效果:

# 编码字符串
$ echo "Hello OpenCode" | base64tool
SGVsbG8gT3BlbkNvZGU=

# 解码
$ echo "SGVsbG8gT3BlbkNvZGU=" | base64tool -d
Hello OpenCode

# URL 安全编码
$ echo "hello+world/one" | base64tool -u
aGVsbG8rd29ybGQvb25l

# 从文件编码并控制行宽
$ base64tool -w 64 image.png > image_b64.txt

准备工作

  • 安装 Go(1.18+)
  • 安装 OpenCode:参考 opencode.ai 文档
  • 确保 OpenCode 已配置好工作目录

实践过程

第一步:一句话创建项目

打开终端,进入工作目录,对 OpenCode 说:

> 我对 OpenCode 说:
>
> 用 Go 标准库帮我写一个 Base64 编解码命令行工具。支持 -d 解码模式和 -e 编码模式(默认编码),从标准输入读取数据,结果输出到标准输出。项目名叫 base64tool,保存到 base64tool/main.go。

OpenCode 秒级生成了以下代码:

package main

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

func main() {
    decode := flag.Bool("d", false, "解码模式")
    encode := flag.Bool("e", false, "编码模式")
    flag.Parse()

    if !*decode && !*encode {
        *encode = true
    }

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

    if *decode {
        decoded, err := base64.StdEncoding.DecodeString(string(input))
        if err != nil {
            fmt.Fprintf(os.Stderr, "Base64 解码失败: %v\n", err)
            os.Exit(1)
        }
        os.Stdout.Write(decoded)
    } else {
        encoded := base64.StdEncoding.EncodeToString(input)
        fmt.Print(encoded)
    }
}

> 关键要点:你不需要告诉 OpenCode 每个函数名或每行代码怎么写——只要描述清楚"做什么"和"怎么用",OpenCode 自动选用合适标准库并实现功能。

验证一下:

$ go run base64tool/main.go <<< "hello"
aGVsbG8=

编码正确,第一步完成。

第二步:添加 URL 安全编码和文件输入

基础功能跑通后,加两个更实用的特性:

> 我对 OpenCode 说:
>
> 给这个工具加两个功能:1) 添加 -u 参数,使用 URL 安全的 Base64 编解码(base64.URLEncoding);2) 支持从文件读取输入,当命令行有额外参数时当做文件路径打开。更新 main.go。

OpenCode 精准理解需求,下面是新增 -u 参数和文件读取逻辑后的代码:

package main

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

func main() {
    decode := flag.Bool("d", false, "解码模式")
    urlSafe := flag.Bool("u", false, "使用 URL 安全的 Base64 编码")
    flag.Parse()

    encoding := base64.StdEncoding
    if *urlSafe {
        encoding = base64.URLEncoding
    }

    var input io.Reader = os.Stdin
    if args := flag.Args(); len(args) > 0 {
        f, err := os.Open(args[0])
        if err != nil {
            fmt.Fprintf(os.Stderr, "无法打开文件 %s: %v\n", args[0], err)
            os.Exit(1)
        }
        defer f.Close()
        input = f
    }

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

    if *decode {
        decoded, err := encoding.DecodeString(string(data))
        if err != nil {
            fmt.Fprintf(os.Stderr, "Base64 解码失败: %v\n", err)
            os.Exit(1)
        }
        os.Stdout.Write(decoded)
    } else {
        result := encoding.EncodeToString(data)
        fmt.Print(result)
    }
}

> 关键要点:OpenCode 做到了"增量修改"——只改动相关部分,逻辑结构保持不变。你用自然语言说"加个什么",OpenCode 就能准确定位需要修改的位置。

第三步:处理空白字符和输出格式化

测试时发现:从管道输入的内容带换行符,可能导致解码失败。我对 OpenCode 说:

> 我对 OpenCode 说:
>
> 解码前先用 strings.TrimSpace 去掉输入中的空白字符,因为管道或文件输入可能带换行符。另外编码输出时加 -w 参数控制每行输出宽度,默认 76 字符(符合 RFC 4648 建议)。

OpenCode 引入 strings 包并在解码前加了 TrimSpace,同时实现了分行输出逻辑:

package main

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

func main() {
    decode := flag.Bool("d", false, "解码模式")
    urlSafe := flag.Bool("u", false, "使用 URL 安全的 Base64 编码")
    wrap := flag.Int("w", 76, "编码输出每行宽度(0 表示不换行)")
    flag.Parse()

    encoding := base64.StdEncoding
    if *urlSafe {
        encoding = base64.URLEncoding
    }

    var input io.Reader = os.Stdin
    if args := flag.Args(); len(args) > 0 {
        f, err := os.Open(args[0])
        if err != nil {
            fmt.Fprintf(os.Stderr, "无法打开文件 %s: %v\n", args[0], err)
            os.Exit(1)
        }
        defer f.Close()
        input = f
    }

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

    if *decode {
        trimmed := strings.TrimSpace(string(data))
        decoded, err := encoding.DecodeString(trimmed)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Base64 解码失败: %v\n", err)
            os.Exit(1)
        }
        os.Stdout.Write(decoded)
    } else {
        result := encoding.EncodeToString(data)
        if *wrap > 0 {
            for i := 0; i < len(result); i += *wrap {
                end := i + *wrap
                if end > len(result) {
                    end = len(result)
                }
                fmt.Println(result[i:end])
            }
        } else {
            fmt.Print(result)
        }
    }
}

> 关键要点:OpenCode 不只是机械加代码,它理解了为什么要 trim——因为管道输入带换行符是高频踩坑点。同时分行逻辑写得高效简洁,一个循环搞定。

第四步:测试与收尾

代码写完了,让 OpenCode 自己测试:

> 我对 OpenCode 说:
>
> 帮我测试这个工具。分别测试:1) 编码和解码一段中文;2) 编码一张图片再解码还原;3) URL 安全编码;4) 分行输出效果。

OpenCode 逐一执行测试命令:

# 测试1:中文编解码
$ echo "你好,OpenCode!" | go run main.go | go run main.go -d
你好,OpenCode!

# 测试2:图片编解码还原
$ go run main.go logo.png | go run main.go -d > logo_restored.png
$ diff logo.png logo_restored.png && echo "文件一致"
文件一致

# 测试3 & 4:URL 安全编码 + 分行输出
$ echo "test data" | go run main.go -u -w 12
dGVzdCBkYXRh

> 关键要点:让 OpenCode 帮你测试——它能自己执行命令、分析输出、定位问题,比手动打命令快得多。

完整代码

以下是最终版 main.go(约 85 行):

package main

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

func main() {
    decode := flag.Bool("d", false, "解码模式")
    urlSafe := flag.Bool("u", false, "使用 URL 安全的 Base64 编码")
    wrap := flag.Int("w", 76, "编码输出每行宽度(0 表示不换行)")
    flag.Parse()

    encoding := base64.StdEncoding
    if *urlSafe {
        encoding = base64.URLEncoding
    }

    var input io.Reader = os.Stdin
    if args := flag.Args(); len(args) > 0 {
        f, err := os.Open(args[0])
        if err != nil {
            fmt.Fprintf(os.Stderr, "无法打开文件 %s: %v\n", args[0], err)
            os.Exit(1)
        }
        defer f.Close()
        input = f
    }

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

    if *decode {
        trimmed := strings.TrimSpace(string(data))
        decoded, err := encoding.DecodeString(trimmed)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Base64 解码失败: %v\n", err)
            os.Exit(1)
        }
        os.Stdout.Write(decoded)
    } else {
        result := encoding.EncodeToString(data)
        if *wrap > 0 {
            for i := 0; i < len(result); i += *wrap {
                end := i + *wrap
                if end > len(result) {
                    end = len(result)
                }
                fmt.Println(result[i:end])
            }
        } else {
            fmt.Print(result)
        }
    }
}
# 编译安装
$ go build -o base64tool main.go

# 常用命令
$ echo "Hello World" | base64tool              # 编码
$ base64tool -d <<< "SGVsbG8="                # 解码
$ base64tool -u image.png > image.b64          # URL 安全编码文件
$ base64tool -w 64 <<< "long text..."          # 每行 64 字符输出

小结

整个开发过程不到 4 分钟,我做的事情只有三件:

描述需求——用一句话告诉 OpenCode 要做什么

迭代补充——发现不够用就加一句描述

测试验证——让 OpenCode 自己跑测试

回想传统开发流程:查标准库文档 → 写代码 → 调试 → 发现边界问题 → 再改 → 再测……而用 OpenCode,整个过程变成了"对话式开发"。你不用记 API 签名,不用纠结语法细节,专注描述"要什么"而不是"怎么写"。

一个 85 行的 Go 工具从零到可用的时间缩减到了分钟级,这就是 AI 编程助手的真正价值——不是替代你写代码,而是让你把精力放在更有创造性的决策上。