fix(file)!: stream uploads through staged storage

- fix: replace multipart form parsing with streaming multipart reads and
  apply request body limits when max_upload_size is configured.
- refactor: route uploads through staging paths before promotion to
  long-term data paths, keeping incomplete uploads out of durable
  storage records.
- test: cover oversized uploads, unlimited uploads, staged cleanup, and
  local storage promotion boundaries.
- docs: document the staged upload model and multipart parent_id query
  parameter.
This commit is contained in:
2026-07-05 17:18:19 +08:00
parent bfeb4b26e4
commit b988b4b15e
10 changed files with 457 additions and 50 deletions
+12 -6
View File
@@ -6,18 +6,24 @@ import (
"io"
)
// StorageBackend abstracts where file content is written, read, and deleted.
// Paths passed to all methods are relative to the backend's root and must
// not contain path traversal sequences.
// StorageBackend abstracts where file content is staged, promoted, read, and
// deleted. Paths passed to all methods are relative to the backend's root and
// must not contain path traversal sequences.
type StorageBackend interface {
// Save writes the contents of reader to path, creating parent directories
// as needed. It returns the number of bytes written.
Save(ctx context.Context, path string, reader io.Reader) (int64, error)
// SaveStaged writes the contents of reader to a staging path, creating
// parent directories as needed. It returns the number of bytes written.
SaveStaged(ctx context.Context, path string, reader io.Reader) (int64, error)
// PromoteStaged moves a staged object to its final path.
PromoteStaged(ctx context.Context, stagedPath, finalPath string) error
// Open returns a reader for the file at path. The caller must close the
// returned ReadCloser.
Open(ctx context.Context, path string) (io.ReadCloser, error)
// DeleteStaged removes a staged object. It is idempotent.
DeleteStaged(ctx context.Context, path string) error
// Delete removes the file at path. It is idempotent — deleting a
// non-existent file is not an error.
Delete(ctx context.Context, path string) error