template-monorepo/generate/api/README.md

212 lines
7.8 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.

# API 定義goctl + go-doc 共用)
## 檔案
| 檔案 | 用途 |
|------|------|
| `gateway.api` | 入口:`info()` + `import` |
| `common.api` | 共用文件型別(`APIErrorStatus`、`ErrorDetail` |
| `auth.api` | Auth 路由scope 28 |
| `member.api` | Member 路由scope 29 |
| `permission.api` | Permission / RBAC 路由scope 31 |
| `normal.api` | 路由與業務 `data` 型別 |
## 指令
```bash
make gen-api # 生成 handler / logic / types
make gen-doc # 生成 docs/openapi/gateway.yamlOpenAPI 3.0
```
## 註解約定
- **Logic `returns`**:只寫業務 data`PingData`
- **文件 `@respdoc`**:寫實際 HTTP JSON`PingOKStatus`、`APIErrorStatus`
- **`@doc`**:單一 API 的 summary / description
- 多狀態碼用 `/* @respdoc-200 ... */` 區塊,放在 `@handler`
- **Request 驗證**:欄位可加 `validate:"required,email"` 等 tag`make gen-api` 後 handler 會自動 `ValidateAll`(見 `generate/goctl/api/handler.tpl`
## 文件分組與欄位說明go-doc 規則)
OpenAPI 由 `generate/doc-generate`go-doc`.api` 直接讀取,下列三種寫法**必須**遵守AI agent 在新增 / 修改 API 時請一併照做:
### 1. Swagger UI 分組tags
每個 `.api` 檔的 `@server (...)` 區塊**必須**帶 `tags:``summary:`,否則 endpoint 會散落在 "default" 群組。
```go
@server (
group: permission // goctl 用:決定 handler 子目錄
prefix: /api/v1/permissions
tags: "Permission - 權限" // ← go-doc 用Swagger UI 分組標題
summary: "Catalog / 角色 / 使用者角色 / 外部映射 / Policy"
)
```
> 一個 `.api` 內可以開**多個** `@server` 區塊(相同 `group` / `prefix` 但不同 `tags:`),把同一模組再拆成子組。
### 2. 欄位中文 description
go-doc 把欄位的 description **只認 backtick 結尾的同一行 `//` 註解**,寫在前一行不會被解析。
```go
// ❌ 不會被當 description
RegisterReq {
// 租戶 slug
TenantSlug string `json:"tenant_slug" validate:"required"`
}
// ✅ 正確寫法backtick 行末 //
RegisterReq {
TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug小寫英數
Email string `json:"email" validate:"required,email"` // 電子郵件
Password string `json:"password" validate:"required,min=8,max=128"` // 密碼8-128 字元)
}
```
**所有 Request 型別**(命名 `*Req` / `*Query` / `*Path` 的 struct的**每個欄位**都要加中文 description。Response / Data 型別可選。
### 3. Enum 列舉值Swagger UI 下拉選單)
`validate:"oneof=A B C"` 是 runtime 驗證用的,**go-doc 不會解析**。要讓 Swagger UI 顯示下拉選單,必須在 tag 內加 `options=A|B|C`(管道分隔):
```go
// ❌ 只有 validate 不會出 enum
Provider string `json:"provider" validate:"required,oneof=google"`
// ✅ 同時保留兩者options= 給 go-docvalidate= 給 runtime
Provider string `json:"provider,options=google" validate:"required,oneof=google"`
Source string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"`
Status string `form:"status,optional,options=open|close" validate:"omitempty,oneof=open close"`
```
**規則**:只要有 `oneof=...`,就一定要在同欄位 tag 加對應 `options=...`,並把可選值也寫進行末註解。
### 4. 修改流程
每次改 `.api` 後**必跑**
```bash
make gen-api # 同步 internal/types/types.gojson tag 帶 options= 給 go-zero runtime
make gen-doc # 同步 docs/openapi/gateway.yamlgitignore本地驗證用
go build ./... # 確保編譯通過
```
驗證:把 `docs/openapi/gateway.yaml` 丟進 https://editor.swagger.io 或 Swagger UI確認分組、description、enum 都正確顯示。
## Middlewarego-zero 正規手段)
**強制原則**:所有需要登入 / 授權的 endpoint 都用 `.api``middleware:` 宣告,**不要**在 `gateway.go``server.Use(...)` 全域掛載。
### 設定方式
`@server``middleware: AuthJWT``middleware: AuthJWT,CasbinRBAC`(多個用逗號):
```go
// 公開(不掛)— 註冊 / 登入 / token refresh / health
@server (
group: auth
prefix: /api/v1/auth
tags: "Auth - 認證(公開)"
)
service gateway { ... register / login / token/exchange / token/refresh ... }
// 需登入AuthJWT— logout / member 自身資料 / Permission catalog & me
@server (
group: auth
prefix: /api/v1/auth
middleware: AuthJWT
tags: "Auth - 認證(需 Bearer"
)
service gateway { ... logout ... }
// 需登入 + RBACAuthJWT,CasbinRBAC— Permission 管理類 endpoint
@server (
group: permission
prefix: /api/v1/permissions
middleware: AuthJWT,CasbinRBAC
tags: "Permission - 權限(管理)"
)
service gateway { ... roles / role-mappings / users / policy/reload ... }
```
`make gen-api``internal/handler/routes.go` 會自動產生:
```go
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT, serverCtx.CasbinRBAC},
[]rest.Route{ ... }...,
),
rest.WithPrefix("/api/v1/permissions"),
)
```
### 撰寫新 middlewarego-zero 標準 struct 模式)
每個 middleware **必須**是 struct + `NewXxxMiddleware(...)` 建構函式 + `Handle(next http.HandlerFunc) http.HandlerFunc` 方法。範例:
```go
// internal/middleware/authjwt_middleware.go ← 檔名由 goctl stringx 決定
package middleware
type AuthJWTMiddleware struct {
tokens domauth.TokenUseCase
}
func NewAuthJWTMiddleware(tokens domauth.TokenUseCase) *AuthJWTMiddleware {
return &AuthJWTMiddleware{tokens: tokens}
}
func (m *AuthJWTMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// ...檢查 / 注入 actor / 呼叫 next(w, r)
}
}
```
**檔名規則**goctl 會依 `.api``middleware:` 名字產生 stub 檔,命名 = `stringx.From(<Name>).ToLower() + "_middleware.go"`,例如:
- `AuthJWT``authjwt_middleware.go`
- `CasbinRBAC``casbinrbac_middleware.go`
新 middleware **必須**直接用這個檔名寫實作goctl 下次 gen-api 才會跳過(看到「已存在」)。
### ServiceContext 注入
`internal/svc/service_context.go` 暴露 `rest.Middleware` 欄位並在 `NewServiceContext` 結尾 wire 起來:
```go
type ServiceContext struct {
...
AuthJWT rest.Middleware
CasbinRBAC rest.Middleware
}
func NewServiceContext(c config.Config) *ServiceContext {
...
// 結尾,所有 use case 都建好之後
sc.AuthJWT = middleware.NewAuthJWTMiddleware(sc.AuthToken).Handle
sc.CasbinRBAC = middleware.NewCasbinRBACMiddleware(sc.PermissionRBAC, middleware.CasbinRBACOptions{}).Handle
return sc
}
```
`.Handle` 是 method value`func(next http.HandlerFunc) http.HandlerFunc`),剛好符合 `rest.Middleware` 簽名。
### Actor context 共享
JWT middleware 解析完 token 後用 `internal/library/actor.WithActor(ctx, tenantID, uid)` 注入 actor。
**所有層**(logic / usecase / 其他 middleware)讀 actor 都用 `actor.ActorFromContext(ctx)` —— 不要在各 logic 套件各自定義 `actorKey struct{}`,否則 context value 進不來。
`internal/logic/member/actor.go``internal/logic/permission/actor.go``library/actor` 的 thin alias方便既有程式用 `member.Actor` / `member.ActorFromContext`,但底層 key 是同一個。
## 與 runtime 對齊
Handler 使用 `response.Write` 輸出:
```json
{ "code": 102000, "message": "SUCCESS", "data": { ... } }
```
失敗時含 `error.biz_code` / `error.scope` 等欄位。Handler parse 錯誤為 Facade scope`10101000`);各模組 logic/usecase 使用對應 scopeAuth=28、Member=29