Springboot---整合对象储存服务MinIO

OSS

「OSS」的英文全称是Object Storage Service,翻译成中文就是「对象存储服务」,官方一点解释就是对象存储是一种使用HTTP API存储和检索非结构化数据和元数据对象的工具。

白话文解释就是将系统所要用的文件上传到云硬盘上,该云硬盘提供了文件下载、上传等一列服务,这样的服务以及技术可以统称为OSS,业内提供OSS服务的厂商很多,知名常用且成规模的七牛云等。一般我们做网站还是服务都希望将文件放在一个指定的服务器,并且更好的管理,并且动静分离更好的提升软件性能我之间写过如何springboot整合七牛云,感兴趣的可以了解一下,但是由于七牛云需要自己购买他的服务器,对于学生做项目不友好,所以今天介绍的是开源可以自己搭建的oss服务

Minio

介绍:
MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储。 它是与 Amazon S3 云存储服务兼容的 API。 使用 MinIO 为机器学习、分析和应用程序数据工作负载构建高性能基础架构。Minio官网

搭建(DOCKER)

docker的好处不用多说了,最主要的是服务的安装与卸载很方便,这里也提供docker的学习笔记,那么现在开始搭建

拉取镜像

使用之间可以先查看下各个版本

 docker pull minio/minio

一般选择star最多的版本

docker pull  minion

在虚拟机或者云服务创建一个文件夹,并且包含一个数据,一个配置文件目录做挂载
在这里插入图片描述运行

docker run -p 9000:9000 -p 9090:9090 \
    --name minio \
    -d --restart=always \
    -e "MINIO_ACCESS_KEY=minioadmin" \
    -e "MINIO_SECRET_KEY=tskradmin" \
    -v /home/docker/minio/data:/data \
    -v /home/docker/minio/config:/root/.minio \
    minio/minio:latest server /data

说明:
-p 9000:9000 -p 9090:9090:将容器的9000端口映射到主机的9000端口,以及将容器的9090端口映射到主机的9090端口。

–name minio:给容器命名为"minio",以便后续管理和操作。

-d --restart=always:在后台模式下运行容器,并设置容器失败或主机重启后自动重启。

-e “MINIO_ACCESS_KEY=minioadmin” -e “MINIO_SECRET_KEY=tskradmin”:设置MinIO的访问密钥和秘钥。请确保你使用的是安全的访问密钥和秘钥。

-v /home/docker/minio/data:/data:挂载主机上的 /home/docker/minio/data 目录到容器内的 /data 目录,用于持久化存储MinIO数据。

-v /home/docker/minio/config:/root/.minio:挂载主机上的 /home/docker/minio/config 目录到容器内的 /root/.minio 目录,用于存储MinIO的配置文件。

minio/minio:latest server /data:指定使用的MinIO镜像及其命令。server /data 表示以服务器模式运行MinIO,并指定数据存储目录为 /data。

注意
目前24年我再次使用的时候 发现新版minio启动的时候需要固定9000端口 不然默认采用动态端口无法访问

docker run -p 9000:9000 -p 9090:9090 \
    --name minio \
    -d --restart=always \
    -e "MINIO_ACCESS_KEY=minioadmin" \
    -e "MINIO_SECRET_KEY=tskradmin" \
    -v /home/docker/minio/data:/data \
    -v /home/docker/minio/config:/root/.minio \
    minio/minio:latest server /data \
    --console-address ":9090" --address ":9000"

固定端口: --console-address “:9090” --address “:9000”

在这里插入图片描述
账号密码是配置文件中指定的

  • MINIO_ACCESS_KEY=账户
  • MINIO_SECRET_KEY=密码

对象储存服务中,数据是按照bucket 桶来的,所以先新建一个桶在这里插入图片描述
自此服务端部署完成

java客户端拉取依赖

        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>${minio.version}</version>
        </dependency>

我的是8.5.1

java客户端处死话:

