feat: add object storage and points leaderboard

This commit is contained in:
richjohnrambo
2026-10-18 18:11:34 +08:00
parent bce5edfaf5
commit b79b781f15
24 changed files with 359 additions and 212 deletions
+62 -11
View File
@@ -1,6 +1,7 @@
package attachments
import (
"bytes"
"context"
"crypto/md5"
"crypto/rand"
@@ -8,12 +9,17 @@ import (
"errors"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"time"
"git.118im.com/im_team/olons_server/internal/config"
"git.118im.com/im_team/olons_server/internal/store"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
const maxImageSize = 5 << 20
@@ -27,10 +33,23 @@ type Repository interface {
type Service struct {
repository Repository
cfg config.UploadConfig
minio *minio.Client
}
func New(repository Repository, cfg config.UploadConfig) *Service {
return &Service{repository: repository, cfg: cfg}
func New(repository Repository, cfg config.UploadConfig) (*Service, error) {
service := &Service{repository: repository, cfg: cfg}
if strings.EqualFold(cfg.Driver, "minio") {
client, err := minio.New(strings.TrimSpace(cfg.Endpoint), &minio.Options{
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
Secure: cfg.Secure,
Region: cfg.Region,
})
if err != nil {
return nil, err
}
service.minio = client
}
return service, nil
}
func (s *Service) UploadImage(ctx context.Context, memberID int64, name string, reader io.Reader) (*store.Attachment, error) {
@@ -52,22 +71,54 @@ func (s *Service) UploadImage(ctx context.Context, memberID int64, name string,
return nil, err
}
fileName := hex.EncodeToString(random) + ext
if err = os.MkdirAll(s.cfg.Dir, 0o750); err != nil {
return nil, err
}
path := filepath.Join(s.cfg.Dir, fileName)
if err = os.WriteFile(path, data, 0o640); err != nil {
storedPath, publicURL, err := s.store(ctx, fileName, mimeType, data)
if err != nil {
return nil, err
}
digest := md5.Sum(data)
publicPath := strings.TrimRight(s.cfg.PublicPath, "/") + "/" + fileName
attachment, err := s.repository.CreateAttachment(ctx, store.AttachmentCreation{
MemberID: memberID, Name: filepath.Base(name), MimeType: mimeType, Path: path,
URL: publicPath, Size: int64(len(data)), Ext: strings.TrimPrefix(ext, "."), MD5: hex.EncodeToString(digest[:]),
MemberID: memberID, Drive: strings.ToLower(strings.TrimSpace(s.cfg.Driver)), Name: filepath.Base(name), MimeType: mimeType, Path: storedPath,
URL: publicURL, Size: int64(len(data)), Ext: strings.TrimPrefix(ext, "."), MD5: hex.EncodeToString(digest[:]),
})
if err != nil {
_ = os.Remove(path)
s.remove(ctx, storedPath)
return nil, err
}
return attachment, nil
}
func (s *Service) store(ctx context.Context, fileName, mimeType string, data []byte) (string, string, error) {
if s.minio != nil {
objectName := path.Join(strings.Trim(s.cfg.Path, "/"), time.Now().UTC().Format("2006/01/02"), fileName)
_, err := s.minio.PutObject(ctx, s.cfg.Bucket, objectName, bytes.NewReader(data), int64(len(data)), minio.PutObjectOptions{ContentType: mimeType})
if err != nil {
return "", "", err
}
return objectName, minioPublicURL(s.cfg.Domain, s.cfg.Bucket, objectName), nil
}
if err := os.MkdirAll(s.cfg.Dir, 0o750); err != nil {
return "", "", err
}
localPath := filepath.Join(s.cfg.Dir, fileName)
if err := os.WriteFile(localPath, data, 0o640); err != nil {
return "", "", err
}
return localPath, strings.TrimRight(s.cfg.PublicPath, "/") + "/" + fileName, nil
}
func (s *Service) remove(ctx context.Context, storedPath string) {
if s.minio != nil {
_ = s.minio.RemoveObject(ctx, s.cfg.Bucket, storedPath, minio.RemoveObjectOptions{})
return
}
_ = os.Remove(storedPath)
}
func minioPublicURL(domain, bucket, objectName string) string {
base := strings.TrimRight(strings.TrimSpace(domain), "/")
segments := strings.Split(strings.Trim(objectName, "/"), "/")
for i := range segments {
segments[i] = url.PathEscape(segments[i])
}
return base + "/" + url.PathEscape(bucket) + "/" + strings.Join(segments, "/")
}
+11
View File
@@ -0,0 +1,11 @@
package attachments
import "testing"
func TestMinioPublicURL(t *testing.T) {
got := minioPublicURL("https://minio.example.com/", "bucket", "images/2026/10/17/a b.jpg")
want := "https://minio.example.com/bucket/images/2026/10/17/a%20b.jpg"
if got != want {
t.Fatalf("minioPublicURL() = %q, want %q", got, want)
}
}