mirror of
https://git.fightbot.fun/hxuanyu/BingPaper.git
synced 2026-02-15 14:39:31 +08:00
基本功能实现
This commit is contained in:
65
internal/storage/local/local.go
Normal file
65
internal/storage/local/local.go
Normal 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
95
internal/storage/s3/s3.go
Normal 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
|
||||
}
|
||||
27
internal/storage/storage.go
Normal file
27
internal/storage/storage.go
Normal 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
|
||||
}
|
||||
74
internal/storage/webdav/webdav.go
Normal file
74
internal/storage/webdav/webdav.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user