MinioClient minioClient = MinioClient.builder()
    .endpoint("http://192.168.249.132:9001")
    .credentials("minioadmin", "minioadmin")
    .build();

核心api

上传文件

PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .bucket("bucket-name") // 桶的名称
    .object("object-name") // 对象的名称(文件名)
    .stream(inputStream, inputStream.available(), -1) // 文件输入流
    .contentType("application/octet-stream") // 文件的 MIME 类型
    .build();

minioClient.putObject(putObjectArgs);

  • bucket:文件存储桶的名称。
  • object:对象(文件名)的名称。
  • stream:文件输入流。
  • size:对象的字节大小。如果未知,设置为 -1。
  • contentType:文件的 MIME 类型。

java测试上传文件

  @Test
    @DisplayName("测试minio上传下载")
    public void testMn() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        client = MinioClient.builder().credentials("minioadmin", "minioadmin")
                .endpoint("http://localhost:9001").build();
        FileInputStream inputStream;
        PutObjectArgs putObjectArgs;
//        读取文件转换为输入流
      
            inputStream = new FileInputStream("D:\\list.html");

//        上传一个对象 需要使用聚合对象包装在里面()

            putObjectArgs = PutObjectArgs.builder()
                    .object("list.html")//文件名
                    .contentType("text/html")//文件类型
                    .bucket("leadnews")//区域名
                    .stream(inputStream, inputStream.available(), -1).build();//转换为流进行传输
            client.putObject(putObjectArgs);
       
    }

这样就可以实现上传了,现在进行封装,
便于使用
建立配置属性类便于动态注入数据

@Data
@ConfigurationProperties(prefix = "minio")  // 文件上传 配置前缀file.oss
public class MinIOConfigProperties implements Serializable {

    private String accessKey;
    private String secretKey;
    private String bucket;
    private String endpoint;
    private String readPath;
}

配置文件

minio:
  accessKey: minioadmin
  secretKey: minioadmin
  bucket: leadnews
  endpoint: http://192.168.249.132:9001
  readPath: http://192.168.249.132:9001

注入配置类实列化



@Data
@Configuration
@EnableConfigurationProperties({MinIOConfigProperties.class})
//当引入FileStorageService接口时
@ConditionalOnClass(FileStorageService.class)
public class MinIOConfig {

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    @Bean
    public MinioClient buildMinioClient() {
        return MinioClient
                .builder()
                .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
                .endpoint(minIOConfigProperties.getEndpoint())
                .build();
    }
}
下载api
InputStream inputStream = minioClient.getObject(
    GetObjectArgs.builder()
        .bucket("bucket-name")
        .object("object-name")
        .build()
);

参数:

  • bucket:检索对象的桶的名称。
  • object:要检索的对象的名称。
删除接口
minioClient.removeObject(
    RemoveObjectArgs.builder()
        .bucket("bucket-name")
        .object("object-name")
        .build()
);
  • bucket:删除对象的桶的名称。
  • object:要删除的对象的名称。
各个接口的单元测试
@Slf4j
@SpringBootTest(classes = MdmAdminApplication.class)
public class MinioStorageTest {

    private MinioClient minioClient;

    private static final String ENDPOINT = "http://localhost:9000";
    private static final String ACCESS_KEY = "minioadmin";
    private static final String SECRET_KEY = "tskradmin";
    private static final String BUCKET_NAME = "apks";
    private static final String CONTENT_TYPE = "application/vnd.android.package-archive";
    private static final String SEPARATOR = "/";

    @BeforeEach
    public void setUp() {
        minioClient = MinioClient.builder()
                .endpoint(ENDPOINT)
                .credentials(ACCESS_KEY, SECRET_KEY)
                .build();
    }

