feat: 自动检测浏览器路径,支持 go-rod 自动下载

- config.go: 添加 detectBrowserPath() 自动检测已安装浏览器
- config.go: 支持 BROWSER_PATH 环境变量
- browser.go: 当 path 为空时让 go-rod 自动下载 Chromium
- config.yaml: 默认 path 为空,添加配置说明
- scripts/setup-browser.sh: 添加浏览器安装脚本
- README.md: 添加浏览器安装文档
This commit is contained in:
chinadoiphin
2025-12-16 20:01:38 +08:00
parent 7b83db9dda
commit c0b4a08f81
7 changed files with 636 additions and 6 deletions

278
.idea/GOHCache.xml generated Normal file
View File

@@ -0,0 +1,278 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoORMHelperCache">
<option name="schemaMapping">
<map>
<entry key="BrowserConfig">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/config/config.go" />
</set>
</value>
</entry>
<entry key="ChatCompletionChunk">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/openai.go" />
</set>
</value>
</entry>
<entry key="ChatCompletionRequest">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/openai.go" />
</set>
</value>
</entry>
<entry key="ChatCompletionResponse">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/openai.go" />
</set>
</value>
</entry>
<entry key="Choice">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/openai.go" />
</set>
</value>
</entry>
<entry key="ChunkChoice">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/openai.go" />
</set>
</value>
</entry>
<entry key="Config">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/config/config.go" />
</set>
</value>
</entry>
<entry key="ContentBlock">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/anthropic.go" />
</set>
</value>
</entry>
<entry key="CursorChatRequest">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/browser/browser.go" />
</set>
</value>
</entry>
<entry key="CursorContext">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/browser/browser.go" />
</set>
</value>
</entry>
<entry key="CursorMessage">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/browser/browser.go" />
</set>
</value>
</entry>
<entry key="CursorPart">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/browser/browser.go" />
</set>
</value>
</entry>
<entry key="CursorSSEEvent">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/anthropic.go" />
</set>
</value>
</entry>
<entry key="Message">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/anthropic.go" />
</set>
</value>
</entry>
<entry key="MessagesRequest">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/anthropic.go" />
</set>
</value>
</entry>
<entry key="MessagesResponse">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/anthropic.go" />
</set>
</value>
</entry>
<entry key="Model">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/models.go" />
</set>
</value>
</entry>
<entry key="ModelsResponse">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/models.go" />
</set>
</value>
</entry>
<entry key="OpenAIMessage">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/openai.go" />
</set>
</value>
</entry>
<entry key="OpenAIUsage">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/openai.go" />
</set>
</value>
</entry>
<entry key="Service">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/browser/browser.go" />
</set>
</value>
</entry>
<entry key="Usage">
<value>
<set>
<option value="file://$PROJECT_DIR$/internal/handler/anthropic.go" />
</set>
</value>
</entry>
</map>
</option>
<option name="scannedPathMapping">
<map>
<entry key="file://$PROJECT_DIR$/cmd/server/main.go">
<value>
<ScannedPath>
<option name="lastModified" value="1765884934000" />
</ScannedPath>
</value>
</entry>
<entry key="file://$PROJECT_DIR$/internal/browser/browser.go">
<value>
<ScannedPath>
<option name="lastModified" value="1765885448000" />
<option name="schema">
<list>
<option value="Service" />
<option value="CursorChatRequest" />
<option value="CursorContext" />
<option value="CursorMessage" />
<option value="CursorPart" />
</list>
</option>
</ScannedPath>
</value>
</entry>
<entry key="file://$PROJECT_DIR$/internal/config/config.go">
<value>
<ScannedPath>
<option name="lastModified" value="1765885080000" />
<option name="schema">
<list>
<option value="Config" />
<option value="BrowserConfig" />
</list>
</option>
</ScannedPath>
</value>
</entry>
<entry key="file://$PROJECT_DIR$/internal/handler/anthropic.go">
<value>
<ScannedPath>
<option name="lastModified" value="1765885633000" />
<option name="schema">
<list>
<option value="MessagesRequest" />
<option value="Message" />
<option value="MessagesResponse" />
<option value="ContentBlock" />
<option value="Usage" />
<option value="CursorSSEEvent" />
</list>
</option>
</ScannedPath>
</value>
</entry>
<entry key="file://$PROJECT_DIR$/internal/handler/models.go">
<value>
<ScannedPath>
<option name="lastModified" value="1765884916000" />
<option name="schema">
<list>
<option value="Model" />
<option value="ModelsResponse" />
</list>
</option>
</ScannedPath>
</value>
</entry>
<entry key="file://$PROJECT_DIR$/internal/handler/openai.go">
<value>
<ScannedPath>
<option name="lastModified" value="1765885479000" />
<option name="schema">
<list>
<option value="ChatCompletionRequest" />
<option value="OpenAIMessage" />
<option value="ChatCompletionResponse" />
<option value="Choice" />
<option value="OpenAIUsage" />
<option value="ChatCompletionChunk" />
<option value="ChunkChoice" />
</list>
</option>
</ScannedPath>
</value>
</entry>
</map>
</option>
<option name="tableStructMapping">
<map>
<entry key="browser_config" value="BrowserConfig" />
<entry key="chat_completion_chunk" value="ChatCompletionChunk" />
<entry key="chat_completion_request" value="ChatCompletionRequest" />
<entry key="chat_completion_response" value="ChatCompletionResponse" />
<entry key="choice" value="Choice" />
<entry key="chunk_choice" value="ChunkChoice" />
<entry key="config" value="Config" />
<entry key="content_block" value="ContentBlock" />
<entry key="cursor_chat_request" value="CursorChatRequest" />
<entry key="cursor_context" value="CursorContext" />
<entry key="cursor_message" value="CursorMessage" />
<entry key="cursor_part" value="CursorPart" />
<entry key="cursor_sse_event" value="CursorSSEEvent" />
<entry key="message" value="Message" />
<entry key="messages_request" value="MessagesRequest" />
<entry key="messages_response" value="MessagesResponse" />
<entry key="model" value="Model" />
<entry key="models_response" value="ModelsResponse" />
<entry key="open_ai_message" value="OpenAIMessage" />
<entry key="open_ai_usage" value="OpenAIUsage" />
<entry key="service" value="Service" />
<entry key="usage" value="Usage" />
</map>
</option>
<option name="lastTimeChecked" value="1765886014493" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -50,6 +50,50 @@ go build -o cursor2api ./cmd/server
服务默认运行在 `http://localhost:3010`
## 浏览器安装
程序需要 Chromium 内核浏览器。有以下几种方式:
### 方式 1: 自动下载 (推荐)
保持 `config.yaml``browser.path` 为空,程序会:
1. 首先自动检测系统已安装的 Chrome/Chromium/Edge
2. 如果未找到,则自动下载 Chromium 到 `~/.cache/rod/browser/`
### 方式 2: 使用安装脚本
```bash
# 运行安装脚本
./scripts/setup-browser.sh
```
### 方式 3: 手动安装
**macOS:**
```bash
brew install --cask chromium
# 或
brew install --cask google-chrome
```
**Linux (Debian/Ubuntu):**
```bash
sudo apt-get update && sudo apt-get install -y chromium-browser
```
**Linux (Alpine):**
```bash
apk add --no-cache chromium
```
### 方式 4: 使用环境变量
```bash
# 指定浏览器路径
export BROWSER_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
./cursor2api
```
## 配置
编辑 `config.yaml`
@@ -61,10 +105,13 @@ port: 3010
# 浏览器设置
browser:
headless: true
path: "/usr/bin/chromium"
# 留空则自动检测或下载,也可手动指定路径
path: ""
```
支持环境变量 `PORT` 覆盖端口配置。
支持环境变量
- `PORT` - 覆盖端口配置
- `BROWSER_PATH` - 覆盖浏览器路径
## API 接口

View File

@@ -9,4 +9,9 @@ browser:
# 是否使用无头模式
headless: true
# Chromium 可执行文件路径
path: "/usr/bin/chromium"
# 留空则自动检测系统浏览器,如果都没有则自动下载 Chromium
# macOS 示例: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
# Linux 示例: /usr/bin/chromium 或 /usr/bin/google-chrome
# Windows 示例: C:\Program Files\Google\Chrome\Application\chrome.exe
# 也可通过环境变量 BROWSER_PATH 设置
path: ""

View File

@@ -52,7 +52,6 @@ func (s *Service) init() {
// 配置浏览器启动参数
l := launcher.New().
Bin(cfg.Browser.Path).
Headless(cfg.Browser.Headless).
Set("disable-blink-features", "AutomationControlled"). // 隐藏自动化特征
Set("no-sandbox").
@@ -61,6 +60,11 @@ func (s *Service) init() {
Set("no-proxy-server"). // 浏览器不使用代理
UserDataDir(userDataDir)
// 如果指定了浏览器路径,则使用指定路径;否则让 go-rod 自动下载
if cfg.Browser.Path != "" {
l = l.Bin(cfg.Browser.Path)
}
u := l.MustLaunch()
s.browser = rod.New().ControlURL(u).MustConnect()

View File

@@ -4,6 +4,7 @@ package config
import (
"log"
"os"
"runtime"
"sync"
"gopkg.in/yaml.v3"
@@ -21,7 +22,7 @@ type Config struct {
type BrowserConfig struct {
// Headless 是否使用无头模式
Headless bool `yaml:"headless"`
// Path Chromium 可执行文件路径
// Path Chromium 可执行文件路径,留空则自动下载
Path string `yaml:"path"`
}
@@ -37,7 +38,7 @@ func Get() *Config {
Port: "3010",
Browser: BrowserConfig{
Headless: true,
Path: "/usr/bin/chromium",
Path: "", // 留空表示自动检测或下载
},
}
load(cfg)
@@ -45,6 +46,42 @@ func Get() *Config {
return cfg
}
// detectBrowserPath 自动检测系统中已安装的浏览器路径
func detectBrowserPath() string {
var paths []string
switch runtime.GOOS {
case "darwin": // macOS
paths = []string{
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Chromium.app/Contents/MacOS/Chromium",
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
}
case "linux":
paths = []string{
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable",
"/snap/bin/chromium",
}
case "windows":
paths = []string{
`C:\Program Files\Google\Chrome\Application\chrome.exe`,
`C:\Program Files (x86)\Google\Chrome\Application\chrome.exe`,
`C:\Program Files\Microsoft\Edge\Application\msedge.exe`,
}
}
for _, p := range paths {
if _, err := os.Stat(p); err == nil {
return p
}
}
return "" // 返回空,让 go-rod 自动下载
}
// load 从配置文件和环境变量加载配置
func load(c *Config) {
// 尝试读取 YAML 配置文件
@@ -63,6 +100,19 @@ func load(c *Config) {
if port := os.Getenv("PORT"); port != "" {
c.Port = port
}
if browserPath := os.Getenv("BROWSER_PATH"); browserPath != "" {
c.Browser.Path = browserPath
}
// 如果浏览器路径未指定,尝试自动检测
if c.Browser.Path == "" {
c.Browser.Path = detectBrowserPath()
if c.Browser.Path != "" {
log.Printf("[配置] 自动检测到浏览器: %s", c.Browser.Path)
} else {
log.Printf("[配置] 未检测到浏览器,将使用 go-rod 自动下载")
}
}
// 输出最终配置
log.Printf("[配置] 端口: %s", c.Port)

240
scripts/setup-browser.sh Executable file
View File

@@ -0,0 +1,240 @@
#!/bin/bash
# Cursor2API 浏览器安装脚本
# 用于安装 Chromium 浏览器依赖
set -e
echo "=========================================="
echo " Cursor2API 浏览器安装脚本"
echo "=========================================="
echo ""
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Darwin*) echo "macos";;
Linux*) echo "linux";;
MINGW*|MSYS*|CYGWIN*) echo "windows";;
*) echo "unknown";;
esac
}
OS=$(detect_os)
echo "[信息] 检测到操作系统: $OS"
# 检查是否已安装浏览器
check_existing_browser() {
echo ""
echo "[检查] 正在检查已安装的浏览器..."
case "$OS" in
macos)
browsers=(
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
"/Applications/Chromium.app/Contents/MacOS/Chromium"
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
)
;;
linux)
browsers=(
"/usr/bin/chromium"
"/usr/bin/chromium-browser"
"/usr/bin/google-chrome"
"/usr/bin/google-chrome-stable"
"/snap/bin/chromium"
)
;;
*)
browsers=()
;;
esac
for browser in "${browsers[@]}"; do
if [ -f "$browser" ]; then
echo "[成功] 找到浏览器: $browser"
echo ""
echo "您可以使用此浏览器,设置环境变量:"
echo " export BROWSER_PATH=\"$browser\""
echo ""
echo "或在 config.yaml 中设置:"
echo " browser:"
echo " path: \"$browser\""
return 0
fi
done
echo "[信息] 未找到已安装的兼容浏览器"
return 1
}
# macOS 安装
install_macos() {
echo ""
echo "[安装] macOS 浏览器安装选项:"
echo ""
echo "方式 1: 使用 Homebrew 安装 Chromium (推荐)"
echo " brew install --cask chromium"
echo ""
echo "方式 2: 使用 Homebrew 安装 Google Chrome"
echo " brew install --cask google-chrome"
echo ""
echo "方式 3: 让 go-rod 自动下载 (无需手动安装)"
echo " 只需保持 config.yaml 中 browser.path 为空"
echo " 程序启动时会自动下载 Chromium 到 ~/.cache/rod/browser/"
echo ""
# 检查是否有 Homebrew
if command -v brew &> /dev/null; then
echo "[提示] 检测到 Homebrew是否自动安装 Chromium? (y/n)"
read -r answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
echo "[安装] 正在安装 Chromium..."
brew install --cask chromium
echo "[成功] Chromium 安装完成!"
echo ""
echo "浏览器路径: /Applications/Chromium.app/Contents/MacOS/Chromium"
fi
else
echo "[提示] 未检测到 Homebrew"
echo "安装 Homebrew: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
fi
}
# Linux 安装
install_linux() {
echo ""
echo "[安装] Linux 浏览器安装选项:"
echo ""
# 检测包管理器
if command -v apt-get &> /dev/null; then
echo "方式 1: 使用 apt 安装 (Debian/Ubuntu)"
echo " sudo apt-get update && sudo apt-get install -y chromium-browser"
echo ""
echo "是否自动安装? (y/n)"
read -r answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
sudo apt-get update && sudo apt-get install -y chromium-browser
echo "[成功] 安装完成!"
fi
elif command -v yum &> /dev/null; then
echo "方式 1: 使用 yum 安装 (CentOS/RHEL)"
echo " sudo yum install -y chromium"
echo ""
echo "是否自动安装? (y/n)"
read -r answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
sudo yum install -y chromium
echo "[成功] 安装完成!"
fi
elif command -v pacman &> /dev/null; then
echo "方式 1: 使用 pacman 安装 (Arch)"
echo " sudo pacman -S chromium"
echo ""
echo "是否自动安装? (y/n)"
read -r answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
sudo pacman -S chromium
echo "[成功] 安装完成!"
fi
elif command -v apk &> /dev/null; then
echo "方式 1: 使用 apk 安装 (Alpine)"
echo " apk add --no-cache chromium"
echo ""
echo "是否自动安装? (y/n)"
read -r answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
apk add --no-cache chromium
echo "[成功] 安装完成!"
fi
else
echo "未检测到已知的包管理器"
fi
echo ""
echo "方式 2: 让 go-rod 自动下载 (无需手动安装)"
echo " 只需保持 config.yaml 中 browser.path 为空"
}
# Docker 安装提示
docker_tips() {
echo ""
echo "=========================================="
echo " Docker 用户提示"
echo "=========================================="
echo ""
echo "如果在 Docker 中运行,请在 Dockerfile 中添加:"
echo ""
echo "# Debian/Ubuntu 基础镜像"
echo "RUN apt-get update && apt-get install -y \\"
echo " chromium \\"
echo " fonts-liberation \\"
echo " libasound2 \\"
echo " libatk-bridge2.0-0 \\"
echo " libatk1.0-0 \\"
echo " libatspi2.0-0 \\"
echo " libcups2 \\"
echo " libdbus-1-3 \\"
echo " libdrm2 \\"
echo " libgbm1 \\"
echo " libgtk-3-0 \\"
echo " libnspr4 \\"
echo " libnss3 \\"
echo " libxcomposite1 \\"
echo " libxdamage1 \\"
echo " libxfixes3 \\"
echo " libxkbcommon0 \\"
echo " libxrandr2 \\"
echo " xdg-utils \\"
echo " && rm -rf /var/lib/apt/lists/*"
echo ""
echo "# Alpine 基础镜像"
echo "RUN apk add --no-cache chromium"
echo ""
echo "# 设置环境变量"
echo "ENV BROWSER_PATH=/usr/bin/chromium"
}
# 主流程
main() {
# 先检查已安装的浏览器
if check_existing_browser; then
echo "[完成] 您已有可用的浏览器!"
docker_tips
exit 0
fi
# 根据操作系统安装
case "$OS" in
macos)
install_macos
;;
linux)
install_linux
;;
windows)
echo ""
echo "[信息] Windows 用户请手动安装 Google Chrome 或 Edge 浏览器"
echo "下载地址: https://www.google.com/chrome/"
;;
*)
echo "[错误] 不支持的操作系统"
exit 1
;;
esac
docker_tips
echo ""
echo "=========================================="
echo " 安装完成后运行程序"
echo "=========================================="
echo ""
echo "go run cmd/server/main.go"
echo ""
echo "或设置环境变量后运行:"
echo "BROWSER_PATH=/path/to/browser go run cmd/server/main.go"
}
main