# 重構計劃:cursor-api-proxy → go-zero + DDD Architecture ## 一、概述 ### 目標 將 cursor-api-proxy 從標準 Go HTTP 架構重構為 go-zero + DDD(Clean Architecture)混合架構。 ### 核心原則 - **internal/**:go-zero 框架特定代碼(強依賴框架) - **pkg/**:框架無關的核心業務邏輯(DIP 反轉依賴) - **api/**:契約優先的 API 定義(goctl 生成基礎代碼) ### 架構分層 ``` ┌─────────────────────────────────────────────────────────────┐ │ api/chat.api │ │ (API 契約定義) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ internal/handler/ │ │ (HTTP Handler 層) │ │ goctl 生成 + 自訂 SSE Handler │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ internal/logic/ │ │ (業務邏輯調度層) │ │ Adapter Pattern: Request → Usecase │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ pkg/usecase/ │ │ (Application Layer) │ │ 業務流程協調、Repository 調用 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ pkg/domain/repository/ │ │ (Repository Interface) │ │ DIP 反轉依賴 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ pkg/repository/ │ │ (Infrastructure Layer) │ │ Repository Interface 實作 │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 二、目錄結構(最終狀態) ``` . ├── api/ # [Contract] go-zero API 定義 │ └── chat.api # OpenAI 相容 API 規格 │ ├── etc/ # [Config] 環境配置 │ ├── chat.yaml # 本地開發 │ └── chat-prod.yaml # 生產環境 │ ├── cmd/ # [Entry] 入口點 │ ├── chat/ # HTTP proxy 服務 │ │ └── chat.go # go-zero 入口 │ └── cli/ # CLI 工具 │ ├── login.go │ ├── accounts.go │ ├── resethwid.go │ └── usage.go │ ├── internal/ # [Framework Layer] go-zero 特定 │ ├── config/ │ │ └── config.go # 配置結構體(使用 rest.RestConf) │ ├── handler/ # HTTP handlers (goctl 生成) │ │ ├── routes.go # 路由註冊 │ │ ├── chat_handler.go # Chat API handler │ │ ├── health_handler.go # Health check handler │ │ └── models_handler.go # Models list handler │ ├── logic/ # 業務邏輯調度層 │ │ ├── chatcompletionlogic.go # Chat completion 邏輯 │ │ ├── geminichatlogic.go # Gemini chat 邏輯 │ │ ├── anthropiclogic.go # Anthropic messages 邏輯 │ │ ├── healthlogic.go # Health 邏輯 │ │ └── modelslogic.go # Models 邏輯 │ ├── middleware/ # 中間件 │ │ ├── auth.go # API Key 驗證 │ │ └── recovery.go # Panic 恢復 │ ├── svc/ # ServiceContext (DI 容器) │ │ └── servicecontext.go │ └── types/ # API types (goctl 生成) │ └── types.go │ ├── pkg/ # [Domain] 框架無關的核心邏輯 │ ├── domain/ # │ │ ├── entity/ # 純粹業務物件(無框架依賴) │ │ │ ├── message.go # Message, Tool, ToolCall │ │ │ ├── chunk.go # StreamChunk │ │ │ ├── account.go # Account, AccountStat │ │ │ └── config.go # ProviderConfig │ │ ├── repository/ # Repository Interface (DIP) │ │ │ ├── account.go # AccountPool interface │ │ │ └── provider.go # Provider interface │ │ ├── usecase/ # Usecase Interface │ │ │ ├── chat.go # ChatUsecase interface │ │ │ └── agent.go # AgentRunner interface │ │ └── const/ # 常數 │ │ ├── models.go # Model ID 常數 │ │ └── errors.go # 錯誤定義 │ │ │ ├── repository/ # │ │ ├── account.go # AccountPool 實作 │ │ └── provider.go # Provider 工廠 │ │ │ ├── usecase/ # │ │ ├── chat.go # ChatUsecase 實作 │ │ ├── agent.go # Agent 執行邏輯 │ │ ├── sanitizer.go # 消息清理 │ │ └── toolcall.go # Tool call 處理 │ │ │ ├── provider/ # Provider 實作 │ │ ├── cursor/ # Cursor CLI provider │ │ │ └── provider.go │ │ └── geminiweb/ # Gemini Web provider │ │ ├── provider.go │ │ ├── browser.go │ │ └── pool.go │ │ │ ├── infrastructure/ # 基礎設施 │ │ ├── process/ # 進程管理 │ │ │ ├── runner.go │ │ │ ├── kill_unix.go │ │ │ └── kill_windows.go │ │ ├── parser/ # SSE parsing │ │ │ └── stream.go │ │ ├── httputil/ # HTTP 工具 │ │ │ └── httputil.go │ │ ├── logger/ # 日誌 │ │ │ └── logger.go │ │ ├── env/ # 環境變數 │ │ │ └── env.go │ │ ├── workspace/ # 工作區管理 │ │ │ └── workspace.go │ │ └── winlimit/ # Windows 命令行限制 │ │ └── winlimit.go │ │ │ └── adapter/ # API 格式適配器 │ ├── openai/ # OpenAI 格式 │ │ └── openai.go │ └── anthropic/ # Anthropic 格式 │ └── anthropic.go │ ├── build/ # [Infrastructure] 建置相關 │ ├── Dockerfile │ └── docker-compose.yml │ ├── scripts/ # 腳本工具 ├── docs/ # 文檔 │ └── MIGRATION_PLAN.md # 本計劃文件 │ ├── Makefile ├── go.mod └── go.sum ``` --- ## 三、執行步驟 ### Phase 1: API 定義與骨架生成 **目標**:建立 go-zero 架構骨架 #### Step 1.1: 初始化 go-zero 專案 ```bash # 安裝 goctl go install github.com/zeromicro/go-zero/tools/goctl@latest # 建立 API 定義目錄 mkdir -p api etc ``` #### Step 1.2: 建立 `api/chat.api` ```api syntax = "v1" info ( title: "Cursor API Proxy" desc: "OpenAI-compatible API proxy for Cursor/Gemini" author: "cursor-api-proxy" version: "1.0" ) // ============ Types ============ type ( // Health HealthRequest {} HealthResponse { Status string `json:"status"` Version string `json:"version"` } // Models ModelsRequest {} ModelsResponse { Object string `json:"object"` Data []ModelData `json:"data"` } ModelData { Id string `json:"id"` Object string `json:"object"` OwnedBy string `json:"owned_by"` } // Chat Completions ChatCompletionRequest { Model string `json:"model"` Messages []Message `json:"messages"` Stream bool `json:"stream,optional"` Tools []Tool `json:"tools,optional"` Functions []Function `json:"functions,optional"` MaxTokens int `json:"max_tokens,optional"` Temperature float64 `json:"temperature,optional"` } Message { Role string `json:"role"` Content interface{} `json:"content"` } Tool { Type string `json:"type"` Function ToolFunction `json:"function"` } ToolFunction { Name string `json:"name"` Description string `json:"description"` Parameters interface{} `json:"parameters"` } Function { Name string `json:"name"` Description string `json:"description,optional"` Parameters interface{} `json:"parameters,optional"` } ChatCompletionResponse { Id string `json:"id"` Object string `json:"object"` Created int64 `json:"created"` Model string `json:"model"` Choices []Choice `json:"choices"` Usage Usage `json:"usage"` } Choice { Index int `json:"index"` Message *RespMessage `json:"message,optional"` Delta *Delta `json:"delta,optional"` FinishReason string `json:"finish_reason"` } RespMessage { Role string `json:"role"` Content string `json:"content,optional"` ToolCalls []ToolCall `json:"tool_calls,optional"` } Delta struct { Role string `json:"role,optional"` Content string `json:"content,optional"` ReasoningContent string `json:"reasoning_content,optional"` ToolCalls []ToolCall `json:"tool_calls,optional"` } ToolCall struct { Index int `json:"index"` Id string `json:"id"` Type string `json:"type"` Function FunctionCall `json:"function"` } FunctionCall struct { Name string `json:"name"` Arguments string `json:"arguments"` } Usage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` } // Anthropic Messages AnthropicRequest { Model string `json:"model"` Messages []Message `json:"messages"` MaxTokens int `json:"max_tokens"` Stream bool `json:"stream,optional"` System string `json:"system,optional"` } AnthropicResponse { Id string `json:"id"` Type string `json:"type"` Role string `json:"role"` Content []ContentBlock `json:"content"` Model string `json:"model"` Usage AnthropicUsage `json:"usage"` } ContentBlock { Type string `json:"type"` Text string `json:"text,optional"` } AnthropicUsage struct { InputTokens int `json:"input_tokens"` OutputTokens int `json:"output_tokens"` } ) // ============ Routes ============ @server( prefix: /v1 group: chat ) service chat-api { @doc("Health check") @handler Health get /health returns (HealthResponse) @doc("List available models") @handler Models get /v1/models returns (ModelsResponse) @doc("Chat completions (OpenAI compatible)") @handler ChatCompletions post /v1/chat/completions (ChatCompletionRequest) @doc("Anthropic messages") @handler AnthropicMessages post /v1/messages (AnthropicRequest) } ``` #### Step 1.3: 生成代碼骨架 ```bash goctl api go -api api/chat.api -dir . --style go_zero ``` #### Step 1.4: 建立 `etc/chat.yaml` ```yaml Name: chat-api Host: ${CURSOR_BRIDGE_HOST:0.0.0.0} Port: ${CURSOR_BRIDGE_PORT:8080} # API Key 驗證(可選) Auth: AccessSecret: ${CURSOR_API_KEY:} AccessExpire: 86400 # Cursor 配置 AgentBin: ${CURSOR_AGENT_BIN:cursor-agent} DefaultModel: ${CURSOR_DEFAULT_MODEL:claude-3.5-sonnet} Provider: ${CURSOR_PROVIDER:cursor} # 超時設定 TimeoutMs: ${CURSOR_TIMEOUT_MS:300000} # 多帳號池 ConfigDirs: - ${HOME}/.cursor-api-proxy/accounts/default MultiPort: false # TLS TLSCertPath: ${CURSOR_TLS_CERT_PATH:} TLSKeyPath: ${CURSOR_TLS_KEY_PATH:} # 日誌 SessionsLogPath: ${CURSOR_SESSIONS_LOG_PATH:} Verbose: ${CURSOR_VERBOSE:false} # Gemini 設定 GeminiAccountDir: ${GEMINI_ACCOUNT_DIR:} GeminiBrowserVisible: ${GEMINI_BROWSER_VISIBLE:false} GeminiMaxSessions: ${GEMINI_MAX_SESSIONS:10} # 工作區設定 Workspace: ${CURSOR_WORKSPACE:} ChatOnlyWorkspace: ${CURSOR_CHAT_ONLY_WORKSPACE:true} WinCmdlineMax: ${CURSOR_WIN_CMDLINE_MAX:32768} # Agent 設定 Force: ${CURSOR_FORCE:false} ApproveMcps: ${CURSOR_APPROVE_MCPS:false} MaxMode: ${CURSOR_MAX_MODE:false} StrictModel: ${CURSOR_STRICT_MODEL:true} ``` #### Step 1.5: 移動生成的檔案 ```bash # goctl 生成 chat.go 到根目錄,需移動到 cmd/ mkdir -p cmd/chat mv chat.go cmd/chat/chat.go ``` --- ### Phase 2: Domain 層建立 **目標**:建立 `pkg/domain/` 目錄結構 #### Step 2.1: 建立實體 ```bash mkdir -p pkg/domain/entity ``` **檔案對照表:** | 新檔案 | 來源 | |--------|------| | `pkg/domain/entity/message.go` | `internal/apitypes/types.go` | | `pkg/domain/entity/chunk.go` | `internal/apitypes/types.go` (StreamChunk) | | `pkg/domain/entity/account.go` | `internal/pool/pool.go` (AccountStat) | | `pkg/domain/entity/config.go` | `internal/config/config.go` (部分) | #### Step 2.2: 建立倉儲介面 ```bash mkdir -p pkg/domain/repository ``` **檔案:** | 新檔案 | 說明 | |--------|------| | `pkg/domain/repository/account.go` | AccountPool interface (從 pool.go 抽取) | | `pkg/domain/repository/provider.go` | Provider interface (從 providers/factory.go 抽取) | #### Step 2.3: 建立用例介面 ```bash mkdir -p pkg/domain/usecase ``` **檔案:** | 新檔案 | 說明 | |--------|------| | `pkg/domain/usecase/chat.go` | ChatUsecase interface | | `pkg/domain/usecase/agent.go` | AgentRunner interface | #### Step 2.4: 建立常數 ```bash mkdir -p pkg/domain/const ``` **檔案:** | 新檔案 | 來源 | |--------|------| | `pkg/domain/const/models.go` | `internal/models/cursormap.go` | | `pkg/domain/const/errors.go` | 新增錯誤定義 | --- ### Phase 3: Infrastructure 層建立 **目標**:建立 `pkg/infrastructure/` 基礎設施層 #### Step 3.1: 建立目錄結構 ```bash mkdir -p pkg/infrastructure/{process,parser,httputil,logger,env,workspace,winlimit} ``` #### Step 3.2: 遷移基礎設施模組 | 原始 | 目標 | |------|------| | `internal/process/process.go` | `pkg/infrastructure/process/runner.go` | | `internal/process/kill_unix.go` | `pkg/infrastructure/process/kill_unix.go` | | `internal/process/kill_windows.go` | `pkg/infrastructure/process/kill_windows.go` | | `internal/parser/stream.go` | `pkg/infrastructure/parser/stream.go` | | `internal/httputil/httputil.go` | `pkg/infrastructure/httputil/httputil.go` | | `internal/logger/logger.go` | `pkg/infrastructure/logger/logger.go` | | `internal/env/env.go` | `pkg/infrastructure/env/env.go` | | `internal/workspace/workspace.go` | `pkg/infrastructure/workspace/workspace.go` | | `internal/winlimit/winlimit.go` | `pkg/infrastructure/winlimit/winlimit.go` | --- ### Phase 4: Repository 層實作 **目標**:實作 `pkg/repository/` #### Step 4.1: 建立目錄 ```bash mkdir -p pkg/repository ``` #### Step 4.2: 遷移檔案 | 原始 | 目標 | |------|------| | `internal/pool/pool.go` | `pkg/repository/account.go` (AccountPool 實作) | | `internal/providers/factory.go` | `pkg/repository/provider.go` (Provider 工廠) | --- ### Phase 5: Provider 層建立 **目標**:遷移 Provider 實作 #### Step 5.1: 建立目錄 ```bash mkdir -p pkg/provider/cursor pkg/provider/geminiweb ``` #### Step 5.2: 遷移檔案 | 原始 | 目標 | |------|------| | `internal/providers/cursor/provider.go` | `pkg/provider/cursor/provider.go` | | `internal/providers/geminiweb/provider.go` | `pkg/provider/geminiweb/provider.go` | | `internal/providers/geminiweb/browser.go` | `pkg/provider/geminiweb/browser.go` | | `internal/providers/geminiweb/pool.go` | `pkg/provider/geminiweb/pool.go` | | `internal/providers/geminiweb/browser_manager.go` | `pkg/provider/geminiweb/browser_manager.go` | | `internal/providers/geminiweb/page.go` | `pkg/provider/geminiweb/page.go` | | `internal/providers/geminiweb/playwright_provider.go` | `pkg/provider/geminiweb/playwright_provider.go` | --- ### Phase 6: Usecase 層建立 **目標**:實作業務邏輯層 #### Step 6.1: 建立目錄 ```bash mkdir -p pkg/usecase ``` #### Step 6.2: 建立檔案 | 新檔案 | 說明 | 來源 | |--------|------|------| | `pkg/usecase/chat.go` | 核心聊天邏輯 | 從 handlers/chat.go 抽取 | | `pkg/usecase/agent.go` | Agent 執行邏輯 | `internal/agent/runner.go` | | `pkg/usecase/sanitizer.go` | 消息清理 | `internal/sanitize/sanitize.go` | | `pkg/usecase/toolcall.go` | Tool call 處理 | `internal/toolcall/toolcall.go` | #### Step 6.3: Agent 相關檔案遷移 | 原始 | 目標 | |------|------| | `internal/agent/runner.go` | `pkg/usecase/runner.go` | | `internal/agent/token.go` | `pkg/usecase/token.go` | | `internal/agent/cmdargs.go` | `pkg/usecase/cmdargs.go` | | `internal/agent/maxmode.go` | `pkg/usecase/maxmode.go` | --- ### Phase 7: Adapter 層建立 **目標**:API 格式轉換器 #### Step 7.1: 建立目錄 ```bash mkdir -p pkg/adapter/openai pkg/adapter/anthropic ``` #### Step 7.2: 遷移檔案 | 原始 | 目標 | |------|------| | `internal/openai/*.go` | `pkg/adapter/openai/*.go` | | `internal/anthropic/*.go` | `pkg/adapter/anthropic/*.go` | --- ### Phase 8: Internal 層重組 **目標**:建立 go-zero 框架層 #### Step 8.1: 更新 Config 修改 `internal/config/config.go` 使用 go-zero 的 `rest.RestConf`: ```go package config import "github.com/zeromicro/go-zero/rest" type Config struct { rest.RestConf // Cursor 配置 AgentBin string DefaultModel string Provider string TimeoutMs int // 多帳號池 ConfigDirs []string MultiPort bool // TLS TLSCertPath string TLSKeyPath string // 日誌 SessionsLogPath string Verbose bool // Gemini GeminiAccountDir string GeminiBrowserVisible bool GeminiMaxSessions int // 工作區 Workspace string ChatOnlyWorkspace bool WinCmdlineMax int // Agent Force bool ApproveMcps bool MaxMode bool StrictModel bool } ``` #### Step 8.2: 建立 ServiceContext ```bash mkdir -p internal/svc ``` 建立 `internal/svc/servicecontext.go`: ```go package svc import ( "cursor-api-proxy/internal/config" "cursor-api-proxy/pkg/domain/repository" "cursor-api-proxy/pkg/provider" "cursor-api-proxy/pkg/repository" "cursor-api-proxy/pkg/usecase" ) type ServiceContext struct { Config config.Config // Domain AccountPool repository.AccountPool ChatUsecase usecase.ChatUsecase // Provider Provider repository.Provider } func NewServiceContext(c config.Config) *ServiceContext { accountPool := repository.NewAccountPool(c.ConfigDirs) prov := provider.NewProvider(c) chatUsecase := usecase.NewChatUsecase(accountPool, prov, c) return &ServiceContext{ Config: c, AccountPool: accountPool, ChatUsecase: chatUsecase, Provider: prov, } } ``` #### Step 8.3: 建立 Logic 層 ```bash mkdir -p internal/logic ``` **SSE 自訂 Handler 說明:** 由於 go-zero 標準 handler 回傳 JSON,但專案需要 SSE streaming,所以 Logic 層需要自訂處理: ```go // internal/logic/chatcompletionlogic.go func (l *ChatCompletionLogic) ChatCompletion(req *types.ChatCompletionRequest) error { // 設定 SSE headers l.w.Header().Set("Content-Type", "text/event-stream") l.w.Header().Set("Cache-Control", "no-cache") l.w.Header().Set("Connection", "keep-alive") flusher, ok := l.w.(http.Flusher) if !ok { return errors.New("streaming not supported") } // 委託給 usecase 處理串流 err := l.svcCtx.ChatUsecase.Stream(l.ctx, usecase.ChatInput{ Model: req.Model, Messages: l.convertMessages(req.Messages), Tools: l.convertTools(req.Tools), Stream: req.Stream, }, func(chunk entity.StreamChunk) { l.writeChunk(chunk) flusher.Flush() }) return err } ``` #### Step 8.4: 建立 Handler 層 ```bash mkdir -p internal/handler ``` **自訂 Handler 處理 SSE:** 由於需要 SSE streaming,Handler 需要: 1. 不使用 goctl 的標準模板 2. 自訂 `http.HandlerFunc` 處理 SSE 3. 在 `routes.go` 中註冊自訂 handler #### Step 8.5: 建立 Middleware ```bash mkdir -p internal/middleware ``` | 新檔案 | 說明 | |--------|------| | `internal/middleware/auth.go` | API Key 驗證 | | `internal/middleware/recovery.go` | Panic 恢復(從 router.go 抽取) | --- ### Phase 9: CLI 工具遷移 **目標**:保留 CLI 工具在 `cmd/cli/` #### Step 9.1: 建立目錄 ```bash mkdir -p cmd/cli ``` #### Step 9.2: 遷移檔案 | 原始 | 目標 | |------|------| | `cmd/args.go` | `cmd/cli/args.go` | | `cmd/login.go` | `cmd/cli/login.go` | | `cmd/accounts.go` | `cmd/cli/accounts.go` | | `cmd/resethwid.go` | `cmd/cli/resethwid.go` | | `cmd/usage.go` | `cmd/cli/usage.go` | | `cmd/sqlite.go` | `cmd/cli/sqlite.go` | | `cmd/gemini-login/main.go` | `cmd/cli/gemini_login.go` | --- ### Phase 10: 清理與測試 #### Step 10.1: 移除舊目錄 確認所有遷移完成後: ```bash # 移除舊的 internal 目錄(保留已遷移的) rm -rf internal/router rm -rf internal/handlers rm -rf internal/providers rm -rf internal/apitypes rm -rf internal/pool rm -rf internal/agent rm -rf internal/parser rm -rf internal/toolcall rm -rf internal/sanitize rm -rf internal/openai rm -rf internal/anthropic rm -rf internal/process rm -rf internal/models rm -rf internal/workspace rm -rf internal/winlimit rm -rf internal/httputil rm -rf internal/logger rm -rf internal/env rm -rf internal/server # 移除舊的 main.go rm main.go # 移除舊的 cmd 目錄(已遷移到 cmd/cli) rm -rf cmd/gemini-login ``` #### Step 10.2: 更新 import 路徑 ```bash # 批量更新 import 路徑 # internal/ → pkg/ (domain 相關) find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/apitypes|cursor-api-proxy/pkg/domain/entity|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/pool|cursor-api-proxy/pkg/repository|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/agent|cursor-api-proxy/pkg/usecase|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/providers|cursor-api-proxy/pkg/provider|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/parser|cursor-api-proxy/pkg/infrastructure/parser|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/toolcall|cursor-api-proxy/pkg/usecase|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/sanitize|cursor-api-proxy/pkg/usecase|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/openai|cursor-api-proxy/pkg/adapter/openai|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/anthropic|cursor-api-proxy/pkg/adapter/anthropic|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/process|cursor-api-proxy/pkg/infrastructure/process|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/models|cursor-api-proxy/pkg/domain/const|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/workspace|cursor-api-proxy/pkg/infrastructure/workspace|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/winlimit|cursor-api-proxy/pkg/infrastructure/winlimit|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/httputil|cursor-api-proxy/pkg/infrastructure/httputil|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/logger|cursor-api-proxy/pkg/infrastructure/logger|g' {} \; find . -name "*.go" -exec sed -i '' 's|cursor-api-proxy/internal/env|cursor-api-proxy/pkg/infrastructure/env|g' {} \; ``` #### Step 10.3: 測試 ```bash # 單元測試 go test ./pkg/domain/... -v go test ./pkg/usecase/... -v go test ./pkg/repository/... -v # 整合測試 go test ./internal/... -v # 建置測試 go build ./cmd/chat go build ./cmd/cli # 功能測試 make env ./cursor-api-proxy -f etc/chat.yaml & curl http://localhost:8080/health curl http://localhost:8080/v1/models ``` --- ## 四、漸進式遷移順序 ### 每日執行計劃 | 階段 | 任務 | 預估時間 | 依賴 | |------|------|---------|------| | Day 1 | Phase 1: API 定義 | 2-3 小時 | 無 | | Day 2 | Phase 2: Domain 層 | 2-3 小時 | Phase 1 | | Day 3 | Phase 3: Infrastructure 層 | 2-3 小時 | Phase 2 | | Day 4 | Phase 4: Repository 層 | 2-3 小時 | Phase 3 | | Day 5 | Phase 5: Provider 層 | 2-3 小時 | Phase 4 | | Day 6 | Phase 6: Usecase 層 | 3-4 小時 | Phase 5 | | Day 7 | Phase 7: Adapter 層 | 2 小時 | Phase 6 | | Day 8 | Phase 8: Internal 層 | 4-5 小時 | Phase 7 | | Day 9 | Phase 9: CLI 工具 | 1-2 小時 | Phase 8 | | Day 10 | Phase 10: 清理與測試 | 3-4 小時 | Phase 9 | ### 漸進式測試策略 每個 Phase 完成後執行: 1. **單元測試**:確保新模組測試通過 2. **編譯測試**:確保代碼可編譯 3. **功能測試**:確保核心功能正常 ```bash # 每日測試腳本 #!/bin/bash set -e echo "=== Running unit tests ===" go test ./pkg/... -v echo "=== Building ===" go build ./cmd/chat go build ./cmd/cli echo "=== Starting server ===" ./cursor-api-proxy -f etc/chat.yaml & PID=$! sleep 2 echo "=== Running functional tests ===" curl -s http://localhost:8080/health | jq . curl -s http://localhost:8080/v1/models | jq . echo "=== Cleaning up ===" kill $PID echo "=== All tests passed ===" ``` --- ## 五、回歸測試清單 ### 功能測試 - [ ] Health check: `GET /health` - [ ] Models list: `GET /v1/models` - [ ] Chat completions (非串流): `POST /v1/chat/completions` with `stream: false` - [ ] Chat completions (串流): `POST /v1/chat/completions` with `stream: true` - [ ] Anthropic messages: `POST /v1/messages` - [ ] Gemini chat completions: `POST /v1/chat/completions` (Provider=gemini-web) - [ ] Tool calls 處理 - [ ] Rate limit 處理 - [ ] Multi-account 輪替 - [ ] API Key 驗證 - [ ] TLS/HTTPS 支援 ### CLI 工具測試 - [ ] `login` 命令 - [ ] `accounts` 命令 - [ ] `resethwid` 命令 - [ ] `usage` 命令 - [ ] `gemini-login` 命令 ### 效能測試 - [ ] SSE streaming latency - [ ] Concurrent requests (10, 50, 100) - [ ] Memory usage baseline - [ ] Long-running stability --- ## 六、風險與注意事項 ### 高風險項目 | 風險 | 描述 | 緩解措施 | |------|------|---------| | SSE Streaming | go-zero 標準 handler 不支援 SSE | 自訂 handler,在 Logic 層處理 SSE | | Context 傳遞 | 需確保 context 正確傳遞到 usecase 層 | 使用 context.Context 作為參數傳遞 | | 全局變數 | pool.go 的 global pool 需改為 DI | 使用 ServiceContext 注入 | | 環境變數 | 需從 `CURSOR_*` 遷移到 go-zero 配置 | 保持環境變數支援,使用 `${VAR:default}` 語法 | ### 注意事項 1. **保持向後相容**:API 端點和行為必須與現有版本一致 2. **環境變數相容**:保持現有環境變數名稱 3. **Makefile 更新**:更新建置命令 4. **日誌格式**:保持現有日誌格式以便分析工具相容 --- ## 七、Makefile 更新 需要新增 go-zero 相關命令: ```makefile # go-zero 代碼生成 .PHONY: api api: goctl api go -api api/chat.api -dir . --style go_zero .PHONY: api-doc api-doc: goctl api doc -api api/chat.api -dir docs/ # 格式化 .PHONY: fmt fmt: go fmt ./... # 測試 .PHONY: test test: go test ./... -v -race # 建置 .PHONY: build build: go build -o bin/chat-api cmd/chat/chat.go # 運行 .PHONY: run run: ./bin/chat-api -f etc/chat.yaml ``` --- ## 八、參考資源 - [go-zero 官方文檔](https://go-zero.dev/) - [go-zero API 定義](https://go-zero.dev/docs/tutorial/api/) - [go-zero 最佳實踐](https://go-zero.dev/docs/tutorials/) - [zero-skills](https://github.com/zeromicro/zero-skills) - [ai-context](https://github.com/zeromicro/ai-context) - [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/33/the-clean-architecture.html) --- ## 九、附錄:檔案遷移對照表 ### 完整檔案對照表 | 原始 | 目標 | 說明 | |------|------|------| | `main.go` | `cmd/chat/chat.go` | HTTP 服務入口 | | `cmd/args.go` | `cmd/cli/args.go` | CLI 參數 | | `cmd/login.go` | `cmd/cli/login.go` | CLI login | | `cmd/accounts.go` | `cmd/cli/accounts.go` | CLI accounts | | `cmd/resethwid.go` | `cmd/cli/resethwid.go` | CLI resethwid | | `cmd/usage.go` | `cmd/cli/usage.go` | CLI usage | | `cmd/sqlite.go` | `cmd/cli/sqlite.go` | SQLite 操作 | | `cmd/gemini-login/main.go` | `cmd/cli/gemini_login.go` | Gemini CLI | | `internal/apitypes/types.go` | `pkg/domain/entity/message.go` | Message 實體 | | `internal/pool/pool.go` | `pkg/repository/account.go` | AccountPool 實作 | | `internal/agent/runner.go` | `pkg/usecase/runner.go` | Agent 執行 | | `internal/agent/token.go` | `pkg/usecase/token.go` | Token 處理 | | `internal/agent/cmdargs.go` | `pkg/usecase/cmdargs.go` | 命令參數 | | `internal/agent/maxmode.go` | `pkg/usecase/maxmode.go` | Max mode | | `internal/providers/factory.go` | `pkg/repository/provider.go` | Provider 工廠 | | `internal/providers/cursor/provider.go` | `pkg/provider/cursor/provider.go` | Cursor provider | | `internal/providers/geminiweb/*.go` | `pkg/provider/geminiweb/*.go` | Gemini provider | | `internal/parser/stream.go` | `pkg/infrastructure/parser/stream.go` | SSE parsing | | `internal/toolcall/toolcall.go` | `pkg/usecase/toolcall.go` | Tool call | | `internal/sanitize/sanitize.go` | `pkg/usecase/sanitize.go` | 消息清理 | | `internal/openai/*.go` | `pkg/adapter/openai/*.go` | OpenAI 格式 | | `internal/anthropic/*.go` | `pkg/adapter/anthropic/*.go` | Anthropic 格式 | | `internal/process/process.go` | `pkg/infrastructure/process/runner.go` | 進程管理 | | `internal/process/kill_*.go` | `pkg/infrastructure/process/kill_*.go` | Kill 實作 | | `internal/models/cursormap.go` | `pkg/domain/const/models.go` | Model 映射 | | `internal/workspace/workspace.go` | `pkg/infrastructure/workspace/workspace.go` | 工作區 | | `internal/winlimit/winlimit.go` | `pkg/infrastructure/winlimit/winlimit.go` | Win limit | | `internal/httputil/httputil.go` | `pkg/infrastructure/httputil/httputil.go` | HTTP 工具 | | `internal/logger/logger.go` | `pkg/infrastructure/logger/logger.go` | 日誌 | | `internal/env/env.go` | `pkg/infrastructure/env/env.go` | 環境變數 | | `internal/config/config.go` | `internal/config/config.go` | 配置(使用 go-zero Config) | | `internal/router/router.go` | `internal/handler/routes.go` | 路由(重寫) | | `internal/handlers/*.go` | `internal/logic/*.go` | 業務邏輯 | --- **文件版本**:v1.0 **建立日期**:2026-04-03 **最後更新**:2026-04-03