antst/go-apispec
GitHub: antst/go-apispec
通过零注解的 Go 源码静态分析自动生成 OpenAPI 3.1 规范的工具。
Stars: 30 | Forks: 0
# go-apispec:从 Go 代码生成 OpenAPI
[](https://github.com/antst/go-apispec/actions/workflows/ci.yml)
[](https://github.com/antst/go-apispec/actions/workflows/release.yml)

[](https://go.dev/)
[](https://github.com/antst/go-apispec/blob/main/LICENSE)
[](https://pkg.go.dev/github.com/antst/go-apispec)
**go-apispec** 会分析你的 Go 源代码并生成 OpenAPI 3.1 规范(YAML 或 JSON)。将其指向你的 module——它会自动检测框架,沿着从路由到 handler 的调用图进行追踪,并从实际代码中推断请求/响应类型。
## 快速开始
```
# 安装
go install github.com/antst/go-apispec/cmd/apispec@latest
# Generate(自动检测 framework)
apispec --dir ./your-project --output openapi.yaml
```
就这么简单。该工具会检测你的框架,找到所有路由,解析 handler 类型,并输出规范。
## 功能
### 框架支持
| 框架 | 路由 | 参数 | 请求体 | 响应 | 挂载/分组 |
|-----------|--------|--------|-------------|-----------|-----------------|
| **Chi** | 完整 | `chi.URLParam`, `r.FormValue`, `r.FormFile`, render pkg | `json.Decode`, `render.DecodeJSON` | `json.Encode`, `render.JSON`, `w.Write` | `Mount`, `Group` |
| **Gin** | 完整 | `c.Param`, `c.Query` | `ShouldBindJSON`, `BindJSON` | `c.JSON`, `c.String`, `c.Data` | `Group` |
| **Echo** | 完整 | `c.Param`, `c.QueryParam`, `r.FormValue`, `r.FormFile` | `c.Bind` | `c.JSON`, `c.String`, `c.Blob` | `Group` |
| **Fiber** | 完整 | `c.Params`, `c.Query`, `c.FormValue`, `c.FormFile` | `c.BodyParser` | `c.JSON`, `c.Status().JSON` | `Mount`, `Group` |
| **Gorilla Mux** | 完整 | 路径模板 `{id}` | `json.Decode` | `json.Encode`, `w.Write` | `PathPrefix`, `Subrouter` |
| **net/http** | 基础 | 路径模板, `r.FormValue`, `r.FormFile` | `json.Decode` | `json.Encode`, `w.Write`, `http.Error` | 嵌套 `ServeMux` |
支持 Go 1.22+ 的 `ServeMux` 方法前缀模式 —— `mux.HandleFunc("GET /health/live", h)` 会生成 `get: /health/live`,而不是字面量 `/GET /health/live` 键。
完全支持同时使用**多个框架**的项目 —— 所有检测到的框架中的所有路由都会出现在规范中。
所有框架还会将 `fmt.Fprintf`、`io.Copy` 和 `io.WriteString` 检测为响应写入。
### 分析能力
**响应检测**
- 从 `w.Header().Set("Content-Type", "image/png")` 推断 Content-Type
- 动态 content-type 回退:`w.Header().Set("Content-Type", doc.MimeType)` → `application/octet-stream`(变量 MIME 类型不会将 Go 字段路径泄漏到规范中)
- 将 `WriteHeader(201)` + `json.Encode(user)` 合并为一个带有 schema 的单个 201 响应
- 错误辅助函数:`writeJSONError(w, http.StatusBadRequest, "msg")` → 带有 ErrorResponse schema 的 400 响应,通过 ParamArgMap 追踪函数参数
- 多次调用同一个辅助函数:`writeJSONError(w, 400, ...)` + `writeJSONError(w, 404, ...)` → 捕获两个状态码并使用正确的 schema
- 状态码变量解析:`status := http.StatusCreated; w.WriteHeader(status)` → 201
- 跨函数状态码:`w.WriteHeader(getStatus())`,其中 `getStatus()` 返回一个常量
- 同一状态码的多种响应类型 → `oneOf` schema
- `[]byte` 响应 → `type: string, format: binary`
- 无响应体的状态码(1xx、204、304)永远不会获得 body schema(根据 RFC 7231)
- 对于在没有显式调用 `WriteHeader` 的情况下写入响应体的 handler,隐式返回 200
**类型解析**
- 泛型结构体实例化:`APIResponse[User]` → 带有 `Data: $ref User` 的 schema
- 接口解析:通过 interface 注册的 handler → 具体实现的 schema
- `interface{}` 参数解析:`respondJSON(w, 201, user)`,其中 `data` 为 `interface{}` → 从调用者的参数解析为具体的 `User` 类型
- 通过 CFG 实现的条件 HTTP 方法:`switch r.Method { case "GET": ... case "POST": ... }` → 独立的操作
- 路由路径变量:`path := "/users"; r.GET(path, h)` → 将变量解析为字面量路径
- Decode 接收者追踪:`json.NewDecoder(file).Decode(&cfg)` 不会被错误分类为请求体
- io.Copy 源追踪:`io.Copy(w, strings.NewReader(...))` → `type: string`;`io.Copy(w, file)` → `format: binary`
**文档提取**
- handler 函数上的 Go 文档注释 → OpenAPI 的 `summary` 和 `description`
- 第一句话 → `summary`,完整注释 → `description`(单句注释不会重复)
- 无需注解 —— 现有的 Go 文档注释可直接生效
- 配置覆盖优先于文档注释
**Schema 推断**
- 通过缺少 `json:",omitempty"` 和 `binding:"required"` 标签推断必填字段
- Validator `dive` 标签:在 `[]string` 上的 `validate:"dive,email"` → items 的 schema 具有 `format: email`
- `Mux.Vars()` map 索引表达式 → 路径参数名称
- `time.Time`、`uuid.UUID` 和自定义类型的类型映射
- 转换器类型的参数:`idStr := c.Param("id"); strconv.Atoi(idStr)` → `integer`;`strconv.ParseBool` → `boolean`;`strconv.ParseFloat` → `number`;`uuid.Parse` → `string/uuid`。适用于内联(`strconv.Atoi(r.FormValue("x"))`)和变量绑定(`if v := r.FormValue("x"); v != "" { strconv.ParseBool(v) }`)惯用法,包括在不同 `if` 初始化作用域中被遮蔽的变量
- `r.FormFile("upload")` → 带有 `string`/`binary` schema 的表单参数
- JSON-DTO 字段流推断:当解码后的 body 字段随后被传递给转换器时(`uuid.Parse(body.SourceID)`,包括指针解引用 `uuid.Parse(*body.TagsetID)`),apispec 会将转换器的 schema(例如 `format: uuid`)反向传播到结构体字段上
- 通过 `apispec:"format=uuid,type=string"` 结构体标签进行显式的字段覆盖 —— 涵盖了流分析无法到达的字段(例如,在 handler 中被读取但从未被解析的 UUID,或者是 `format: date-time` / `format: email` 提示)
- 当 handler 通过 `json.Decode`、`json.Unmarshal`、`c.Bind`、`c.BodyParser` 等读取 body 时,会自动发射 `requestBody.required: true`
- 通过空白标记字段进行结构体级别的验证 —— 带有标签 `apispec:"minProperties=1,anyOf=displayName|storageBucketId|temporaryLocation"` 的 `_ struct{}` —— 发射 OpenAPI 的 `minProperties` 和每个字段的 `anyOf` 块。该标记在 JSON 中是不可见的;标签值使用 `,` 表示键值对,并使用 `|` 分隔 `anyOf` 中的字段名称。对于至少需要一个字段的 PATCH endpoint 非常有用
- 安全方案检测 —— 读取 `r.Header.Get("Authorization")` 并修剪方案前缀(`strings.TrimPrefix(_, "Bearer ")` 等)的 handler 会获得一个 OpenAPI `components.securitySchemes` 条目和一个按操作的 `security:` 引用。检测会传递性地跟随调用图,因此共享的 `ValidateBearerToken(r, ...)` 辅助函数将与内联模式一样被识别。可识别的前缀:`Bearer` → `http`/`bearer`,`Basic` → `http`/`basic`,原始 header(无前缀)→ header 中的 `apiKey`。冗余的 `Authorization` header 参数会被自动抑制。通过在 `apispec.yaml` 中声明 `securitySchemes:` 来覆盖或扩展 —— 对于相同的方案名称,配置条目优先于检测结果
**输出质量**
- 确定性的 YAML/JSON —— 排序的 map 键,跨运行的输出完全相同,对于 CI diff 很安全
- 默认使用短名称 —— `DocumentHandler.GetContent` 而不是 `github.com/org/.../http.Deps.DocumentHandler.GetContent`
- 配置合并 —— `--config` 扩展自动检测到的框架默认值,而不是替换它们
### 调用图可视化
交互式 Cytoscape.js 图表具有:
- 具有缩放、平移和点击高亮功能的层次树布局
- CFG 分支着色:绿色(if-then),红色虚线(if-else),紫色(switch-case)
- 显示 case 值的分支标签(例如,“GET”、“POST”)
- 针对大型图表的分页模式(1000+ 条边)
- PNG/SVG 导出
```
apispec --dir ./my-project --output openapi.yaml --diagram diagram.html
```
## 使用方法
```
# 基础生成
apispec --dir ./my-project --output openapi.yaml
# 使用自定义 config
apispec --dir ./my-project --config apispec.yaml --output openapi.yaml
# Legacy 命名(完全限定的 operationIds 和 schema 名称)
apispec --dir ./my-project --output openapi.yaml --short-names=false
# 附带 call graph diagram
apispec --dir ./my-project --output openapi.yaml --diagram diagram.html
# 跳过 CGO packages
apispec --dir ./my-project --output openapi.yaml --skip-cgo
# 调整大型 codebases 的限制
apispec --dir ./my-project --output openapi.yaml --max-nodes 100000 --max-recursion-depth 15
# Performance profiling
apispec --dir ./my-project --output openapi.yaml --cpu-profile --mem-profile
```
### 主要参数
| 参数 | 缩写 | 默认值 | 描述 |
|------|-------|---------|-------------|
| `--output` | `-o` | `openapi.json` | 输出文件(`.yaml`/`.json`) |
| `--dir` | `-d` | `.` | 项目目录 |
| `--config` | `-c` | — | 自定义 YAML 配置 |
| `--short-names` | — | `true` | 从名称中剥离 module 路径 |
| `--diagram` | `-g` | — | 将调用图保存为 HTML |
| `--title` | `-t` | `Generated API` | API 标题 |
| `--api-version` | `-v` | `1.0.0` | API 版本 |
| `--skip-cgo` | — | `true` | 跳过 CGO 包 |
| `--max-nodes` | `-mn` | `50000` | 最大调用图节点数 |
| `--max-recursion-depth` | `-mrd` | `10` | 最大递归深度 |
| `--verbose` | `-vb` | `false` | 详细输出 |
完整参数列表:`apispec --help`
## 编程方式使用
```
import (
"os"
"github.com/antst/go-apispec/generator"
"github.com/antst/go-apispec/spec"
"gopkg.in/yaml.v3"
)
func main() {
cfg := spec.DefaultChiConfig() // or DefaultGinConfig, DefaultEchoConfig, etc.
gen := generator.NewGenerator(cfg)
openapi, err := gen.GenerateFromDirectory("./your-project")
if err != nil { panic(err) }
data, _ := yaml.Marshal(openapi)
os.WriteFile("openapi.yaml", data, 0644)
}
```
## 配置
自动检测适用于大多数项目。对于自定义行为,请创建 `apispec.yaml`:
```
info:
title: My API
version: 2.0.0
shortNames: true # false for legacy fully-qualified names
framework:
routePatterns:
- callRegex: ^(?i)(GET|POST|PUT|DELETE|PATCH)$
recvTypeRegex: ^github\.com/gin-gonic/gin\.\*(Engine|RouterGroup)$
handlerArgIndex: 1
methodFromCall: true
pathFromArg: true
handlerFromArg: true
typeMapping:
- goType: time.Time
openapiType: { type: string, format: date-time }
- goType: uuid.UUID
openapiType: { type: string, format: uuid }
externalTypes:
- name: github.com/gin-gonic/gin.H
openapiType: { type: object, additionalProperties: true }
```
每个框架的完整配置示例:请参阅 `internal/spec/config.go` 中的 `Default*Config()` 函数以获取内置模式。
## 工作原理
```
Go source → Package loading & type-checking → Framework detection
→ AST traversal → Call graph + CFG construction → Pattern matching
→ OpenAPI mapping → YAML/JSON output
```
1. 加载并对 module 中的所有 Go 包进行类型检查
2. 从导入中检测 Web 框架(支持同时使用多个框架)
3. 构建从路由注册到 handler 的调用图
4. 通过 `golang.org/x/tools/go/cfg` 构建控制流图(CFG)以进行分支分析
5. 将路由、请求、响应和参数模式与调用图进行匹配
6. 使用 CFG 和类型分析解析条件方法、泛型类型和接口实现
7. 将 Go 类型映射到 OpenAPI schema(结构体、枚举、别名、泛型、验证器)
8. 使用排序后的键进行序列化,以确保输出确定性
## 已知限制
这些是静态分析的固有局限性 —— 即在不执行代码的情况下分析代码:
| 限制 | 示例 | 发生的情况 |
|-----------|---------|-------------|
| **基于反射的路由** | 通过 `reflect.Value.Call` 注册的路由 | 在静态分析中不可见 |
| **计算的路径** | `r.GET("/api/" + version, handler)` | 字符串拼接不被计算;仅解析字面量和变量赋值的路径 |
| **复杂的跨函数值** | `func compute() int { return a + b }; WriteHeader(compute())` | 仅解析具有单个常量返回的函数;不追踪计算出的值 |
## 交互式图表服务器
```
# 构建并启动
go build -o apidiag ./cmd/apidiag
./apidiag --dir ./my-project --port 8080
# 打开 http://localhost:8080
```
提供一个 Web UI,用于通过过滤、分页和导出来探索调用图。请参阅 [cmd/apidiagREADME.md](cmd/apidiag/README.md)。
## 开发
```
# 构建
make build
# 测试
make test
# Lint
make lint
# Coverage
make coverage
# 在故意更改输出后更新 golden files
# (生成至临时目录,显示 diff,需要确认)
go test ./internal/engine/ -run TestUpdateGolden -v
```
### 项目结构
```
go-apispec/
├── cmd/apispec/ CLI entry point
├── cmd/apidiag/ Interactive diagram server
├── generator/ High-level generator API
├── spec/ Public types (re-exports from internal/spec)
├── internal/
│ ├── core/ Framework detection
│ ├── engine/ Generation engine
│ ├── metadata/ AST analysis and metadata extraction
│ └── spec/ OpenAPI mapping, patterns, schemas
├── pkg/patterns/ Gitignore-style pattern matching
└── testdata/ Framework fixtures + golden files
```
### Golden 文件测试
每个 `testdata/` 目录都有 `expected_openapi.json`(短名称)和 `expected_openapi_legacy.json`(完全限定名称)。Golden 文件仅使用相对路径 —— 没有特定于机器的绝对路径。
**单一代码路径**:`generateGoldenSpec()` 是比较和生成共用的单个函数。测试永远不会覆盖 golden 文件。
```
# Compare(在 CI 中运行 — 不匹配时失败):
go test ./internal/engine/ -run TestGolden -v
# 在故意更改后更新(显式,从不自动):
go test ./internal/engine/ -run TestUpdateGolden -v
```
## 许可证
Apache License 2.0 —— 请参阅 [LICENSE](LICENSE)。
最初 fork 自 Ehab Terra 的 [apispec](https://github.com/ehabterra/apispec)。
标签:EVTX分析, Go, OpenAPI, Ruby工具, SOC Prime, Syscall, Web开发, 云安全监控, 代码生成, 开发工具, 日志审计, 渗透测试工具, 自动化payload嵌入, 静态分析