feat: 添加本地缓存功能,减少API调用
- 实现SQLite缓存模块,支持高效查询和存储 - 添加缓存键生成策略(基于原文+语言对的SHA256哈希) - 集成缓存到Translator类,先查缓存再调用API - 添加缓存管理命令:cache clear, cache stats, cache cleanup - 实现组合缓存清理策略(数量限制+时间过期) - 添加完整的单元测试 - 更新配置文件模板,添加缓存配置 - 更新文档和版本记录 版本: v0.5.1
This commit is contained in:
157
taolun.md
157
taolun.md
@@ -224,4 +224,159 @@
|
||||
|
||||
**关联文档**:
|
||||
- [AGENTS.md#分阶段迁移策略](AGENTS.md#分阶段迁移策略)
|
||||
- [changelog.md#0.2.0](changelog.md#020)
|
||||
- [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#本地缓存实现经验)
|
||||
Reference in New Issue
Block a user