11 KiB
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。這些之後要接時再按服務邊界新增。
快速開始
cd haixun-backend
go mod download
make run
預設服務:
http://127.0.0.1:8890
健康檢查:
curl http://127.0.0.1:8890/api/v1/health
8D Node 爬蟲 worker 驗證
style-8d job 由 worker_type=node 消費。啟動 Gateway 與 Redis 後,另開一個終端:
make node-worker-style-8d
也可以在 repo 根目錄執行:
npm run worker:style-8d
常用環境變數:
HAIXUN_BACKEND_URL=http://127.0.0.1:8890
HAIXUN_WORKER_SECRET=... # 若 etc/gateway.yaml 設了 InternalWorker.Secret,worker 需帶同一把
HAIXUN_NODE_WORKER_ID=local-8d # 可選,方便辨識 lock holder
HAIXUN_8D_MIN_SAMPLES=1 # 驗證期預設 1;要嚴格一點可調高
前端在人設詳情頁按「開始 8D 分析」後,任務會進入:
確認連線 -> 抓取樣本 -> AI 8D -> 儲存策略
目前 Node worker 先用 Playwright 抓 Threads 公開頁樣本並產生可驗證的 8D 結構;若公開頁無法讀到足夠樣本,job 會標記為 failed 並顯示原因,不會停在等待狀態。
專案結構
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:
{
"code": 102000,
"message": "SUCCESS",
"data": {}
}
成功固定:
HTTP 200
code = 102000
message = SUCCESS
失敗格式:
{
"code": 33101000,
"message": "缺少 AI provider token",
"error": {
"biz_code": "33101000",
"scope": 33,
"category": 104,
"detail": 0
}
}
錯誤碼採 SSCCCDDD:
SS = scope,服務或模組範圍
CCC = category,錯誤分類
DDD = detail,細分錯誤碼,未細分時為 000
目前 scope:
10 = Facade / request parse / validation
32 = Setting
33 = AI
34 = Job
35 = Auth
36 = Member
37 = Permission
常用 category:
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:
GET /api/v1/settings/user/user_123?page=1&pageSize=10
回應的分頁資訊放在 data.pagination,資料陣列放在 data.list:
{
"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 interfacedomain/usecase:usecase interface 與 DTOrepository:Mongo / Redis 實作usecase:業務能力實作
provider
internal/model/ai/provider 只負責外部 AI API adapter:
- 不讀 setting
- 不碰 HTTP handler
- 不存 token
- token 每次由 request 帶入
Setting Model
設定使用 typed setting 形式:
{
"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:
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:
POST /api/v1/auth/register
POST /api/v1/auth/login
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
register / login 回傳:
{
"access_token": "...",
"refresh_token": "...",
"expires_in": 900,
"uid": "user_uid",
"token_type": "Bearer"
}
保護路由使用:
Authorization: Bearer <access_token>
本機開發可以開啟 Auth.DevHeaderFallback,用 header 模擬登入:
X-Tenant-ID: default
X-UID: user_uid
Member API:
GET /api/v1/members/me
PATCH /api/v1/members/me
Permission API:
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 / 回應洩漏):
Authorization: Bearer sk-...
Content-Type: application/json
{
"provider": "opencode-go",
"model": "deepseek-v4-pro",
"messages": [
{ "role": "user", "content": "請幫我寫一段文案" }
]
}
API:
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:只回傳 catalog(id、label、streams),不含 models、不含 token。POST /providers/:provider/models:向 provider 的/models動態拉清單,需帶Authorization: Bearer <token>。- 回應與錯誤訊息不會 echo token;provider 原始錯誤 body 也不會直接回傳給前端。
串流 endpoint 使用 SSE:
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 cancellation:API 先寫
cancel_requested與 Redis cancel signal,worker checkpoint 讀取後呼叫AcknowledgeCancel(jobId, workerID)。
OpenCode Go 注意事項
第一版 OpenCode Go 先走 OpenAI-compatible /chat/completions:
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。
下一步建議
- 用
goctl重新生成 handler / logic / types,確認.api與手寫版本對齊。 - 補
settingrepository 測試與 Mongo integration 測試。 - 補 AI provider mock,讓
logic/ai不需要真的打 provider 也能測。 - 新增 credential service 或 Vault/KMS 整合,但不要把 token 放進 provider config。
- 新增 worker/job model,讓 Go worker 與 Node Playwright worker 共用同一套 job contract。
設計文件
- Job 核心系統規劃:通用 job template、run、schedule、事件、取消語意與 worker contract。