Files
yoyo/internal/translator/translator.go
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

263 lines
6.6 KiB
Go
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.
package translator
import (
"context"
"fmt"
"time"
"github.com/titor/fanyi/internal/cache"
"github.com/titor/fanyi/internal/config"
"github.com/titor/fanyi/internal/content"
"github.com/titor/fanyi/internal/provider"
)
// Translator 核心翻译类
type Translator struct {
config *config.Config
provider provider.Provider
prompt *PromptManager
contentParser *content.Parser
cache cache.Cache
}
// NewTranslator 创建翻译器实例
func NewTranslator(config *config.Config, provider provider.Provider) *Translator {
translator := &Translator{
config: config,
provider: provider,
prompt: NewPromptManager(config.Prompts),
contentParser: content.NewParser(config.SkipKeywords),
}
// 初始化缓存(如果启用)
if config.Cache.Enabled {
cacheConfig := &cache.CacheConfig{
Enabled: config.Cache.Enabled,
MaxRecords: config.Cache.MaxRecords,
ExpireDays: config.Cache.ExpireDays,
DBPath: config.Cache.DBPath,
}
if cacheInstance, err := cache.NewSQLiteCache(cacheConfig); err == nil {
translator.cache = cacheInstance
}
}
return translator
}
// Translate 执行翻译
func (t *Translator) Translate(ctx context.Context, text string, options *TranslateOptions) (*TranslateResult, error) {
// 设置超时
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(t.config.Timeout)*time.Second)
defer cancel()
// 基础字符过滤
filteredText := content.FilterBasic(text, nil)
// 内容解析(包含代码检测)
parseResult, parseErr := t.contentParser.Parse(filteredText)
// 选择Prompt
prompt := ""
if options.PromptName != "" {
prompt = t.prompt.GetPrompt(options.PromptName)
}
// 如果包含代码且解析成功使用增强的Prompt
if parseErr == nil && parseResult.HasCode {
enhancedPrompt := t.contentParser.BuildPrompt(parseResult)
if enhancedPrompt != "" {
if prompt != "" {
prompt = prompt + "\n\n" + enhancedPrompt
} else {
prompt = enhancedPrompt
}
}
}
// 构建请求
req := &provider.TranslateRequest{
Text: filteredText,
FromLang: options.FromLang,
ToLang: options.ToLang,
Prompt: prompt,
Model: t.selectModel(options.Model),
Options: options.ExtraOptions,
}
// 检查缓存
if t.cache != nil {
cacheKey := cache.GenerateCacheKey(filteredText, options.FromLang, options.ToLang)
if cachedEntry, err := t.cache.Get(ctx, cacheKey); err == nil && cachedEntry != nil {
// 缓存命中
return &TranslateResult{
Original: text,
Translated: cachedEntry.TranslatedText,
FromLang: cachedEntry.FromLang,
ToLang: cachedEntry.ToLang,
Model: cachedEntry.Model,
Usage: &provider.Usage{
PromptTokens: cachedEntry.PromptTokens,
CompletionTokens: cachedEntry.CompletionTokens,
TotalTokens: cachedEntry.TotalTokens,
},
}, nil
}
}
// 调用厂商API
resp, err := t.provider.Translate(timeoutCtx, req)
if err != nil {
return nil, fmt.Errorf("翻译失败: %w", err)
}
translatedText := resp.Text
// 如果包含代码且解析成功,重构结果
if parseErr == nil && parseResult.HasCode {
translatedText = t.contentParser.Reconstruct(parseResult, resp.Text)
}
// 保存到缓存
if t.cache != nil {
cacheKey := cache.GenerateCacheKey(filteredText, options.FromLang, options.ToLang)
cacheEntry := &cache.CacheEntry{
CacheKey: cacheKey,
OriginalText: filteredText,
TranslatedText: translatedText,
FromLang: resp.FromLang,
ToLang: resp.ToLang,
Model: resp.Model,
PromptName: options.PromptName,
PromptContent: prompt,
PromptTokens: resp.Usage.PromptTokens,
CompletionTokens: resp.Usage.CompletionTokens,
TotalTokens: resp.Usage.TotalTokens,
}
// 异步保存缓存,不阻塞翻译结果返回
go t.cache.Set(context.Background(), cacheEntry)
}
// 构建结果
return &TranslateResult{
Original: text,
Translated: translatedText,
FromLang: resp.FromLang,
ToLang: resp.ToLang,
Model: resp.Model,
Usage: resp.Usage,
}, nil
}
// TranslateWithProvider 使用指定厂商执行翻译
func (t *Translator) TranslateWithProvider(ctx context.Context, text string, providerName string, options *TranslateOptions) (*TranslateResult, error) {
// 创建指定厂商实例
providerConfig, err := t.config.GetProviderConfig(providerName)
if err != nil {
return nil, fmt.Errorf("获取厂商配置失败: %w", err)
}
// 创建厂商实例
providerInstance, err := provider.CreateProvider(providerName, provider.ProviderConfig{
APIHost: providerConfig.APIHost,
APIKey: providerConfig.APIKey,
Model: providerConfig.Model,
})
if err != nil {
return nil, fmt.Errorf("创建厂商实例失败: %w", err)
}
// 临时切换厂商
originalProvider := t.provider
t.provider = providerInstance
defer func() {
t.provider = originalProvider
}()
// 执行翻译
return t.Translate(ctx, text, options)
}
// selectModel 选择模型
func (t *Translator) selectModel(model string) string {
if model != "" {
return model
}
return t.config.DefaultModel
}
// GetProvider 获取当前厂商
func (t *Translator) GetProvider() provider.Provider {
return t.provider
}
// GetConfig 获取配置
func (t *Translator) GetConfig() *config.Config {
return t.config
}
// GetPromptManager 获取Prompt管理器
func (t *Translator) GetPromptManager() *PromptManager {
return t.prompt
}
// SetTimeout 设置超时时间
func (t *Translator) SetTimeout(seconds int) {
t.config.Timeout = seconds
}
// TranslateOptions 翻译选项
type TranslateOptions struct {
FromLang string
ToLang string
PromptName string
Model string
Temperature float64
ExtraOptions map[string]interface{}
}
// TranslateResult 翻译结果
type TranslateResult struct {
Original string
Translated string
FromLang string
ToLang string
Model string
Usage *provider.Usage
}
// String 返回翻译结果的字符串表示
func (r *TranslateResult) String() string {
return r.Translated
}
// TranslateResultWithInfo 带详细信息的翻译结果
type TranslateResultWithInfo struct {
Result *TranslateResult
Duration time.Duration
Provider string
Timestamp time.Time
}
// BatchTranslate 批量翻译结果
type BatchTranslateRequest struct {
Texts []string
Options *TranslateOptions
}
// BatchTranslateResult 批量翻译结果
type BatchTranslateResult struct {
Results []*TranslateResult
Errors []error
Summary BatchTranslateSummary
}
// BatchTranslateSummary 批量翻译摘要
type BatchTranslateSummary struct {
Total int
Success int
Failed int
Duration time.Duration
AvgTokens int
}