Files
yoyo/taolun.md
z.to b71f76c8b3 feat: 添加本地缓存功能,减少API调用
- 实现SQLite缓存模块,支持高效查询和存储
- 添加缓存键生成策略(基于原文+语言对的SHA256哈希)
- 集成缓存到Translator类,先查缓存再调用API
- 添加缓存管理命令:cache clear, cache stats, cache cleanup
- 实现组合缓存清理策略(数量限制+时间过期)
- 添加完整的单元测试
- 更新配置文件模板,添加缓存配置
- 更新文档和版本记录

版本: v0.5.1
2026-03-29 21:10:28 +08:00

382 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 讨论记录 (taolun.md)
> 本文档记录开发过程中的重要讨论,以时间轴方式存储,便于版本追溯。
## 使用说明
- 每次重要讨论后更新此文件
- 使用上下文压缩总结,突出重点
- 格式:时间 - 版本号 - 主题
## 时间轴记录
### [2026-03-28 22:30] 版本 0.0.1 - 确定技术栈
**原因**: 项目启动,需要确定技术栈
**分析**:
- 比较Go、Deno+TS、Node.js+TS
- Go优势原生二进制、性能好、CLI工具友好
- 用户不会Go但愿意学习
**解决方案**:
- 使用Go语言开发
- 采用面向对象设计模式
- 支持多个大模型厂商
**相关决策**:
- 项目结构采用`cmd/``internal/`布局
- 使用YAML配置文件
- 实现工厂模式和策略模式
**关联版本**: [changelog.md#0.0.1](changelog.md#001)
---
### [2026-03-28 23:00] 版本 0.0.1 - 设计OOP架构
**原因**: 用户要求面向对象开发模式
**分析**:
- Go不是传统OOP语言但可通过结构体和接口实现
- 需要三个核心类:配置、厂商、翻译器
**解决方案**:
- `Config`类:全局配置管理
- `Provider`接口:厂商抽象
- `Translator`类:核心翻译逻辑
- `ProviderFactory`:工厂模式创建厂商实例
**相关链接**:
- [AGENTS.md#OOP设计模式](AGENTS.md#oop设计模式)
- [changelog.md#0.0.1](changelog.md#001)
---
### [2026-03-28 23:30] 版本 0.0.1 - 制定开发规范
**原因**: 建立规范的开发流程
**分析**:
- 需要记录讨论过程、版本变更和知识积累
- 版本号需要遵循语义化版本规范
**解决方案**:
- 创建taolun.md记录讨论
- 创建changelog.md记录版本
- 创建memory.md记录知识纠正
- 版本号格式:大版本.新功能.小修复(00-99)
**关联文档**:
- [changelog.md#0.0.1](changelog.md#001)
- [memory.md#版本管理](memory.md#版本管理)
---
### [2026-03-28 23:45] 版本 0.0.1 - 创建项目初衷文档
**原因**: 需要一个地方记录项目初衷和愿景
**分析**:
- 项目需要明确的目标和方向
- 创始人需要记录个人想法和灵感
- 与其他文档taolun.md、changelog.md、memory.md区分
**解决方案**:
- 创建`why.md`文件专门记录项目初衷
- 规定只能由项目所有者编辑
- 提供基本结构建议,但不强制内容
**文档规范**:
- 文件位置:项目根目录
- 权限:仅用户可编辑
- 内容:项目愿景、目标、个人笔记等
**关联文档**:
- [AGENTS.md#文档管理](AGENTS.md#开发规范)
- [changelog.md#0.0.1](changelog.md#001)
---
### [2026-03-28 23:50] 版本 0.0.2 - 实现核心架构
**原因**: 开始实现项目核心功能
**分析**:
- 根据OOP设计模式实现三个核心类
- 需要先实现配置加载和厂商接口
- 创建基本的CLI入口点
**解决方案**:
1. **Config类实现**
- 支持YAML配置文件加载
- 环境变量替换
- 配置验证和默认值
2. **Provider接口实现**
- 定义统一的翻译接口
- 工厂模式创建厂商实例
- 实现硅基流动厂商作为示例
3. **Translator类实现**
- 核心翻译逻辑
- Prompt管理
- 超时控制
4. **CLI入口点**
- 命令行参数解析
- 配置加载
- 翻译执行
**技术细节**:
- 使用`gopkg.in/yaml.v3`处理YAML
- 实现工厂模式注册机制
- 使用context处理超时和取消
- 添加基本单元测试
**关联文档**:
- [AGENTS.md#OOP设计模式](AGENTS.md#oop设计模式)
- [changelog.md#0.0.2](changelog.md#002)
---
### [2026-03-29 00:00] 版本 0.0.3 - 环境变量加载修复
**原因**: 测试CLI时发现环境变量没有正确加载
**分析**:
- 配置文件中使用`${ENV_VAR}`语法
- Go的`os.ExpandEnv`只在加载时替换
- 需要先加载.env文件到环境变量
**解决方案**:
1. 添加`github.com/joho/godotenv`依赖
2. 在main函数开始时调用`godotenv.Load()`
3. 更新memory.md记录踩坑经验
**技术细节**:
- godotenv会自动查找当前目录的.env文件
- 如果文件不存在会返回错误,可以忽略
- 不影响已有的环境变量
**关联文档**:
- [memory.md#环境变量加载问题](memory.md#环境变量加载问题)
- [changelog.md#0.0.3](changelog.md#003)
---
### [2026-03-29 10:00] 版本 0.2.0 - 语言代码解析设计
**原因**: 用户需要通过 `--lang` 参数指定目标语言,支持多种语言代码格式
**分析**:
- 需要支持标准BCP47格式`zh-CN``en-US`
- 需要支持简短别名(如 `cn``en`
- 需要支持中文名称(如 `chinese``english`
- 需要智能解析和错误提示
**解决方案**:
1. 创建 `internal/lang/lang.go` 模块
2. 实现语言代码映射表和解析函数
3. 支持大小写不敏感和模糊匹配
4. 提供语言名称获取和建议功能
**技术细节**:
- 使用 `map[string]string` 存储语言代码映射
- 实现 `ParseLanguageCode()` 函数进行智能解析
- 支持30+种语言和变体
- 添加完整的单元测试
**关联文档**:
- [AGENTS.md#语言代码处理](AGENTS.md#语言代码处理)
- [changelog.md#0.2.0](changelog.md#020)
---
### [2026-03-29 10:30] 版本 0.2.0 - onboard配置向导
**原因**: 用户需要友好的配置界面,特别是第一次使用时
**分析**:
- 需要交互式配置向导
- 需要支持选择厂商、输入API密钥、设置默认值
- 需要生成标准的YAML配置文件
- 需要支持强制重新配置
**解决方案**:
1. 使用 `github.com/AlecAivazis/survey/v2`
2. 实现分步配置流程:选择厂商 → 配置厂商 → 全局设置 → 保存
3. 提供友好的错误处理和用户提示
4. 支持 `--force` 参数强制重新配置
**技术细节**:
- 使用 `survey.Select``survey.Input``survey.Confirm` 组件
- 实现厂商默认配置和自定义选项
- 生成完整的配置文件包含所有必要字段
- 支持配置文件存在性检查
**关联文档**:
- [AGENTS.md#Onboard配置向导](AGENTS.md#onboard配置向导)
- [changelog.md#0.2.0](changelog.md#020)
---
### [2026-03-29 11:00] 版本 0.2.0 - 分阶段迁移策略
**原因**: 需要平衡开发便利性和最终上线需求
**分析**:
- 开发阶段需要简单配置方式(`.env` + `configs/config.yaml`
- 上线前需要迁移到用户配置目录(`~/.config/yoo/yoo.yml`
- 需要平滑的迁移路径和向后兼容性
**解决方案**:
1. **第一阶段(当前)**: 继续使用 `.env` + `configs/config.yaml`
2. **第二阶段(上线前)**: 实现配置文件路径查找和迁移工具
3. **第三阶段(最终)**: 移除 `.env` 依赖,完全使用配置文件
**技术细节**:
- 配置文件路径优先级:命令行 > 环境变量 > 用户目录 > 当前目录
- 保持向后兼容性,支持旧配置格式
- 提供配置验证和错误提示
- 实现配置迁移工具(计划)
**关联文档**:
- [AGENTS.md#分阶段迁移策略](AGENTS.md#分阶段迁移策略)
- [changelog.md#0.2.0](changelog.md#020)
---
### [2026-03-29 12:00] 版本 0.4.0 - 管道符功能
**原因**: 用户需要与其他命令行工具联合使用
**分析**:
- 用户希望支持管道符功能,如 `cat a.txt | yoyo | grep "who are you"`
- 需要检测管道输入并从stdin读取内容
- 需要控制统计信息输出,避免污染管道输出
- 需要保持向后兼容性
**解决方案**:
1. **管道输入检测**: 实现 `isPipeInput()` 函数,使用 `os.Stdin.Stat()` 检测管道
2. **stdin读取**: 实现 `readFromStdin()` 函数,使用 `bufio.Scanner` 读取所有输入
3. **静默模式**: 添加 `--quiet``-q` 参数,控制统计信息输出
4. **输出重定向**: 将统计信息输出到stderr避免污染管道输出
**技术细节**:
- 使用 `os.ModeCharDevice` 检测是否为管道设备
- 使用 `strings.Join()` 合并多行输入为单个字符串
- 统计信息输出到 `os.Stderr` 而不是 `os.Stdout`
- 修复 content/filter.go 中的正则表达式转义问题
**使用示例**:
```bash
# 基本管道功能
echo "Hello world" | yoyo
cat file.txt | yoyo --lang=en
# 静默模式
echo "Hello world" | yoyo -q
echo "Hello world" | yoyo --quiet
# 与其他命令组合
cat file.txt | yoyo | grep "你好"
yoyo "Hello" | wc -l
```
**关联文档**:
- [AGENTS.md#管道符功能](AGENTS.md#管道符功能)
- [changelog.md#0.4.0](changelog.md#040)
---
### [2026-03-29 15:00] 版本 0.5.0 - 本地缓存功能设计
**原因**: 用户希望减少API调用添加本地缓存功能
**分析**:
- 需要存储翻译结果避免重复调用API
- 需要设计缓存键策略,确保缓存准确性
- 需要考虑数据库选择、事务处理、性能优化
- 需要设计缓存管理策略
**解决方案**:
1. **数据库选择**: 使用SQLite
- 轻量级,无需服务器
- 支持ACID事务
- Go生态支持良好 (`github.com/mattn/go-sqlite3`)
- 适合嵌入式应用
2. **缓存键设计**:
- 使用文本内容 + 源语言 + 目标语言
- 生成SHA256哈希作为缓存键
- 规范化输入:移除多余空白字符,统一语言代码格式
3. **事务处理**:
- 使用事务保证数据一致性
- 插入操作在事务中执行
- 查询操作不需要显式事务
4. **保存时机**:
- 在输出结果之前保存到数据库
- 确保数据持久化
- 异步保存,不阻塞翻译结果返回
5. **性能优化**:
- 为缓存键创建索引
- 使用哈希键减少存储空间
- 限制缓存表大小(可配置)
- 使用WAL模式提高并发性能
6. **缓存策略**:
- 采用组合策略:数量限制+时间过期
- 默认启用缓存功能
- 提供手动清理命令
7. **存储位置**:
- 数据库文件存储在用户配置目录 `~/.config/yoyo/cache.db`
- 符合XDG规范
- 支持自定义路径配置
**技术细节**:
- 使用 `github.com/mattn/go-sqlite3` 驱动
- 实现 `internal/cache/cache.go` 模块
- 缓存表结构:`id`, `cache_key`, `original_text`, `translated_text`, `from_lang`, `to_lang`, `model`, `prompt`, `created_at`
- 缓存键生成:`sha256(text + "|" + fromLang + "|" + toLang)`
- 查询缓存时使用 `SELECT translated_text FROM cache WHERE cache_key = ?`
- 插入缓存时使用 `INSERT OR IGNORE INTO cache (...) VALUES (...)`
**关联文档**:
- [AGENTS.md#本地缓存功能设计](AGENTS.md#本地缓存功能设计)
- [changelog.md#0.5.0](changelog.md#050)
---
### [2026-03-29 20:00] 版本 0.5.1 - 缓存功能修复
**原因**: 缓存功能测试中发现的问题
**分析**:
1. **VACUUM事务错误**: 缓存清空命令中VACUUM不能在事务中执行
2. **NULL值转换错误**: 缓存统计查询在空表时MIN(created_at)返回NULL导致转换错误
3. **过期清理策略**: 当expire_days=0时清理逻辑不工作
**解决方案**:
1. **修复VACUUM事务错误**:
- 将VACUUM移到事务之外执行
- 先删除记录再执行VACUUM
2. **修复NULL值转换错误**:
- 使用 `sql.NullString``sql.NullFloat64` 类型
- 检查 `Valid` 字段判断是否为NULL
- 只在有记录时才查询时间范围
3. **修复过期清理策略**:
- 当cleanupTTL为0时清理所有记录
- 添加条件判断:`if c.cleanupTTL == 0 { ... }`
**技术细节**:
```go
// 修复VACUUM事务错误
func (c *SQLiteCache) Clear(ctx context.Context) error {
// 先删除所有记录
_, err := c.db.ExecContext(ctx, `DELETE FROM translation_cache`)
if err != nil {
return fmt.Errorf("清空缓存失败: %w", err)
}
// 然后执行VACUUM不能在事务中执行
_, err = c.db.ExecContext(ctx, `VACUUM`)
if err != nil {
return fmt.Errorf("清理数据库失败: %w", err)
}
return nil
}
// 修复NULL值转换错误
var oldestStr, newestStr sql.NullString
var avgTokens sql.NullFloat64
err = c.db.QueryRowContext(ctx, `SELECT MIN(created_at), MAX(created_at), AVG(total_tokens) FROM translation_cache`).Scan(&oldestStr, &newestStr, &avgTokens)
if oldestStr.Valid {
stats.OldestRecord, _ = time.Parse("2006-01-02 15:04:05", oldestStr.String)
}
```
**关联文档**:
- [changelog.md#0.5.1](changelog.md#051)
- [memory.md#本地缓存实现经验](memory.md#本地缓存实现经验)