    @Test
    @DisplayName("测试minio客户端 上传apk文件")
    public void testMinioClient() {
//       String FILE_PATH = "E:\\APKS\\da_1701653930625.apk";
       String FILE_PATH = "C:\\Users\\侯\\Desktop\\dev_work.zip";
        assertDoesNotThrow(() -> {
            try (FileInputStream inputStream = new FileInputStream(FILE_PATH)) {
                File file = new File(FILE_PATH);
                PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                        .object("test"+ "/" +file.getName()) //如果需要加目录就是手动拼接
//                        .object(file.getName())
                        .contentType(CONTENT_TYPE)
                        .bucket(BUCKET_NAME)
                        .stream(inputStream, inputStream.available(), -1)
                        .build();

                minioClient.putObject(putObjectArgs);
                System.out.println("文件上传成功访问路径...."+ENDPOINT +"/"+BUCKET_NAME+"/"+"test"+ "/" +file.getName());
            }
        });
    }
    @Test
    @DisplayName("测试minio客户端 上传图片文件")
    public void testMinioImgClient() {
        assertDoesNotThrow(() -> {
            //上传后的名门
            String FILE_PATH = "D:\\3.png";
            try (FileInputStream inputStream = new FileInputStream("D:\\2.png")) {
                File file = new File(FILE_PATH);
                String fileName = file.getName();
                String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
                String contentType;
                String directory;
                switch (fileExtension) {
                    case "png":
                        contentType = "image/png";
                        directory = "img";
                        break;
                    case "jpg":
                    case "jpeg":
                        contentType = "image/jpeg";
                        directory = "img";
                        break;
                    case "gif":
                        contentType = "image/gif";
                        directory = "img";
                        break;
                    default:
                        throw new IllegalArgumentException("Unsupported file type: " + fileExtension);
                }
                String objectName = directory + "/" + fileName;
                PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                        .object(objectName)
                        .contentType(contentType)
                        .bucket(BUCKET_NAME)
                        .stream(inputStream, inputStream.available(), -1)
                        .build();

                minioClient.putObject(putObjectArgs);
                System.out.println("文件上传成功访问路径.... " + ENDPOINT + "/" + BUCKET_NAME + "/" + objectName);
            }
        });
    }
    @Test
    @DisplayName("测试minio客户端 下载文件")
    public void testMinioClientDownload() {
        String pathUrl = "http://localhost:9000/apks/img/2.png";
        String key = pathUrl.replace(ENDPOINT + SEPARATOR, "");
        int index = key.indexOf(SEPARATOR);
        String bucket = key.substring(0, index);
        String filePath = key.substring(index + 1);
        //下载到本地的时候不同系统 路径分隔符不同 这里使用File.separator
        String localFilePath = "E:"+File.separator+"ALI"+File.separator + new File(filePath).getName(); // 指定本地保存路径

        try (InputStream inputStream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucket)
                        .object(filePath)
                        .build());
             FileOutputStream outputStream = new FileOutputStream(localFilePath)) {
            byte[] buf = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buf)) > 0) {
                outputStream.write(buf, 0, bytesRead);
            }
            System.out.println("文件下载成功, 保存路径: " + localFilePath);
        } catch (Exception e) {
            log.error("minio 下载文件出错。URL: {}", pathUrl, e);
            throw new RuntimeException("从MinIO下载文件失败", e);
        }
    }

    @Test
    @DisplayName("测试minio客户端 删除文件")
    public void testMinioClientRemove() {
        String fileName = "3.png";

        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(BUCKET_NAME)
                            .object("img" + SEPARATOR + fileName)
                            .build());
            System.out.println("文件删除成功: " + fileName);
        } catch (Exception e) {
            log.error("minio 删除文件出错。URL: {}", fileName, e);
            throw new RuntimeException("从MinIO删除文件失败", e);
        }
    }

}

注意只有给你的对应的桶权限才可以再游览器访问文件

