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. |