S3: add public_url, fix Content-Disposition, add oss-file proxy

This commit is contained in:
Lizandro Guarnizo
2026-06-10 09:34:17 -05:00
parent 0fc7b8b94d
commit cf078a6e5f
4 changed files with 165 additions and 2 deletions
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"context"
"fmt"
"log"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
func main() {
minioClient, err := minio.New("135.181.2.237:9000", &minio.Options{
Creds: credentials.NewStaticV4("admin", "lsE6TAihW98iaXKDWG+6gGLYAFdyp2iC", ""),
Secure: false,
BucketLookup: minio.BucketLookupPath,
})
if err != nil {
log.Fatalf("Error: %v", err)
}
ctx := context.Background()
var count int64
opts := minio.ListObjectsOptions{Recursive: true}
for obj := range minioClient.ListObjects(ctx, "vcard-usite", opts) {
if obj.Err != nil {
log.Fatalf("Error: %v", obj.Err)
}
count++
if count%500 == 0 {
fmt.Printf("MinIO: %d objetos...\n", count)
}
}
fmt.Printf("\nTotal en MinIO: %d objetos\n", count)
}
+114
View File
@@ -0,0 +1,114 @@
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
func main() {
alibabaEndpoint := "oss-eu-central-1.aliyuncs.com"
alibabaKey := "LTAI5tCxbGnEBAj9RS5JpLWw"
alibabaSecret := "qAS8Rsi3MpRrV043fgxnW6fmUsQ8GN"
bucketName := "vcard-usite"
minioEndpoint := "135.181.2.237:9000"
minioKey := "admin"
minioSecret := "lsE6TAihW98iaXKDWG+6gGLYAFdyp2iC"
log.Println("Conectando a Alibaba OSS...")
alibabaClient, err := oss.New(alibabaEndpoint, alibabaKey, alibabaSecret)
if err != nil {
log.Fatalf("Error conectando a Alibaba: %v", err)
}
alibabaBucket, err := alibabaClient.Bucket(bucketName)
if err != nil {
log.Fatalf("Error obteniendo bucket Alibaba: %v", err)
}
log.Println("Conectando a MinIO S3...")
minioClient, err := minio.New(minioEndpoint, &minio.Options{
Creds: credentials.NewStaticV4(minioKey, minioSecret, ""),
Secure: false,
BucketLookup: minio.BucketLookupPath,
})
if err != nil {
log.Fatalf("Error conectando a MinIO: %v", err)
}
ctx := context.Background()
// Asegurar que el bucket existe en MinIO
exists, err := minioClient.BucketExists(ctx, bucketName)
if err != nil {
log.Fatalf("Error verificando bucket MinIO: %v", err)
}
if !exists {
err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
if err != nil {
log.Fatalf("Error creando bucket MinIO: %v", err)
}
log.Printf("Bucket '%s' creado en MinIO", bucketName)
}
log.Println("Listando objetos de Alibaba OSS...")
var totalObjects, skipped int64
marker := oss.Marker("vcf/vcf-Guido_Maffet-nxh4.vcf")
for {
resp, err := alibabaBucket.ListObjects(oss.MaxKeys(1000), marker)
if err != nil {
log.Fatalf("Error listando objetos Alibaba: %v", err)
}
for _, obj := range resp.Objects {
totalObjects++
// Saltar si ya existe en MinIO (mismo tamaño)
info, err := minioClient.StatObject(ctx, bucketName, obj.Key, minio.StatObjectOptions{})
if err == nil && info.Size == obj.Size {
skipped++
continue
}
fmt.Printf("[%d] Migrando: %s (%s)...\n", totalObjects, obj.Key, formatSize(obj.Size))
reader, err := alibabaBucket.GetObject(obj.Key)
if err != nil {
log.Printf(" ERROR leyendo de Alibaba: %v", err)
continue
}
_, err = minioClient.PutObject(ctx, bucketName, obj.Key, reader, obj.Size, minio.PutObjectOptions{})
reader.Close()
if err != nil {
log.Printf(" ERROR subiendo a MinIO: %v", err)
continue
}
fmt.Printf(" ✓ OK\n")
time.Sleep(10 * time.Millisecond)
}
if !resp.IsTruncated {
break
}
marker = oss.Marker(resp.NextMarker)
}
log.Printf("Migración completada. %d objetos totales, %d saltados (ya existen), %d migrados.", totalObjects, skipped, totalObjects-skipped)
}
func formatSize(bytes int64) string {
if bytes < 1024 {
return fmt.Sprintf("%d B", bytes)
} else if bytes < 1024*1024 {
return fmt.Sprintf("%.1f KB", float64(bytes)/1024)
} else if bytes < 1024*1024*1024 {
return fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024))
}
return fmt.Sprintf("%.1f GB", float64(bytes)/(1024*1024*1024))
}
+1
View File
@@ -17,6 +17,7 @@ type OssApi struct {
BucketName string `gorm:"not null" json:"bucket_name"` // Nombre del bucket o access point
Region string `gorm:"size:50" json:"region"` // Región, opcional
IsActive bool `gorm:"default:true" json:"is_active"` // Activar o desactivar config
PublicURL string `gorm:"size:255" json:"public_url"` // URL pública para S3 (opcional)
Notes string `gorm:"type:text" json:"notes"`
}
+15 -2
View File
@@ -163,6 +163,7 @@ type s3OSS struct {
client *minio.Client
bucketName string
endpoint string
publicURL string
}
func newS3OSS(cfg *models.OssApi) (OSSProvider, error) {
@@ -190,18 +191,26 @@ func newS3OSS(cfg *models.OssApi) (OSSProvider, error) {
client: client,
bucketName: cfg.BucketName,
endpoint: cfg.Endpoint,
publicURL: cfg.PublicURL,
}, nil
}
func (s *s3OSS) BucketName() string { return s.bucketName }
func (s *s3OSS) Endpoint() string { return s.endpoint }
func (s *s3OSS) PublicURL(objectKey string) string {
if s.publicURL != "" {
return fmt.Sprintf("%s/%s/%s", s.publicURL, s.bucketName, objectKey)
}
return fmt.Sprintf("%s/%s/%s", s.endpoint, s.bucketName, objectKey)
}
func (s *s3OSS) UploadFile(objectKey, filePath string) error {
ctx := context.Background()
_, err := s.client.FPutObject(ctx, s.bucketName, objectKey, filePath, minio.PutObjectOptions{})
_, err := s.client.FPutObject(ctx, s.bucketName, objectKey, filePath, minio.PutObjectOptions{
UserMetadata: map[string]string{
"Content-Disposition": "attachment",
},
})
if err != nil {
return fmt.Errorf("error subiendo archivo a S3: %w", err)
}
@@ -275,7 +284,11 @@ func (s *s3OSS) SignedURL(objectKey string, expireSeconds int) (string, error) {
func (s *s3OSS) UploadFromReader(objectKey, contentType string, r io.Reader) error {
ctx := context.Background()
opts := minio.PutObjectOptions{}
opts := minio.PutObjectOptions{
UserMetadata: map[string]string{
"Content-Disposition": "attachment",
},
}
if contentType != "" {
opts.ContentType = contentType
}