在这里插入图片描述
是一个json文件 可以配置那些buckt公开可以访问

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "*"
                ]
            },
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::apks/*"
            ]
        }
    ]
}

我这里指定的是apk,并且这些上传后的地址就是 minio服务地址 + 文件名地址(如果想要目录就上传时候拼接"/")

有一个好处
对于apk,压缩包等文件
是的,对于浏览器无法直接打开的文件类型,如 zip、tar、apk、iso 等,浏览器通常会自动触发下载行为。这是因为这些文件类型的 Content-Type 指示浏览器它们是二进制文件,无法在浏览器中直接展示。上传对象服务器后的地址,在游览器中直接打开就会触发行为

如果是手写下载接口还需要再响应体中写流
    @GetMapping("/download/apk/{dir}/{filename}")
    public void downloadApk(HttpServletResponse response, @PathVariable("dir") String dir, @PathVariable("filename") String filename) throws IOException {
      //夸文件的分割符号
        File apkFile = new File(storageService.properties.getLocal().getPath() + File.separator + dir + File.separator + filename);
//        String apkUrl =storageService.properties.getLocal().getUrl()+dir+"/"+filename;
//        File apkFile = new File("E:\\APKS\\da_1701653930625.apk");
        if (!apkFile.exists()) {
            response.sendError(404, "安装包不存在");
        }
        response.setContentType("application/vnd.android.package-archive");
        response.setHeader("Content-Disposition", "attachment; filename=app.apk");
        FileUtil.writeToStream(apkFile, response.getOutputStream());
    }

各个api封装为接口

文件处理接口

public interface FileStorageService {


    /**
     *  上传图片文件
     * @param prefix  文件前缀
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  文件全路径
     */
    public String uploadImgFile(String prefix, String filename,InputStream inputStream);

    /**
     *  上传html文件
     * @param prefix  文件前缀
     * @param filename   文件名
     * @param inputStream  文件流
     * @return  文件全路径
     */
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);

    /**
     * 删除文件
     * @param pathUrl  文件全路径
     */
    public void delete(String pathUrl);

    /**
     * 下载文件
     * @param pathUrl  文件全路径
     * @return
     *
     */
    public byte[]  downLoadFile(String pathUrl);

}

imp

@Slf4j
@EnableConfigurationProperties(MinIOConfigProperties.class)
@Import(MinIOConfig.class)
public class MinIOFileStorageService implements FileStorageService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    private final static String separator = "/";

    /**
     * @param dirPath
     * @param filename  yyyy/mm/dd/file.jpg
     * @return
     */
    public String builderFilePath(String dirPath,String filename) {
        StringBuilder stringBuilder = new StringBuilder(50);
        if(!StringUtils.isEmpty(dirPath)){
            stringBuilder.append(dirPath).append(separator);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        String todayStr = sdf.format(new Date());
        stringBuilder.append(todayStr).append(separator);
        stringBuilder.append(filename);
        return stringBuilder.toString();
    }

    /**
     *  上传图片文件
     * @param prefix  文件前缀
     * @param filename  文件名
     * @param inputStream 文件流
     * @return  文件全路径
     */
    @Override
    public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType("image/jpg")
                    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator+minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString();
        }catch (Exception ex){
            log.error("minio put file error.",ex);
            throw new RuntimeException("上传文件失败");
        }
    }

    /**
     *  上传html文件
     * @param prefix  文件前缀
     * @param filename   文件名
     * @param inputStream  文件流
     * @return  文件全路径
     */
    @Override
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType("text/html")
                    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator+minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString();
        }catch (Exception ex){
            log.error("minio put file error.",ex);
            ex.printStackTrace();
            throw new RuntimeException("上传文件失败");
        }
    }

    /**
     * 删除文件
     * @param pathUrl  文件全路径
     */
    @Override
    public void delete(String pathUrl) {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
        int index = key.indexOf(separator);
        String bucket = key.substring(0,index);
        String filePath = key.substring(index+1);
        // 删除Objects
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
        try {
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            log.error("minio remove file error.  pathUrl:{}",pathUrl);
            e.printStackTrace();
        }
    }


    /**
     * 下载文件
     * @param pathUrl  文件全路径
     * @return  文件流
     *
     */
    @Override
    public byte[] downLoadFile(String pathUrl)  {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
        int index = key.indexOf(separator);
        String bucket = key.substring(0,index);
        String filePath = key.substring(index+1);
        InputStream inputStream = null;
        try {
            inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
        } catch (Exception e) {
            log.error("minio down file error.  pathUrl:{}",pathUrl);
            e.printStackTrace();
        }

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while (true) {
            try {
                if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
            } catch (IOException e) {
                e.printStackTrace();
            }
            byteArrayOutputStream.write(buff, 0, rc);
        }
        return byteArrayOutputStream.toByteArray();
    }
}

