feat: add doc generate

This commit is contained in:
王性驊 2025-09-30 17:33:29 +08:00
parent b7c67fb9e6
commit 8dd4d406f4
24 changed files with 1273 additions and 1112 deletions

View File

@ -1,69 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0] - 2025-09-30
### Added
- **OpenAPI 3.0 Support** - Generate both Swagger 2.0 and OpenAPI 3.0 specifications
- New `--spec-version` (`-s`) flag to choose between `swagger2.0` and `openapi3.0`
- Automatic conversion from Swagger 2.0 to OpenAPI 3.0 format
- Support for OpenAPI 3.0 features:
- Server objects with complete URLs (instead of host/basePath)
- Components/schemas (instead of definitions)
- Components/securitySchemes (instead of securityDefinitions)
- RequestBody separated from parameters
- Integration with `github.com/getkin/kin-openapi` library for OpenAPI 3.0
### Changed
- Updated CLI help text to reflect dual specification support
- Enhanced Makefile examples to generate both Swagger 2.0 and OpenAPI 3.0
- Version bumped to 1.1.0
## [1.0.0] - 2025-09-30
### Added
- Initial release as standalone tool
- Extracted from go-zero project and made independent
- Support for converting go-zero `.api` files to Swagger 2.0 specification
- JSON and YAML output formats
- Command-line interface using cobra
- Support for all go-zero API features:
- Info properties (title, description, version, host, basePath, etc.)
- Type definitions (structs, arrays, maps, pointers)
- Tag-based parameter handling (json, form, path, header)
- Validation options (range, enum, default, example, optional)
- Security definitions
- Code-msg wrapper for responses
- Definition references
- Internal utility functions to replace go-zero dependencies
- Comprehensive documentation
- Example API files
- Makefile for easy building
- MIT License
### Changed
- Module name from `github.com/zeromicro/go-zero` to `go-doc`
- Removed dependency on `go-zero/tools/goctl/internal/version`
- Removed dependency on `go-zero/tools/goctl/util/*`
- Removed dependency on `google.golang.org/grpc/metadata`
- Project structure reorganized to follow Go best practices
- `cmd/go-doc/` for main entry point
- `internal/swagger/` for core logic
- `internal/util/` for utilities
### Removed
- Dependency on go-zero runtime components
- Go-zero specific version information
---
## Attribution
This project was originally part of the [go-zero](https://github.com/zeromicro/go-zero)
project's swagger generation plugin. We are grateful to the go-zero team for their
excellent work.

View File

@ -1,255 +0,0 @@
# 🔄 Migration Guide: From go-zero Plugin to Standalone Tool
This document explains how `go-doc` was extracted from go-zero and made independent.
## 📊 What Changed?
### 1. **Module Independence**
**Before (go-zero plugin):**
```go
// Part of go-zero's internal tools
package swagger // in tools/goctl/api/plugin/swagger/
```
**After (standalone):**
```go
module go-doc
package swagger // in internal/swagger/
```
### 2. **Dependency Reduction**
#### Removed Internal Dependencies
| Dependency | Replaced With | Reason |
|:-----------|:--------------|:-------|
| `go-zero/tools/goctl/internal/version` | Custom version in `main.go` | Internal package not accessible |
| `go-zero/tools/goctl/util` | `go-doc/internal/util` | Self-contained utilities |
| `go-zero/tools/goctl/util/stringx` | `go-doc/internal/util` | Custom string manipulation |
| `go-zero/tools/goctl/util/pathx` | `go-doc/internal/util` | Custom path utilities |
| `google.golang.org/grpc/metadata` | Direct map access | Simplified KV retrieval |
#### Kept Essential Dependencies
```go
require (
github.com/go-openapi/spec v0.21.0 // Swagger spec
github.com/spf13/cobra v1.8.1 // CLI framework
github.com/zeromicro/go-zero/tools/goctl v1.9.0 // API parser only
gopkg.in/yaml.v2 v2.4.0 // YAML output
)
```
### 3. **Project Structure**
**Before:**
```
go-zero/
└── tools/
└── goctl/
└── api/
└── plugin/
└── swagger/
├── swagger.go
├── parameter.go
└── ...
```
**After:**
```
go-doc/
├── cmd/
│ └── go-doc/
│ └── main.go # Standalone entry point
├── internal/
│ ├── swagger/ # Core logic (from go-zero)
│ │ ├── swagger.go
│ │ ├── parameter.go
│ │ └── ...
│ └── util/ # Self-contained utilities
│ ├── util.go
│ ├── stringx.go
│ └── pathx.go
└── example/
```
### 4. **Import Path Changes**
**Before:**
```go
import (
"github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
"google.golang.org/grpc/metadata"
)
```
**After:**
```go
import (
"go-doc/internal/util"
)
```
### 5. **Metadata Handling**
**Before (using gRPC metadata):**
```go
func getStringFromKV(properties map[string]string, key string) string {
md := metadata.New(properties)
val := md.Get(key)
if len(val) == 0 {
return ""
}
return val[0]
}
```
**After (direct map access):**
```go
func getStringFromKVOrDefault(properties map[string]string, key string, def string) string {
if len(properties) == 0 {
return def
}
val, ok := properties[key]
if !ok {
return def
}
str, err := strconv.Unquote(val)
if err != nil || len(str) == 0 {
return def
}
return str
}
```
### 6. **Version Information**
**Before:**
```go
ext.Add("x-goctl-version", version.BuildVersion)
ext.Add("x-github", "https://github.com/zeromicro/go-zero")
```
**After:**
```go
ext.Add("x-generator", "go-doc")
ext.Add("x-github", "https://github.com/danielchan-25/go-doc")
```
## 🔧 Implementation Details
### Custom Utilities Created
#### 1. **util.TrimWhiteSpace**
```go
// Replaces: go-zero/tools/goctl/util.TrimWhiteSpace
func TrimWhiteSpace(s string) string {
return strings.Map(func(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}, s)
}
```
#### 2. **util.FieldsAndTrimSpace**
```go
// Replaces: go-zero/tools/goctl/util.FieldsAndTrimSpace
func FieldsAndTrimSpace(s string, fn func(rune) bool) []string {
fields := strings.FieldsFunc(s, fn)
result := make([]string, 0, len(fields))
for _, field := range fields {
trimmed := strings.TrimSpace(field)
if len(trimmed) > 0 {
result = append(result, trimmed)
}
}
return result
}
```
#### 3. **util.String (for ToCamel/Untitle)**
```go
// Replaces: go-zero/tools/goctl/util/stringx
type String struct {
source string
}
func From(s string) String {
return String{source: s}
}
func (s String) ToCamel() string { /* implementation */ }
func (s String) Untitle() string { /* implementation */ }
```
#### 4. **util.MkdirIfNotExist**
```go
// Replaces: go-zero/tools/goctl/util/pathx.MkdirIfNotExist
func MkdirIfNotExist(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return os.MkdirAll(dir, 0755)
}
return nil
}
```
## ✅ Compatibility
### What's Still Compatible?
**API File Format** - 100% compatible with go-zero `.api` files
**Swagger Output** - Generates identical OpenAPI 2.0 specifications
**All Features** - All swagger generation features preserved
**Tag Syntax** - Same tag options (range, enum, default, example, etc.)
### What's Different?
⚠️ **Generated Metadata**
- `x-generator: "go-doc"` instead of `x-goctl-version`
- `x-github` points to go-doc repository
- Build date format remains the same
⚠️ **Module Name**
- Import as `go-doc` not part of go-zero
⚠️ **Binary Name**
- `go-doc` instead of `goctl api plugin -plugin swagger`
## 🚀 Migration Steps for Users
If you were using go-zero's swagger plugin:
### Old Way (go-zero plugin):
```bash
goctl api plugin -plugin swagger -api example.api -dir output
```
### New Way (standalone go-doc):
```bash
go-doc -a example.api -d output
```
## 📝 Benefits of Independence
1. ✅ **Smaller Binary** - Only swagger generation, no full goctl
2. ✅ **Faster Installation** - Fewer dependencies
3. ✅ **Clearer Purpose** - Single responsibility
4. ✅ **Easier Maintenance** - Self-contained codebase
5. ✅ **Flexible Updates** - Independent release cycle
## 🙏 Attribution
This tool was extracted from the excellent [go-zero](https://github.com/zeromicro/go-zero)
project. We are grateful to the go-zero team for their foundational work.
---
**Original Source:** https://github.com/zeromicro/go-zero/tree/master/tools/goctl/api/plugin/swagger
**License:** MIT (both original and this project)

View File

@ -1,279 +0,0 @@
# OpenAPI 3.0 使用指南
## 📚 概述
從 v1.1.0 開始,`go-doc` 支援生成 **Swagger 2.0****OpenAPI 3.0** 兩種規格。
## 🚀 快速開始
### 生成 Swagger 2.0(預設)
```bash
go-doc -a example/example.api -d output
```
### 生成 OpenAPI 3.0
```bash
go-doc -a example/example.api -d output -s openapi3.0
```
### 生成 OpenAPI 3.0 YAML
```bash
go-doc -a example/example.api -d output -s openapi3.0 -y
```
## 🔍 兩種規格的差異
### 1. 版本宣告
**Swagger 2.0:**
```json
{
"swagger": "2.0"
}
```
**OpenAPI 3.0:**
```json
{
"openapi": "3.0.3"
}
```
### 2. 伺服器定義
**Swagger 2.0:**
```json
{
"host": "example.com",
"basePath": "/v1",
"schemes": ["http", "https"]
}
```
**OpenAPI 3.0:**
```json
{
"servers": [
{"url": "http://example.com/v1"},
{"url": "https://example.com/v1"}
]
}
```
### 3. 資料模型定義
**Swagger 2.0:**
```json
{
"definitions": {
"User": {
"type": "object",
"properties": {...}
}
}
}
```
**OpenAPI 3.0:**
```json
{
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {...}
}
}
}
}
```
### 4. 安全定義
**Swagger 2.0:**
```json
{
"securityDefinitions": {
"apiKey": {
"type": "apiKey",
"in": "header",
"name": "Authorization"
}
}
}
```
**OpenAPI 3.0:**
```json
{
"components": {
"securitySchemes": {
"apiKey": {
"type": "apiKey",
"in": "header",
"name": "Authorization"
}
}
}
}
```
### 5. 請求內容
**Swagger 2.0:**
```json
{
"parameters": [
{
"in": "body",
"name": "body",
"schema": {"$ref": "#/definitions/User"}
}
]
}
```
**OpenAPI 3.0:**
```json
{
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/User"}
}
}
}
}
```
### 6. 回應內容
**Swagger 2.0:**
```json
{
"responses": {
"200": {
"description": "Success",
"schema": {"$ref": "#/definitions/User"}
}
}
}
```
**OpenAPI 3.0:**
```json
{
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/User"}
}
}
}
}
}
```
## 🎯 使用範例
### 完整範例:生成多種格式
```bash
# Swagger 2.0 JSON
go-doc -a example/example.api -d output -f api-swagger2
# Swagger 2.0 YAML
go-doc -a example/example.api -d output -f api-swagger2 -y
# OpenAPI 3.0 JSON
go-doc -a example/example.api -d output -f api-openapi3 -s openapi3.0
# OpenAPI 3.0 YAML
go-doc -a example/example.api -d output -f api-openapi3 -s openapi3.0 -y
```
### 使用 Makefile
```bash
# 生成所有範例(包含 Swagger 2.0 和 OpenAPI 3.0
make example
# 僅編譯
make build
# 測試
make test
```
## ⚙️ 轉換邏輯
`go-doc` 的內部轉換流程:
1. **解析 `.api` 檔案** → 使用 go-zero 的 API parser
2. **生成 Swagger 2.0 結構** → 基礎的 OpenAPI 2.0 規格
3. **轉換為 OpenAPI 3.0**(如果指定)→ 自動轉換:
- `definitions``components/schemas`
- `securityDefinitions``components/securitySchemes`
- `host/basePath/schemes``servers`
- body parameters → `requestBody`
4. **序列化輸出** → JSON 或 YAML 格式
## 🔧 技術細節
### 使用的函式庫
- **Swagger 2.0**: `github.com/go-openapi/spec`
- **OpenAPI 3.0**: `github.com/getkin/kin-openapi`
### 轉換函數
- `spec2Swagger()`: 將 go-zero API 轉為 Swagger 2.0
- `convertSwagger2ToOpenAPI3()`: 將 Swagger 2.0 轉為 OpenAPI 3.0
### 型別轉換
```go
// Swagger 2.0 Type
type: "string"
// OpenAPI 3.0 Type
type: &Types{"string"}
```
## 📖 參考資源
- [OpenAPI 3.0 Specification](https://spec.openapis.org/oas/v3.0.3)
- [Swagger 2.0 Specification](https://swagger.io/specification/v2/)
- [go-zero API 規範](https://go-zero.dev/docs/tutorials)
## ❓ 常見問題
### Q: 應該使用哪個版本?
**A:**
- **Swagger 2.0**: 如果需要與舊工具或系統相容
- **OpenAPI 3.0**: 推薦使用,功能更強大且為現代標準
### Q: 可以同時生成兩種格式嗎?
**A:** 可以!執行兩次命令即可:
```bash
go-doc -a api.api -d out -f api-v2
go-doc -a api.api -d out -f api-v3 -s openapi3.0
```
### Q: 轉換有什麼限制嗎?
**A:** 大部分功能都能完整轉換。唯一差異是:
- OpenAPI 3.0 的 server URLs 會根據 schemes 自動生成多個
- 某些 Swagger 2.0 特定的擴展可能不會轉換
### Q: 如何驗證生成的文檔?
**A:** 可以使用以下工具:
- **Swagger Editor**: https://editor.swagger.io/
- **Swagger UI**: 本地運行或線上版本
- **OpenAPI Generator**: 生成客戶端/伺服器代碼來驗證
## 🎉 總結
`go-doc` 現在支援:
- ✅ Swagger 2.0 (OpenAPI 2.0)
- ✅ OpenAPI 3.0
- ✅ JSON 和 YAML 輸出
- ✅ 自動轉換
- ✅ 完整的 go-zero API 特性支援
開始使用:
```bash
go-doc -a your-api.api -d output -s openapi3.0
```

View File

@ -1,103 +0,0 @@
# 🚀 Quick Start Guide
## 安裝
### 從原始碼編譯
```bash
git clone <your-repo>
cd go-doc
make build
```
編譯完成後,執行檔位於 `bin/go-doc`
## 基本使用
### 1⃣ 生成 Swagger 2.0(預設)
```bash
./bin/go-doc -a example/example.api -d output
```
生成檔案:`output/example.json`
### 2⃣ 生成 OpenAPI 3.0
```bash
./bin/go-doc -a example/example.api -d output -s openapi3.0
```
### 3⃣ 生成 YAML 格式
```bash
./bin/go-doc -a example/example.api -d output -y
```
### 4⃣ 自訂檔名
```bash
./bin/go-doc -a example/example.api -d output -f my-api
```
## 常用命令組合
```bash
# Swagger 2.0 JSON
./bin/go-doc -a api.api -d docs
# Swagger 2.0 YAML
./bin/go-doc -a api.api -d docs -y
# OpenAPI 3.0 JSON
./bin/go-doc -a api.api -d docs -s openapi3.0
# OpenAPI 3.0 YAML
./bin/go-doc -a api.api -d docs -s openapi3.0 -y
```
## 完整參數
```
-a, --api string API 檔案路徑(必要)
-d, --dir string 輸出目錄(必要)
-f, --filename string 輸出檔名(不含副檔名)
-s, --spec-version string 規格版本swagger2.0 或 openapi3.0預設swagger2.0
-y, --yaml 生成 YAML 格式預設JSON
-h, --help 顯示說明
-v, --version 顯示版本
```
## 使用 Makefile
```bash
# 編譯
make build
# 生成範例
make example
# 清理
make clean
# 運行測試
make test
# 查看所有命令
make help
```
## 測試所有格式
```bash
./test_all_formats.sh
```
## 查看範例
生成的範例檔案位於:
- `example/test_output/example_swagger2.json` - Swagger 2.0 JSON
- `example/test_output/example_swagger2.yaml` - Swagger 2.0 YAML
- `example/test_output/example_openapi3.json` - OpenAPI 3.0 JSON
- `example/test_output/example_openapi3.yaml` - OpenAPI 3.0 YAML
## 更多資訊
- 完整文檔:`README.md`
- OpenAPI 3.0 指南:`OPENAPI3_GUIDE.md`
- 版本變更:`CHANGELOG.md`

824
README.md
View File

@ -2,99 +2,128 @@
[![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.23-blue)](https://go.dev/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-1.2.0-blue)](CHANGELOG.md)
**go-doc** is a standalone tool that converts [go-zero](https://github.com/zeromicro/go-zero) `.api` files into OpenAPI specifications.
**go-doc** 是一個獨立的命令行工具,專門用於將 [go-zero](https://github.com/zeromicro/go-zero) `.api` 文件轉換為 OpenAPI 規範文檔。
Originally part of the go-zero project, go-doc is now an independent, easy-to-use command-line tool for generating API documentation in both **Swagger 2.0** and **OpenAPI 3.0** formats.
原本是 go-zero 專案的一部分,現在已獨立出來,成為一個易於使用的工具,支援生成 **Swagger 2.0****OpenAPI 3.0** 兩種格式。
## ✨ Features
---
- 🚀 **Standalone Binary** - No dependencies on go-zero runtime
- 📝 **Dual Specification Support** - Generate both **Swagger 2.0** and **OpenAPI 3.0** formats
- 🎯 **Rich Type Support** - Handles structs, arrays, maps, pointers, and nested types
- 🏷️ **Tag-based Configuration** - Support for `json`, `form`, `path`, `header` tags
- 📊 **Advanced Validations** - Range, enum, default, example values
- 🔐 **Security Definitions** - Custom authentication configurations
- 📦 **Multiple Output Formats** - JSON or YAML output
- 🎨 **Definition References** - Optional use of definitions/schemas for cleaner output
- 🔄 **Automatic Conversion** - Seamless conversion from Swagger 2.0 to OpenAPI 3.0
## 📑 目錄
## 📦 Installation
- [✨ 特性](#-特性)
- [📦 安裝](#-安裝)
- [🚀 快速開始](#-快速開始)
- [📖 API 文件格式](#-api-文件格式)
- [🆕 @respdoc 多狀態碼回應](#-respdoc-多狀態碼回應)
- [🔧 進階功能](#-進階功能)
- [🎯 OpenAPI 3.0 vs Swagger 2.0](#-openapi-30-vs-swagger-20)
- [💡 使用範例](#-使用範例)
- [🧪 測試](#-測試)
- [📚 專案結構](#-專案結構)
- [🔄 從 go-zero 遷移](#-從-go-zero-遷移)
- [📝 版本記錄](#-版本記錄)
- [❓ 常見問題](#-常見問題)
- [🤝 貢獻](#-貢獻)
- [📄 授權](#-授權)
### From Source
---
## ✨ 特性
- 🚀 **獨立二進制** - 無需依賴 go-zero 運行時
- 📝 **雙規格支援** - 生成 **Swagger 2.0****OpenAPI 3.0** 格式
- 🎯 **豐富的類型支援** - 處理結構體、陣列、映射、指針和嵌套類型
- 🏷️ **基於標籤的配置** - 支援 `json`、`form`、`path`、`header` 標籤
- 📊 **進階驗證** - 範圍、枚舉、預設值、範例值
- 🔐 **安全定義** - 自定義身份驗證配置
- 📦 **多種輸出格式** - JSON 或 YAML 輸出
- 🎨 **定義引用** - 可選使用 definitions/schemas 使輸出更簡潔
- 🔄 **自動轉換** - Swagger 2.0 到 OpenAPI 3.0 的無縫轉換
- **🆕 多狀態碼回應** - 使用 `@respdoc` 定義多個 HTTP 狀態碼
- **🆕 業務錯誤碼映射** - 將業務錯誤碼映射到不同的錯誤類型OpenAPI 3.0 使用 `oneOf`
---
## 📦 安裝
### 從源碼編譯
```bash
git clone https://github.com/danielchan-25/go-doc.git
git clone <your-repo>
cd go-doc
go build -o bin/go-doc ./cmd/go-doc
make build
```
### Using Go Install
編譯完成後,執行檔位於 `bin/go-doc`
### 使用 Go Install
```bash
go install github.com/danielchan-25/go-doc/cmd/go-doc@latest
```
## 🚀 Quick Start
---
### Basic Usage
## 🚀 快速開始
### 基本使用
```bash
# Generate Swagger 2.0 (default)
# 生成 Swagger 2.0(預設)
go-doc -a example/example.api -d output
# Generate OpenAPI 3.0
# 生成 OpenAPI 3.0(推薦)
go-doc -a example/example.api -d output -s openapi3.0
# Generate YAML format
# 生成 YAML 格式
go-doc -a example/example.api -d output -y
# Generate OpenAPI 3.0 in YAML
# 生成 OpenAPI 3.0 YAML
go-doc -a example/example.api -d output -s openapi3.0 -y
# Specify custom filename
# 自訂檔名
go-doc -a example/example.api -d output -f my-api
```
### Command Line Options
### 命令行選項
```
Flags:
-a, --api string API file path (required)
-d, --dir string Output directory (required)
-f, --filename string Output filename without extension (optional, defaults to API filename)
-h, --help help for go-doc
-s, --spec-version string OpenAPI specification version: swagger2.0 or openapi3.0 (default "swagger2.0")
-v, --version version for go-doc
-y, --yaml Generate YAML format instead of JSON
-a, --api string API 文件路徑(必要)
-d, --dir string 輸出目錄(必要)
-f, --filename string 輸出檔名(不含副檔名)
-h, --help 顯示說明
-s, --spec-version string OpenAPI 規格版本swagger2.0 或 openapi3.0預設swagger2.0
-v, --version 顯示版本
-y, --yaml 生成 YAML 格式預設JSON
```
### Specification Versions
### 使用 Makefile
#### Swagger 2.0 (Default)
```bash
go-doc -a example/example.api -d output
# or explicitly
go-doc -a example/example.api -d output -s swagger2.0
# 編譯
make build
# 生成範例
make example
# 清理
make clean
# 運行測試
make test
# 查看所有命令
make help
```
#### OpenAPI 3.0
```bash
go-doc -a example/example.api -d output -s openapi3.0
```
---
**Key Differences:**
- **Swagger 2.0**: Uses `host`, `basePath`, `schemes` at root level
- **OpenAPI 3.0**: Uses `servers` array with complete URLs
- **Swagger 2.0**: Definitions under `definitions`
- **OpenAPI 3.0**: Schemas under `components/schemas`
- **Swagger 2.0**: Security definitions under `securityDefinitions`
- **OpenAPI 3.0**: Security schemes under `components/securitySchemes`
- **OpenAPI 3.0**: Request bodies are separated from parameters
## 📖 API 文件格式
## 📖 API File Format
### Basic Structure
### 基本結構
```go
syntax = "v1"
@ -127,46 +156,46 @@ service MyAPI {
}
```
### Supported Info Properties
### 支援的 Info 屬性
- `title` - API title
- `description` - API description
- `version` - API version
- `host` - API host (e.g., "api.example.com")
- `basePath` - Base path (e.g., "/v1")
- `schemes` - Protocols (e.g., "http,https")
- `consumes` - Request content types
- `produces` - Response content types
- `contactName`, `contactURL`, `contactEmail` - Contact information
- `licenseName`, `licenseURL` - License information
- `useDefinitions` - Use Swagger definitions (true/false)
- `wrapCodeMsg` - Wrap response in `{code, msg, data}` structure
- `securityDefinitionsFromJson` - Security definitions in JSON format
- `title` - API 標題
- `description` - API 描述
- `version` - API 版本
- `host` - API 主機(如 "api.example.com"
- `basePath` - 基礎路徑(如 "/v1"
- `schemes` - 協議(如 "http,https"
- `consumes` - 請求內容類型
- `produces` - 回應內容類型
- `contactName`, `contactURL`, `contactEmail` - 聯絡資訊
- `licenseName`, `licenseURL` - 授權資訊
- `useDefinitions` - 使用 Swagger definitionstrue/false
- `wrapCodeMsg` - 將回應包裝在 `{code, msg, data}` 結構中
- `securityDefinitionsFromJson` - JSON 格式的安全定義
### Tag Options
### 標籤選項
#### JSON Tags
#### JSON 標籤
```go
type Example {
// Range validation
// 範圍驗證
Age int `json:"age,range=[1:150]"`
// Default value
// 預設值
Status string `json:"status,default=active"`
// Example value
// 範例值
Email string `json:"email,example=user@example.com"`
// Enum values
// 枚舉值
Role string `json:"role,options=admin|user|guest"`
// Optional field
// 可選欄位
Phone string `json:"phone,optional"`
}
```
#### Form Tags
#### Form 標籤
```go
type QueryRequest {
@ -176,7 +205,7 @@ type QueryRequest {
}
```
#### Path Tags
#### Path 標籤
```go
type PathRequest {
@ -184,7 +213,7 @@ type PathRequest {
}
```
#### Header Tags
#### Header 標籤
```go
type HeaderRequest {
@ -192,9 +221,225 @@ type HeaderRequest {
}
```
## 🔧 Advanced Features
---
### Security Definitions
## 🆕 @respdoc 多狀態碼回應
### 概述
`@respdoc` 註解允許您為單個 API 端點定義多個 HTTP 狀態碼的回應,並支援為同一狀態碼定義多種業務錯誤格式。
**重要特性:**
- ✅ 支援多個 HTTP 狀態碼200, 201, 400, 401, 404, 500 等)
- ✅ 支援業務錯誤碼映射(如 300101, 300102 等)
- ✅ **OpenAPI 3.0 使用 `oneOf` 表示多種錯誤格式**
- ✅ Swagger 2.0 在描述中列出所有可能的錯誤
### 基本語法
#### 單一回應類型
```go
service MyAPI {
@doc (
description: "用戶查詢接口"
)
/*
@respdoc-200 (UserResponse) // 成功
@respdoc-400 (ErrorResponse) // 錯誤請求
@respdoc-401 (UnauthorizedError) // 未授權
@respdoc-500 (ServerError) // 服務器錯誤
*/
@handler getUser
get /user/:id (UserRequest) returns (UserResponse)
}
```
#### 多業務錯誤碼(重點功能)⭐
```go
service MyAPI {
@doc (
description: "創建訂單"
)
/*
@respdoc-201 (OrderResponse) // 創建成功
@respdoc-400 (
300101: (ValidationError) 參數驗證失敗
300102: (InsufficientStockError) 庫存不足
300103: (InvalidPaymentError) 支付方式無效
) // 客戶端錯誤
@respdoc-401 (UnauthorizedError) // 未授權
@respdoc-500 (ServerError) // 服務器錯誤
*/
@handler createOrder
post /order (OrderRequest) returns (OrderResponse)
}
```
### 完整範例
```go
syntax = "v1"
info (
title: "訂單 API"
description: "訂單管理系統"
version: "v1"
host: "api.example.com"
basePath: "/v1"
useDefinitions: true
)
type (
CreateOrderReq {
ProductID string `json:"product_id"`
Quantity int `json:"quantity"`
PaymentMethod string `json:"payment_method"`
}
OrderResponse {
OrderID string `json:"order_id"`
Status string `json:"status"`
Total float64 `json:"total"`
}
ValidationError {
Code string `json:"code"`
Message string `json:"message"`
Fields []string `json:"fields"`
}
InsufficientStockError {
Code string `json:"code"`
Message string `json:"message"`
ProductID string `json:"product_id"`
Required int `json:"required"`
Available int `json:"available"`
}
InvalidPaymentError {
Code string `json:"code"`
Message string `json:"message"`
Method string `json:"method"`
}
UnauthorizedError {
Code string `json:"code"`
Message string `json:"message"`
Reason string `json:"reason"`
}
ServerError {
Code string `json:"code"`
Message string `json:"message"`
TraceID string `json:"trace_id"`
}
)
@server (
tags: "order"
)
service OrderAPI {
@doc (
description: "創建訂單"
)
/*
@respdoc-201 (OrderResponse) // 創建成功
@respdoc-400 (
300101: (ValidationError) 參數驗證失敗
300102: (InsufficientStockError) 庫存不足
300103: (InvalidPaymentError) 支付方式無效
) // 客戶端錯誤
@respdoc-401 (UnauthorizedError) // 未授權
@respdoc-500 (ServerError) // 服務器錯誤
*/
@handler createOrder
post /order (CreateOrderReq) returns (OrderResponse)
}
```
### 生成結果對比
#### Swagger 2.0 輸出
```json
{
"paths": {
"/v1/order": {
"post": {
"responses": {
"201": {
"description": "// 創建成功",
"schema": {
"$ref": "#/definitions/OrderResponse"
}
},
"400": {
"description": "客戶端錯誤\n\nPossible errors:\n300101: ValidationError - 參數驗證失敗\n300102: InsufficientStockError - 庫存不足\n300103: InvalidPaymentError - 支付方式無效",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
}
}
}
}
}
```
#### OpenAPI 3.0 輸出(使用 oneOf
```json
{
"paths": {
"/v1/order": {
"post": {
"responses": {
"201": {
"description": "// 創建成功",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrderResponse"
}
}
}
},
"400": {
"description": "客戶端錯誤",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ValidationError"
},
{
"$ref": "#/components/schemas/InsufficientStockError"
},
{
"$ref": "#/components/schemas/InvalidPaymentError"
}
]
}
}
}
}
}
}
}
}
}
```
**優勢:** OpenAPI 3.0 使用 `oneOf` 精確表示多種可能的錯誤類型!
---
## 🔧 進階功能
### 安全定義
```go
info (
@ -212,11 +457,11 @@ info (
authType: apiKey
)
service MyAPI {
// Routes here will use apiKey authentication
// 此處的路由將使用 apiKey 身份驗證
}
```
### Code-Msg Wrapper
### Code-Msg 包裝
```go
info (
@ -225,7 +470,7 @@ info (
)
```
Response will be wrapped as:
回應將被包裝為:
```json
{
"code": 0,
@ -234,48 +479,407 @@ Response will be wrapped as:
}
```
## 📂 Project Structure
---
## 🎯 OpenAPI 3.0 vs Swagger 2.0
### 規格版本
#### Swagger 2.0(預設)
```bash
go-doc -a example/example.api -d output
# 或明確指定
go-doc -a example/example.api -d output -s swagger2.0
```
#### OpenAPI 3.0(推薦)
```bash
go-doc -a example/example.api -d output -s openapi3.0
```
### 主要差異
| 特性 | Swagger 2.0 | OpenAPI 3.0 |
|:---|:---|:---|
| **版本聲明** | `swagger: "2.0"` | `openapi: "3.0.3"` |
| **服務器** | `host`, `basePath`, `schemes` | `servers` 陣列 |
| **Schema 定義** | `definitions` | `components/schemas` |
| **安全定義** | `securityDefinitions` | `components/securitySchemes` |
| **請求體** | `parameters[in=body]` | `requestBody` |
| **多類型回應** | ❌ 不支援 | ✅ `oneOf`/`anyOf`/`allOf` |
### 範例對比
**Swagger 2.0:**
```json
{
"swagger": "2.0",
"host": "example.com",
"basePath": "/v1",
"definitions": {
"User": {...}
},
"securityDefinitions": {
"apiKey": {...}
}
}
```
**OpenAPI 3.0:**
```json
{
"openapi": "3.0.3",
"servers": [
{"url": "http://example.com/v1"},
{"url": "https://example.com/v1"}
],
"components": {
"schemas": {
"User": {...}
},
"securitySchemes": {
"apiKey": {...}
}
}
}
```
---
## 💡 使用範例
### 場景 1RESTful CRUD
```go
/*
@respdoc-200 (UserResponse) // 獲取成功
@respdoc-404 (NotFoundError) // 用戶不存在
@respdoc-401 (UnauthorizedError) // 未授權
*/
@handler getUser
get /user/:id (UserIdReq) returns (UserResponse)
/*
@respdoc-201 (UserResponse) // 創建成功
@respdoc-400 (ValidationError) // 參數錯誤
@respdoc-409 (ConflictError) // 用戶已存在
*/
@handler createUser
post /user (CreateUserReq) returns (UserResponse)
/*
@respdoc-200 (UserResponse) // 更新成功
@respdoc-400 (ValidationError) // 參數錯誤
@respdoc-404 (NotFoundError) // 用戶不存在
*/
@handler updateUser
put /user/:id (UpdateUserReq) returns (UserResponse)
/*
@respdoc-204 // 刪除成功
@respdoc-404 (NotFoundError) // 用戶不存在
*/
@handler deleteUser
delete /user/:id (UserIdReq)
```
### 場景 2複雜業務流程
```go
/*
@respdoc-200 (PaymentResponse) // 支付成功
@respdoc-400 (
400001: (InsufficientBalanceError) 餘額不足
400002: (InvalidCardError) 無效的卡號
400003: (ExpiredCardError) 卡已過期
400004: (DailyLimitError) 超過每日限額
) // 支付失敗
@respdoc-401 (UnauthorizedError) // 未授權
@respdoc-422 (ProcessingError) // 處理中請勿重複提交
*/
@handler processPayment
post /payment (PaymentRequest) returns (PaymentResponse)
```
### 場景 3狀態機轉換
```go
/*
@respdoc-200 (OrderResponse) // 狀態更新成功
@respdoc-400 (
400001: (InvalidStateError) 無效的狀態轉換
400002: (OrderCancelledError) 訂單已取消
400003: (OrderCompletedError) 訂單已完成
) // 狀態轉換失敗
*/
@handler updateOrderStatus
put /order/:id/status (UpdateStatusReq) returns (OrderResponse)
```
---
## 🧪 測試
### 測試所有格式
```bash
# 測試 Swagger 2.0 和 OpenAPI 3.0
./test_all_formats.sh
# 測試 @respdoc 功能
./test_respdoc.sh
```
### 手動測試
```bash
# 生成並檢查
go-doc -a example/example_respdoc.api -d output -s openapi3.0
# 查看生成的文檔
cat output/example_respdoc.json | jq '.paths."/v1/order".post.responses'
```
---
## 📚 專案結構
```
go-doc/
├── cmd/
│ └── go-doc/ # Main entry point
│ └── go-doc/ # 主程式入口
│ └── main.go
├── internal/
│ ├── swagger/ # Core swagger generation logic
│ │ ├── swagger.go
│ │ ├── parameter.go
│ │ ├── path.go
│ ├── swagger/ # 核心邏輯
│ │ ├── respdoc.go # @respdoc 解析
│ │ ├── response.go # 回應生成
│ │ ├── openapi3.go # OpenAPI 3.0 轉換
│ │ ├── swagger.go # Swagger 2.0 生成
│ │ ├── path.go # 路徑處理
│ │ ├── parameter.go # 參數處理
│ │ ├── definition.go # 定義處理
│ │ └── ...
│ └── util/ # Internal utilities
│ └── util/ # 工具函數
│ ├── util.go
│ ├── stringx.go
│ └── pathx.go
├── example/ # Example API files
│ ├── example.api
│ └── example_cn.api
├── example/
│ ├── example.api # 範例 API
│ ├── example_cn.api # 中文範例
│ ├── example_respdoc.api # @respdoc 範例
│ └── test_output/ # 生成的文檔
├── bin/ # 編譯後的執行檔
├── go.mod
└── README.md
├── go.sum
├── Makefile
├── README.md
├── LICENSE
├── CHANGELOG.md
├── test_all_formats.sh # 測試腳本
└── test_respdoc.sh # @respdoc 測試
```
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## 📝 License
This project is licensed under the MIT License.
## 🙏 Acknowledgments
- Original code from [go-zero](https://github.com/zeromicro/go-zero) project
- Built on top of [go-openapi/spec](https://github.com/go-openapi/spec)
## 📧 Contact
For questions or issues, please open an issue on GitHub.
---
Made with ❤️ by extracting and enhancing go-zero's swagger generation capabilities.
## 🔄 從 go-zero 遷移
### 主要變更
#### 1. 模組獨立
**之前go-zero plugin:**
```go
// 屬於 go-zero 內部工具
package swagger // in tools/goctl/api/plugin/swagger/
```
**現在standalone:**
```go
module go-doc
package swagger // in internal/swagger/
```
#### 2. 依賴簡化
| 依賴 | 替換為 | 原因 |
|:---|:---|:---|
| `go-zero/tools/goctl/internal/version` | 自定義版本 | 內部包不可訪問 |
| `go-zero/tools/goctl/util` | `go-doc/internal/util` | 自包含工具 |
| `go-zero/tools/goctl/util/stringx` | `go-doc/internal/util` | 自定義字串處理 |
| `go-zero/tools/goctl/util/pathx` | `go-doc/internal/util` | 自定義路徑處理 |
| `google.golang.org/grpc/metadata` | 原生 map 處理 | 移除外部依賴 |
#### 3. 保留的依賴
- ✅ `github.com/go-openapi/spec` - Swagger 2.0
- ✅ `github.com/getkin/kin-openapi` - OpenAPI 3.0
- ✅ `github.com/spf13/cobra` - CLI 框架
- ✅ `github.com/zeromicro/go-zero/tools/goctl/api/spec` - API 解析
- ✅ `gopkg.in/yaml.v2` - YAML 支援
### 遷移步驟
1. **安裝 go-doc**
```bash
go build -o bin/go-doc ./cmd/go-doc
```
2. **替換命令**
**之前:**
```bash
goctl api plugin -plugin goctl-swagger="swagger -filename api.json" -api user.api -dir .
```
**現在:**
```bash
go-doc -a user.api -d . -f api
```
3. **新功能**
使用 OpenAPI 3.0
```bash
go-doc -a user.api -d . -f api -s openapi3.0
```
使用 @respdoc
```go
/*
@respdoc-200 (Response) // 成功
@respdoc-400 (Error) // 錯誤
*/
```
---
## 📝 版本記錄
### [1.2.0] - 2025-09-30
#### 新增
- **@respdoc 註解支援** - 為單個端點定義多個 HTTP 狀態碼
- **業務錯誤碼映射** - 將不同的業務錯誤碼映射到特定錯誤類型
- **OpenAPI 3.0 oneOf 支援** - 使用 `oneOf` 表示多種可能的錯誤回應
- 測試腳本 `test_respdoc.sh`
- 支援解析 `HandlerDoc` 註釋(在 `@doc()` 外)
#### 增強
- 回應生成現在支援多個狀態碼200, 201, 400, 401, 404, 500 等)
- OpenAPI 3.0 生成器為業務錯誤碼創建 `oneOf` schema
- Swagger 2.0 在描述中列出所有可能的錯誤
### [1.1.0] - 2025-09-30
#### 新增
- **OpenAPI 3.0 支援** - 生成 Swagger 2.0 和 OpenAPI 3.0 規格
- 新增 `--spec-version` (`-s`) 標誌
- 自動從 Swagger 2.0 轉換到 OpenAPI 3.0
- 整合 `github.com/getkin/kin-openapi` 函式庫
### [1.0.0] - 2025-09-30
#### 新增
- 初始發布為獨立工具
- 從 go-zero 專案提取並獨立
- 支援將 go-zero `.api` 文件轉換為 Swagger 2.0 規格
- JSON 和 YAML 輸出格式
- 使用 cobra 的命令行介面
---
## ❓ 常見問題
### Q: 應該使用哪個版本?
**A:**
- **Swagger 2.0**: 如果需要與舊工具或系統相容
- **OpenAPI 3.0**: 推薦使用,功能更強大且為現代標準
### Q: 為什麼 OpenAPI 3.0 使用 oneOf
**A:** `oneOf` 是 OpenAPI 3.0 的標準方式來表示"多選一"的 schema。這讓 API 文檔更精確,工具(如 Swagger UI、代碼生成器可以正確理解和處理多種可能的回應類型。
### Q: Swagger 2.0 為什麼不支援 oneOf
**A:** Swagger 2.0 規範不包含 `oneOf` 關鍵字。我們在描述中列出所有可能的錯誤,並使用第一個類型作為 schema 示例。
### Q: 可以同時生成兩種格式嗎?
**A:** 可以!執行兩次命令即可:
```bash
go-doc -a api.api -d out -f api-v2
go-doc -a api.api -d out -f api-v3 -s openapi3.0
```
### Q: 可以不定義業務錯誤碼嗎?
**A:** 可以!如果只有一種錯誤類型,直接使用單一回應格式:
```go
@respdoc-400 (ValidationError) // 參數錯誤
```
### Q: 如何驗證生成的文檔?
**A:** 可以使用以下工具:
- **Swagger Editor**: https://editor.swagger.io/
- **Swagger UI**: 本地運行或線上版本
- **OpenAPI Generator**: 生成客戶端/伺服器代碼來驗證
---
## 🤝 貢獻
歡迎貢獻!請隨時提交 Pull Request。
### 開發
```bash
# 克隆專案
git clone <your-repo>
cd go-doc
# 安裝依賴
go mod download
# 運行測試
make test
# 編譯
make build
# 運行範例
make example
```
---
## 📄 授權
MIT License
Copyright (c) 2025 Daniel Chan
本專案最初是 [go-zero](https://github.com/zeromicro/go-zero) 專案 swagger 生成插件的一部分。我們感謝 go-zero 團隊的出色工作。
---
## 🎉 總結
**go-doc v1.2.0** 提供了強大的 API 文檔生成功能:
**雙規格支援** - Swagger 2.0 和 OpenAPI 3.0
**多狀態碼回應** - 使用 @respdoc 定義完整的 HTTP 回應
**業務錯誤碼映射** - 支援複雜的錯誤場景
**OpenAPI 3.0 oneOf** - 專業的多類型回應表示
**完整測試** - 自動化測試腳本
**詳細文檔** - 完整的使用指南
開始使用:
```bash
go-doc -a your-api.api -d docs -s openapi3.0
```
查看範例:
- 基本範例:`example/example.api`
- @respdoc 範例:`example/example_respdoc.api`
- 測試腳本:`./test_respdoc.sh`
---
**🌟 如果這個專案對您有幫助,請給個 Star**

View File

@ -9,7 +9,7 @@ import (
)
var (
version = "1.1.0"
version = "1.2.0"
commit = "dev"
date = "unknown"
)

View File

@ -34,39 +34,39 @@ type (
Id int `json:"id,example=10"`
Name string `json:"name,example=keson.an"`
}
// 錯誤響應
ErrorResponse struct {
Code string `json:"code" swagger:"description:錯誤代碼"`
Message string `json:"message" swagger:"description:錯誤訊息"`
Details string `json:"details,omitempty" swagger:"description:詳細信息"`
}
// 具體錯誤類型
ValidationError struct {
Code string `json:"code" swagger:"description:驗證錯誤代碼"`
Message string `json:"message" swagger:"description:驗證錯誤訊息"`
Fields []string `json:"fields" swagger:"description:錯誤字段列表"`
}
InsufficientStockError struct {
Code string `json:"code" swagger:"description:庫存不足錯誤代碼"`
Message string `json:"message" swagger:"description:庫存不足錯誤訊息"`
ProductID string `json:"product_id" swagger:"description:商品ID"`
Required int `json:"required" swagger:"description:需要數量"`
Available int `json:"available" swagger:"description:可用數量"`
}
InvalidPaymentError struct {
Code string `json:"code" swagger:"description:支付錯誤代碼"`
Message string `json:"message" swagger:"description:支付錯誤訊息"`
Method string `json:"method" swagger:"description:支付方式"`
}
UnauthorizedError struct {
Code string `json:"code" swagger:"description:未授權錯誤代碼"`
Message string `json:"message" swagger:"description:未授權錯誤訊息"`
Reason string `json:"reason" swagger:"description:未授權原因"`
}
// 錯誤響應
ErrorResponse {
Code string `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
// 具體錯誤類型
ValidationError {
Code string `json:"code"`
Message string `json:"message"`
Fields []string `json:"fields"`
}
InsufficientStockError {
Code string `json:"code"`
Message string `json:"message"`
ProductID string `json:"product_id"`
Required int `json:"required"`
Available int `json:"available"`
}
InvalidPaymentError {
Code string `json:"code"`
Message string `json:"message"`
Method string `json:"method"`
}
UnauthorizedError {
Code string `json:"code"`
Message string `json:"message"`
Reason string `json:"reason"`
}
)
@server (
@ -79,7 +79,6 @@ type (
service Swagger {
@doc (
description: "query 接口"
bizCodeEnumDescription: " 1003-用不存在<br>1004-非法操作" // 接口级别业务错误码枚举描述会覆盖全局的业务错误码json 格式,key 为业务错误码value 为该错误码的描述,仅当 wrapCodeMsg 为 true 且 useDefinitions 为 false 时生效
)
/*
@respdoc-201 (QueryResp) // 創建成功

View File

@ -1,39 +0,0 @@
#!/bin/bash
# 1. 检查并安装 swagger
if ! command -v swagger &> /dev/null; then
echo "swagger 未安装,正在从 GitHub 安装..."
# 这里使用 go-swagger 的安装方式
go install github.com/go-swagger/go-swagger/cmd/swagger@latest
if [ $? -ne 0 ]; then
echo "安装 swagger 失败"
exit 1
fi
echo "swagger 安装成功"
else
echo "swagger 已安装"
fi
mkdir bin output
export GOBIN=$(pwd)/bin
# 2. 安装最新版 goctl
go install ../../..
if [ $? -ne 0 ]; then
echo "安装 goctl 失败"
exit 1
fi
echo "goctl 安装成功"
# 3. 生成 swagger 文件
echo "正在生成 swagger 文件..."
./bin/goctl api swagger --api example_cn.api --dir output
if [ $? -ne 0 ]; then
echo "生成 swagger 文件失败"
exit 1
fi
# 4. 启动 swagger 服务
echo "启动 swagger 服务..."
swagger serve ./output/example_cn.json

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

View File

@ -1,39 +0,0 @@
#!/bin/bash
# 1. Check and install swagger if not exists
if ! command -v swagger &> /dev/null; then
echo "swagger not found, installing from GitHub..."
# Using go-swagger installation method
go install github.com/go-swagger/go-swagger/cmd/swagger@latest
if [ $? -ne 0 ]; then
echo "Failed to install swagger"
exit 1
fi
echo "swagger installed successfully"
else
echo "swagger already installed"
fi
mkdir bin output
export GOBIN=$(pwd)/bin
# 2. Install latest goctl version
go install ../../..
if [ $? -ne 0 ]; then
echo "Failed to install goctl"
exit 1
fi
echo "goctl installed successfully"
# 3. Generate swagger files
echo "Generating swagger files..."
./bin/goctl api swagger --api example.api --dir output
if [ $? -ne 0 ]; then
echo "Failed to generate swagger files"
exit 1
fi
# 4. Start swagger server
echo "Starting swagger server..."
swagger serve ./output/example.json

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View File

@ -1,91 +0,0 @@
#!/bin/bash
# 检查Docker是否运行的函数
is_docker_running() {
if ! docker info >/dev/null 2>&1; then
return 1 # Docker未运行
else
return 0 # Docker正在运行
fi
}
mkdir bin output
export GOBIN=$(pwd)/bin
# 1. 检查并安装Docker如果不存在
if ! command -v docker &> /dev/null; then
echo "未检测到Docker正在尝试安装..."
# 使用官方脚本安装Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
rm get-docker.sh
# 验证安装
if ! command -v docker &> /dev/null; then
echo "Docker安装失败"
exit 1
fi
# 将当前用户加入docker组可能需要重新登录
sudo usermod -aG docker $USER
echo "Docker安装成功。您可能需要注销并重新登录使更改生效。"
else
echo "Docker已安装"
fi
# 2. 安装最新版goctl
go install ../../..
if [ $? -ne 0 ]; then
echo "goctl安装失败"
exit 1
fi
echo "goctl 安装成功"
# 3. 生成swagger文件
echo "正在生成swagger文件..."
./bin/goctl api swagger --api example_cn.api --dir output
if [ $? -ne 0 ]; then
echo "swagger文件生成失败"
exit 1
fi
# 检查Docker是否运行
if ! is_docker_running; then
echo "Docker未运行请先启动Docker服务"
exit 1
fi
# 4. 清理现有的swagger-ui容器
echo "正在清理现有的swagger-ui容器..."
docker rm -f swagger-ui 2>/dev/null && echo "已移除现有的swagger-ui容器"
# 5. 在Docker中运行swagger-ui
echo "正在启动swagger-ui容器..."
docker run -d --name swagger-ui -p 8080:8080 \
-e SWAGGER_JSON=/tmp/example.json \
-v $(pwd)/output/example_cn.json:/tmp/example.json \
swaggerapi/swagger-ui
if [ $? -ne 0 ]; then
echo "swagger-ui容器启动失败"
exit 1
fi
# 等待1秒确保服务就绪
echo "等待swagger-ui初始化..."
sleep 1
# 显示访问信息并尝试打开浏览器
SWAGGER_URL="http://localhost:8080"
echo -e "\nSwagger UI 已准备就绪,访问地址: \033[1;34m${SWAGGER_URL}\033[0m"
echo "正在尝试在默认浏览器中打开..."
# 跨平台打开浏览器
case "$(uname -s)" in
Linux*) xdg-open "$SWAGGER_URL";;
Darwin*) open "$SWAGGER_URL";;
CYGWIN*|MINGW*|MSYS*) start "$SWAGGER_URL";;
*) echo "无法在当前操作系统自动打开浏览器";;
esac

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View File

@ -1,80 +0,0 @@
#!/bin/bash
is_docker_running() {
if ! docker info >/dev/null 2>&1; then
return 1 # Docker is not running
else
return 0 # Docker is running
fi
}
mkdir bin output
export GOBIN=$(pwd)/bin
# 1. Check and install Docker if not exists
if ! command -v docker &> /dev/null; then
echo "Docker not found, attempting to install..."
# Install Docker using official installation script
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
rm get-docker.sh
# Verify installation
if ! command -v docker &> /dev/null; then
echo "Failed to install Docker"
exit 1
fi
# Add current user to docker group (may require logout/login)
sudo usermod -aG docker $USER
echo "Docker installed successfully. You may need to logout and login again for changes to take effect."
else
echo "Docker already installed"
fi
# 2. Install latest goctl version
go install ../../..
if [ $? -ne 0 ]; then
echo "Failed to install goctl"
exit 1
fi
echo "goctl installed successfully"
# 3. Generate swagger files
echo "Generating swagger files..."
./bin/goctl api swagger --api example.api --dir output
if [ $? -ne 0 ]; then
echo "Failed to generate swagger files"
exit 1
fi
if ! is_docker_running; then
echo "Docker is not running, Pls start Docker first"
fi
# 4. Clean up any existing swagger-ui container
echo "Cleaning up existing swagger-ui containers..."
docker rm -f swagger-ui 2>/dev/null && echo "Removed existing swagger-ui container"
# 5. Run swagger-ui in Docker
echo "Starting swagger-ui in Docker..."
docker run -d --name swagger-ui -p 8080:8080 -e SWAGGER_JSON=/tmp/example.json -v $(pwd)/output/example.json:/tmp/example.json swaggerapi/swagger-ui
if [ $? -ne 0 ]; then
echo "Failed to start swagger-ui container"
exit 1
fi
echo "Waiting for swagger-ui to initialize..."
sleep 1
SWAGGER_URL="http://localhost:8080"
echo -e "\nSwagger UI is ready at: \033[1;34m${SWAGGER_URL}\033[0m"
echo "Opening in default browser..."
case "$(uname -s)" in
Linux*) xdg-open "$SWAGGER_URL";;
Darwin*) open "$SWAGGER_URL";;
CYGWIN*|MINGW*|MSYS*) start "$SWAGGER_URL";;
*) echo "System not supported";;
esac

View File

@ -7,6 +7,7 @@ import (
)
type Context struct {
Api *spec.ApiSpec // API 規範,用於查找類型定義
UseDefinitions bool
WrapCodeMsg bool
BizCodeEnumDescription string
@ -16,13 +17,16 @@ func testingContext(_ *testing.T) Context {
return Context{}
}
func contextFromApi(info spec.Info) Context {
if len(info.Properties) == 0 {
return Context{}
func contextFromApi(api *spec.ApiSpec) Context {
if len(api.Info.Properties) == 0 {
return Context{
Api: api,
}
}
return Context{
UseDefinitions: getBoolFromKVOrDefault(info.Properties, propertyKeyUseDefinitions, defaultValueOfPropertyUseDefinition),
WrapCodeMsg: getBoolFromKVOrDefault(info.Properties, propertyKeyWrapCodeMsg, false),
BizCodeEnumDescription: getStringFromKVOrDefault(info.Properties, propertyKeyBizCodeEnumDescription, "business code"),
Api: api,
UseDefinitions: getBoolFromKVOrDefault(api.Info.Properties, propertyKeyUseDefinitions, defaultValueOfPropertyUseDefinition),
WrapCodeMsg: getBoolFromKVOrDefault(api.Info.Properties, propertyKeyWrapCodeMsg, false),
BizCodeEnumDescription: getStringFromKVOrDefault(api.Info.Properties, propertyKeyBizCodeEnumDescription, "business code"),
}
}

View File

@ -266,17 +266,91 @@ func convertResponse(response *spec.Response, produces []string) *openapi3.Respo
if response.Schema != nil {
newResp.Content = make(openapi3.Content)
for _, contentType := range produces {
mediaType := &openapi3.MediaType{
Schema: convertSchemaToSchemaRef(response.Schema),
// 檢查是否有業務錯誤碼x-biz-codes
bizCodes, hasBizCodes := response.Extensions["x-biz-codes"]
if hasBizCodes {
// 使用 oneOf 來表示多個可能的回應類型
for _, contentType := range produces {
mediaType := &openapi3.MediaType{
Schema: createOneOfSchemaFromBizCodes(bizCodes),
}
newResp.Content[contentType] = mediaType
}
} else {
// 普通回應
for _, contentType := range produces {
mediaType := &openapi3.MediaType{
Schema: convertSchemaToSchemaRef(response.Schema),
}
newResp.Content[contentType] = mediaType
}
newResp.Content[contentType] = mediaType
}
}
return &openapi3.ResponseRef{Value: newResp}
}
// createOneOfSchemaFromBizCodes 從業務錯誤碼創建 oneOf schema
func createOneOfSchemaFromBizCodes(bizCodesInterface interface{}) *openapi3.SchemaRef {
bizCodes, ok := bizCodesInterface.(map[string]BizCode)
if !ok {
// 嘗試轉換為 map[string]interface{}
bizCodesMap, ok := bizCodesInterface.(map[string]interface{})
if !ok {
return nil
}
// 轉換為 BizCode
bizCodes = make(map[string]BizCode)
for code, val := range bizCodesMap {
if valMap, ok := val.(map[string]interface{}); ok {
bizCode := BizCode{
Code: code,
}
if schema, ok := valMap["Schema"].(string); ok {
bizCode.Schema = schema
}
if desc, ok := valMap["Description"].(string); ok {
bizCode.Description = desc
}
bizCodes[code] = bizCode
}
}
}
if len(bizCodes) == 0 {
return nil
}
// 創建 oneOf schema
oneOfSchemas := make([]*openapi3.SchemaRef, 0, len(bizCodes))
for code, bizCode := range bizCodes {
// 創建每個業務錯誤碼的 schema 引用
ref := "#/components/schemas/" + bizCode.Schema
schemaRef := &openapi3.SchemaRef{
Ref: ref,
}
// 添加描述作為擴展
if bizCode.Description != "" {
schemaRef.Value = &openapi3.Schema{
Description: "業務錯誤碼 " + code + ": " + bizCode.Description,
}
}
oneOfSchemas = append(oneOfSchemas, schemaRef)
}
// 返回包含 oneOf 的 schema
return &openapi3.SchemaRef{
Value: &openapi3.Schema{
OneOf: oneOfSchemas,
},
}
}
// convertSchemaToSchemaRef converts a Swagger 2.0 schema to OpenAPI 3.0 SchemaRef
func convertSchemaToSchemaRef(schema *spec.Schema) *openapi3.SchemaRef {
if schema == nil {

View File

@ -89,7 +89,7 @@ func spec2Path(ctx Context, group apiSpec.Group, route apiSpec.Route) spec.PathI
Deprecated: getBoolFromKVOrDefault(route.AtDoc.Properties, propertyKeyDeprecated, false),
Parameters: parametersFromType(ctx, route.Method, route.RequestType),
Security: security,
Responses: jsonResponseFromType(ctx, route.AtDoc, route.ResponseType),
Responses: jsonResponseFromTypeWithDocs(ctx, route.AtDoc, route.ResponseType, route.HandlerDoc),
},
}
externalDocsDescription := getStringFromKVOrDefault(route.AtDoc.Properties, propertyKeyExternalDocsDescription, "")

249
internal/swagger/respdoc.go Normal file
View File

@ -0,0 +1,249 @@
package swagger
import (
"fmt"
"regexp"
"strconv"
"strings"
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
// RespDoc 表示一個回應文檔定義
type RespDoc struct {
StatusCode int // HTTP 狀態碼
Schema string // 回應類型名稱(單一回應)
BizCodes map[string]BizCode // 業務錯誤碼映射(多個回應)
Description string // 描述
}
// BizCode 表示業務錯誤碼定義
type BizCode struct {
Code string // 業務錯誤碼(如 300101
Schema string // 對應的類型名稱
Description string // 描述
}
var (
// @respdoc-200 (TypeName) description
respdocSimpleRegex = regexp.MustCompile(`@respdoc-(\d+)\s+\((\w+)\)(?:\s+(.+))?`)
// 業務錯誤碼格式: 300101: (ValidationError) description
bizCodeRegex = regexp.MustCompile(`(\d+):\s+\((\w+)\)(?:\s+(.+))?`)
)
// parseRespDocsFromDoc 從 Doc 數組中解析所有 @respdoc 定義
func parseRespDocsFromDoc(doc apiSpec.Doc) []RespDoc {
// Doc 是字符串數組,合併為一個文本
var text string
for _, line := range doc {
text += line + "\n"
}
return parseRespDocsFromText(text)
}
// parseRespDocs 從 AtDoc 註解中解析所有 @respdoc 定義
func parseRespDocs(atDoc apiSpec.AtDoc) []RespDoc {
return parseRespDocsFromText(atDoc.Text)
}
// parseRespDocsFromText 從文本中解析所有 @respdoc 定義
func parseRespDocsFromText(text string) []RespDoc {
var respDocs []RespDoc
if text == "" {
return respDocs
}
lines := strings.Split(text, "\n")
var currentRespDoc *RespDoc
var inMultiLine bool
for _, line := range lines {
line = strings.TrimSpace(line)
// 跳過空行和純註釋符號
if line == "" || line == "/*" || line == "*/" || strings.HasPrefix(line, "//") {
continue
}
// 移除行首的註釋符號
line = strings.TrimPrefix(line, "*")
line = strings.TrimSpace(line)
// 檢查是否為 @respdoc 開頭
if strings.HasPrefix(line, "@respdoc-") {
// 保存上一個 respDoc
if currentRespDoc != nil {
respDocs = append(respDocs, *currentRespDoc)
}
// 解析新的 respdoc
currentRespDoc = parseRespDocLine(line)
// 檢查是否為多行格式(含有左括號但不含右括號,或右括號後還有註釋符號)
if strings.Contains(line, "(") && !strings.Contains(line, ")") {
inMultiLine = true
} else {
inMultiLine = false
}
// 如果是單行格式,直接添加
if !inMultiLine && currentRespDoc != nil {
respDocs = append(respDocs, *currentRespDoc)
currentRespDoc = nil
}
} else if inMultiLine && currentRespDoc != nil {
// 處理多行格式中的業務錯誤碼
if strings.Contains(line, ":") && strings.Contains(line, "(") {
bizCode := parseBizCodeLine(line)
if bizCode != nil {
if currentRespDoc.BizCodes == nil {
currentRespDoc.BizCodes = make(map[string]BizCode)
}
currentRespDoc.BizCodes[bizCode.Code] = *bizCode
}
}
// 檢查是否為多行結束(含有右括號和註釋)
if strings.Contains(line, ")") && strings.Contains(line, "//") {
// 提取描述
parts := strings.SplitN(line, "//", 2)
if len(parts) == 2 {
currentRespDoc.Description = strings.TrimSpace(parts[1])
}
respDocs = append(respDocs, *currentRespDoc)
currentRespDoc = nil
inMultiLine = false
}
}
}
// 添加最後一個 respDoc
if currentRespDoc != nil {
respDocs = append(respDocs, *currentRespDoc)
}
return respDocs
}
// parseRespDocLine 解析單行 @respdoc 定義
func parseRespDocLine(line string) *RespDoc {
// 移除 @respdoc- 前綴
line = strings.TrimPrefix(line, "@respdoc-")
// 提取狀態碼
parts := strings.SplitN(line, " ", 2)
if len(parts) == 0 {
return nil
}
statusCode, err := strconv.Atoi(strings.TrimSpace(parts[0]))
if err != nil {
return nil
}
respDoc := &RespDoc{
StatusCode: statusCode,
}
if len(parts) < 2 {
return respDoc
}
rest := strings.TrimSpace(parts[1])
// 檢查是否為簡單格式: (TypeName) description
matches := respdocSimpleRegex.FindStringSubmatch("@respdoc-" + line)
if len(matches) >= 3 {
respDoc.Schema = matches[2]
if len(matches) >= 4 {
respDoc.Description = strings.TrimSpace(matches[3])
}
return respDoc
}
// 檢查是否為多行格式開始: (
if strings.HasPrefix(rest, "(") && !strings.Contains(rest, ")") {
// 多行格式,等待後續行處理
return respDoc
}
return respDoc
}
// parseBizCodeLine 解析業務錯誤碼行
func parseBizCodeLine(line string) *BizCode {
matches := bizCodeRegex.FindStringSubmatch(line)
if len(matches) < 3 {
return nil
}
bizCode := &BizCode{
Code: matches[1],
Schema: matches[2],
}
if len(matches) >= 4 {
bizCode.Description = strings.TrimSpace(matches[3])
}
return bizCode
}
// findTypeByName 在 API 規範中查找類型定義
func findTypeByName(api *apiSpec.ApiSpec, typeName string) apiSpec.Type {
if typeName == "" {
return nil
}
for _, tp := range api.Types {
if defStruct, ok := tp.(apiSpec.DefineStruct); ok {
if defStruct.Name() == typeName {
return tp
}
}
}
return nil
}
// getHTTPStatusText 獲取 HTTP 狀態碼的標準文本
func getHTTPStatusText(code int) string {
switch code {
case 200:
return "OK"
case 201:
return "Created"
case 202:
return "Accepted"
case 204:
return "No Content"
case 400:
return "Bad Request"
case 401:
return "Unauthorized"
case 403:
return "Forbidden"
case 404:
return "Not Found"
case 405:
return "Method Not Allowed"
case 409:
return "Conflict"
case 422:
return "Unprocessable Entity"
case 429:
return "Too Many Requests"
case 500:
return "Internal Server Error"
case 502:
return "Bad Gateway"
case 503:
return "Service Unavailable"
case 504:
return "Gateway Timeout"
default:
return fmt.Sprintf("HTTP %d", code)
}
}

View File

@ -2,12 +2,31 @@ package swagger
import (
"net/http"
"strings"
"github.com/go-openapi/spec"
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses {
return jsonResponseFromTypeWithDocs(ctx, atDoc, tp, nil)
}
func jsonResponseFromTypeWithDocs(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type, handlerDoc apiSpec.Doc) *spec.Responses {
// 首先檢查是否有 @respdoc 註解(從 handlerDoc 或 atDoc
var respDocs []RespDoc
if len(handlerDoc) > 0 {
respDocs = parseRespDocsFromDoc(handlerDoc)
}
if len(respDocs) == 0 {
respDocs = parseRespDocs(atDoc)
}
if len(respDocs) > 0 {
return jsonResponseFromRespDocs(ctx, atDoc, respDocs, tp)
}
// 原有邏輯:使用默認的 200 回應
if tp == nil {
return &spec.Responses{
ResponsesProps: spec.ResponsesProps{
@ -63,3 +82,120 @@ func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *sp
},
}
}
// jsonResponseFromRespDocs 從 @respdoc 註解生成多狀態碼回應
func jsonResponseFromRespDocs(ctx Context, atDoc apiSpec.AtDoc, respDocs []RespDoc, defaultType apiSpec.Type) *spec.Responses {
responses := &spec.Responses{
ResponsesProps: spec.ResponsesProps{
StatusCodeResponses: make(map[int]spec.Response),
},
}
for _, respDoc := range respDocs {
// 如果有單一 Schema直接使用
if respDoc.Schema != "" {
tp := findTypeByName(ctx.Api, respDoc.Schema)
if tp == nil && respDoc.StatusCode == http.StatusOK && defaultType != nil {
// 如果找不到類型且是 200使用默認類型
tp = defaultType
}
response := createResponseFromType(ctx, atDoc, tp, respDoc.Description)
responses.StatusCodeResponses[respDoc.StatusCode] = response
continue
}
// 如果有多個業務錯誤碼BizCodes為 Swagger 2.0 創建一個通用回應
// OpenAPI 3.0 會在 convertSwagger2ToOpenAPI3 中特殊處理
if len(respDoc.BizCodes) > 0 {
// 對於 Swagger 2.0,我們使用 oneOf/anyOf 概念的註釋
// 但 Swagger 2.0 不支持 oneOf所以使用第一個類型作為示例
// 並在描述中列出所有可能的類型
var firstType apiSpec.Type
descriptions := []string{}
for code, bizCode := range respDoc.BizCodes {
tp := findTypeByName(ctx.Api, bizCode.Schema)
if firstType == nil && tp != nil {
firstType = tp
}
desc := code + ": " + bizCode.Schema
if bizCode.Description != "" {
desc += " - " + bizCode.Description
}
descriptions = append(descriptions, desc)
}
description := respDoc.Description
if len(descriptions) > 0 {
if description != "" {
description += "\n\n"
}
description += "Possible errors:\n" + strings.Join(descriptions, "\n")
}
response := createResponseFromType(ctx, atDoc, firstType, description)
// 在 VendorExtensible 中存儲業務錯誤碼信息,供 OpenAPI 3.0 使用
if response.Extensions == nil {
response.Extensions = make(spec.Extensions)
}
response.Extensions["x-biz-codes"] = respDoc.BizCodes
responses.StatusCodeResponses[respDoc.StatusCode] = response
}
}
// 如果沒有定義 200 回應,添加默認的
if _, ok := responses.StatusCodeResponses[http.StatusOK]; !ok && defaultType != nil {
responses.StatusCodeResponses[http.StatusOK] = createResponseFromType(ctx, atDoc, defaultType, "")
}
return responses
}
// createResponseFromType 從類型創建回應對象
func createResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type, description string) spec.Response {
if tp == nil {
return spec.Response{
ResponseProps: spec.ResponseProps{
Description: description,
Schema: &spec.Schema{},
},
}
}
props := spec.SchemaProps{
AdditionalProperties: mapFromGoType(ctx, tp),
Items: itemsFromGoType(ctx, tp),
}
if ctx.UseDefinitions {
structName, ok := containsStruct(tp)
if ok {
props.Ref = spec.MustCreateRef(getRefName(structName))
return spec.Response{
ResponseProps: spec.ResponseProps{
Description: description,
Schema: &spec.Schema{
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
},
},
}
}
}
p, _ := propertiesFromType(ctx, tp)
props.Type = typeFromGoType(ctx, tp)
props.Properties = p
return spec.Response{
ResponseProps: spec.ResponseProps{
Description: description,
Schema: &spec.Schema{
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
},
},
}
}

View File

@ -10,7 +10,7 @@ import (
)
func spec2Swagger(api *apiSpec.ApiSpec) (*spec.Swagger, error) {
ctx := contextFromApi(api.Info)
ctx := contextFromApi(api)
extensions, info := specExtensions(api.Info)
var securityDefinitions spec.SecurityDefinitions
securityDefinitionsFromJson := getStringFromKVOrDefault(api.Info.Properties, "securityDefinitionsFromJson", `{}`)

50
test_respdoc.sh Executable file
View File

@ -0,0 +1,50 @@
#!/bin/bash
# 測試 respdoc 功能
set -e
echo "🧪 測試 @respdoc 多狀態碼回應功能"
echo "================================"
OUTPUT_DIR="example/test_output"
mkdir -p "$OUTPUT_DIR"
echo ""
echo "📝 測試 1: Swagger 2.0 with @respdoc"
./bin/go-doc -a example/example_respdoc.api -d "$OUTPUT_DIR" -f respdoc_swagger2
CODES=$(jq -r '.paths."/v1/query".get.responses | keys | join(", ")' "$OUTPUT_DIR/respdoc_swagger2.json")
echo "✅ 狀態碼: $CODES"
echo ""
echo "📝 測試 2: OpenAPI 3.0 with @respdoc"
./bin/go-doc -a example/example_respdoc.api -d "$OUTPUT_DIR" -f respdoc_openapi3 -s openapi3.0
CODES=$(jq -r '.paths."/v1/query".get.responses | keys | join(", ")' "$OUTPUT_DIR/respdoc_openapi3.json")
echo "✅ 狀態碼: $CODES"
echo ""
echo "📝 測試 3: 檢查 400 回應的 oneOfOpenAPI 3.0"
HAS_ONEOF=$(jq '.paths."/v1/query".get.responses."400".content."application/json".schema | has("oneOf")' "$OUTPUT_DIR/respdoc_openapi3.json")
if [ "$HAS_ONEOF" = "true" ]; then
TYPES=$(jq -r '.paths."/v1/query".get.responses."400".content."application/json".schema.oneOf | map(."$ref" | split("/") | last) | join(", ")' "$OUTPUT_DIR/respdoc_openapi3.json")
echo "✅ 使用 oneOf包含類型: $TYPES"
else
echo "❌ 沒有使用 oneOf"
exit 1
fi
echo ""
echo "📝 測試 4: 檢查所有錯誤類型的 schema"
SCHEMAS=$(jq -r '.components.schemas | keys | map(select(. | contains("Error"))) | join(", ")' "$OUTPUT_DIR/respdoc_openapi3.json")
echo "✅ 錯誤類型 schemas: $SCHEMAS"
echo ""
echo "📝 測試 5: Swagger 2.0 檢查業務錯誤碼描述"
DESC=$(jq -r '.paths."/v1/query".get.responses."400".description' "$OUTPUT_DIR/respdoc_swagger2.json" | head -3)
echo "✅ 400 描述:"
echo "$DESC"
echo ""
echo "🎉 所有 @respdoc 測試通過!"
echo ""
echo "生成的檔案:"
ls -lh "$OUTPUT_DIR"/respdoc_*