feat: add independent platform modules and multi-provider storage

This commit is contained in:
2026-09-23 14:51:14 +08:00
parent f91034477b
commit 208a9c3fbd
57 changed files with 4890 additions and 48 deletions

68
storage/storage.go Normal file
View File

@@ -0,0 +1,68 @@
// Package storage defines streaming object storage; callers own returned bodies.
package storage
import (
"context"
"errors"
"io"
"net/url"
"strings"
"time"
"unicode"
"unicode/utf8"
)
var ErrNotFound = errors.New("storage: object not found")
var ErrTooLarge = errors.New("storage: object exceeds limit")
type Info struct {
Key string `json:"key"`
Size int64 `json:"size"`
ContentType string `json:"contentType"`
ModifiedAt time.Time `json:"modifiedAt"`
}
type Object struct {
Info
Body io.ReadCloser
}
type Store interface {
Put(context.Context, string, io.Reader, int64, string) (Info, error)
Get(context.Context, string) (Object, error)
Delete(context.Context, string) error
}
// Presigner is an optional capability. URLs grant temporary read access and
// must not be logged. Providers may expire them earlier when credentials expire.
type Presigner interface {
PresignGet(context.Context, string, time.Duration) (*url.URL, error)
}
func Check(ctx context.Context, key string) error {
if ctx == nil {
return errors.New("storage: nil context")
}
if err := ctx.Err(); err != nil {
return err
}
if key == "" || len(key) > 1024 || !utf8.ValidString(key) || strings.Contains(key, "\\") || strings.IndexFunc(key, unicode.IsControl) >= 0 {
return errors.New("storage: invalid key")
}
for _, part := range strings.Split(key, "/") {
if part == "" || part == "." || part == ".." {
return errors.New("storage: invalid key path")
}
}
return nil
}
type ContextReader struct {
Context context.Context
Reader io.Reader
}
func (r ContextReader) Read(p []byte) (int, error) {
if err := r.Context.Err(); err != nil {
return 0, err
}
return r.Reader.Read(p)
}