封装为模块

如果是springcloud分布式业务,文件处理可以封装为模块给其他模块使用
新建模块

    <modules>
        <module>heima-leadnews-basic</module>  <modules>
        <module>heima-file-starter</module>
    </modules>

    </modules>

在项目新建模块,依赖如上配置yaml文件不写,因为是做成模块给其他模块使用的
在该模块的resource目录新建meta-inf元数据配置文件
在这里插入图片描述
内容

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.file.service.impl.MinIOFileStorageService

这个配置的作用是为了将一个模块(或库)的功能引入到应用程序中,以便在应用程序的其他部分中可以使用这个模块提供的功能。这个模块可能包含一些服务、组件或类,你可以使用@Autowired等注解来注入它们,以便在应用程序中使用。
通常情况下,如果你没有一个显式的启动类(Spring Boot应用的入口类),或者你不想将这个模块的功能包含在应用程序的启动类中,你可以使用这种方式,通过spring.factories配置来自动启用这个模块。
这个模块的配置会在应用程序启动时被Spring Boot自动加载和初始化,从而使你可以在应用程序中使用它提供的功能,而不需要手动实例化或配置相关的Bean。这种方式可以使应用程序的模块化更好,遵循了依赖注入和解耦的原则。

在需要使用的模块导入该模块即可

进行优化

可以存储的对象有很多种 所以可以设置一个校验方法根据文件后缀判断上传类型和oss对应目录


    private static final String CONTENT_TYPE = "contentType";
    private static final String REAL_PATH = "realPath";

    public HashMap<String, String> validateFileType(String fileName) {
        HashMap<String, String> hashMap = new HashMap<>();
        String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        String contentType;
        String directory;

        switch (fileExtension) {
            case "png":
                contentType = "image/png";
                directory = "img";
                break;
            case "jpg":
            case "jpeg":
                contentType = "image/jpeg";
                directory = "img";
                break;
            case "gif":
                contentType = "image/gif";
                directory = "img";
                break;
            case "apk":
                contentType = "application/vnd.android.package-archive";
                directory = "apk";
                break;
            case "zip":
                contentType = "application/zip";
                directory = "archive";
                break;
            case "tar":
                contentType = "application/x-tar";
                directory = "archive";
                break;
            case "pdf":
                contentType = "application/pdf";
                directory = "documents";
                break;
            case "doc":
            case "docx":
                contentType = "application/msword";
                directory = "documents";
                break;
            case "xls":
            case "xlsx":
                contentType = "application/vnd.ms-excel";
                directory = "documents";
                break;
            case "ppt":
            case "pptx":
                contentType = "application/vnd.ms-powerpoint";
                directory = "documents";
                break;
            case "txt":
                contentType = "text/plain";
                directory = "documents";
                break;
            case "mp3":
                contentType = "audio/mpeg";
                directory = "audio";
                break;
            case "wav":
                contentType = "audio/wav";
                directory = "audio";
                break;
            case "mp4":
                contentType = "video/mp4";
                directory = "video";
                break;
            case "avi":
                contentType = "video/x-msvideo";
                directory = "video";
                break;
            case "mkv":
                contentType = "video/x-matroska";
                directory = "video";
                break;
            default:
                throw new IllegalArgumentException("Unsupported file type: " + fileExtension);
        }

        hashMap.put(CONTENT_TYPE, contentType);
        hashMap.put(REAL_PATH, directory);
        return hashMap;
    }

