package services import ( "fmt" "io" "log" "time" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // OSSObject representa un objeto/archivo listado en el bucket type OSSObject struct { Key string `json:"key"` Size int64 `json:"size"` LastModified time.Time `json:"last_modified"` ETag string `json:"etag"` } // OSSListResult resultado paginado de listado de objetos type OSSListResult struct { Objects []OSSObject `json:"objects"` CommonPrefixes []string `json:"prefixes"` // "carpetas" virtuales IsTruncated bool `json:"is_truncated"` NextMarker string `json:"next_marker"` } type OSSService struct { Client *oss.Client Bucket *oss.Bucket BucketName string Endpoint string } // Configuración del OSS type OSSConfig struct { Endpoint string AccessKeyID string AccessKeySecret string BucketName string } // Constructor del servicio OSS func NewOSSService(cfg OSSConfig) (*OSSService, error) { client, err := oss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret) if err != nil { return nil, fmt.Errorf("error creando cliente OSS: %w", err) } bucket, err := client.Bucket(cfg.BucketName) if err != nil { return nil, fmt.Errorf("error obteniendo bucket: %w", err) } return &OSSService{ Client: client, Bucket: bucket, BucketName: cfg.BucketName, Endpoint: cfg.Endpoint, }, nil } // Subir un archivo func (s *OSSService) UploadFile(objectKey string, filePath string) error { err := s.Bucket.PutObjectFromFile(objectKey, filePath) if err != nil { return fmt.Errorf("error subiendo archivo: %w", err) } log.Printf("Archivo '%s' subido como '%s'", filePath, objectKey) return nil } // Listar archivos en el bucket func (s *OSSService) ListFiles() ([]string, error) { objects, err := s.Bucket.ListObjects() if err != nil { return nil, fmt.Errorf("error listando archivos: %w", err) } var keys []string for _, obj := range objects.Objects { keys = append(keys, obj.Key) } return keys, nil } // NewOSSServiceFromDB construye OSSService desde el último registro activo func NewOSSServiceFromDB() (*OSSService, error) { cfg, err := models.GetLastActiveOssApi() if err != nil { return nil, fmt.Errorf("error obteniendo configuración OSS activa: %w", err) } return NewOSSService(OSSConfig{ Endpoint: cfg.Endpoint, AccessKeyID: cfg.AccessKeyID, AccessKeySecret: cfg.AccessKeySecret, BucketName: cfg.BucketName, }) } // Eliminar un archivo del bucket func (s *OSSService) DeleteFile(objectKey string) error { err := s.Bucket.DeleteObject(objectKey) if err != nil { return fmt.Errorf("error eliminando archivo '%s' de OSS: %w", objectKey, err) } log.Printf("Archivo eliminado de OSS: %s", objectKey) return nil } // ListObjects lista objetos del bucket con soporte de prefijo (carpeta), delimitador y paginación. func (s *OSSService) ListObjects(prefix, marker string, maxKeys int) (*OSSListResult, error) { if maxKeys <= 0 || maxKeys > 1000 { maxKeys = 100 } opts := []oss.Option{ oss.MaxKeys(maxKeys), oss.Delimiter("/"), } if prefix != "" { opts = append(opts, oss.Prefix(prefix)) } if marker != "" { opts = append(opts, oss.Marker(marker)) } resp, err := s.Bucket.ListObjects(opts...) if err != nil { return nil, fmt.Errorf("error listando objetos OSS: %w", err) } result := &OSSListResult{ IsTruncated: resp.IsTruncated, NextMarker: resp.NextMarker, } for _, obj := range resp.Objects { result.Objects = append(result.Objects, OSSObject{ Key: obj.Key, Size: obj.Size, LastModified: obj.LastModified, ETag: obj.ETag, }) } for _, cp := range resp.CommonPrefixes { result.CommonPrefixes = append(result.CommonPrefixes, cp) } return result, nil } // SignedURL genera una URL firmada temporal para descargar/previsualizar un objeto. func (s *OSSService) SignedURL(objectKey string, expireSeconds int) (string, error) { if expireSeconds <= 0 { expireSeconds = 3600 } url, err := s.Bucket.SignURL(objectKey, oss.HTTPGet, int64(expireSeconds)) if err != nil { return "", fmt.Errorf("error generando URL firmada: %w", err) } return url, nil } // UploadFromReader sube un archivo desde un io.Reader con el content-type dado. func (s *OSSService) UploadFromReader(objectKey, contentType string, r io.Reader) error { var opts []oss.Option if contentType != "" { opts = append(opts, oss.ContentType(contentType)) } err := s.Bucket.PutObject(objectKey, r, opts...) if err != nil { return fmt.Errorf("error subiendo objeto '%s': %w", objectKey, err) } log.Printf("Objeto subido a OSS: %s", objectKey) return nil } // DeleteObjects elimina múltiples objetos en una sola llamada (batch). func (s *OSSService) DeleteObjects(keys []string) error { _, err := s.Bucket.DeleteObjects(keys) if err != nil { return fmt.Errorf("error eliminando objetos en batch: %w", err) } return nil }