package store import ( "database/sql" "fmt" "github.com/yourname/cyrene-ai/pkg/logger" "time" ) // File 文件元数据模型 type File struct { ID string `json:"id"` UserID string `json:"user_id"` Filename string `json:"filename"` StoredPath string `json:"stored_path"` MimeType string `json:"mime_type"` Size int64 `json:"size"` Hash string `json:"hash"` IsPublic bool `json:"is_public"` CreatedAt time.Time `json:"created_at"` } // FileStore 文件元数据持久化存储 type FileStore struct { db *sql.DB } // NewFileStore 使用已有数据库连接初始化文件存储并自动建表 func NewFileStore(db *sql.DB) (*FileStore, error) { store := &FileStore{db: db} if err := store.migrate(); err != nil { return nil, fmt.Errorf("文件表迁移失败: %w", err) } logger.Println("[FileStore] 文件持久化存储已初始化") return store, nil } // migrate 自动创建文件表结构 func (s *FileStore) migrate() error { queries := []string{ `CREATE TABLE IF NOT EXISTS files ( id VARCHAR(36) PRIMARY KEY, user_id VARCHAR(255) NOT NULL, filename VARCHAR(500) NOT NULL, stored_path VARCHAR(1000) NOT NULL, mime_type VARCHAR(255) NOT NULL DEFAULT 'application/octet-stream', size BIGINT NOT NULL DEFAULT 0, hash VARCHAR(64) NOT NULL DEFAULT '', is_public BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() )`, `CREATE INDEX IF NOT EXISTS idx_files_user_id ON files(user_id)`, `CREATE INDEX IF NOT EXISTS idx_files_hash ON files(hash)`, `CREATE INDEX IF NOT EXISTS idx_files_created_at ON files(user_id, created_at DESC)`, } for _, q := range queries { if _, err := s.db.Exec(q); err != nil { return fmt.Errorf("迁移SQL执行失败: %w\nSQL: %s", err, q) } } return nil } // CreateFile 创建文件元数据记录 func (s *FileStore) CreateFile(f *File) error { _, err := s.db.Exec( `INSERT INTO files (id, user_id, filename, stored_path, mime_type, size, hash, is_public, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, f.ID, f.UserID, f.Filename, f.StoredPath, f.MimeType, f.Size, f.Hash, f.IsPublic, f.CreatedAt, ) if err != nil { return fmt.Errorf("创建文件记录失败: %w", err) } return nil } // GetFile 根据ID获取文件元数据 func (s *FileStore) GetFile(id string) (*File, error) { var f File err := s.db.QueryRow( `SELECT id, user_id, filename, stored_path, mime_type, size, hash, is_public, created_at FROM files WHERE id = $1`, id, ).Scan(&f.ID, &f.UserID, &f.Filename, &f.StoredPath, &f.MimeType, &f.Size, &f.Hash, &f.IsPublic, &f.CreatedAt) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, fmt.Errorf("查询文件失败: %w", err) } return &f, nil } // GetUserFiles 获取用户的所有文件,支持分页 func (s *FileStore) GetUserFiles(userID string, page, limit int) ([]File, int, error) { if page <= 0 { page = 1 } if limit <= 0 || limit > 100 { limit = 20 } offset := (page - 1) * limit // 获取总数 var total int if err := s.db.QueryRow( `SELECT COUNT(*) FROM files WHERE user_id = $1`, userID, ).Scan(&total); err != nil { return nil, 0, fmt.Errorf("查询文件总数失败: %w", err) } // 分页查询 rows, err := s.db.Query( `SELECT id, user_id, filename, stored_path, mime_type, size, hash, is_public, created_at FROM files WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3`, userID, limit, offset, ) if err != nil { return nil, 0, fmt.Errorf("查询用户文件失败: %w", err) } defer rows.Close() var files []File for rows.Next() { var f File if err := rows.Scan(&f.ID, &f.UserID, &f.Filename, &f.StoredPath, &f.MimeType, &f.Size, &f.Hash, &f.IsPublic, &f.CreatedAt); err != nil { return nil, 0, fmt.Errorf("扫描文件行失败: %w", err) } files = append(files, f) } if files == nil { files = []File{} } return files, total, rows.Err() } // GetFileByHash 根据SHA256哈希查找文件(用于去重) func (s *FileStore) GetFileByHash(hash string) (*File, error) { if hash == "" { return nil, nil } var f File err := s.db.QueryRow( `SELECT id, user_id, filename, stored_path, mime_type, size, hash, is_public, created_at FROM files WHERE hash = $1 ORDER BY created_at ASC LIMIT 1`, hash, ).Scan(&f.ID, &f.UserID, &f.Filename, &f.StoredPath, &f.MimeType, &f.Size, &f.Hash, &f.IsPublic, &f.CreatedAt) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, fmt.Errorf("按哈希查询文件失败: %w", err) } return &f, nil } // DeleteFile 删除文件元数据记录 func (s *FileStore) DeleteFile(id string) error { _, err := s.db.Exec(`DELETE FROM files WHERE id = $1`, id) if err != nil { return fmt.Errorf("删除文件记录失败: %w", err) } return nil }