但是这样上传还是不够通用, 因为是根据文件名来的,而DDD架构中服务层只关心服务 所以不能接收仅仅局限于Springboot框架的MultipartFile file类型

public String upload(MultipartFile file, String path) {
        try {
            
            //如果BucketName不存在,则创建
            log.info("Uploading:{}",properties.getMinio().getBucketName());
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(properties.getMinio().getBucketName()).build());
            if (!found) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getMinio().getBucketName()).build());
            }

            String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
            Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(path);
            if (mediaType.isPresent()) {
                contentType = mediaType.get().toString();
            }
            log.info("contentType:{}",contentType);
            InputStream inputStream = file.getInputStream();
            HashMap<String, String> map = validateFileType(file.getName());
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(properties.getMinio().getBucketName())
                            .contentType(contentType)
                            .object(path)
                            .stream(inputStream, inputStream.available(), -1)
                            .build()
            );

        } catch (Exception e) {
            try {
                throw new ServerException("上传文件失败:",500, e.getMessage());
            } catch (ServerException ex) {
                throw new RuntimeException(ex);
            }
        }

        return properties.getMinio().getEndPoint() + "/" + properties.getMinio().getBucketName() + "/" + path;
    }

    private static final String CONTENT_TYPE = "contentType";
    private static final String REAL_PATH = "realPath";

    /**
     * 返回上传的目录路径路径
     * 以及对应的contentType
     * @param fileName
     * @return
     */
    public HashMap<String, String> validateFileType(String fileName) {
        HashMap<String, String> hashMap = new HashMap<>();
        String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        String contentType;
        String directory;

        switch (fileExtension) {
            case "png":
                contentType = "image/png";
                directory = "img";
                break;
            case "jpg":
            case "jpeg":
                contentType = "image/jpeg";
                directory = "img";
                break;
            case "gif":
                contentType = "image/gif";
                directory = "img";
                break;
            case "apk":
                contentType = "application/vnd.android.package-archive";
                directory = "apk";
                break;
            case "zip":
                contentType = "application/zip";
                directory = "archive";
                break;
            case "tar":
                contentType = "application/x-tar";
                directory = "archive";
                break;
            case "pdf":
                contentType = "application/pdf";
                directory = "documents";
                break;
            case "doc":
            case "docx":
                contentType = "application/msword";
                directory = "documents";
                break;
            case "xls":
            case "xlsx":
                contentType = "application/vnd.ms-excel";
                directory = "documents";
                break;
            case "ppt":
            case "pptx":
                contentType = "application/vnd.ms-powerpoint";
                directory = "documents";
                break;
            case "txt":
                contentType = "text/plain";
                directory = "documents";
                break;
            case "mp3":
                contentType = "audio/mpeg";
                directory = "audio";
                break;
            case "wav":
                contentType = "audio/wav";
                directory = "audio";
                break;
            case "mp4":
                contentType = "video/mp4";
                directory = "video";
                break;
            case "avi":
                contentType = "video/x-msvideo";
                directory = "video";
                break;
            case "mkv":
                contentType = "video/x-matroska";
                directory = "video";
                break;
            default:
                throw new IllegalArgumentException("Unsupported file type: " + fileExtension);
        }

        hashMap.put(CONTENT_TYPE, contentType);
        hashMap.put(REAL_PATH, directory);
        return hashMap;
    }

如果系统有多种Storage 保存,所以可以采取更简单的方法
Storage 接口

public abstract class StorageService {
    public StorageProperties properties;

