基本能力编写完成

This commit is contained in:
2025-12-31 14:23:53 +08:00
parent ac5aa1eb70
commit 2b51050ca8
33 changed files with 5464 additions and 7 deletions

178
internal/cache/file_cache.go vendored Normal file
View File

@@ -0,0 +1,178 @@
package cache
import (
"compress/gzip"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/gitcodestatic/gitcodestatic/internal/logger"
"github.com/gitcodestatic/gitcodestatic/internal/models"
"github.com/gitcodestatic/gitcodestatic/internal/storage"
)
// FileCache 基于文件+DB的缓存实现
type FileCache struct {
store storage.Store
statsDir string
}
// NewFileCache 创建文件缓存
func NewFileCache(store storage.Store, statsDir string) *FileCache {
return &FileCache{
store: store,
statsDir: statsDir,
}
}
// Get 获取缓存
func (c *FileCache) Get(ctx context.Context, cacheKey string) (*models.StatsResult, error) {
// 从DB查询缓存元数据
cache, err := c.store.StatsCache().GetByCacheKey(ctx, cacheKey)
if err != nil {
return nil, err
}
if cache == nil {
return nil, nil // 缓存不存在
}
// 读取结果文件
stats, err := c.loadStatsFromFile(cache.ResultPath)
if err != nil {
logger.Logger.Error().Err(err).Str("cache_key", cacheKey).Msg("failed to load stats from file")
return nil, err
}
// 更新命中次数
if err := c.store.StatsCache().UpdateHitCount(ctx, cache.ID); err != nil {
logger.Logger.Warn().Err(err).Int64("cache_id", cache.ID).Msg("failed to update hit count")
}
result := &models.StatsResult{
CacheHit: true,
CachedAt: &cache.CreatedAt,
CommitHash: cache.CommitHash,
Statistics: stats,
}
logger.Logger.Info().
Str("cache_key", cacheKey).
Int64("cache_id", cache.ID).
Msg("cache hit")
return result, nil
}
// Set 设置缓存
func (c *FileCache) Set(ctx context.Context, repoID int64, branch string, constraint *models.StatsConstraint,
commitHash string, stats *models.Statistics) error {
// 生成缓存键
cacheKey := GenerateCacheKey(repoID, branch, constraint, commitHash)
// 保存统计结果到文件
resultPath := filepath.Join(c.statsDir, cacheKey+".json.gz")
if err := c.saveStatsToFile(stats, resultPath); err != nil {
return fmt.Errorf("failed to save stats to file: %w", err)
}
// 获取文件大小
fileInfo, err := os.Stat(resultPath)
if err != nil {
return fmt.Errorf("failed to stat result file: %w", err)
}
// 创建缓存记录
cache := &models.StatsCache{
RepoID: repoID,
Branch: branch,
ConstraintType: constraint.Type,
ConstraintValue: SerializeConstraint(constraint),
CommitHash: commitHash,
ResultPath: resultPath,
ResultSize: fileInfo.Size(),
CacheKey: cacheKey,
}
if err := c.store.StatsCache().Create(ctx, cache); err != nil {
// 如果创建失败,删除已保存的文件
os.Remove(resultPath)
return fmt.Errorf("failed to create cache record: %w", err)
}
logger.Logger.Info().
Str("cache_key", cacheKey).
Int64("cache_id", cache.ID).
Int64("file_size", fileInfo.Size()).
Msg("cache saved")
return nil
}
// InvalidateByRepoID 使指定仓库的所有缓存失效
func (c *FileCache) InvalidateByRepoID(ctx context.Context, repoID int64) error {
// 查询该仓库的所有缓存
// 注意:这里简化实现,实际应该先查询再删除文件
if err := c.store.StatsCache().DeleteByRepoID(ctx, repoID); err != nil {
return fmt.Errorf("failed to delete cache records: %w", err)
}
logger.Logger.Info().Int64("repo_id", repoID).Msg("cache invalidated")
return nil
}
// saveStatsToFile 保存统计结果到文件gzip压缩
func (c *FileCache) saveStatsToFile(stats *models.Statistics, filePath string) error {
// 确保目录存在
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// 创建文件
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer file.Close()
// 创建gzip writer
gzipWriter := gzip.NewWriter(file)
defer gzipWriter.Close()
// 编码JSON
encoder := json.NewEncoder(gzipWriter)
if err := encoder.Encode(stats); err != nil {
return fmt.Errorf("failed to encode stats: %w", err)
}
return nil
}
// loadStatsFromFile 从文件加载统计结果
func (c *FileCache) loadStatsFromFile(filePath string) (*models.Statistics, error) {
// 打开文件
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
// 创建gzip reader
gzipReader, err := gzip.NewReader(file)
if err != nil {
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gzipReader.Close()
// 解码JSON
var stats models.Statistics
decoder := json.NewDecoder(gzipReader)
if err := decoder.Decode(&stats); err != nil {
return nil, fmt.Errorf("failed to decode stats: %w", err)
}
return &stats, nil
}