Integração e Gerenciamento de Arquivos com Alibaba Cloud OSS Utilizando Java

Para interagir com o Alibaba Cloud Object Storage Service (OSS), a autenticação é o requisito inicial. É necessário provisionar um AccessKey e um AccessKeySecret através do painel de controle do provedor de nuvem. Estas credenciais atuam como a base para a assinatura criptográfica de todas as requisições enviadas à API do serviço.

Configuração do Ambiente e Dependências

Para construir um módulo robusto de integração, adicione as seguintes dependências ao seu arquivo pom.xml. Esta configuração inclui o SDK oficial do Alibaba Cloud, utilitários para manipulação de I/O e o framework JUnit para validação dos fluxos.

<dependencies>
    <!-- SDK Oficial do Alibaba Cloud OSS -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.15.1</version>
    </dependency>
    
    <!-- Utilitários para manipulação de arquivos -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>

    <!-- Framework de Testes -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Gerenciamento de Configurações

Centralize as credenciais e parâmetros de conexão em um arquivo oss.properties, localizado no diretório src/main/resources. Esta abordagem evita o acoplamento de dados sensíveis diretamente no código-fonte.

# Configurações de acesso ao Alibaba Cloud OSS
aliyun.oss.endpoint=oss-cn-hangzhou.aliyuncs.com
aliyun.oss.bucket-name=production-assets-bucket
aliyun.oss.base-path=media/uploads/
aliyun.oss.access-key-id=YOUR_ACCESS_KEY_ID
aliyun.oss.access-key-secret=YOUR_ACCESS_KEY_SECRET

Em seguida, crie uma classe de configuração para carregar e encapsular essas propriedades de forma segura, lidando com a conversão de caracteres adequadamente.

package com.example.storage.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class OssProperties {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
    private String basePath;

    public OssProperties() {
        Properties props = new Properties();
        try (InputStream is = getClass().getClassLoader().getResourceAsStream("oss.properties")) {
            if (is != null) {
                props.load(is);
                this.endpoint = props.getProperty("aliyun.oss.endpoint");
                this.accessKeyId = props.getProperty("aliyun.oss.access-key-id");
                this.accessKeySecret = props.getProperty("aliyun.oss.access-key-secret");
                this.bucketName = props.getProperty("aliyun.oss.bucket-name");
                this.basePath = props.getProperty("aliyun.oss.base-path", "");
            } else {
                throw new RuntimeException("Arquivo oss.properties não encontrado.");
            }
        } catch (IOException e) {
            throw new RuntimeException("Falha ao carregar configurações do OSS", e);
        }
    }

    // Getters omitidos para brevidade
    public String getEndpoint() { return endpoint; }
    public String getAccessKeyId() { return accessKeyId; }
    public String getAccessKeySecret() { return accessKeySecret; }
    public String getBucketName() { return bucketName; }
    public String getBasePath() { return basePath; }
}

Implementação do Serviço de Armazenamento

A classe principal OssStorageService atua como uma abstração sobre o cliennte nativo. Nesta versão otimizada, o fluxo de I/O utiliza o bloco try-with-resources para garantir o fechamento automático de streams, a extração de chaves de objetos utiliza a classe java.net.URL para evitar manipulação frágil de strings, e a detecção de MIME type é delegada ao NIO do Java.

package com.example.storage.service;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.DeleteObjectsResult;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.example.storage.config.OssProperties;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

public class OssStorageService {
    private final OssProperties properties;

    public OssStorageService() {
        this.properties = new OssProperties();
    }

    public String upload(File file) {
        String extension = getFileExtension(file.getName());
        String objectKey = properties.getBasePath() + UUID.randomUUID().toString().replace("-", "") + "." + extension;
        return executeUpload(file, objectKey);
    }

    public String overwrite(File file, String existingUrl) {
        String objectKey = extractObjectKey(existingUrl);
        if (objectKey == null) return null;
        return executeUpload(file, objectKey);
    }

    public String replace(File file, String existingUrl) {
        remove(existingUrl);
        return upload(file);
    }

    public boolean remove(String fileUrl) {
        String objectKey = extractObjectKey(fileUrl);
        if (objectKey == null) return false;

        OSS client = createClient();
        try {
            client.deleteObject(properties.getBucketName(), objectKey);
            return true;
        } catch (Exception e) {
            System.err.println("Erro ao remover objeto: " + e.getMessage());
            return false;
        } finally {
            client.shutdown();
        }
    }