    /**
     * 根据文件名,生成带时间戳的新文件名
     *
     * @param fileName 文件名
     * @return 返回带时间戳的文件名
     */
    public String getNewFileName(String fileName) {
        // 主文件名,不包含扩展名
        String prefix = FileNameUtil.getPrefix(fileName);
        // 文件扩展名
        String suffix = FileNameUtil.getSuffix(fileName);
        // 把当天HH:mm:ss,转换成秒
        long time = DateUtil.timeToSecond(DateUtil.formatTime(new Date()));
        // 新文件名
        return prefix + "_" + time + "." + suffix;
    }

    /**
     * 生成路径,不包含文件名
     *
     * @return 返回生成的路径
     */
    public String getPath() {
        // 文件路径
        String path = DateUtil.format(new Date(), "yyyyMMdd");

        // 如果有前缀,则也带上
        if (StringUtils.hasText(properties.getConfig().getPrefix())) {
            path = properties.getConfig().getPrefix() + "/" + path;
            System.out.println("prex"+properties.getConfig().getPrefix() );
        }

        return path;
    }

    /**
     * 根据文件名,生成路径
     *
     * @param fileName 文件名
     * @return 生成文件路径
     */
    public String getPath(String fileName) {
        return getPath() + "/" + getNewFileName(fileName);
    }

    /**
     * 文件上传
     *
     * @param data 文件字节数组
     * @param path 文件路径,包含文件名
     * @return 返回http地址
     */
    public abstract String upload(byte[] data, String path);

    /**
     * 文件上传
     *
     * @param inputStream 字节流
     * @param path        文件路径,包含文件名
     * @return 返回http地址
     */
    public abstract String upload(InputStream inputStream, String path);

}

minio实现 这样直接上传流 更加通用 但是如果是安装包或者重要信息 建议还是类型判断这样,因为文件包可能会损坏

public class MinioStorageService extends StorageService {
    private final MinioClient minioClient;
  
    public MinioStorageService(StorageProperties properties) {
        this.properties = properties;

        minioClient = MinioClient.builder().endpoint(properties.getMinio().getEndPoint())
                .credentials(properties.getMinio().getAccessKey(), properties.getMinio().getSecretKey()).build();
    }

    @Override
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        try {
            //如果BucketName不存在,则创建
            log.info("Uploading:{}",properties.getMinio().getBucketName());
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(properties.getMinio().getBucketName()).build());
            if (!found) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getMinio().getBucketName()).build());
            }
//直接是二进制流
            String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
            Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(path);
            if (mediaType.isPresent()) {
                contentType = mediaType.get().toString();
            }

            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(properties.getMinio().getBucketName())
                            .contentType(contentType)
                            .object(path)
                            .stream(inputStream, inputStream.available(), -1)
                            .build()
            );

        } catch (Exception e) {
            try {
                throw new ServerException("上传文件失败:",500, e.getMessage());
            } catch (ServerException ex) {
                throw new RuntimeException(ex);
            }
        }
//服务地址/桶/文件名
        return properties.getMinio().getEndPoint() + "/" + properties.getMinio().getBucketName() + "/" + path;
    }


}

本地实现

public class LocalStorageService extends StorageService {

    public LocalStorageService(StorageProperties properties) {
        this.properties = properties;
    }

    @Override
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }


    @SneakyThrows
    @Override
    public String upload(InputStream inputStream, String path) {

        try {
//            log.info("配置文件路径:{}",properties.getLocal().getPath() );//D://MDM
//            log.info("文件路径{}", path);//真实路径20240807/da_1701653930625_60037.apk
            //上传到本地
            File file = new File(properties.getLocal().getPath() + File.separator + path);
            // 没有目录,则自动创建目录
            File parent = file.getParentFile();
            if (parent != null && !parent.mkdirs() && !parent.isDirectory()) {
                throw new IOException("目录 '" + parent + "' 创建失败");
            }
            FileCopyUtils.copy(inputStream, Files.newOutputStream(file.toPath()));
        } catch (Exception e) {
            throw new ServerException("上传文件失败:", e);
        }
//        log.info("properties.getConfig().getDomain{}",properties.getConfig().getDomain());//域名(http://localhost:8080/")
//        log.info("properties.getLocal().getUrl{}",properties.getLocal().getUrl());  接口路径
        return properties.getConfig().getDomain() + "/" + properties.getLocal().getUrl() + "/" + path;
    }
}

