template-monorepo/README.md

382 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Portal API Gateway (PGW)
基於 [go-zero](https://github.com/zeromicro/go-zero) 的 API Gateway提供統一 HTTP JSON 回應、8 碼業務錯誤碼,以及由 `.api` 定義驅動的程式碼與 OpenAPI 3.0 文件生成。
## 功能概覽
- **REST API**go-zero `rest` 服務,預設 `http://0.0.0.0:8888`
- **統一回應格式**:成功 / 失敗皆為 `Status` envelope`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](https://go-zero.dev/docs/tasks/installation/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` 找不到。
## 快速開始(首次)
```bash
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 + RedisNotification / Member OTP
需要持久化通知、異步重試或 member 驗證時:
```bash
make deps-up # Mongo :27017、Redis :6379
make mongo-index # 建立 notifications / notification_dlq 索引
make run-dev # 使用 etc/gateway.dev.yaml
```
詳見 [deploy/README.md](deploy/README.md)、[etc/README.md](etc/README.md)。選用 MailHog`make deps-up-smtp`。
產生 OpenAPI會先編譯 `generate/doc-generate` 內的 go-doc
```bash
make gen-doc # 輸出 docs/openapi/gateway.yaml
```
健康檢查:
```bash
curl -s http://127.0.0.1:8888/api/v1/health | jq
```
預期回應:
```json
{
"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 handlergoctl 生成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 cachemongo-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 documentAccount、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
→ handlerresponse.Write
→ logictypes ↔ usecase DTO
→ model/{module}/usecase
→ model/{module}/repository
→ MongoDB / Redis
```
更細的 entity / repository / usecase 撰寫規則見 [docs/model.md](docs/model.md)。
更細的說明見各子目錄 README
- [generate/api/README.md](generate/api/README.md) — `.api``@respdoc` 約定
- [internal/response/README.md](internal/response/README.md) — Handler / Logic 分工
- [internal/library/errors/README.md](internal/library/errors/README.md) — 錯誤碼與 HTTP 對照
- [internal/library/mongo/README.md](internal/library/mongo/README.md) — MongoDB / Redis cache 流程與用法
- [docs/model.md](docs/model.md) — `internal/model/{module}` 分層entity / repository / usecase
- [docs/identity-member-design.md](docs/identity-member-design.md) — **Draft** Identity / Member / Permission 模組架構ZITADEL、LDAP、SCIM、B2B 自定義權限)
## 開發約定
### 1. 新增 API 流程
1.`generate/api/` 新增或修改 `.api`(並在 `gateway.api` import
2. `make gen-api` 生成程式碼(**新** handler 會自動使用 `response.Write`;已存在檔案不會覆寫,需手動刪除後再生成或自行修改)。
3.`internal/logic/` 做 types 映射並呼叫模組 UseCase只回傳 **data****error**
4. `make gen-doc` 更新 OpenAPI 文件。
5. `go test ./...`
### 2. Logic 與 Handler
```go
// 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**
```json
{
"code": 0,
"message": "SUCCESS",
"data": { }
}
```
**失敗HTTP 依錯誤類別,如 404**
```json
{
"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`
```go
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 範例:
```go
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 只做輸入與映射,錯誤直接往上:
```go
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 等)。
```bash
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`**400** `InputInvalidFormat`Logic 通常不必再驗證。
- 自訂規則放在 `internal/library/validate/custom/`(依業務新增),透過 `NewWithDefaultEN(custom.YourOption)` 註冊。
- `validate.ValidationErrors``field` / `message`;勿與 `internal/library/errors/validate.go`(驗證 8 碼組成)混淆。
```go
if err := l.svcCtx.Validator.ValidateAll(req); err != nil {
return nil, err // handler: response.WrapRequestError(err)
}
```
### 7. 新增業務模組檢查清單
1. 建立 `internal/model/{module}/`,依上方目錄放置 `entity`、`enum`、`repository`、`usecase`。
2.`internal/svc/service_context.go` 組裝 repository → usecase掛上介面欄位。
3.`generate/api/` 定義路由,`make gen-api`。
4.`internal/logic/` 實作types ↔ usecase DTO呼叫 `svcCtx.{Module}UC`
5. `make gen-doc`、`go test ./...`;介面變更後執行 mockgen 更新 `internal/model/{module}/mock/`
完整步驟見 [docs/model.md](docs/model.md) 第 11 節。
### 8. 錯誤碼 API 速查
```go
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](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`
```yaml
Name: gateway
Host: 0.0.0.0
Port: 8888
```
本地覆寫可新增 `etc/gateway-local.yaml`(已列入 `.gitignore`,勿提交機密)。
## 測試
```bash
go test ./...
```
## 授權
內部專案,授權依組織規範為準。