diff --git a/cmd/count-oss/main.go b/cmd/count-oss/main.go new file mode 100644 index 0000000..d0bc519 --- /dev/null +++ b/cmd/count-oss/main.go @@ -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) +} diff --git a/cmd/migrate-oss/main.go b/cmd/migrate-oss/main.go new file mode 100644 index 0000000..168f9e4 --- /dev/null +++ b/cmd/migrate-oss/main.go @@ -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)) +} diff --git a/pkg/models/oss_api.go b/pkg/models/oss_api.go index e447e12..2d95823 100755 --- a/pkg/models/oss_api.go +++ b/pkg/models/oss_api.go @@ -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"` } diff --git a/pkg/services/oss_service.go b/pkg/services/oss_service.go index 29ac6f6..0298244 100755 --- a/pkg/services/oss_service.go +++ b/pkg/services/oss_service.go @@ -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 }