Prechádzať zdrojové kódy

feat: 对oss文件操作的实现,根据配置切换local|minio,默认local

Acechengui 1 rok pred
rodič
commit
e0d6c13b6a

+ 7 - 0
DataRoom/dataroom-core/pom.xml

@@ -65,5 +65,12 @@
             <artifactId>jackson-datatype-json-org</artifactId>
             <version>${jackson.version.core}</version>
         </dependency>
+
+        <!-- Minio -->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>${minio.version}</version>
+        </dependency>
     </dependencies>
 </project>

+ 94 - 0
DataRoom/dataroom-core/src/main/java/com/gccloud/dataroom/core/config/MinioConfig.java

@@ -0,0 +1,94 @@
+package com.gccloud.dataroom.core.config;
+
+import io.minio.MinioClient;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Minio 配置信息
+ *
+ * @author Acechengui
+ */
+@Configuration
+@ConfigurationProperties(prefix = "minio")
+public class MinioConfig
+{
+    /**
+     * 服务地址
+     */
+    private String url;
+
+    /**
+     * 用户名
+     */
+    private String accessKey;
+
+    /**
+     * 密码
+     */
+    private String secretKey;
+
+    /**
+     * 存储桶名称
+     */
+    private String bucketName;
+
+    public String getUrl()
+    {
+        return url;
+    }
+
+    public void setUrl(String url)
+    {
+        this.url = url;
+    }
+
+    public String getAccessKey()
+    {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey)
+    {
+        this.accessKey = accessKey;
+    }
+
+    public String getSecretKey()
+    {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey)
+    {
+        this.secretKey = secretKey;
+    }
+
+    public String getBucketName()
+    {
+        return bucketName;
+    }
+
+    public void setBucketName(String bucketName)
+    {
+        this.bucketName = bucketName;
+    }
+
+    @Bean
+    public MinioClient getMinioClient() {
+        if (StringUtils.isEmpty(url) || StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(secretKey)) {
+            // 如果未配置Minio相关的配置项,则使用本地文件存储
+            // 或者返回一个默认的MinioClient实例,用于本地文件存储
+            return createDefaultMinioClient();
+        }
+        return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
+    }
+
+    private MinioClient createDefaultMinioClient() {
+        return MinioClient.builder()
+                .endpoint("http://minio.example.com")
+                .credentials("accessKey", "secretKey")
+                .build();
+    }
+}

+ 20 - 0
DataRoom/dataroom-core/src/main/java/com/gccloud/dataroom/core/module/file/enums/FileUploadType.java

@@ -0,0 +1,20 @@
+package com.gccloud.dataroom.core.module.file.enums;
+
+/**
+ * @Description Description
+ * @Author Acechengui
+ * @Date Created in 2023-10-14
+ */
+public enum FileUploadType {
+    LOCAL("local"),
+    MINIO("minio");
+
+    // 以上是枚举的成员,必须先定义,而且使用分号结束
+    private final String value;
+    FileUploadType(String value) {
+        this.value = value;
+    }
+    public String getValue() {
+        return value;
+    }
+}

+ 57 - 0
DataRoom/dataroom-core/src/main/java/com/gccloud/dataroom/core/module/file/service/FileOperationStrategy.java