七牛云

public class QiniuStorageService extends StorageService {
    private final UploadManager uploadManager;

    public QiniuStorageService(StorageProperties properties) {
        this.properties = properties;

        uploadManager = new UploadManager(new Configuration(Region.autoRegion()));

    }

    @Override
    public String upload(byte[] data, String path) {
        try {
            String token = Auth.create(properties.getQiniu().getAccessKey(), properties.getQiniu().getSecretKey()).
                    uploadToken(properties.getQiniu().getBucketName());

            Response res = uploadManager.put(data, path, token);
            if (!res.isOK()) {
                throw new ServerException(res.toString());
            }

            return properties.getConfig().getDomain() + "/" + path;
        } catch (Exception e) {
            throw new ServerException("上传文件失败:", e);
        }
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        try {
            byte[] data = IOUtils.toByteArray(inputStream);
            return this.upload(data, path);
        } catch (IOException e) {
            throw new ServerException("上传文件失败:", e);
        }
    }

}

等等这样 就可以通用的进行上传了,再controller层进行解析拼接上传后的目录

public class MinioStorageService extends StorageService {
    private final MinioClient minioClient;

    public MinioStorageService(StorageProperties properties) {
        this.properties = properties;

        minioClient = MinioClient.builder().endpoint(properties.getMinio().getEndPoint())
                .credentials(properties.getMinio().getAccessKey(), properties.getMinio().getSecretKey()).build();
    }

    @Override
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        try {
            //如果BucketName不存在,则创建
            log.info("Uploading:{}",properties.getMinio().getBucketName());
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(properties.getMinio().getBucketName()).build());
            if (!found) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getMinio().getBucketName()).build());
            }

            String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
            Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(path);
            if (mediaType.isPresent()) {
                contentType = mediaType.get().toString();
            }
            path=validateFileType(path);
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(properties.getMinio().getBucketName())
                            .contentType(contentType)
                            .object(path)
                            .stream(inputStream, inputStream.available(), -1)
                            .build()
            );

        } catch (Exception e) {
            try {
                throw new ServerException("上传文件失败:",500, e.getMessage());
            } catch (ServerException ex) {
                throw new RuntimeException(ex);
            }
        }

        return properties.getMinio().getEndPoint() + "/" + properties.getMinio().getBucketName() + "/" + path;
    }

    /**
     * 定制目录结构
     * @param fileName
     * @return
     */
    public String validateFileType(String fileName) {
        String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        String directory;
        switch (fileExtension) {
            case "png":
            case "gif":
            case "jpg":
            case "jpeg":
                directory = "img";
                break;
            case "apk":
                directory = "apk";
                break;
            case "zip":
            case "tar":
                directory = "archive";
                break;
            case "pdf":
            case "ppt":
            case "pptx":
            case "txt":
            case "xls":
            case "xlsx":
            case "doc":
            case "docx":
                directory = "documents";
                break;
            case "mp3":
            case "wav":
                directory = "audio";
                break;
            case "mp4":
            case "mkv":
            case "avi":
                directory = "video";
                break;
            default:
                throw new IllegalArgumentException("Unsupported file type: " + fileExtension);
        }


        return directory+"/"+fileName;
    }


}

这样就可以有目录结构,也可以再次添加时间结构

    // 主文件名,不包含扩展名
        String prefix = FileNameUtil.getPrefix(fileName);
        // 文件扩展名
        String suffix = FileNameUtil.getSuffix(fileName);
        // 把当天HH:mm:ss,转换成秒
        long time = DateUtil.timeToSecond(DateUtil.formatTime(new Date()));
        // 新文件名
        return prefix + "_" + time + "." + suffix;

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝胖子不是胖子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值