"backend/internal/git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "dca85c86223e04ffd36472104cab99e6ac8c6b60"
Commit 50288e6b authored by shaw's avatar shaw
Browse files

fix: 修复模型定价文件更新url

parent ab3e44e4
...@@ -1281,8 +1281,8 @@ func setDefaults() { ...@@ -1281,8 +1281,8 @@ func setDefaults() {
viper.SetDefault("rate_limit.oauth_401_cooldown_minutes", 10) viper.SetDefault("rate_limit.oauth_401_cooldown_minutes", 10)
// Pricing - 从 model-price-repo 同步模型定价和上下文窗口数据(固定到 commit,避免分支漂移) // Pricing - 从 model-price-repo 同步模型定价和上下文窗口数据(固定到 commit,避免分支漂移)
viper.SetDefault("pricing.remote_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.json") viper.SetDefault("pricing.remote_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/main/model_prices_and_context_window.json")
viper.SetDefault("pricing.hash_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.sha256") viper.SetDefault("pricing.hash_url", "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/main/model_prices_and_context_window.sha256")
viper.SetDefault("pricing.data_dir", "./data") viper.SetDefault("pricing.data_dir", "./data")
viper.SetDefault("pricing.fallback_file", "./resources/model-pricing/model_prices_and_context_window.json") viper.SetDefault("pricing.fallback_file", "./resources/model-pricing/model_prices_and_context_window.json")
viper.SetDefault("pricing.update_interval_hours", 24) viper.SetDefault("pricing.update_interval_hours", 24)
......
...@@ -189,10 +189,38 @@ func (s *PricingService) checkAndUpdatePricing() error { ...@@ -189,10 +189,38 @@ func (s *PricingService) checkAndUpdatePricing() error {
return s.downloadPricingData() return s.downloadPricingData()
} }
// 检查文件是否过期 // 先加载本地文件(确保服务可用),再检查是否需要更新
if err := s.loadPricingData(pricingFile); err != nil {
logger.LegacyPrintf("service.pricing", "[Pricing] Failed to load local file, downloading: %v", err)
return s.downloadPricingData()
}
// 如果配置了哈希URL,通过远程哈希检查是否有更新
if s.cfg.Pricing.HashURL != "" {
remoteHash, err := s.fetchRemoteHash()
if err != nil {
logger.LegacyPrintf("service.pricing", "[Pricing] Failed to fetch remote hash on startup: %v", err)
return nil // 已加载本地文件,哈希获取失败不影响启动
}
s.mu.RLock()
localHash := s.localHash
s.mu.RUnlock()
if localHash == "" || remoteHash != localHash {
logger.LegacyPrintf("service.pricing", "[Pricing] Remote hash differs on startup (local=%s remote=%s), downloading...",
localHash[:min(8, len(localHash))], remoteHash[:min(8, len(remoteHash))])
if err := s.downloadPricingData(); err != nil {
logger.LegacyPrintf("service.pricing", "[Pricing] Download failed, using existing file: %v", err)
}
}
return nil
}
// 没有哈希URL时,基于文件年龄检查
info, err := os.Stat(pricingFile) info, err := os.Stat(pricingFile)
if err != nil { if err != nil {
return s.downloadPricingData() return nil // 已加载本地文件
} }
fileAge := time.Since(info.ModTime()) fileAge := time.Since(info.ModTime())
...@@ -205,21 +233,11 @@ func (s *PricingService) checkAndUpdatePricing() error { ...@@ -205,21 +233,11 @@ func (s *PricingService) checkAndUpdatePricing() error {
} }
} }
// 加载本地文件 return nil
return s.loadPricingData(pricingFile)
} }
// syncWithRemote 与远程同步(基于哈希校验) // syncWithRemote 与远程同步(基于哈希校验)
func (s *PricingService) syncWithRemote() error { func (s *PricingService) syncWithRemote() error {
pricingFile := s.getPricingFilePath()
// 计算本地文件哈希
localHash, err := s.computeFileHash(pricingFile)
if err != nil {
logger.LegacyPrintf("service.pricing", "[Pricing] Failed to compute local hash: %v", err)
return s.downloadPricingData()
}
// 如果配置了哈希URL,从远程获取哈希进行比对 // 如果配置了哈希URL,从远程获取哈希进行比对
if s.cfg.Pricing.HashURL != "" { if s.cfg.Pricing.HashURL != "" {
remoteHash, err := s.fetchRemoteHash() remoteHash, err := s.fetchRemoteHash()
...@@ -228,8 +246,13 @@ func (s *PricingService) syncWithRemote() error { ...@@ -228,8 +246,13 @@ func (s *PricingService) syncWithRemote() error {
return nil // 哈希获取失败不影响正常使用 return nil // 哈希获取失败不影响正常使用
} }
if remoteHash != localHash { s.mu.RLock()
logger.LegacyPrintf("service.pricing", "%s", "[Pricing] Remote hash differs, downloading new version...") localHash := s.localHash
s.mu.RUnlock()
if localHash == "" || remoteHash != localHash {
logger.LegacyPrintf("service.pricing", "[Pricing] Remote hash differs (local=%s remote=%s), downloading new version...",
localHash[:min(8, len(localHash))], remoteHash[:min(8, len(remoteHash))])
return s.downloadPricingData() return s.downloadPricingData()
} }
logger.LegacyPrintf("service.pricing", "%s", "[Pricing] Hash check passed, no update needed") logger.LegacyPrintf("service.pricing", "%s", "[Pricing] Hash check passed, no update needed")
...@@ -237,6 +260,7 @@ func (s *PricingService) syncWithRemote() error { ...@@ -237,6 +260,7 @@ func (s *PricingService) syncWithRemote() error {
} }
// 没有哈希URL时,基于时间检查 // 没有哈希URL时,基于时间检查
pricingFile := s.getPricingFilePath()
info, err := os.Stat(pricingFile) info, err := os.Stat(pricingFile)
if err != nil { if err != nil {
return s.downloadPricingData() return s.downloadPricingData()
...@@ -264,11 +288,12 @@ func (s *PricingService) downloadPricingData() error { ...@@ -264,11 +288,12 @@ func (s *PricingService) downloadPricingData() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
var expectedHash string // 获取远程哈希(用于同步锚点,不作为完整性校验)
var remoteHash string
if strings.TrimSpace(s.cfg.Pricing.HashURL) != "" { if strings.TrimSpace(s.cfg.Pricing.HashURL) != "" {
expectedHash, err = s.fetchRemoteHash() remoteHash, err = s.fetchRemoteHash()
if err != nil { if err != nil {
return fmt.Errorf("fetch remote hash: %w", err) logger.LegacyPrintf("service.pricing", "[Pricing] Failed to fetch remote hash (continuing): %v", err)
} }
} }
...@@ -277,11 +302,13 @@ func (s *PricingService) downloadPricingData() error { ...@@ -277,11 +302,13 @@ func (s *PricingService) downloadPricingData() error {
return fmt.Errorf("download failed: %w", err) return fmt.Errorf("download failed: %w", err)
} }
if expectedHash != "" { // 哈希校验:不匹配时仅告警,不阻止更新
actualHash := sha256.Sum256(body) // 远程哈希文件可能与数据文件不同步(如维护者更新了数据但未更新哈希文件)
if !strings.EqualFold(expectedHash, hex.EncodeToString(actualHash[:])) { dataHash := sha256.Sum256(body)
return fmt.Errorf("pricing hash mismatch") dataHashStr := hex.EncodeToString(dataHash[:])
} if remoteHash != "" && !strings.EqualFold(remoteHash, dataHashStr) {
logger.LegacyPrintf("service.pricing", "[Pricing] Hash mismatch warning: remote=%s data=%s (hash file may be out of sync)",
remoteHash[:min(8, len(remoteHash))], dataHashStr[:8])
} }
// 解析JSON数据(使用灵活的解析方式) // 解析JSON数据(使用灵活的解析方式)
...@@ -296,11 +323,14 @@ func (s *PricingService) downloadPricingData() error { ...@@ -296,11 +323,14 @@ func (s *PricingService) downloadPricingData() error {
logger.LegacyPrintf("service.pricing", "[Pricing] Failed to save file: %v", err) logger.LegacyPrintf("service.pricing", "[Pricing] Failed to save file: %v", err)
} }
// 保存哈希 // 使用远程哈希作为同步锚点,防止重复下载
hash := sha256.Sum256(body) // 当远程哈希不可用时,回退到数据本身的哈希
hashStr := hex.EncodeToString(hash[:]) syncHash := dataHashStr
if remoteHash != "" {
syncHash = remoteHash
}
hashFile := s.getHashFilePath() hashFile := s.getHashFilePath()
if err := os.WriteFile(hashFile, []byte(hashStr+"\n"), 0644); err != nil { if err := os.WriteFile(hashFile, []byte(syncHash+"\n"), 0644); err != nil {
logger.LegacyPrintf("service.pricing", "[Pricing] Failed to save hash: %v", err) logger.LegacyPrintf("service.pricing", "[Pricing] Failed to save hash: %v", err)
} }
...@@ -308,7 +338,7 @@ func (s *PricingService) downloadPricingData() error { ...@@ -308,7 +338,7 @@ func (s *PricingService) downloadPricingData() error {
s.mu.Lock() s.mu.Lock()
s.pricingData = data s.pricingData = data
s.lastUpdated = time.Now() s.lastUpdated = time.Now()
s.localHash = hashStr s.localHash = syncHash
s.mu.Unlock() s.mu.Unlock()
logger.LegacyPrintf("service.pricing", "[Pricing] Downloaded %d models successfully", len(data)) logger.LegacyPrintf("service.pricing", "[Pricing] Downloaded %d models successfully", len(data))
...@@ -486,16 +516,6 @@ func (s *PricingService) validatePricingURL(raw string) (string, error) { ...@@ -486,16 +516,6 @@ func (s *PricingService) validatePricingURL(raw string) (string, error) {
return normalized, nil return normalized, nil
} }
// computeFileHash 计算文件哈希
func (s *PricingService) computeFileHash(filePath string) (string, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:]), nil
}
// GetModelPricing 获取模型价格(带模糊匹配) // GetModelPricing 获取模型价格(带模糊匹配)
func (s *PricingService) GetModelPricing(modelName string) *LiteLLMModelPricing { func (s *PricingService) GetModelPricing(modelName string) *LiteLLMModelPricing {
s.mu.RLock() s.mu.RLock()
......
...@@ -865,10 +865,10 @@ rate_limit: ...@@ -865,10 +865,10 @@ rate_limit:
pricing: pricing:
# URL to fetch model pricing data (default: pinned model-price-repo commit) # URL to fetch model pricing data (default: pinned model-price-repo commit)
# 获取模型定价数据的 URL(默认:固定 commit 的 model-price-repo) # 获取模型定价数据的 URL(默认:固定 commit 的 model-price-repo)
remote_url: "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.json" remote_url: "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/refs/heads/main//model_prices_and_context_window.json"
# Hash verification URL (optional) # Hash verification URL (optional)
# 哈希校验 URL(可选) # 哈希校验 URL(可选)
hash_url: "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.sha256" hash_url: "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/refs/heads/main//model_prices_and_context_window.sha256"
# Local data directory for caching # Local data directory for caching
# 本地数据缓存目录 # 本地数据缓存目录
data_dir: "./data" data_dir: "./data"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment