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

项目介绍

JWT(JSON Web Token)是开发中常用的身份认证令牌,调试时经常需要快速查看其中的 header 和 payload 内容。每次都要复制到 jwt.io 太麻烦,自己写一个命令行工具又快又趁手。

本文将展示如何用 OpenCode 这款终端 AI 编程助手,在 4 分钟内用 Go 写出一个实用的 JWT 解析工具,支持参数输入、管道输入、过期时间展示等功能,最终代码约 70 行。

准备工作

  • 安装 Go(1.18+)
  • 安装 OpenCode(参考 opencode.ai
  • 终端中进入工作目录,准备好跟 AI 对话

实践过程

第一步:生成核心代码

打开终端,进入项目目录,直接对 OpenCode 说出需求:

> 我对 OpenCode 说:
>
> 帮我用 Go 写一个命令行 JWT 解析工具,接收一个 JWT token 作为参数,用 . 分割成三部分,对 header 和 payload 做 base64url 解码,然后格式化输出 JSON。

OpenCode 会分析需求,创建 main.go,自动处理边界条件和错误信息:

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
	"strings"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintln(os.Stderr, "Usage: jwtdec <token>")
		os.Exit(1)
	}

	parts := strings.Split(os.Args[1], ".")
	if len(parts) != 3 {
		fmt.Fprintln(os.Stderr, "Error: invalid JWT format")
		os.Exit(1)
	}

	printPart("Header", parts[0])
	printPart("Payload", parts[1])
}

func printPart(label, encoded string) {
	decoded, err := base64.RawURLEncoding.DecodeString(encoded)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s decoding failed\n", label)
		os.Exit(1)
	}

	var data map[string]interface{}
	json.Unmarshal(decoded, &data)

	out, _ := json.MarshalIndent(data, "", "  ")
	fmt.Printf("=== %s ===\n%s\n", label, out)
}

关键点:OpenCode 自动选择了 base64.RawURLEncoding(JWT 使用不带 = 填充的 URL 安全编码),无需我们手动指定。

第二步:增加管道输入支持

命令行工具要能跟管道配合才顺手:

> 我对 OpenCode 说:
>
> 如果命令行参数为空,就从 stdin 读取 token。

import "io"

// main 中参数解析改为:
token := ""
if len(os.Args) > 1 {
	token = os.Args[1]
} else {
	data, _ := io.ReadAll(os.Stdin)
	token = strings.TrimSpace(string(data))
}

这样既能 jwtdec eyJhbG... 直接传参,也能 echo "eyJhbG..." | jwtdec 管道输入。

第三步:显示 token 过期时间

JWT payload 中常包含 exp 字段(Unix 时间戳),直接显示可读的过期时间更实用:

> 我对 OpenCode 说:
>
> 解析 payload 中的 exp 字段,格式化显示过期时间和剩余有效期。

OpenCode 在 printPart 函数末尾追加了过期判断逻辑,同时自动引入了 time 包:

if exp, ok := data["exp"]; ok {
	if expFloat, ok := exp.(float64); ok {
		expTime := time.Unix(int64(expFloat), 0)
		remaining := time.Until(expTime)
		if remaining > 0 {
			fmt.Printf("  Expires in: %v\n", remaining.Round(time.Second))
		} else {
			fmt.Printf("  Expired %v ago\n", (-remaining).Round(time.Second))
		}
	}
}

JSON 中的数字经 json.Unmarshalinterface{} 会变成 float64,OpenCode 正确处理了这个类型转换细节。

第四步:添加紧凑输出模式

有时我们只需要原始 JSON 内容,不需要缩进:

> 我对 OpenCode 说:
>
> 加一个 -c 的 flag 参数,传了 -c 就紧凑输出(不带缩进),否则默认带缩进。

OpenCode 使用 flag 标准库添加参数解析,并通过条件判断控制 json.MarshalIndent 的缩进参数:

var compact bool
flag.BoolVar(&compact, "c", false, "Compact output")
flag.Parse()

整个交互过程不到 4 分钟,每一个功能都是直接用自然语言描述需求,OpenCode 负责翻译成正确的 Go 代码。

完整代码

package main

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

func main() {
	var compact bool
	flag.BoolVar(&compact, "c", false, "Compact output")
	flag.Parse()

	token := ""
	if args := flag.Args(); len(args) > 0 {
		token = args[0]
	} else {
		data, _ := io.ReadAll(os.Stdin)
		token = strings.TrimSpace(string(data))
	}
	if token == "" {
		fmt.Fprintln(os.Stderr, "Usage: jwtdec [-c] <token>")
		fmt.Fprintln(os.Stderr, "       echo <token> | jwtdec [-c]")
		os.Exit(1)
	}

	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		fmt.Fprintln(os.Stderr, "Error: invalid JWT format")
		os.Exit(1)
	}

	printPart("Header", parts[0], compact)
	printPart("Payload", parts[1], compact)
	fmt.Printf("\nSignature: %s\n", parts[2])
}

func printPart(label, encoded string, compact bool) {
	decoded, err := base64.RawURLEncoding.DecodeString(encoded)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: invalid %s encoding\n", label)
		os.Exit(1)
	}

	var data map[string]interface{}
	if err := json.Unmarshal(decoded, &data); err != nil {
		fmt.Fprintf(os.Stderr, "Error: invalid %s JSON\n", label)
		os.Exit(1)
	}

	indent := "  "
	if compact {
		indent = ""
	}
	out, _ := json.MarshalIndent(data, "", indent)

	fmt.Printf("=== %s ===\n%s\n", label, out)

	if exp, ok := data["exp"]; ok {
		if expFloat, ok := exp.(float64); ok {
			expTime := time.Unix(int64(expFloat), 0)
			remaining := time.Until(expTime)
			if remaining > 0 {
				fmt.Printf("  Expires in: %v\n", remaining.Round(time.Second))
			} else {
				fmt.Printf("  Expired %v ago\n", (-remaining).Round(time.Second))
			}
		}
	}
}

编译运行:

go build -o jwtdec .
./jwtdec eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxODkzNDU2MDAwfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

输出效果:

=== Header ===
{
  "alg": "HS256",
  "typ": "JWT"
}

=== Payload ===
{
  "exp": 1893456000,
  "name": "John Doe",
  "sub": "1234567890"
}
  Expires in: 95040h0m0s

Signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

小结

整个开发过程不到 4 分钟,四个功能全部由自然语言驱动,OpenCode 完成了代码编写、类型处理、错误处理等全部细节工作。

与传统手动编码相比,用 OpenCode 开发这类小工具的效率提升非常明显:不用查 API 文档、不用处理类型转换细节、不用纠结错误处理写法——你只需要想清楚"要做什么",AI 帮你解决"怎么做"。这正是终端 AI 编程助手适合的场景:快速验证想法,把小需求立刻变成可运行的工具。

如果你也经常需要在终端里处理各种数据格式,不妨试试用 OpenCode 打造自己的命令行工具箱。