@@ -0,0 +1,57 @@
+package com.gccloud.dataroom.core.module.file.service;
+
+import com.gccloud.dataroom.core.module.file.entity.DataRoomFileEntity;
+import com.gccloud.dataroom.core.module.file.enums.FileUploadType;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 文件操作策略类
+ * @author Acechengui
+ */
+@Service
+@Slf4j
+public class FileOperationStrategy {
+    private final IDataRoomOssService dataRoomOssService;
+
+    public FileOperationStrategy(IDataRoomOssService  localFileService, IDataRoomOssService  minioFileService) {
+        this.dataRoomOssService = localFileService != null ? localFileService : minioFileService;
+    }
+
+    public static class LocalFileCondition implements Condition {
+        @Override
+        public boolean matches(ConditionContext context, @NotNull AnnotatedTypeMetadata metadata) {
+            String uploadType = context.getEnvironment().getProperty("gc.starter.file.uploadType");
+            return FileUploadType.LOCAL.getValue().equalsIgnoreCase(uploadType) ||
+                   !FileUploadType.MINIO.getValue().equalsIgnoreCase(uploadType);
+        }
+    }
+
+    public static class MinioFileCondition implements Condition {
+        @Override
+        public boolean matches(ConditionContext context, @NotNull AnnotatedTypeMetadata metadata) {
+            String uploadType = context.getEnvironment().getProperty("gc.starter.file.uploadType");
+            return FileUploadType.MINIO.getValue().equalsIgnoreCase(uploadType);
+        }
+    }
+
+    public DataRoomFileEntity upload(MultipartFile file, DataRoomFileEntity entity, HttpServletResponse response, HttpServletRequest request) {
+        return dataRoomOssService.upload(file, entity, response, request);
+    }
+
+    public void download(String fileId, HttpServletResponse response, HttpServletRequest request) {
+        dataRoomOssService.download(fileId, response, request);
+    }
+
+    public void delete(String fileId) {
+        dataRoomOssService.delete(fileId);
+    }
+}

+ 4 - 1
DataRoom/dataroom-core/src/main/java/com/gccloud/dataroom/core/module/file/service/impl/DataRoomLocalFileServiceImpl.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.gccloud.dataroom.core.config.DataRoomConfig;
 import com.gccloud.dataroom.core.config.bean.FileConfig;
 import com.gccloud.dataroom.core.module.file.entity.DataRoomFileEntity;
+import com.gccloud.dataroom.core.module.file.service.FileOperationStrategy;
 import com.gccloud.dataroom.core.module.file.service.IDataRoomFileService;
 import com.gccloud.dataroom.core.module.file.service.IDataRoomOssService;
 import com.gccloud.common.exception.GlobalException;
@@ -12,6 +13,7 @@ import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Conditional;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
@@ -28,7 +30,8 @@ import java.net.URLEncoder;
 /**
  * 文件管理
  */
