|
|
||
|---|---|---|
| cmd | ||
| deploy | ||
| docs | ||
| etc | ||
| generate | ||
| internal | ||
| scripts | ||
| test/e2e | ||
| .gitignore | ||
| .golangci.yml | ||
| AGENTS.md | ||
| Makefile | ||
| README.md | ||
| docker-compose.yml | ||
| gateway.go | ||
| go.mod | ||
| go.sum | ||
README.md
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) |
| Make | ✓ | 執行 gen-api / gen-doc 等 |
| goctl | ✓ | make gen-api 用;可 make tools 安裝 |
| goimports | 建議 | make fmt 用;make tools 安裝 |
| golangci-lint | 建議 | make lint 用;make tools 安裝(v2,見 .golangci.yml) |
| jq | 選用 | README 範例 curl | jq |
請確認 $(go env GOPATH)/bin 在 PATH,否則 go install 的 goctl 找不到。
快速開始(首次)
cd gateway
# 1. 工具(goctl、goimports)
make tools
# 2. 依賴
go mod download
# 3. 依 .api 生成程式碼(可選,repo 內通常已有)
make gen-api
# 4. 啟動
make run
# 或:go run gateway.go -f etc/gateway.yaml
本機 Mongo + Redis(Notification / Member OTP)
需要持久化通知、異步重試或 member 驗證時:
make deps-up # Mongo :27017、Redis :6379
make mongo-index # 建立 notifications / notification_dlq 索引
make run-dev # 使用 etc/gateway.dev.yaml
詳見 deploy/README.md、etc/README.md。選用 MailHog:make deps-up-smtp。
產生 OpenAPI(會先編譯 generate/doc-generate 內的 go-doc):
make gen-doc # 輸出 docs/openapi/gateway.yaml
健康檢查:
curl -s http://127.0.0.1:8888/api/v1/health | jq
預期回應:
{
"code": 0,
"message": "SUCCESS",
"data": { "pong": "ok" }
}
常用指令
執行 make 或 make help 可看完整列表。
| 指令 | 說明 |
|---|---|
make tools |
首次安裝 goctl、goimports |
make gen-api |
由 gateway.api 生成 Go(自訂 generate/goctl 模板) |
make gen-doc |
編譯 go-doc 並生成 docs/openapi/gateway.yaml |
make test |
執行測試 |
make fmt |
gofmt + goimports |
make lint |
golangci-lint 靜態檢查(必須 0 issues) |
make lint-fix |
自動修正可修的 lint / import 問題 |
make fix |
fmt + lint-fix + lint(提交前建議) |
make check |
fix + test(PR / AI 完成前必跑) |
make run |
啟動 Gateway(無 DB) |
make deps-up |
Docker 啟動 Mongo + Redis |
make run-dev |
啟動 Gateway(etc/gateway.dev.yaml,需 Docker) |
make config-check |
驗證 yaml 可載入 |
make mongo-index |
建立 notification Mongo 索引 |
專案結構
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/mongo/ # DocumentDB + Redis cache(mongo-driver v2)
│ ├── 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 對照
- internal/library/mongo/README.md — MongoDB / Redis cache 流程與用法
- docs/model.md —
internal/model/{module}分層(entity / repository / usecase) - docs/identity-member-design.md — Identity / Member / Permission 模組架構(ZITADEL、LDAP、SCIM、B2B 自定義權限)
- docs/auth-unified-registration.md — Gateway 統一註冊 / 登入(
/api/v1/auth/*,含 Email、Social、JWT)
開發約定
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/auth — Auth scope
var errb = errs.For(code.Auth)
// internal/logic/member — Member scope
var errb = errs.For(code.Member)
// internal/handler/... — 由模板生成;parse/validate 錯誤用 Facade scope(response.RequestErrScope)
data, err := l.Ping()
response.Write(r.Context(), w, data, err)
有 request 的 API 會自動包含 httpx.Parse;解析失敗會映射為 400 InputInvalidFormat(Facade scope 10101000)。
3. HTTP JSON 格式
成功(HTTP 200)
{
"code": 102000,
"message": "SUCCESS",
"data": { }
}
失敗(HTTP 依錯誤類別,如 404;Member scope 範例)
{
"code": 29301000,
"message": "member not found",
"error": {
"biz_code": "29301000",
"scope": 29,
"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 映射 | 使用該模組 scope(code.Auth / code.Member);cross-module 錯誤原樣 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 範例(Member scope):
var errb = errs.For(code.Member)
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. Lint 與自動修正
設定檔:.golangci.yml(golangci-lint v2,standard + revive / gosec / errorlint 等)。
make lint-fix # 自動修(goimports、部分 linter auto-fix)
make lint # 僅檢查
make fix # fmt + lint-fix + lint
make check # fix + test(建議每次改 Go 後執行)
無法自動修的須手改;AI / 協作者應以 make check 通過為完成條件(見 .cursor/rules/golangci-lint.mdc)。
6. 請求驗證(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)
}
7. 新增業務模組檢查清單
- 建立
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 節。
8. 錯誤碼 API 速查
import (
errs "gateway/internal/library/errors"
"gateway/internal/library/errors/code"
)
// logic / usecase:依模組選 scope
var authErr = errs.For(code.Auth) // 28301000 = ResNotFound
var memberErr = errs.For(code.Member) // 29301000 = ResNotFound
return nil, memberErr.ResNotFound("member", id)
return nil, authErr.InputMissingRequired("email").WithCause(err)
Category 與 HTTP 對照見 internal/library/errors/README.md。
9. API 文件(@ 註解)
在 .api 中:
returns (FooVO):只描述 data(給 goctl / Logic)。/* @respdoc-200 (FooOKStatus) ... */:描述實際 HTTP body(給 OpenAPI)。@doc(summary, description):單一接口說明。
範例見 generate/api/normal.api。
10. 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 ./...
授權
內部專案,授權依組織規範為準。