// Package storage 定義物件儲存介面與 LocalFS 實作。 // // 對齊 storage.md §1 與 PRD interface-contracts.md §8.4: // - 雛形使用 LocalFSStore(檔案系統)+ 假 presigned URL(HMAC 簽名) // - Phase 1 新增 S3Store(同 interface),業務邏輯不用動 // // Key 命名規範見 storage.md §2(例:`models/{user_id}/{model_id}.nef`)。 package storage import ( "context" "errors" "io" "time" ) // ========================================================================== // Errors // ========================================================================== var ( // ErrNotFound 表示指定 key 的 object 不存在。 ErrNotFound = errors.New("storage: object not found") // ErrAlreadyExists 表示 object 已存在且 Put 被要求不覆蓋(Phase 1)。 ErrAlreadyExists = errors.New("storage: object already exists") // ErrInvalidKey 表示 key 含非法字元(例:包含 "..", 嘗試 path traversal)。 ErrInvalidKey = errors.New("storage: invalid key") // ErrInvalidSignature 表示 presigned URL 簽名錯誤或過期。 ErrInvalidSignature = errors.New("storage: invalid or expired signature") ) // ========================================================================== // Types // ========================================================================== // Object 是儲存物件的描述(metadata;不含實際內容)。 // // 對齊 storage.md §1 的 ObjectInfo。 type Object struct { Key string `json:"key"` Size int64 `json:"size"` ContentType string `json:"contentType"` LastModified time.Time `json:"lastModified"` ETag string `json:"etag,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // ========================================================================== // Store interface // ========================================================================== // Store 是物件儲存的抽象。 // // 實作必須: // - 並發安全(多 goroutine 同時 Put / Get 不 panic) // - Key 驗證(防止 path traversal) // - 語意對齊:Exists 回 (false, nil) 表不存在,其他錯誤走 err type Store interface { // Put 上傳一個 object;若已存在則覆蓋。 // size 為預期大小(bytes);實作可用於早期檢查或配額控制。 // meta 可為 nil(無額外 metadata)。 Put(ctx context.Context, key string, r io.Reader, size int64, meta map[string]string) error // Get 下載一個 object;caller 必須 Close() reader。 // 不存在回 ErrNotFound。 Get(ctx context.Context, key string) (io.ReadCloser, *Object, error) // Stat 取得 object 的 metadata(不下載內容)。 // 不存在回 ErrNotFound。 Stat(ctx context.Context, key string) (*Object, error) // Exists 判斷 key 是否存在(Minor-4 / PRD §8.4 要求)。 // 語意:true = 存在可用;false = 不存在(非 error)。 // 其他錯誤(權限 / IO)回 (false, err)。 Exists(ctx context.Context, key string) (bool, error) // Delete 刪除 object;不存在為 no-op,不回 error。 Delete(ctx context.Context, key string) error // List 列出指定 prefix 下的所有 object。 List(ctx context.Context, prefix string) ([]*Object, error) // PresignedGetURL 產生一個限時下載 URL。 // 對 LocalFS:回傳 baseURL + /key?expires=&signature=(由 api-server 驗證) // 對 S3:回傳原生 AWS presigned URL PresignedGetURL(ctx context.Context, key string, ttl time.Duration) (string, error) // PresignedPutURL 產生一個限時上傳 URL。 // 對 LocalFS:回傳 baseURL + /key?expires=&signature=&mode=put // 對 S3:回傳原生 AWS presigned PUT URL PresignedPutURL(ctx context.Context, key string, ttl time.Duration) (string, error) }