package com.ard.oss.service.impl; import com.ard.common.config.OssConfiguration; import com.ard.oss.model.CredentialsDTO; import com.ard.oss.model.enums.OssTypeEnum; import com.ard.oss.service.IOssService; import io.minio.*; import io.minio.credentials.AssumeRoleProvider; import io.minio.errors.*; import io.minio.http.Method; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import org.springframework.stereotype.Service; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.ProviderException; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.Objects; import static com.ard.common.config.OssConfiguration.*; /** * @author sean * @version 0.3 * @date 2021/12/23 */ @Service @Slf4j public class MinIOServiceImpl implements IOssService { private MinioClient client; @Override public String getOssType() { return OssTypeEnum.MINIO.getType(); } public CredentialsDTO getCredentials111() { try { AssumeRoleProvider provider = new AssumeRoleProvider(endpoint, accessKey, secretKey, Math.toIntExact(OssConfiguration.expire), null, OssConfiguration.region, null, null, null, null); return new CredentialsDTO(provider.fetch(), OssConfiguration.expire); } catch (NoSuchAlgorithmException e) { log.debug("Failed to obtain sts."); e.printStackTrace(); } return null; } @Override public CredentialsDTO getCredentials() { try { // 创建自签证书的 OkHttpClient TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {} @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } } }; SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, trustAllCerts, new SecureRandom()); OkHttpClient httpClient = new OkHttpClient.Builder() .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0]) .hostnameVerifier((hostname, session) -> true) .build(); // 构建 AssumeRoleProvider,使用自定义 httpClient AssumeRoleProvider provider = new AssumeRoleProvider( endpoint, accessKey, secretKey, Math.toIntExact(OssConfiguration.expire), null, OssConfiguration.region, null, null, null, httpClient ); // 获取 STS 凭证 var stsCredentials = provider.fetch(); return new CredentialsDTO(stsCredentials, OssConfiguration.expire); } catch (ProviderException e) { log.error("Failed to parse STS response", e); throw new RuntimeException("STS response parse failed", e); } catch (NoSuchAlgorithmException | java.security.KeyManagementException e) { log.error("Failed to create custom SSL context", e); throw new RuntimeException("SSL setup failed", e); } } @Override public URL getObjectUrl(String bucket, String objectKey) { try { return new URL( client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucket) .object(objectKey) .expiry(Math.toIntExact(OssConfiguration.expire)) .build())); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) { throw new RuntimeException("The file does not exist on the OssConfiguration."); } } @Override public Boolean deleteObject(String bucket, String objectKey) { try { client.removeObject(RemoveObjectArgs.builder() .bucket(bucket) .object(objectKey) .build()); } catch (MinioException | NoSuchAlgorithmException | IOException | InvalidKeyException e) { log.error("Failed to delete file."); e.printStackTrace(); return false; } return true; } @Override public InputStream getObject(String bucket, String objectKey) { try { GetObjectResponse object = client.getObject(GetObjectArgs.builder() .bucket(bucket) .object(objectKey) .build()); return new ByteArrayInputStream(object.readAllBytes()); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) { e.printStackTrace(); } return InputStream.nullInputStream(); } @Override public void putObject(String bucket, String objectKey, InputStream input) { try { client.statObject(StatObjectArgs.builder() .bucket(bucket) .object(objectKey) .build()); throw new RuntimeException("The filename already exists."); } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) { log.info("The file does not exist, start uploading."); try { // 设置 partSize 为 10MB long partSize = 10 * 1024 * 1024; ObjectWriteResponse response = client.putObject( PutObjectArgs.builder() .bucket(bucket) .object(objectKey) .stream(input, -1, partSize) .build()); log.info("Upload File: {}", response.etag()); } catch (MinioException | IOException | InvalidKeyException | NoSuchAlgorithmException ex) { log.error("Failed to upload File {}.", objectKey); ex.printStackTrace(); } } } @Override public void putObject(String bucket, String objectKey, InputStream input, String contentType) { try { // 检查文件是否已经存在 client.statObject(StatObjectArgs.builder() .bucket(bucket) .object(objectKey) .build()); throw new RuntimeException("The file already exists: " + objectKey); } catch (ErrorResponseException e) { // 如果文件不存在,则继续上传 log.info("The file does not exist, start uploading."); try { // 设置 partSize 为 10MB long partSize = 10 * 1024 * 1024; // 每块 10MB PutObjectArgs.Builder putArgsBuilder = PutObjectArgs.builder() .bucket(bucket) .object(objectKey) .stream(input, -1, partSize); // 如果提供了 Content-Type,则设置该类型 if (contentType != null && !contentType.isEmpty()) { putArgsBuilder.contentType(contentType); } // 上传文件 ObjectWriteResponse response = client.putObject(putArgsBuilder.build()); log.info("Uploaded file successfully: {}, ETag: {}", objectKey, response.etag()); } catch (MinioException | IOException | InvalidKeyException | NoSuchAlgorithmException ex) { log.error("Failed to upload file: {}", objectKey, ex); throw new RuntimeException("File upload failed: " + objectKey, ex); } } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) { // 如果 statObject 发生错误,说明服务端错误或其他问题 log.error("Failed to check if file exists: {}", objectKey, e); throw new RuntimeException("Failed to check if file exists: " + objectKey, e); } } @Override public void createClient() { if (Objects.nonNull(this.client)) { return; } try { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {} @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; } } }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustAllCerts, new SecureRandom()); SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); OkHttpClient httpClient = new OkHttpClient.Builder() .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]) .hostnameVerifier((hostname, session) -> true) .build(); this.client = MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .httpClient(httpClient) .build(); } catch (Exception e) { throw new RuntimeException("Failed to create unsafe MinioClient", e); } } }