34 KiB
34 KiB
重構計劃: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/ # <Domain Layer>
│ │ ├── 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/ # <Infrastructure Layer>
│ │ ├── account.go # AccountPool 實作
│ │ └── provider.go # Provider 工廠
│ │
│ ├── usecase/ # <Application Layer>
│ │ ├── 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 專案
# 安裝 goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest
# 建立 API 定義目錄
mkdir -p api etc
Step 1.2: 建立 api/chat.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: 生成代碼骨架
goctl api go -api api/chat.api -dir . --style go_zero
Step 1.4: 建立 etc/chat.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: 移動生成的檔案
# goctl 生成 chat.go 到根目錄,需移動到 cmd/
mkdir -p cmd/chat
mv chat.go cmd/chat/chat.go
Phase 2: Domain 層建立
目標:建立 pkg/domain/ 目錄結構
Step 2.1: 建立實體
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: 建立倉儲介面
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: 建立用例介面
mkdir -p pkg/domain/usecase
檔案:
| 新檔案 | 說明 |
|---|---|
pkg/domain/usecase/chat.go |
ChatUsecase interface |
pkg/domain/usecase/agent.go |
AgentRunner interface |
Step 2.4: 建立常數
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: 建立目錄結構
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: 建立目錄
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: 建立目錄
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: 建立目錄
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: 建立目錄
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:
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
mkdir -p internal/svc
建立 internal/svc/servicecontext.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 層
mkdir -p internal/logic
SSE 自訂 Handler 說明:
由於 go-zero 標準 handler 回傳 JSON,但專案需要 SSE streaming,所以 Logic 層需要自訂處理:
// 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 層
mkdir -p internal/handler
自訂 Handler 處理 SSE:
由於需要 SSE streaming,Handler 需要:
- 不使用 goctl 的標準模板
- 自訂
http.HandlerFunc處理 SSE - 在
routes.go中註冊自訂 handler
Step 8.5: 建立 Middleware
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: 建立目錄
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: 移除舊目錄
確認所有遷移完成後:
# 移除舊的 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 路徑
# 批量更新 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: 測試
# 單元測試
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 完成後執行:
- 單元測試:確保新模組測試通過
- 編譯測試:確保代碼可編譯
- 功能測試:確保核心功能正常
# 每日測試腳本
#!/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/completionswithstream: false - Chat completions (串流):
POST /v1/chat/completionswithstream: 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} 語法 |
注意事項
- 保持向後相容:API 端點和行為必須與現有版本一致
- 環境變數相容:保持現有環境變數名稱
- Makefile 更新:更新建置命令
- 日誌格式:保持現有日誌格式以便分析工具相容
七、Makefile 更新
需要新增 go-zero 相關命令:
# 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
八、參考資源
九、附錄:檔案遷移對照表
完整檔案對照表
| 原始 | 目標 | 說明 |
|---|---|---|
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