11 KiB
y94# Portal API Gateway (PGW)
基於 go-zero 的 API Gateway,提供統一 HTTP JSON 回應、8 碼業務錯誤碼,以及由 .api 定義驅動的程式碼與 OpenAPI 3.0 文件生成。
功能概覽
- REST API:go-zero
rest服務,預設http://0.0.0.0:8888 - 統一回應格式:成功 / 失敗皆為
Statusenvelope(code、message、data、error) - 結構化錯誤:8 碼
SSCCCDDD(Scope + Category + Detail),可映射 HTTP / gRPC - 程式碼生成:
goctl+ 自訂 handler 模板(自動response.Write) - API 文件:
go-doc由同一套.api產出 OpenAPI 3.0 YAML
環境需求
| 工具 | 說明 |
|---|---|
| Go | ≥ 1.26.1(見 go.mod) |
| goctl | go-zero 程式碼生成 |
| Make | 執行 gen-api / gen-doc(可選) |
安裝 goctl(若尚未安裝):
go install github.com/zeromicro/go-zero/tools/goctl@latest
快速開始
# 克隆後進入專案目錄
cd gateway
# 下載依賴
go mod download
# 依 .api 生成 handler / logic / types(若已生成可跳過)
make gen-api
# 啟動服務
go run gateway.go -f etc/gateway.yaml
健康檢查:
curl -s http://127.0.0.1:8888/api/v1/health | jq
預期回應:
{
"code": 0,
"message": "SUCCESS",
"data": { "pong": "ok" }
}
常用指令
| 指令 | 說明 |
|---|---|
make gen-api |
由 generate/api/gateway.api 生成 Go 程式碼(使用 generate/goctl 模板) |
make gen-doc |
生成 docs/openapi/gateway.yaml(OpenAPI 3.0) |
go run gateway.go -f etc/gateway.yaml |
啟動 Gateway |
go test ./... |
執行測試 |
專案結構
gateway/
├── gateway.go # 程式入口
├── etc/gateway.yaml # 服務設定(埠號等)
├── generate/
│ ├── api/ # API 定義(goctl + go-doc 共用)
│ ├── goctl/api/handler.tpl # 自訂 handler 模板 → response.Write
│ └── doc-generate/ # go-doc(.api → OpenAPI)
├── internal/
│ ├── handler/ # HTTP handler(goctl 生成,response.Write)
│ ├── logic/ # HTTP 編排:types ↔ usecase DTO,只回 data + error
│ ├── types/ # goctl 生成的請求/回應型別
│ ├── response/ # 統一 JSON 回應封裝
│ ├── svc/ # ServiceContext:組裝各模組 UseCase
│ ├── config/ # go-zero RestConf 等服務設定
│ ├── library/errors/ # 全專案唯一結構化錯誤(8 碼 SSCCCDDD)
│ ├── library/validate/ # struct 驗證(go-playground/validator + 翻譯)
│ ├── library/errlog/ # 可選 slog 日誌輔助
│ └── model/ # 業務模型根目錄(見下方)
│ └── {module}/ # 例如 member/、order/
└── docs/
├── model.md # model/{module} 內 entity / repository / usecase 細節
└── openapi/ # gen-doc 輸出(預設 .gitignore)
業務模型(internal/model/{module}/)
業務領域程式碼一律放在 internal/model/ 底下,再依模組分子目錄(例如 internal/model/member/)。不使用 pkg/。HTTP 層(handler / logic / types)仍由 goctl 管理;持久化與業務規則在 model/{module}/ 內。
internal/model/
└── member/ # 模組名稱(member、order…)
├── entity/ # MongoDB document(Account、User…)
├── enum/ # 列舉 / 值物件(Platform、Status…)
├── repository/ # Repository 介面 + 實作(Mongo / cache)
├── usecase/ # UseCase 介面、Request/Response DTO、實作
├── config/ # 模組用設定 struct(不含 go-zero RestConf)
├── errors.go # 模組 sentinel(如 ErrNotFound),非第二套錯誤碼
├── const.go # 模組常數
├── redis.go # Redis key 命名與 helper
└── mock/ # mockgen 產物,勿手改
依賴方向:
handler → logic → model/{module}/usecase(介面)
↓
model/{module}/repository(介面)
↓
entity / enum / MongoDB·Redis
entity、enum 不依賴 repository、usecase、logic
logic 不 import model/{module}/repository 或 model/{module}/entity
請求鏈:
HTTP Request
→ handler(response.Write)
→ logic(types ↔ usecase DTO)
→ model/{module}/usecase
→ model/{module}/repository
→ MongoDB / Redis
更細的 entity / repository / usecase 撰寫規則見 docs/model.md。
更細的說明見各子目錄 README:
- generate/api/README.md —
.api與@respdoc約定 - internal/response/README.md — Handler / Logic 分工
- internal/library/errors/README.md — 錯誤碼與 HTTP 對照
- docs/model.md —
internal/model/{module}分層(entity / repository / usecase)
開發約定
1. 新增 API 流程
- 在
generate/api/新增或修改.api(並在gateway.apiimport)。 make gen-api生成程式碼(新 handler 會自動使用response.Write;已存在檔案不會覆寫,需手動刪除後再生成或自行修改)。- 在
internal/logic/做 types 映射並呼叫模組 UseCase,只回傳 data 與 error。 make gen-doc更新 OpenAPI 文件。go test ./...
2. Logic 與 Handler
// internal/logic/... — 編排與映射;有持久化時呼叫 svcCtx.{Module}UC
var errb = errs.For(code.Facade)
func (l *PingLogic) Ping() (*types.PingData, error) {
return &types.PingData{Pong: "ok"}, nil // 簡單 API 可直接回 types
}
// internal/handler/... — 由模板生成
data, err := l.Ping()
response.Write(r.Context(), w, data, err)
有 request 的 API 會自動包含 httpx.Parse;解析失敗會映射為 400 InputInvalidFormat。
3. HTTP JSON 格式
成功(HTTP 200)
{
"code": 0,
"message": "SUCCESS",
"data": { }
}
失敗(HTTP 依錯誤類別,如 404)
{
"code": 10301000,
"message": "user not found",
"error": {
"biz_code": "10301000",
"scope": 10,
"category": 301,
"detail": 0
}
}
4. 錯誤處理(全專案單一型別)
對外 API 只使用 gateway/internal/library/errors 的 *errs.Error(8 碼 SSCCCDDD)。不要在模組內再維護第二套業務錯誤碼套件。
各層職責:
| 層 | 職責 | 回傳 |
|---|---|---|
| repository | 忠實反映基礎設施(Mongo / Redis / driver) | *errs.Error(DB*、ResInvalidMeasureID 等)+ WithCause;可預期「無資料」可回模組 errors.go 的 sentinel |
| usecase | 業務規則(狀態、權限、組合多 repo) | *errs.Error(Res*、Auth*、Svc* 等);sentinel 轉成對外語意;已是正確的 *errs.Error 可原樣往上傳 |
| logic | HTTP 輸入檢查、types 映射 | 僅在進 usecase 前用 Input*;其餘 原樣 return nil, err,不二次包裝 |
| handler | 序列化 | response.Write(內建 errs.FromError) |
模組頂層 sentinel 範例(internal/model/member/errors.go,package member):
var (
ErrNotFound = errors.New("member: not found")
ErrInvalidObjectID = errors.New("member: invalid object id")
)
Repository 對照建議:
| 狀況 | 回傳 |
|---|---|
mongo.ErrNoDocuments |
member.ErrNotFound(由 usecase 轉 ResNotFound) |
| ObjectID 格式錯 | errb.ResInvalidMeasureID("account_id") |
| duplicate key | errb.DBDuplicate(...) |
| 連線 / 暫時不可用 | errb.DBUnavailable(...).WithCause(err) |
| 其他 driver 錯 | errb.DBError(...).WithCause(err) |
Usecase 範例:
var errb = errs.For(code.Facade)
acc, err := uc.Account.FindOne(ctx, id)
if err != nil {
if errors.Is(err, member.ErrNotFound) {
return nil, errb.ResNotFound("member", id).WithCause(err)
}
if e := errs.FromError(err); e != nil {
return nil, err // DB* 等基礎設施錯誤原樣傳遞
}
return nil, errb.DBError("get member failed").WithCause(err)
}
Logic 只做輸入與映射,錯誤直接往上:
out, err := l.svcCtx.MemberUC.GetByID(l.ctx, &memberusecase.GetByIDRequest{ID: req.Id})
if err != nil {
return nil, err
}
禁止: repository 回傳裸 fmt.Errorf 給上層;logic 再包一層無語意錯誤;logic 直接操作 Mongo / repository 實作。
5. 請求驗證(library/validate)
svc.ServiceContext.Validator:啟動時以validate.NewWithDefaultEN()建立,可傳入validate.Option註冊自訂 tag。- Handler 模板(
generate/goctl/api/handler.tpl)在httpx.Parse之後自動ValidateAll(&req),失敗經WrapRequestError→ 400InputInvalidFormat;Logic 通常不必再驗證。 - 自訂規則放在
internal/library/validate/custom/(依業務新增),透過NewWithDefaultEN(custom.YourOption)註冊。 validate.ValidationErrors含field/message;勿與internal/library/errors/validate.go(驗證 8 碼組成)混淆。
if err := l.svcCtx.Validator.ValidateAll(req); err != nil {
return nil, err // handler: response.WrapRequestError(err)
}
6. 新增業務模組檢查清單
- 建立
internal/model/{module}/,依上方目錄放置entity、enum、repository、usecase。 - 在
internal/svc/service_context.go組裝 repository → usecase,掛上介面欄位。 - 在
generate/api/定義路由,make gen-api。 - 在
internal/logic/實作:types ↔ usecase DTO,呼叫svcCtx.{Module}UC。 make gen-doc、go test ./...;介面變更後執行 mockgen 更新internal/model/{module}/mock/。
完整步驟見 docs/model.md 第 11 節。
7. 錯誤碼 API 速查
import (
errs "gateway/internal/library/errors"
"gateway/internal/library/errors/code"
)
var errb = errs.For(code.Facade)
return nil, errb.ResNotFound("user", id)
return nil, errb.InputMissingRequired("email").WithCause(err)
Category 與 HTTP 對照見 internal/library/errors/README.md。
8. API 文件(@ 註解)
在 .api 中:
returns (FooVO):只描述 data(給 goctl / Logic)。/* @respdoc-200 (FooOKStatus) ... */:描述實際 HTTP body(給 OpenAPI)。@doc(summary, description):單一接口說明。
範例見 generate/api/normal.api。
9. OpenAPI 產物與 Git
預設 .gitignore 會忽略 docs/openapi/*.yaml。若需提交給前端,請在 .gitignore 註解相關規則後執行 make gen-doc 並加入版本控制。
設定
主設定檔:etc/gateway.yaml
Name: gateway
Host: 0.0.0.0
Port: 8888
本地覆寫可新增 etc/gateway-local.yaml(已列入 .gitignore,勿提交機密)。
測試
go test ./...
授權
內部專案,授權依組織規範為準。