
先说个冷笑话:Go 可能是语法最简洁的主流后端语言,但新建一个项目一点都不简洁——你要手动创建目录、初始化 go mod init、写 main.go、配路由、接处理器、加 Makefile、配 .gitignore……等你写完第一行业务代码,半小时已经过去了。
隔壁 JavaScript 有 create-react-app,Java 有 Spring Initializr,Go 呢?复制粘贴上一个项目的结构,然后祈祷不要漏东西。
受不了了,我写了个工具叫 go-initializer,几秒钟生成一个直接能跑的项目。这是它的开发记录和一些设计决策。
输入项目名、模块路径、项目类型、框架、附加组件,工具会输出一个 zip 包,里面是生产级项目结构:
go.mod — 依赖已经声明好main.go — 接好了选定的框架Makefile、.gitignore、README.mdDockerfile(多阶段构建、非 root 用户、Alpine 基础镜像).env.example(根据你的配置自动生成所需的环境变量)支持 5 种项目类型:微服务(Microservice)、API 服务器(API Server)、CLI 应用、简单项目、AI Agent。框架方面支持 14+ 个,包括 Gin、Echo、Fiber、Chi、Cobra、gRPC、LangChainGo、OpenAI、Gemini、Ollama 等。
设计原则只有一条:开发者用自己习惯的方式用。
1. 交互式 TUI 终端向导
goini new
基于 charmbracelet/huh 构建,按下方向键选项目类型、框架、附加组件,界面随选择实时刷新,全键盘操作。
2. 全命令行(CI/CD 友好)
goini new --name myservice --module github.com/me/myservice \
--type microservice --framework gin --addons redis,zap --docker
参数全给就不弹任何提示。工具会自动检测非 TTY 环境,可选参数静默跳过,必填参数缺失则报错退出。
3. REST API / Web UI
curl -X POST https://api.goinitializer.com/api/generate \
-H "Content-Type: application/json" \
-d '{"name":"myapp","module":"github.com/me/myapp","type":"api-server","framework":"fiber"}' \
--output myapp.zip
网页版 goinitializer.com 调的也是同一套 API。
最重要的设计决策:CLI 和 REST API 调用的代码完全相同。
所有项目类型注册到一个 GeneratorRegistry(本质就是一个 map[string]Generator),新增项目类型只需实现接口并注册到 map 中:
type Generator interface {
Generate(ctx context.Context, req *types.CreateProjectRequest) ([]byte, error)
}
CLI 调用 GeneratorRegistry["microservice"].Generate(ctx, req),HTTP Handler 也调同一个方法,输出完全一致。这样我可以独立于传输层测试生成逻辑,永远不用担心 CLI 和 API 行为不一致的问题。
大多数模板引擎都是字符串替换,但涉及 Golly 框架集成或缓存附加组件这种复杂场景时,我用了 dave/jennifer——一个基于 Go AST(抽象语法树)的代码生成库。
用 Jennifer 可以用程序构造 Go 源文件:
f := jen.NewFile("main")
f.Func().Id("main").Params().Block(
jen.Id("app").Op(":=").Qual("github.com/go-golly/golly", "New").Call(),
jen.Id("app").Dot("Run").Call(),
)
比字符串模板安全得多——不会生成语法错误的 Go 代码。代价是代码量更大,所以只在结构足够复杂时才用它。
这个项目类型不是简单放一个"引入了 OpenAI SDK 的文件"就完事了。每个支持的 AI 提供商都会生成:
llm/client.go — 带类型的 LLM 客户端agent/agent.go — 完整的 ReAct 风格工具调用循环tools/tools.go — 各提供商对应格式的工具定义(OpenAI 的 ChatCompletionToolParam、Gemini 的 genai.FunctionDeclaration、Ollama 的 JSON Schema 结构体、LangChainGo 的 tools.Tool 接口)生成出来的 Agent 是真的能跑的那种。搭好脚手架、填入 API Key、go run .,一个带工具调用的 Agent 循环就跑起来了,不需要任何额外配置。
API 是公开的,这一块花了不少时间。
限流:基于 IP 的令牌桶算法,用的是 golang.org/x/time/rate。rateLimiterStore 按需创建每个 IP 的限流器,用 lastSeen 追踪活跃度,后台 goroutine 每 5 分钟清理一次 10 分钟未活跃的记录。无需引入 Redis 等外部依赖就能防止内存无限增长。
防止 Zip Slip 攻击:CLI 的 extractZip() 用 Go 1.23 的 os.OpenRoot() 将所有文件写入限制在输出目录内,单个文件最大 100 MiB,防止解压炸弹。
安全响应头:中间件设置了 X-Content-Type-Options、X-Frame-Options、严格 CSP(default-src 'none')、Referrer-Policy、Permissions-Policy。
请求体大小限制:只有 /api/generate 路由有 MaxBytesReader,上限 64 KB,防止大Payload 攻击。
两阶段表单设计是个有意思的挑战。第一阶段问名称、模块、Go 版本、项目类型;第二阶段问框架——但第二阶段有哪些框架选项,取决于第一阶段选了哪种项目类型。
charmbracelet/huh 的 OptionsFunc 支持这种动态依赖:
huh.NewSelect[string]().
Title("Framework").
OptionsFunc(func() []huh.Option[string] {
return frameworksFor(opts.projectType)
}, &opts.projectType)
传入 &opts.projectType 依赖指针,projectType 变化时 huh 自动重新计算选项函数。表单原地重渲染,不需要重启或跳出新提示。
goini add 命令,向已有项目注入附加组件# Homebrew 安装
brew tap neo7337/goini
brew install goini
# 或者直接下载 GitHub Releases 的二进制
# https://github.com/neo7337/go-initializer/releases
在线体验:goinitializer.com
源码:github.com/neo7337/go-initializer
欢迎提 Issue 和 PR,特别是你用的框架或想要的功能目前还不支持的话。
原文链接:dev.to/@adi73