目的:分析参考项目 (cc-switch/Rust) 与当前项目 (cc-r/Go) 在 Gemini 配置管理的实现差异,识别配置问题。
| 方面 | cc-switch (参考) | cc-r (当前) | 状态 |
|---|---|---|---|
| 语言框架 | Rust/Tauri | Go/Wails3 | - |
| Gemini 目录 | ~/.gemini/ |
~/.gemini/ |
✅ 一致 |
| 供应商配置 | DB 或配置文件 | ~/.code-switch/gemini-providers.json |
✅ 合理 |
| 代理前缀 | /gemini |
/gemini |
✅ 一致 |
| 备份文件名 | 未在代码中找到 | .cc-studio.backup |
❌ 命名错误 |
| 配置验证 | 有(严格模式) | 缺失 |
cc-r 代码 (services/geminiservice.go:656, 689):
backupPath := envPath + ".cc-studio.backup" // ❌ 错误!问题:
- 命名为
.cc-studio.backup,但项目是 "code-switch" - 来自旧项目迁移的遗留代码
- 当禁用代理时,恢复备份会使用错误的文件名
修复:
backupPath := envPath + ".cc-switch.backup" // 或 ".code-switch.backup"cc-switch (src-tauri/src/gemini_config.rs:226-278):
// 两级验证
validate_gemini_settings() // 基础格式检查
validate_gemini_settings_strict() // 要求必需字段cc-r:
// 仅有检测,无验证
detectGeminiAuthType(provider) // 只是识别类型SwitchProvider() 的问题:
func (s *GeminiService) SwitchProvider(id string) error {
// ... 找到 provider ...
// ❌ 直接写入,无验证
authType := detectGeminiAuthType(provider)
switch authType {
case GeminiAuthAPIKey:
if provider.APIKey == "" {
// 应该在这里拒绝!
}
}
}影响:
- 用户可以切换到配置不完整的供应商(缺 API Key)
- Gemini 读取配置时会失败,但用户不知道原因
修复:
func (s *GeminiService) SwitchProvider(id string) error {
// ... 找到 provider ...
authType := detectGeminiAuthType(provider)
// 新增:验证配置完整性
if authType != GeminiAuthOAuth && provider.APIKey == "" {
return fmt.Errorf("无法切换:API Key 未设置")
}
// ... 继续写入配置 ...
}cc-r 代码 (services/geminiservice.go:232-262):
case GeminiAuthPackycode, GeminiAuthAPIKey, GeminiAuthGeneric:
// ... 所有 API Key 类型都写同样的值
if err := writeGeminiSettings(map[string]any{
"security": map[string]any{
"auth": map[string]any{
"selectedType": string(GeminiAuthAPIKey), // ❌ 统一为 "gemini-api-key"
},
},
}); err != nil {
// ...
}问题:
- PackyCode 是特殊的第三方供应商,应该有自己的认证标记
- 所有 API Key 类型都写
"gemini-api-key",无法区分 - 会导致 Gemini CLI 无法识别 PackyCode 的特殊配置需求
修复:按认证类型写不同的 selectedType
switch authType {
case GeminiAuthOAuth:
// ... oauth-personal ...
case GeminiAuthPackycode:
if err := writeGeminiSettings(map[string]any{
"security": map[string]any{
"auth": map[string]any{
"selectedType": "packycode", // ✅ 区分类型
},
},
}); err != nil {
// ...
}
case GeminiAuthAPIKey:
if err := writeGeminiSettings(map[string]any{
"security": map[string]any{
"auth": map[string]any{
"selectedType": "gemini-api-key", // ✅ 保留原有
},
},
}); err != nil {
// ...
}
}代码 (services/geminiservice.go:234-247):
envConfig := provider.EnvConfig
if envConfig == nil {
envConfig = make(map[string]string) // ❌ 创建空 map
}
// 然后尝试从 provider 字段补充配置
if provider.BaseURL != "" && envConfig["GOOGLE_GEMINI_BASE_URL"] == "" {
envConfig["GOOGLE_GEMINI_BASE_URL"] = provider.BaseURL
}问题:
- 如果
EnvConfig为 nil,会丢失预设中的配置 - 应该先复制预设配置,再覆盖
修复:
envConfig := make(map[string]string)
// 1. 复制预设配置
if provider.EnvConfig != nil {
for k, v := range provider.EnvConfig {
envConfig[k] = v
}
}
// 2. 补充来自 provider 字段的配置
if provider.BaseURL != "" && envConfig["GOOGLE_GEMINI_BASE_URL"] == "" {
envConfig["GOOGLE_GEMINI_BASE_URL"] = provider.BaseURL
}
// ...代码 (services/geminiservice.go:708-725):
func buildProxyURL(relayAddr string) string {
addr := strings.TrimSpace(relayAddr)
if addr == "" {
addr = ":18100" // ❌ 歧义!
}
// ...
if strings.HasPrefix(host, ":") {
host = "127.0.0.1" + host
}
// ...
}问题:
:18100在 Go 中表示监听所有网卡(0.0.0.0:18100),但代码假设它是端口main.go:88传入的":18100"实际上应该指向127.0.0.1:18100- 这与安全架构冲突(代理应只监听本地回环)
修复:在调用点明确使用本地地址
// main.go:88
geminiService := services.NewGeminiService("127.0.0.1:18100") // ✅ 明确本地或改进 buildProxyURL() 的处理逻辑。
代码 (services/geminiservice_test.go:8, 233):
func TestGeminiService_GetPresets(t *testing.T) {
svc := NewGeminiService() // ❌ 缺参数
// ...
}问题:
NewGeminiService(relayAddr string)要求传入地址参数- 测试中缺少参数,导致编译失败
- 影响
go test ./...整体执行
修复:
func TestGeminiService_GetPresets(t *testing.T) {
svc := NewGeminiService(":18100") // ✅ 添加参数
presets := svc.GetPresets()
// ...
}
func TestGeminiPreset_Fields(t *testing.T) {
svc := NewGeminiService(":18100") // ✅ 添加参数
presets := svc.GetPresets()
// ...
}代码 (services/geminiservice.go:391):
lines := strings.Split(content, "\n")问题:
- Windows 上 .env 文件可能有
\r\n行尾 strings.Split()会在每个\n处分割,保留\rstrings.TrimSpace()会去除\r,但逻辑不够清晰
改进(非阻断):
// 使用 bufio.Scanner 或
lines := strings.Split(strings.ReplaceAll(content, "\r\n", "\n"), "\n")cc-switch (src-tauri/src/gemini_config.rs:8-17):
pub fn get_gemini_dir() -> PathBuf {
if let Some(custom) = crate::settings::get_gemini_override_dir() {
return custom; // 支持覆盖
}
// ...
}cc-r:无此机制,硬编码 ~/.gemini/
影响:低(大多数用户不需要,高级用户可修改)
缺失的测试:
- ❌
buildProxyURL()的各种输入格式 - ❌
SwitchProvider()的切换逻辑 - ❌
EnableProxy()/DisableProxy()的备份恢复 - ❌ 配置验证的失败场景
建议:添加这些测试,确保重构后功能完整。
| 优先级 | 问题 | 文件:行 | 修复工作量 |
|---|---|---|---|
| 🔴 P0 | 测试参数缺失 | geminiservice_test.go:8,233 |
5 分钟 |
| 🔴 P0 | 配置验证缺失 | geminiservice.go:SwitchProvider |
15 分钟 |
| 🟠 P1 | 备份文件命名 | geminiservice.go:656,689 |
5 分钟 |
| 🟠 P1 | selectedType 值 | geminiservice.go:232-262 |
10 分钟 |
| 🟡 P2 | envConfig 处理 | geminiservice.go:234-247 |
10 分钟 |
| 🟡 P2 | 代理地址歧义 | geminiservice.go:708-725, main.go:88 |
10 分钟 |
| 🟢 P3 | 行尾处理 | geminiservice.go:391 |
5 分钟 |
| 🟢 P3 | 测试覆盖 | geminiservice_test.go |
30 分钟 |
总计:约 90 分钟可修复所有问题
- cc-switch 验证逻辑:
src-tauri/src/gemini_config.rs:226-278 - cc-switch 文件写入:
src-tauri/src/gemini_config.rs:287-337 - cc-switch 单元测试:
src-tauri/src/gemini_config.rs:375-656
修复后请确保:
-
go test ./services -v -run TestGemini通过 -
EnableProxy()后,.cc-switch.backup或.code-switch.backup文件存在 - 切换到无 API Key 的供应商时返回错误
- PackyCode 供应商的 selectedType 为 "packycode"
-
buildProxyURL()返回http://127.0.0.1:18100/gemini - 前端能正确切换 Gemini 供应商
分析日期:2025-11-29
分析工具:Claude Code (Haiku 4.5)
版本:1.0