thread-master/README.md

11 KiB
Raw Permalink Blame History

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。
  • authnative email/password 登入、JWT access/refresh token、logout revoke。
  • member:目前登入會員的 profile 讀寫。
  • permissionpermission 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.Secretworker 需帶同一把
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.WriteSSE 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/repositoryrepository interface
  • domain/usecaseusecase interface 與 DTO
  • repositoryMongo / 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

資料模型:

  • memberstenant-scoped profile、email、bcrypt password hash、roles。
  • permissions:平台 permission catalog。
  • role_permissionstenant + 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:只回傳 catalogid、label、streams不含 models、不含 token。
  • POST /providers/:provider/models:向 provider 的 /models 動態拉清單,需帶 Authorization: Bearer <token>
  • 回應與錯誤訊息不會 echo tokenprovider 原始錯誤 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 是 workerIDrelease / 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

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 mocklogic/ai 不需要真的打 provider 也能測。
  4. 新增 credential service 或 Vault/KMS 整合,但不要把 token 放進 provider config。
  5. 新增 worker/job model讓 Go worker 與 Node Playwright worker 共用同一套 job contract。

設計文件