Files
moteva/server/internal/infrastructure/objectstorage/aliyun.go
T
root 44406b72db Initial commit: img-infinite-canvas AI design workbench MVP
Moteva-style AI design workbench replica. Home prompt creates a project;
the project page provides an infinite canvas with node drag, zoom/pan,
a design chat panel, history replay, image asset upload, and project
save/regenerate.

- Backend: go-zero, DDD layering, sqlc/pgx, optional PostgreSQL; memory
  or Redis cache; asynq job queue; MinIO/S3/R2/OSS object storage;
  sky-valley/pi agent runtime adapter.
- Frontend: Next.js App Router + Vite artifact build, TypeScript, i18n,
  shadcn/ui components, auth (OTP/Turnstile/Google/WeChat).
- Deploy: Docker Compose and k3s manifests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 23:15:37 +08:00

121 lines
2.7 KiB
Go

package objectstorage
import (
"bytes"
"context"
"strings"
"img_infinite_canvas/internal/domain/design"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
)
type AliyunOptions struct {
Bucket string
Endpoint string
AccessKey string
SecretKey string
PublicBase string
ExpiresIn int64
}
type AliyunStorage struct {
bucketName string
publicBase string
expiresIn int64
bucket *oss.Bucket
}
func NewAliyunStorage(ctx context.Context, opts AliyunOptions) (*AliyunStorage, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
client, err := oss.New(opts.Endpoint, opts.AccessKey, opts.SecretKey)
if err != nil {
return nil, err
}
exists, err := client.IsBucketExist(opts.Bucket)
if err != nil {
return nil, err
}
if !exists {
if err := client.CreateBucket(opts.Bucket); err != nil {
return nil, err
}
}
bucket, err := client.Bucket(opts.Bucket)
if err != nil {
return nil, err
}
if opts.ExpiresIn <= 0 {
opts.ExpiresIn = 900
}
return &AliyunStorage{
bucketName: opts.Bucket,
publicBase: strings.TrimRight(opts.PublicBase, "/"),
expiresIn: opts.ExpiresIn,
bucket: bucket,
}, nil
}
func (s *AliyunStorage) CreateUpload(ctx context.Context, req design.AssetUploadRequest) (design.AssetUpload, error) {
if err := ctx.Err(); err != nil {
return design.AssetUpload{}, err
}
objectKey := objectKey(req.FileName)
signedURL, err := s.bucket.SignURL(objectKey, oss.HTTPPut, s.expiresIn, oss.ContentType(req.ContentType))
if err != nil {
return design.AssetUpload{}, err
}
publicURL := signedURL
if s.publicBase != "" {
publicURL = publicObjectURL(s.publicBase, objectKey)
}
return design.AssetUpload{
Driver: "aliyun",
Bucket: s.bucketName,
ObjectKey: objectKey,
UploadURL: signedURL,
PublicURL: publicURL,
Headers: map[string]string{
"Content-Type": req.ContentType,
},
ExpiresIn: s.expiresIn,
}, nil
}
func (s *AliyunStorage) PutObject(ctx context.Context, req design.AssetObjectRequest) (design.AssetObject, error) {
if err := ctx.Err(); err != nil {
return design.AssetObject{}, err
}
objectKey := objectKey(req.FileName)
if err := s.bucket.PutObject(objectKey, bytes.NewReader(req.Data), oss.ContentType(req.ContentType)); err != nil {
return design.AssetObject{}, err
}
publicURL := publicObjectURL(s.publicBase, objectKey)
return design.AssetObject{
Driver: "aliyun",
Bucket: s.bucketName,
ObjectKey: objectKey,
PublicURL: publicURL,
}, nil
}
func (s *AliyunStorage) DeleteObject(ctx context.Context, publicURL string) error {
if err := ctx.Err(); err != nil {
return err
}
objectKey, ok := objectKeyFromPublicURL(s.publicBase, publicURL)
if !ok {
return nil
}
return s.bucket.DeleteObject(objectKey)
}