-@Service
+@Conditional(FileOperationStrategy.LocalFileCondition.class)
+@Service("localFileService")
 @Slf4j
 @ConditionalOnProperty(prefix = "gc.starter.dataroom.component", name = "IDataRoomOssService", havingValue = "DataRoomLocalFileServiceImpl", matchIfMissing = true)
 public class DataRoomLocalFileServiceImpl implements IDataRoomOssService {

+ 142 - 0
DataRoom/dataroom-core/src/main/java/com/gccloud/dataroom/core/module/file/service/impl/DataRoomMinioServiceImpl.java

@@ -0,0 +1,142 @@
+package com.gccloud.dataroom.core.module.file.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.gccloud.common.exception.GlobalException;
+import com.gccloud.dataroom.core.config.DataRoomConfig;
+import com.gccloud.dataroom.core.config.MinioConfig;
+import com.gccloud.dataroom.core.config.bean.FileConfig;
+import com.gccloud.dataroom.core.module.file.entity.DataRoomFileEntity;
+import com.gccloud.dataroom.core.module.file.service.FileOperationStrategy;
+import com.gccloud.dataroom.core.module.file.service.IDataRoomFileService;
+import com.gccloud.dataroom.core.module.file.service.IDataRoomOssService;
+import com.gccloud.dataroom.core.utils.MinioFileInterface;
+import io.minio.MinioClient;
+import io.minio.PutObjectArgs;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Minio文件管理
+ *
+ * @author Acechengui
+ */
+@Service("minioFileService")
+@Conditional(FileOperationStrategy.MinioFileCondition.class)
+@Slf4j
+public class DataRoomMinioServiceImpl implements IDataRoomOssService {
+
+    @Resource
+    private MinioConfig minioConfig;
+
+    @Resource
+    private MinioClient minioclient;
+
+    @Resource
+    private DataRoomConfig bigScreenConfig;
+
+    @Resource
+    private IDataRoomFileService sysFileService;
+    @Resource
+    private MinioFileInterface minioFileInterface;
+
+    /**
+     * 上传文件
+     *
+     */
+    @SneakyThrows
+    @Override
+    public DataRoomFileEntity upload(MultipartFile file, DataRoomFileEntity fileEntity, HttpServletResponse response, HttpServletRequest request) {
+        String originalFilename = file.getOriginalFilename();
+        // 提取文件后缀名
+        String extension = FilenameUtils.getExtension(originalFilename);
+        FileConfig fileConfig = bigScreenConfig.getFile();
+        if (!fileConfig.getAllowedFileExtensionName().contains("*") && !fileConfig.getAllowedFileExtensionName().contains(extension)) {
+            log.error("不支持 {} 文件类型",extension);
+            throw new GlobalException("不支持的文件类型");
+        }
+        String module = request.getParameter("module");
+        // 不同业务自己控制
+        if (StringUtils.isBlank(module)) {
+            fileEntity.setModule("other");
+        }
+        // 重命名
+        String newFileName = IdWorker.getIdStr() + "." + extension;
+        // 组装路径:获取当前日期并格式化为"yyyy/mm/dd"格式的字符串
+        String filePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + newFileName;
+        InputStream inputStream = file.getInputStream();
+        PutObjectArgs args = PutObjectArgs.builder()
+                    .bucket(minioConfig.getBucketName())
+                    .object(filePath)
+                    .stream(inputStream, file.getSize(), -1)
+                    .contentType(file.getContentType())
+                    .build();
+        minioclient.putObject(args);
+
+        String url = minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + filePath;
+        fileEntity.setOriginalName(originalFilename);
+        fileEntity.setNewName(newFileName);
+        fileEntity.setPath(filePath);
+        fileEntity.setSize(file.getSize());
+        fileEntity.setExtension(extension);
+        fileEntity.setUrl(url);
+        return fileEntity;
+    }
+
+    /**
+     * 下载文件
+     *
+     */
+    @SneakyThrows
+    @Override
+    public void download(String fileId, HttpServletResponse response, HttpServletRequest request) {
+        DataRoomFileEntity fileEntity = sysFileService.getById(fileId);
+        if (fileEntity == null) {
+            response.setStatus(HttpStatus.NOT_FOUND.value());
+            log.error("下载的文件不存在");
+            return;
+        }
+        response.setContentType("application/x-msdownload");
+        response.setContentType("multipart/form-data");
+        // 不设置前端无法从header获取文件名
+        response.setHeader("Access-Control-Expose-Headers", "filename");
+        response.setHeader("filename", URLEncoder.encode(fileEntity.getOriginalName(), "UTF-8"));
+        // 解决下载的文件不携带后缀
+        response.setHeader("Content-Disposition", "attachment;fileName="+URLEncoder.encode(fileEntity.getOriginalName(), "UTF-8"));
+        try {
+            InputStream is = minioFileInterface.download(fileEntity.getPath());
+            IOUtils.copy(is, response.getOutputStream());
+            is.close();
+            response.getOutputStream().close();
+        } catch (Exception e) {
+            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+            log.error(String.format("下载文件%s失败", fileEntity.getOriginalName()));
+        } finally {
+            sysFileService.updateDownloadCount(1, fileId);
+        }
+    }
+
+    /**
+     * 删除文件
+     */
+    @SneakyThrows
+    @Override
+    public void delete(String fileId) {
+        String path = sysFileService.getById(fileId).getPath();
+        minioFileInterface.deleteObject(path.substring(path.indexOf(minioConfig.getBucketName())+minioConfig.getBucketName().length()+1));
+    }
+}

+ 98 - 0
DataRoom/dataroom-core/src/main/java/com/gccloud/dataroom/core/utils/MinioFileInterface.java

@@ -0,0 +1,98 @@
+package com.gccloud.dataroom.core.utils;
+
+import com.gccloud.dataroom.core.config.MinioConfig;
+import io.minio.BucketExistsArgs;
+import io.minio.GetObjectArgs;
+import io.minio.GetPresignedObjectUrlArgs;
+import io.minio.MakeBucketArgs;
+import io.minio.MinioClient;
+import io.minio.RemoveObjectArgs;
+import io.minio.errors.ErrorResponseException;
+import io.minio.errors.InsufficientDataException;
+import io.minio.errors.InternalException;
+import io.minio.errors.InvalidResponseException;
+import io.minio.errors.ServerException;
+import io.minio.errors.XmlParserException;
+import io.minio.http.Method;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Minio快捷操作工具类
+ *
+ * @author Acechengui
+ */
+@Component
+public class MinioFileInterface {
+    @Autowired
+    private MinioConfig minioConfig;
+
+    private MinioClient init() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
+        MinioClient build = MinioClient.builder().endpoint(minioConfig.getUrl()).credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build();
+        //判断bucket是否存在,没有就创建
+        boolean found =build.bucketExists(BucketExistsArgs.builder().bucket(minioConfig.getBucketName()).build());
+        if (!found) {
+            build.makeBucket(MakeBucketArgs.builder().bucket(minioConfig.getBucketName()).build());
+        }
+        return build;
+    }
+
+    /**
+     * 删除文件
+     * @param objectName 文件名(路径)
+     */
+    public void deleteObject(String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
+        MinioClient minioClient = init();
+        //删除文件时,如果对应文件夹下的文件已删除完,文件夹会自动删除
+        minioClient.removeObject(RemoveObjectArgs.builder()
+                .bucket(minioConfig.getBucketName())
+                .object(objectName)
+                //.versionId("my-versionid") //还可以删除指定版本号的对象
+                .build());
+    }
+
+    /**
+     * 下载文件
+     * @param objectName 文件名(路径)
+     */
+    public InputStream download(String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
+        MinioClient minioClient = init();
+        // 获取文件输入流
+        return minioClient.getObject(
+                GetObjectArgs.builder()
+                        .bucket(minioConfig.getBucketName())
+                        .object(objectName)
+                        .build());
+    }
+
+    /**
+     * 生成一个GET请求的分享链接。
+     * 失效时间默认是7天。
+     *
+     * @param bucketName 存储桶名称
+     * @param objectName 存储桶里的对象名称
+     * @param expires    失效时间(以秒为单位),默认是7天,不得大于七天
+     * @return url
+     */
+    public String presignedGetObject(String bucketName, String objectName, Integer expires) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
+        String url = "";
+        if (expires == null){
+            expires = 604800;
+        }
+        GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
+                .method(Method.GET)
+                .bucket(bucketName)
+                .object(objectName)
+                // .expiry(expires)
+                .build();
+        MinioClient minioClient = init();
+        url = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
+        return url;
+    }
+
+}

+ 13 - 0
DataRoom/dataroom-server/src/main/resources/application.yml

@@ -33,6 +33,19 @@ spring:
     resources:
       static-locations: classpath:/static/,classpath:/META-INF/resources/,classpath:/META-INF/resources/webjars/,file:${gc.starter.file.basePath}
 
+gc:
+  starter:
+    file:
+      # minio | local
+      uploadType: local
+
+# Minio配置
+#minio:
+#  url: http://192.168.20.98:9000
+#  accessKey: admin
+#  secretKey: 123456
+#  bucketName: test
+
 mybatis-plus:
   # mybatis plus xml配置文件扫描,多个通过分号隔开
   mapper-locations: classpath*:mapper/**/*.xml

+ 1 - 0
DataRoom/pom.xml

@@ -44,6 +44,7 @@
         <swagger-models.version>1.5.21</swagger-models.version>
         <jackson.version.core>2.13.3</jackson.version.core>
         <json.version>20220320</json.version>
+        <minio.version>8.2.2</minio.version>
         <dataset.core.version>2.0.0.RELEASE</dataset.core.version>
         <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
     </properties>