1069 lines
34 KiB
Markdown
1069 lines
34 KiB
Markdown
|
|
# 重構計劃: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 專案
|
|||
|
|
|
|||
|
|
```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
|