基本功能实现

This commit is contained in:
2026-01-26 21:53:34 +08:00
commit c6e5e655f9
28 changed files with 4803 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
package local
import (
"context"
"io"
"os"
"path/filepath"
"BingDailyImage/internal/storage"
)
type LocalStorage struct {
root string
}
func NewLocalStorage(root string) (*LocalStorage, error) {
if err := os.MkdirAll(root, 0755); err != nil {
return nil, err
}
return &LocalStorage{root: root}, nil
}
func (l *LocalStorage) Put(ctx context.Context, key string, r io.Reader, contentType string) (storage.StoredObject, error) {
path := filepath.Join(l.root, key)
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return storage.StoredObject{}, err
}
f, err := os.Create(path)
if err != nil {
return storage.StoredObject{}, err
}
defer f.Close()
n, err := io.Copy(f, r)
if err != nil {
return storage.StoredObject{}, err
}
return storage.StoredObject{
Key: key,
ContentType: contentType,
Size: n,
}, nil
}
func (l *LocalStorage) Get(ctx context.Context, key string) (io.ReadCloser, string, error) {
path := filepath.Join(l.root, key)
f, err := os.Open(path)
if err != nil {
return nil, "", err
}
// 这里很难从文件扩展名以外的地方获得 contentType除非存储时记录
// 简单处理
return f, "", nil
}
func (l *LocalStorage) Delete(ctx context.Context, key string) error {
path := filepath.Join(l.root, key)
return os.Remove(path)
}
func (l *LocalStorage) PublicURL(key string) (string, bool) {
return "", false
}

95
internal/storage/s3/s3.go Normal file
View File

@@ -0,0 +1,95 @@
package s3
import (
"context"
"fmt"
"io"
"strings"
"BingDailyImage/internal/storage"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
type S3Storage struct {
session *session.Session
client *s3.S3
bucket string
publicURLPrefix string
}
func NewS3Storage(endpoint, region, bucket, accessKey, secretKey, publicURLPrefix string, forcePathStyle bool) (*S3Storage, error) {
config := &aws.Config{
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
Endpoint: aws.String(endpoint),
S3ForcePathStyle: aws.Bool(forcePathStyle),
}
sess, err := session.NewSession(config)
if err != nil {
return nil, err
}
return &S3Storage{
session: sess,
client: s3.New(sess),
bucket: bucket,
publicURLPrefix: publicURLPrefix,
}, nil
}
func (s *S3Storage) Put(ctx context.Context, key string, r io.Reader, contentType string) (storage.StoredObject, error) {
uploader := s3manager.NewUploader(s.session)
output, err := uploader.UploadWithContext(ctx, &s3manager.UploadInput{
Bucket: aws.String(s.bucket),
Key: aws.String(key),
Body: r,
ContentType: aws.String(contentType),
})
if err != nil {
return storage.StoredObject{}, err
}
publicURL := ""
if s.publicURLPrefix != "" {
publicURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(s.publicURLPrefix, "/"), key)
} else {
publicURL = output.Location
}
return storage.StoredObject{
Key: key,
ContentType: contentType,
PublicURL: publicURL,
}, nil
}
func (s *S3Storage) Get(ctx context.Context, key string) (io.ReadCloser, string, error) {
output, err := s.client.GetObjectWithContext(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(key),
})
if err != nil {
return nil, "", err
}
return output.Body, aws.StringValue(output.ContentType), nil
}
func (s *S3Storage) Delete(ctx context.Context, key string) error {
_, err := s.client.DeleteObjectWithContext(ctx, &s3.DeleteObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(key),
})
return err
}
func (s *S3Storage) PublicURL(key string) (string, bool) {
if s.publicURLPrefix != "" {
return fmt.Sprintf("%s/%s", strings.TrimSuffix(s.publicURLPrefix, "/"), key), true
}
// 也可以生成签名 URL但这里简单处理
return "", false
}

View File

@@ -0,0 +1,27 @@
package storage
import (
"context"
"io"
)
type StoredObject struct {
Key string
ContentType string
Size int64
PublicURL string
}
type Storage interface {
Put(ctx context.Context, key string, r io.Reader, contentType string) (StoredObject, error)
Get(ctx context.Context, key string) (io.ReadCloser, string, error)
Delete(ctx context.Context, key string) error
PublicURL(key string) (string, bool)
}
var GlobalStorage Storage
func InitStorage() error {
// 实际初始化在 main.go 中根据配置调用对应的初始化函数
return nil
}

View File

@@ -0,0 +1,74 @@
package webdav
import (
"context"
"fmt"
"io"
"path"
"strings"
"BingDailyImage/internal/storage"
"github.com/studio-b12/gowebdav"
)
type WebDAVStorage struct {
client *gowebdav.Client
publicURLPrefix string
}
func NewWebDAVStorage(url, username, password, publicURLPrefix string) (*WebDAVStorage, error) {
client := gowebdav.NewClient(url, username, password)
if err := client.Connect(); err != nil {
// 有些 webdav 不支持 Connect我们可以忽略错误或者做简单的探测
}
return &WebDAVStorage{
client: client,
publicURLPrefix: publicURLPrefix,
}, nil
}
func (w *WebDAVStorage) Put(ctx context.Context, key string, r io.Reader, contentType string) (storage.StoredObject, error) {
// 确保目录存在
dir := path.Dir(key)
if dir != "." && dir != "/" {
if err := w.client.MkdirAll(dir, 0755); err != nil {
return storage.StoredObject{}, err
}
}
err := w.client.WriteStream(key, r, 0644)
if err != nil {
return storage.StoredObject{}, err
}
publicURL := ""
if w.publicURLPrefix != "" {
publicURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(w.publicURLPrefix, "/"), key)
}
return storage.StoredObject{
Key: key,
ContentType: contentType,
PublicURL: publicURL,
}, nil
}
func (w *WebDAVStorage) Get(ctx context.Context, key string) (io.ReadCloser, string, error) {
reader, err := w.client.ReadStream(key)
if err != nil {
return nil, "", err
}
return reader, "", nil
}
func (w *WebDAVStorage) Delete(ctx context.Context, key string) error {
return w.client.Remove(key)
}
func (w *WebDAVStorage) PublicURL(key string) (string, bool) {
if w.publicURLPrefix != "" {
return fmt.Sprintf("%s/%s", strings.TrimSuffix(w.publicURLPrefix, "/"), key), true
}
return "", false
}