thread-master/README.md

397 lines
11 KiB
Markdown
Raw Permalink Normal View History

2026-06-26 08:37:04 +00:00
# Haixun Backend
新的巡樓後端核心。這個資料夾刻意不直接複製 `template-monorepo` 的產物碼只沿用它的架構模式、goctl handler template 概念與必要 runtime library讓後續可以用更乾淨的邊界重建服務。
## 目前範圍
第一版先放六個核心能力:
- `setting`:通用設定模型,支援 `scope + scope_id + key` 儲存不同類型設定。
- `ai`:可替換 AI provider interface第一版支援 OpenCode Go 與 Grok/xAI並提供 SSE 串流回應。
- `job`:通用背景任務系統,支援 template/run/schedule/event、Redis queue/lock、進度、retry 與 cooperative cancel。
- `auth`native email/password 登入、JWT access/refresh token、logout revoke。
- `member`:目前登入會員的 profile 讀寫。
- `permission`permission catalog 與目前會員權限查詢。
暫時不包含 template-monorepo 裡較重的 OAuth / OTP / MFA / Zitadel 整合,也不包含 notification、Playwright worker。這些之後要接時再按服務邊界新增。
## 快速開始
```bash
cd haixun-backend
go mod download
make run
```
預設服務:
```text
http://127.0.0.1:8890
```
健康檢查:
```bash
curl http://127.0.0.1:8890/api/v1/health
```
### 8D Node 爬蟲 worker 驗證
`style-8d` job 由 `worker_type=node` 消費。啟動 Gateway 與 Redis 後,另開一個終端:
```bash
make node-worker-style-8d
```
也可以在 repo 根目錄執行:
```bash
npm run worker:style-8d
```
常用環境變數:
```text
HAIXUN_BACKEND_URL=http://127.0.0.1:8890
HAIXUN_WORKER_SECRET=... # 若 etc/gateway.yaml 設了 InternalWorker.Secretworker 需帶同一把
HAIXUN_NODE_WORKER_ID=local-8d # 可選,方便辨識 lock holder
HAIXUN_8D_MIN_SAMPLES=1 # 驗證期預設 1要嚴格一點可調高
```
前端在人設詳情頁按「開始 8D 分析」後,任務會進入:
```text
確認連線 -> 抓取樣本 -> AI 8D -> 儲存策略
```
目前 Node worker 先用 Playwright 抓 Threads 公開頁樣本並產生可驗證的 8D 結構若公開頁無法讀到足夠樣本job 會標記為 `failed` 並顯示原因,不會停在等待狀態。
## 專案結構
```text
haixun-backend/
gateway.go # go-zero server 入口
Makefile # gen-api / fmt / test / run
etc/ # runtime config
generate/
api/ # goctl .api 定義
goctl/api/handler.tpl # 從 template-monorepo 精簡改來的 handler 模板
internal/
config/ # config struct
handler/ # HTTP handler目前手寫之後可由 goctl 生成
logic/ # API 編排層
model/
setting/ # 通用設定 model
ai/ # AI provider interface + adapter
job/ # Job template/run/schedule/event usecase + repository
auth/ # JWT token issue/refresh/logout + Redis revoke store
member/ # Native member profile + password hash
permission/ # Permission catalog + role permission mapping
worker/ # 常駐背景 worker / scheduler / reaper
library/ # 最小 runtime library
response/ # 統一 JSON response envelope
svc/ # ServiceContext 組裝依賴
types/ # API request/response types
```
## 分層規則
## Response 與錯誤碼標準
所有一般 JSON API 都必須回傳同一層 envelope
```json
{
"code": 102000,
"message": "SUCCESS",
"data": {}
}
```
成功固定:
```text
HTTP 200
code = 102000
message = SUCCESS
```
失敗格式:
```json
{
"code": 33101000,
"message": "缺少 AI provider token",
"error": {
"biz_code": "33101000",
"scope": 33,
"category": 104,
"detail": 0
}
}
```
錯誤碼採 `SSCCCDDD`
```text
SS = scope服務或模組範圍
CCC = category錯誤分類
DDD = detail細分錯誤碼未細分時為 000
```
目前 scope
```text
10 = Facade / request parse / validation
32 = Setting
33 = AI
34 = Job
35 = Auth
36 = Member
37 = Permission
```
常用 category
```text
101 = InputInvalidFormat
104 = InputMissingRequired
204 = DBUnavailable
301 = ResourceNotFound
303 = ResourceConflict
401 = AuthUnauthorized
505 = AuthForbidden
601 = SystemInternal
802 = ServiceThirdParty
```
實作規則:
- Handler 成功/失敗都用 `internal/response.Write`SSE endpoint 例外。
- Request parse / validation 錯誤用 `response.WrapRequestError`,會落在 Facade scope。
- Model/usecase 內建立錯誤時使用 `errors.For(code.<Scope>)` builder不要手刻數字。
- 不要把 provider 原始錯誤完整洩漏到前端;必要時只保留可排查的摘要。
### 分頁標準
列表型 API 的 query 使用 `page` / `pageSize`
```text
GET /api/v1/settings/user/user_123?page=1&pageSize=10
```
回應的分頁資訊放在 `data.pagination`,資料陣列放在 `data.list`
```json
{
"code": 102000,
"message": "SUCCESS",
"data": {
"pagination": {
"total": 42,
"page": 1,
"pageSize": 10,
"totalPages": 5
},
"list": []
}
}
```
規則:
- `page` 從 1 開始。
- `pageSize <= 0` 時由 server 套用預設值。
- `pageSize` 超過 server 上限時由 server 截斷。
- `totalPages = ceil(total / pageSize)`
- response 內的 `page/pageSize` 必須回傳 server 正規化後的值。
### logic
`internal/logic/*` 只負責一次 API 請求的流程編排:
- 轉換 HTTP types 與 usecase DTO
- 呼叫一個或多個 model usecase
- 不直接操作 Mongo / Redis
- 不放 provider HTTP 細節
### model
`internal/model/*` 放可重複使用的業務能力:
- `domain/entity`:資料結構
- `domain/repository`repository interface
- `domain/usecase`usecase interface 與 DTO
- `repository`Mongo / Redis 實作
- `usecase`:業務能力實作
### provider
`internal/model/ai/provider` 只負責外部 AI API adapter
- 不讀 setting
- 不碰 HTTP handler
- 不存 token
- token 每次由 request 帶入
## Setting Model
設定使用 typed setting 形式:
```json
{
"scope": "user",
"scope_id": "user_123",
"key": "ai.default",
"value": {
"provider": "opencode-go",
"model": "deepseek-v4-pro",
"temperature": 0.7,
"max_tokens": 2000
},
"version": 1
}
```
API
```text
GET /api/v1/settings/:scope/:scope_id?page=1&pageSize=10
GET /api/v1/settings/:scope/:scope_id/:key
PUT /api/v1/settings/:scope/:scope_id/:key
DELETE /api/v1/settings/:scope/:scope_id/:key
```
`setting` model 不知道 AI、Threads、crawler 等業務含義。各業務 model 自己解讀對應 key 的 value。
## Auth / Member / Permission
這版從 `template-monorepo` 精簡搬入會員、權限與 token 的核心概念,但不搬 OAuth / OTP / MFA / Zitadel 依賴。
Auth 採 native email/password
```text
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
```
`register` / `login` 回傳:
```json
{
"access_token": "...",
"refresh_token": "...",
"expires_in": 900,
"uid": "user_uid",
"token_type": "Bearer"
}
```
保護路由使用:
```http
Authorization: Bearer <access_token>
```
本機開發可以開啟 `Auth.DevHeaderFallback`,用 header 模擬登入:
```http
X-Tenant-ID: default
X-UID: user_uid
```
Member API
```text
GET /api/v1/members/me
PATCH /api/v1/members/me
```
Permission API
```text
GET /api/v1/permissions/catalog?tree=true
GET /api/v1/permissions/me?include_tree=true
```
資料模型:
- `members`tenant-scoped profile、email、bcrypt password hash、roles。
- `permissions`:平台 permission catalog。
- `role_permissions`tenant + role_key 對 permission catalog 的綁定。
- Redis `auth:jwt:*`access/refresh pair 與 blacklist。Redis 未配置時仍可簽發 token但 refresh/logout revoke 不會持久化。
## AI Provider
AI token 不存在 config呼叫時每次帶入且**只放 HTTP header**,不要放 JSON body避免 log / 回應洩漏):
```http
Authorization: Bearer sk-...
Content-Type: application/json
{
"provider": "opencode-go",
"model": "deepseek-v4-pro",
"messages": [
{ "role": "user", "content": "請幫我寫一段文案" }
]
}
```
API
```text
GET /api/v1/ai/providers
POST /api/v1/ai/providers/:provider/models
POST /api/v1/ai/chat
POST /api/v1/ai/chat/stream
```
- `GET /providers`:只回傳 catalogid、label、streams不含 models、不含 token。
- `POST /providers/:provider/models`:向 provider 的 `/models` 動態拉清單,需帶 `Authorization: Bearer <token>`
- 回應與錯誤訊息不會 echo tokenprovider 原始錯誤 body 也不會直接回傳給前端。
串流 endpoint 使用 SSE
```text
event: delta
data: {"type":"delta","text":"..."}
event: done
data: {"type":"done","finish_reason":"stop"}
```
## Job System
Job 系統的詳細設計在 `docs/job-system-plan.md`。目前 runtime 原則:
- MongoDB 的 `job_runs` 是狀態真相來源claim、cancel、complete、fail、retry 必須使用 conditional update避免 worker 與 API 互相覆蓋狀態。
- Redis `jobs:lock:<jobId>` 的 value 是 `workerID`release / refresh 必須檢查 owner只能由持有 lock 的 worker 操作。
- Worker 執行長任務時要定期呼叫 `RefreshRunLock(jobId, workerID, ttlSeconds)`,避免 reaper 誤判過期。
- Runner 支援 `RegisterStepHandler(stepID, handler)` 註冊自訂 step handler未註冊時會走 demo handler。自訂 handler 可用 `StepContext.Heartbeat` 續約 lock。
- 取消採 cooperative cancellationAPI 先寫 `cancel_requested` 與 Redis cancel signalworker checkpoint 讀取後呼叫 `AcknowledgeCancel(jobId, workerID)`
## OpenCode Go 注意事項
第一版 OpenCode Go 先走 OpenAI-compatible `/chat/completions`
```text
https://opencode.ai/zen/go/v1/chat/completions
```
目前已處理 Kimi 模型 `temperature = 1` 的特殊規則。部分 OpenCode Go 模型官方文件標示為 Anthropic-compatible `/messages`,後續可在 `internal/model/ai/provider` 新增 messages adapter不需要改 logic 或前端 SSE contract。
## 下一步建議
1.`goctl` 重新生成 handler / logic / types確認 `.api` 與手寫版本對齊。
2.`setting` repository 測試與 Mongo integration 測試。
3. 補 AI provider mock`logic/ai` 不需要真的打 provider 也能測。
4. 新增 credential service 或 Vault/KMS 整合,但不要把 token 放進 provider config。
5. 新增 worker/job model讓 Go worker 與 Node Playwright worker 共用同一套 job contract。
## 設計文件
- [Job 核心系統規劃](docs/job-system-plan.md):通用 job template、run、schedule、事件、取消語意與 worker contract。