From fdae339b178094e6ee2fbf523d2a810e492dc26e Mon Sep 17 00:00:00 2001 From: yangyang01000846 <15195822163@163.com> Date: Thu, 27 Nov 2025 10:58:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9Aminio=E5=A4=A7?= =?UTF-8?q?=E6=96=87=E4=BB=B6web=E4=BA=A4=E4=BA=92=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E5=93=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sdm/data/config/MinioConfig.java | 4 +- .../com/sdm/data/service/IMinioService.java | 9 ++- .../impl/MinioFileIDataFileServiceImpl.java | 39 +++---------- .../sdm/data/service/minio/MinioService.java | 58 +++++++++++++++++++ data/src/main/resources/application-dev.yml | 3 +- data/src/main/resources/application-local.yml | 1 + data/src/main/resources/application-prod.yml | 3 +- data/src/main/resources/application-test.yml | 3 +- 8 files changed, 83 insertions(+), 37 deletions(-) diff --git a/data/src/main/java/com/sdm/data/config/MinioConfig.java b/data/src/main/java/com/sdm/data/config/MinioConfig.java index bee3a529..da0375e7 100644 --- a/data/src/main/java/com/sdm/data/config/MinioConfig.java +++ b/data/src/main/java/com/sdm/data/config/MinioConfig.java @@ -4,7 +4,6 @@ import io.minio.MinioClient; import io.minio.admin.MinioAdminClient; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,6 +25,9 @@ public class MinioConfig { private String spdmBucket; // 普通业务数据桶 + // allocateDirect 单次缓冲的数据 + private int directMemory; + // 初始化超级管理员客户端(用于 admin 操作:桶、用户、策略) @Bean public MinioAdminClient minioAdminClient() { diff --git a/data/src/main/java/com/sdm/data/service/IMinioService.java b/data/src/main/java/com/sdm/data/service/IMinioService.java index f36a21f1..4be7ffb4 100644 --- a/data/src/main/java/com/sdm/data/service/IMinioService.java +++ b/data/src/main/java/com/sdm/data/service/IMinioService.java @@ -1,5 +1,6 @@ package com.sdm.data.service; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; import java.io.InputStream; @@ -108,7 +109,13 @@ public interface IMinioService { * @param objectName 文件名(objectKey) */ byte[] downloadFile(String objectName) throws Exception; - + + /** + * 从MinIO下载文件到response + * @param objectName 文件名(objectKey) + */ + void downloadFile(String objectName, HttpServletResponse response,String encodedFileName,Boolean preview,String contentType) throws Exception; + /** * 从MinIO下载文件 * @param objectName 文件名(objectKey) diff --git a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java index bfb66eff..a3ef8039 100644 --- a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java +++ b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java @@ -61,7 +61,10 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; @@ -1176,22 +1179,9 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { response.sendError(HttpServletResponse.SC_NON_AUTHORITATIVE_INFORMATION, "用户无权限"); return; } - // 从MinIO下载文件 - byte[] fileData = minioService.downloadFile(fileObjectKey); - - // 设置响应头 - response.reset(); - response.setContentType("application/octet-stream;charset=UTF-8"); String encodedFileName = URLEncoder.encode(fileMetadataInfo.getOriginalName(), StandardCharsets.UTF_8); - response.addHeader("Content-Disposition", "attachment;filename=" + encodedFileName); - response.addHeader("Content-Length", String.valueOf(fileData.length)); - - // 写入响应流 - OutputStream outputStream = response.getOutputStream(); - outputStream.write(fileData); - outputStream.flush(); - outputStream.close(); + minioService.downloadFile(fileObjectKey,response,encodedFileName,false,""); } catch (Exception e) { log.error("下载文件失败", e); try { @@ -1823,32 +1813,17 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } - // 检查文件是否存在且为图片类型 String objectKey = fileMetadataInfo.getObjectKey(); if (objectKey == null || objectKey.isEmpty()) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } - // 从minioService获取文件数据 - byte[] fileData = minioService.downloadFile(objectKey); - - // 设置响应头信息 - // 由于FileMetadataInfo中没有fileType字段,我们从originalName推断MIME类型 String originalName = fileMetadataInfo.getOriginalName(); + // 由于FileMetadataInfo中没有fileType字段,我们从originalName推断MIME类型 String contentType = getContentTypeByFileName(originalName); - - response.setContentType(contentType); - response.setContentLength(fileData.length); - response.setHeader("Content-Disposition", "inline; filename=\"" + - URLEncoder.encode(originalName, StandardCharsets.UTF_8.toString()) + "\""); - - // 将文件数据写入响应输出流 - try (OutputStream outputStream = response.getOutputStream()) { - outputStream.write(fileData); - outputStream.flush(); - } + minioService.downloadFile(objectKey,response,originalName,true,contentType); } catch (Exception e) { log.error("预览图片失败: fileId={}", fileId, e); try { diff --git a/data/src/main/java/com/sdm/data/service/minio/MinioService.java b/data/src/main/java/com/sdm/data/service/minio/MinioService.java index 812bb4d2..1662009f 100644 --- a/data/src/main/java/com/sdm/data/service/minio/MinioService.java +++ b/data/src/main/java/com/sdm/data/service/minio/MinioService.java @@ -1,6 +1,7 @@ package com.sdm.data.service.minio; import com.alibaba.fastjson2.JSON; +import com.sdm.common.entity.constants.NumberConstants; import com.sdm.common.log.CoreLogger; import com.sdm.data.config.MinioConfig; import com.sdm.data.service.IMinioService; @@ -10,9 +11,12 @@ import io.minio.http.Method; import io.minio.messages.DeleteError; import io.minio.messages.DeleteObject; import io.minio.messages.Item; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @@ -21,6 +25,10 @@ import javax.annotation.PostConstruct; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.DecimalFormat; @@ -36,6 +44,8 @@ public class MinioService implements IMinioService { private final MinioClient minioClient; private final MinioConfig minioConfig; + // 可配置缓冲区大小) 16KB + private static final int DEFAULT_BUFFER_SIZE = 16384; @Autowired public MinioService(MinioClient minioClient, MinioConfig minioConfig) { @@ -329,6 +339,54 @@ public class MinioService implements IMinioService { return downloadFile(objectName, minioConfig.getSpdmBucket()); } + @Override + public void downloadFile(String objectName, HttpServletResponse response,String encodedFileName,Boolean preview,String contentType) throws Exception { + downloadFile(objectName, minioConfig.getSpdmBucket(),response,encodedFileName,preview,contentType); + } + + // 流式响应的返回 + public void downloadFile(String objectName, String bucketName, HttpServletResponse response,String encodedFileName,Boolean preview, String contentType) throws Exception { + // 1. 获取文件元数据(大小) + StatObjectResponse stat = minioClient.statObject( + StatObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build()); + long fileSize = stat.size(); + // 1. 获取MinIO的流式数据(使用try-with-resources确保资源释放) + try (InputStream stream = minioClient.getObject( + GetObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build()); + ReadableByteChannel inChannel = Channels.newChannel(stream); + WritableByteChannel outChannel = Channels.newChannel(response.getOutputStream())) { + String mediaType = org.apache.commons.lang3.StringUtils.isBlank(contentType) ? + MediaType.APPLICATION_OCTET_STREAM_VALUE: contentType ; + response.setContentType(mediaType); + String contentDisposition = preview ? + "inline; filename=\"" + encodedFileName + "\"" :"attachment; filename=\"" + encodedFileName + "\""; + response.setHeader(HttpHeaders.CONTENT_DISPOSITION,contentDisposition); + response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); + response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize)); + // 3. 使用NIO通道+ByteBuffer(缓冲区,可根据性能调整) + ByteBuffer buffer = ByteBuffer.allocateDirect(minioConfig.getDirectMemory()<= NumberConstants.ZERO ? + DEFAULT_BUFFER_SIZE:minioConfig.getDirectMemory()); + while (inChannel.read(buffer) != -1) { + // 切换为读模式 + buffer.flip(); + outChannel.write(buffer); + buffer.compact(); + } + buffer.flip(); + while (buffer.hasRemaining()) { + outChannel.write(buffer); + } + response.flushBuffer(); + } + } + + @Override public InputStream getMinioInputStream(String objectName) { return getMinioInputStream(objectName, minioConfig.getSpdmBucket()); diff --git a/data/src/main/resources/application-dev.yml b/data/src/main/resources/application-dev.yml index 7f6f38b0..1b3d9a15 100644 --- a/data/src/main/resources/application-dev.yml +++ b/data/src/main/resources/application-dev.yml @@ -113,4 +113,5 @@ minio: secret-key: minioadmin secure: false secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问) - spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限) \ No newline at end of file + spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限) + directMemory: 16384 # 16kb \ No newline at end of file diff --git a/data/src/main/resources/application-local.yml b/data/src/main/resources/application-local.yml index a8651678..379d4052 100644 --- a/data/src/main/resources/application-local.yml +++ b/data/src/main/resources/application-local.yml @@ -102,6 +102,7 @@ minio: secure: false secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问) spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限) + directMemory: 16384 # 16kb management: diff --git a/data/src/main/resources/application-prod.yml b/data/src/main/resources/application-prod.yml index e31e2b8e..205c8970 100644 --- a/data/src/main/resources/application-prod.yml +++ b/data/src/main/resources/application-prod.yml @@ -154,4 +154,5 @@ minio: secret-key: minioadmin secure: false secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问) - spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限) \ No newline at end of file + spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限) + directMemory: 16384 # 16kb \ No newline at end of file diff --git a/data/src/main/resources/application-test.yml b/data/src/main/resources/application-test.yml index 263cf8d3..e61b476c 100644 --- a/data/src/main/resources/application-test.yml +++ b/data/src/main/resources/application-test.yml @@ -152,4 +152,5 @@ minio: secret-key: minioadmin secure: false secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问) - spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限) \ No newline at end of file + spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限) + directMemory: 16384 # 16kb \ No newline at end of file