优化结构
This commit is contained in:
@@ -1,202 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
type fileStorage struct {
|
||||
root string
|
||||
urlPrefix string
|
||||
}
|
||||
|
||||
func New(config *conf.AdminBackend) (biz.FileStorage, error) {
|
||||
storageType := "local"
|
||||
if config != nil && config.Storage != nil && config.Storage.Type != "" {
|
||||
storageType = strings.ToLower(config.Storage.Type)
|
||||
}
|
||||
if storageType == "qiniu" {
|
||||
return newQiniuStorage(config.Storage.Qiniu)
|
||||
}
|
||||
if storageType != "local" {
|
||||
var object *conf.AdminBackend_ObjectStore
|
||||
switch storageType {
|
||||
case "aliyun-oss":
|
||||
return newAliyunStorage(config.Storage.AliyunOss)
|
||||
case "huawei-obs":
|
||||
return newHuaweiStorage(config.Storage.HuaweiObs)
|
||||
case "tencent-cos":
|
||||
return newTencentStorage(config.Storage.TencentCos)
|
||||
case "aws-s3":
|
||||
return newAWSStorage(storageType, config.Storage.AwsS3)
|
||||
case "cloudflare-r2":
|
||||
return newAWSStorage(storageType, config.Storage.CloudflareR2)
|
||||
case "minio":
|
||||
object = config.Storage.Minio
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported storage type %q", storageType)
|
||||
}
|
||||
return newS3Storage(storageType, object)
|
||||
}
|
||||
root, prefix := "uploads/file", "uploads/file"
|
||||
if config != nil && config.Local != nil {
|
||||
if config.Local.StorePath != "" {
|
||||
root = config.Local.StorePath
|
||||
}
|
||||
if config.Local.PathPrefix != "" {
|
||||
prefix = config.Local.PathPrefix
|
||||
}
|
||||
}
|
||||
root = filepath.Clean(root)
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("create upload directory: %w", err)
|
||||
}
|
||||
return &fileStorage{root: root, urlPrefix: "/" + strings.Trim(prefix, "/")}, nil
|
||||
}
|
||||
|
||||
func composeFiles(ctx context.Context, storage biz.FileStorage, names []string, destination string) (*biz.StoredFile, string, error) {
|
||||
var stored *biz.StoredFile
|
||||
hash, err := composeStreams(ctx, names, storage.Open, func(ctx context.Context, destination string, reader io.Reader) error {
|
||||
var putErr error
|
||||
stored, putErr = storage.Put(ctx, destination, reader)
|
||||
return putErr
|
||||
}, storage.Delete, destination)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return stored, hash, nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) resolve(name string) (string, error) {
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("key不能为空")
|
||||
}
|
||||
clean := filepath.Clean(strings.TrimPrefix(name, "/"))
|
||||
if clean == "." || filepath.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
|
||||
return "", fmt.Errorf("invalid storage path")
|
||||
}
|
||||
return filepath.Join(s.root, clean), nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) Put(ctx context.Context, name string, reader io.Reader) (*biz.StoredFile, error) {
|
||||
path, err := s.resolve(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size, copyErr := io.Copy(file, reader)
|
||||
closeErr := file.Close()
|
||||
if copyErr != nil {
|
||||
return nil, copyErr
|
||||
}
|
||||
if closeErr != nil {
|
||||
return nil, closeErr
|
||||
}
|
||||
return &biz.StoredFile{Name: filepath.Base(name), Path: name, URL: s.urlPrefix + "/" + strings.TrimPrefix(filepath.ToSlash(name), "/"), Size: size}, nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) Open(ctx context.Context, name string) (io.ReadCloser, error) {
|
||||
path, err := s.resolve(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
func (s *fileStorage) Delete(ctx context.Context, name string) error {
|
||||
path, err := s.resolve(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Match the compatible Local.DeleteFile behavior: a missing object is an error and the
|
||||
// media DB record is retained when the physical file cannot be removed.
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return fmt.Errorf("文件不存在")
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fileStorage) Compose(ctx context.Context, names []string, destination string) (*biz.StoredFile, string, error) {
|
||||
return composeFiles(ctx, s, names, destination)
|
||||
}
|
||||
func (s *fileStorage) DeletePrefix(ctx context.Context, prefix string) error {
|
||||
path, err := s.resolve(strings.TrimSuffix(prefix, "/") + "/placeholder")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(path)
|
||||
if !strings.HasPrefix(dir, s.root+string(filepath.Separator)) {
|
||||
return fmt.Errorf("invalid storage path")
|
||||
}
|
||||
if err = os.RemoveAll(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int) ([]*biz.StoredFile, string, bool, error) {
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
root, err := s.resolve(strings.TrimSuffix(prefix, "/") + "/placeholder")
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
walkRoot := filepath.Dir(root)
|
||||
items := []*biz.StoredFile{}
|
||||
err = filepath.WalkDir(walkRoot, func(path string, entry os.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
if os.IsNotExist(walkErr) {
|
||||
return nil
|
||||
}
|
||||
return walkErr
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if entry.IsDir() || entry.Type()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
relative, _ := filepath.Rel(s.root, path)
|
||||
relative = filepath.ToSlash(relative)
|
||||
if relative <= cursor {
|
||||
return nil
|
||||
}
|
||||
info, infoErr := entry.Info()
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
}
|
||||
items = append(items, &biz.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size(), LastModified: info.ModTime(), ContentType: mime.TypeByExtension(filepath.Ext(entry.Name()))})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].Path < items[j].Path })
|
||||
hasMore := len(items) > limit
|
||||
if hasMore {
|
||||
items = items[:limit]
|
||||
}
|
||||
next := ""
|
||||
if len(items) > 0 {
|
||||
next = items[len(items)-1].Path
|
||||
}
|
||||
return items, next, hasMore, nil
|
||||
}
|
||||
Reference in New Issue
Block a user