    public int removeBatch(List<String> fileUrls) {
        List<String> keys = fileUrls.stream()
                .map(this::extractObjectKey)
                .filter(key -> key != null)
                .collect(Collectors.toList());

        if (keys.isEmpty()) return 0;

        OSS client = createClient();
        try {
            DeleteObjectsRequest request = new DeleteObjectsRequest(properties.getBucketName()).withKeys(keys);
            DeleteObjectsResult result = client.deleteObjects(request);
            return result.getDeletedObjects().size();
        } finally {
            client.shutdown();
        }
    }

    private String executeUpload(File file, String objectKey) {
        OSS client = createClient();
        try (InputStream inputStream = new FileInputStream(file)) {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(detectMimeType(file.getName()));
            metadata.setCacheControl("no-cache");

            PutObjectRequest request = new PutObjectRequest(properties.getBucketName(), objectKey, inputStream, metadata);
            client.putObject(request);

            return String.format("https://%s.%s/%s", properties.getBucketName(), properties.getEndpoint(), objectKey);
        } catch (Exception e) {
            throw new RuntimeException("Falha durante o upload do arquivo", e);
        } finally {
            client.shutdown();
        }
    }

    private OSS createClient() {
        return new OSSClientBuilder().build(
            properties.getEndpoint(), 
            properties.getAccessKeyId(), 
            properties.getAccessKeySecret()
        );
    }

    private String extractObjectKey(String fileUrl) {
        try {
            URL url = new URL(fileUrl);
            String path = url.getPath();
            return path.startsWith("/") ? path.substring(1) : path;
        } catch (Exception e) {
            return null;
        }
    }

    private String getFileExtension(String filename) {
        int dotIndex = filename.lastIndexOf('.');
        return (dotIndex == -1) ? "" : filename.substring(dotIndex + 1);
    }

    private String detectMimeType(String filename) {
        try {
            String mimeType = Files.probeContentType(new File(filename).toPath());
            return mimeType != null ? mimeType : "application/octet-stream";
        } catch (Exception e) {
            return "application/octet-stream";
        }
    }
}

Validação através de Testes Automatizados

Para garantir o comportamento esperado do serviço, crie uma suíte de testes unitários que valide os fluxos de envio e substituição de arquivos no bucket remoto.

package com.example.storage;

import com.example.storage.service.OssStorageService;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.Arrays;

public class OssIntegrationTest {

    @Test
    public void testUploadAndOverwriteOperations() {
        OssStorageService storageService = new OssStorageService();
        File originalFile = new File("src/test/resources/sample_image.jpg");

        // Executa o upload inicial
        String remoteUrl = storageService.upload(originalFile);
        System.out.println("URL do arquivo enviado: " + remoteUrl);

        // Sobrescreve o conteúdo mantendo a mesma URL
        if (remoteUrl != null) {
            File updatedFile = new File("src/test/resources/sample_image_v2.jpg");
            String updatedUrl = storageService.overwrite(updatedFile, remoteUrl);
            System.out.println("URL após sobrescrita: " + updatedUrl);
        }
    }

    @Test
    public void testDeletionOperations() {
        OssStorageService storageService = new OssStorageService();
        
        // Remoção de um único arquivo
        boolean isDeleted = storageService.remove("https://production-assets-bucket.oss-cn-hangzhou.aliyuncs.com/media/uploads/old_file.png");
        System.out.println("Arquivo removido com sucesso: " + isDeleted);

        // Remoção em lote
        int deletedCount = storageService.removeBatch(Arrays.asList(
            "https://production-assets-bucket.oss-cn-hangzhou.aliyuncs.com/media/uploads/temp1.jpg",
            "https://production-assets-bucket.oss-cn-hangzhou.aliyuncs.com/media/uploads/temp2.jpg"
        ));
        System.out.println("Total de arquivos excluídos no lote: " + deletedCount);
    }
}

Referência de Métodos do Serviço

Assinatura do Método Descrição do Comportamento
upload(File file) Realiza o upload de um único arquivo gerando um nome aleatório baseado em UUID.
overwrite(File file, String existingUrl) Substitui o conteúdo binário de um arquivo existente no bucket, preservando o caminho e nome originais. Útil para evitar problemas de cache no navegador.
replace(File file, String existingUrl) Exclui o arquivo original apontado pela URL e realiza o upload do novo arquivo com um identificador completamente novo.
remove(String fileUrl) Extrai a chave do objeto a partir da URL fornecida e o remove do bucket de armazanamento.
removeBatch(List<String> fileUrls) Otimiza a exclusão de múltiplos arquivos agrupando as requisições em uma única chamada de API para o OSS, retornando o número de itens processados.

Tags: java aliyun-oss object-storage cloud-storage file-upload

Publicado em 6-11 00:59 por Thomas