阿里云OSS对象存储功能的实现步骤 + MinIO本地存储的实现步骤 + 配置文件的编写

一、阿里云OSS对象存储功能的实现步骤

首先 -> 注册阿里云 -> 开通OSS对象存储服务 -> 创建Bucket -> 创建AccessKey 

  1. 获取四个参数编写到yml文件  参考配置文件1.

aliyun:
  oss:
    endpoint: https://oss-cn-hangzhou.aliyuncs.com
    access-key-id: LTAI5tS9qa9H2uftNY
    access-key-secret: VNEIKZfxBpa4HO9YLDx2i
    bucket-name: big-event993

  2. 编写配置类

AliyunOSSConfig
package com.zxd.AliOSS;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @Auther: Zxd
 * @Date: 2024/07/12  20:58
 * @Description:
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSConfig {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

}

  3. 编写OSS工具类          

AliOSSUtil
package com.zxd.AliOSS;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * @Auther: Zxd
 * @Date: 2024/07/12  14:47
 * @Description: 阿里云oss工具类
 */
@Component
public class AliOSSUtil {

    @Autowired
    private AliyunOSSConfig aliyunOSSConfig;

    /**
     * 上传文件到OSS(Object Storage Service)。
     *
     * @param originalFilename 原始文件名,包含文件扩展名。
     * @param in 文件的输入流。
     * @return 上传后的文件URL。
     * @throws Exception 如果上传过程中发生错误。
     */
    public String uploadFile(String originalFilename, InputStream in) throws Exception {
        // 从原始文件名中提取文件扩展名
        String fileExtension = getFileExtension(originalFilename);
        // 根据文件扩展名决定文件存储的目录
        String directory = getDirectoryByExtension(fileExtension);

        // 生成唯一ID,用于文件名的一部分(在文件名后面-四位随机字符),以避免文件名冲突
        String uniqueId = UUID.randomUUID().toString().substring(0, 4); // 取UUID的前4个字符
        // 构造新文件名,原始文件名加上唯一ID和原始文件扩展名
        String newFileName = originalFilename.substring(0, originalFilename.lastIndexOf('.')) + "-" + uniqueId + originalFilename.substring(originalFilename.lastIndexOf('.'));

        // 构造新的objectName,添加目录
        String newObjectName = directory + "/" + newFileName;

        // 创建OSS客户端实例
        OSS ossClient = new OSSClientBuilder().build(aliyunOSSConfig.getEndpoint(), aliyunOSSConfig.getAccessKeyId(), aliyunOSSConfig.getAccessKeySecret());
        String url = "";
        try {
            // 创建PutObjectRequest对象,准备上传文件
            PutObjectRequest putObjectRequest = new PutObjectRequest(aliyunOSSConfig.getBucketName(), newObjectName, in);

            // 执行文件上传操作
            PutObjectResult result = ossClient.putObject(putObjectRequest);

            // URL编码处理
            // 只对文件名部分进行编码
            String encodedFileName = URLEncoder.encode(newFileName, StandardCharsets.UTF_8.toString()).replace("+", "%20");
            // 不对路径中的斜杠进行编码
            String encodedObjectName = newObjectName.substring(0, newObjectName.lastIndexOf('/') + 1) + encodedFileName;

            // 构造上传后文件的URL url组成: https://bucket名称.区域节点/objectName
            url = "https://" + aliyunOSSConfig.getBucketName() + "." + aliyunOSSConfig.getEndpoint().substring(aliyunOSSConfig.getEndpoint().lastIndexOf("/") + 1) + "/" + encodedObjectName;
        } catch (OSSException oe) {
            // 处理OSS异常,打印错误信息
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            // 处理客户端异常,打印错误信息
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            // 关闭OSS客户端,释放资源
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        // 返回上传后的文件URL
        return url;
    }

    /**
     * 获取文件名的扩展名。
     *
     * 该方法通过查找字符串中最后一个'.'的位置来确定文件的扩展名。如果找到了'.',则返回'.'后面的部分,
     * 并将其转换为小写;如果未找到'.',则返回空字符串。
     *
     * @param objectName 待处理的文件名或路径名字符串。
     * @return 文件名的扩展名,以小写形式表示,如果不存在扩展名则返回空字符串。
     */
    private String getFileExtension(String objectName) {
        int dotIndex = objectName.lastIndexOf('.');
        return dotIndex != -1 ? objectName.substring(dotIndex + 1).toLowerCase() : "";
    }


    /**
     * 根据文件扩展名获取文件目录类别。
     *
     * 此方法通过文件的扩展名来确定文件的类型,并返回相应的目录类别字符串。
     * 支持的文件类型包括音频、视频、图片、文档、压缩文件、代码、数据库、配置、设计和其他类型。
     *
     * @param extension 文件的扩展名(不包含点号)
     * @return 对应的目录类别字符串,如"audio"、"video"、"image"等。
     */
    private String getDirectoryByExtension(String extension) {
        // 根据文件扩展名确定文件类型并返回相应的目录类别
        switch (extension) {
            // 音频文件类型
            case "mp3":
            case "wav":
            case "aac":
            case "ogg":
            case "flac":
            case "m4a":
            case "wma":
                return "audio";

            // 视频文件类型
            case "mp4":
            case "avi":
            case "mov":
            case "mkv":
            case "flv":
            case "wmv":
            case "webm":
            case "mpg":
            case "mpeg":
            case "rmvb":
                return "video";

            // 图片文件类型
            case "png":
            case "jpg":
            case "jpeg":
            case "gif":
            case "bmp":
            case "tiff":
            case "ico":
            case "svg":
                return "image";

            // 文档文件类型
            case "doc":
            case "docx":
            case "ppt":
            case "pptx":
            case "xls":
            case "xlsx":
            case "pdf":
            case "txt":
            case "md":
            case "rtf":
                return "word";

            // 压缩文件类型
            case "zip":
            case "rar":
            case "gz":
            case "tar":
                return "zip";

            // 代码文件类型
            case "java":
            case "py":
            case "js":
            case "html":
            case "htm":
            case "css":
            case "cpp":
            case "h":
            case "cs":
                return "code";

            // 数据库文件类型
            case "sqlite":
            case "db":
            case "sql":
                return "databases";

            // 配置文件类型
            case "ini":
            case "config":
            case "yaml":
            case "json":
            case "log":
            case "sh":
            case "bat":
                return "configurations";

            // 设计文件类型
            case "psd":
            case "ai":
            case "sketch":
                return "designs";

            // 默认情况,对于未识别的扩展名
            default:
                return "others";
        }
    }

    /**
     * 通过URL删除存储桶内的数据。
     *
     * @param fileUrl 文件的URL地址。
     * @throws Exception 如果删除过程中发生错误。
     */
    public void deleteFileByUrl(String fileUrl) throws Exception {
        // URL解码
        String decodedUrl = URLDecoder.decode(fileUrl, StandardCharsets.UTF_8.toString());

        // 提取bucket名称和对象名称
        String bucketName = aliyunOSSConfig.getBucketName();
        String endpoint = aliyunOSSConfig.getEndpoint();
        String baseUrl = "https://" + bucketName + "." + endpoint.substring(endpoint.lastIndexOf("/") + 1);

        // 计算objectName
        String objectName = decodedUrl.replace(baseUrl + "/", "");

        // 创建OSS客户端实例
        OSS ossClient = new OSSClientBuilder().build(aliyunOSSConfig.getEndpoint(), aliyunOSSConfig.getAccessKeyId(), aliyunOSSConfig.getAccessKeySecret());

        try {
            // 执行文件删除操作
            ossClient.deleteObject(bucketName, objectName);
        } catch (OSSException oe) {
            // 处理OSS异常,打印错误信息
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            // 处理客户端异常,打印错误信息
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            // 关闭OSS客户端,释放资源
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

  4. 图片上传controller层示例

FileUploadOSSController
package com.zxd.AliOSS;

import com.zxd.pojo.ApiResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * @Auther: Zxd
 * @Date: 2024/07/12  10:15
 * @Description:
 */

@RestController
@Tag(name = "文件上传")
public class FileUploadOSSController {

    @Autowired
    private AliOSSUtil aliOSSUtil;

    @PostMapping("/uploadOSS")
    @Operation(summary = "OSS文件上传")
    public ApiResult<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
        // 把文件内容存储到阿里云OSS上
        // 获取文件名
        String originalFilename = file.getOriginalFilename();
        // 调用工具类(文件名,文件数据流)上传到阿里云并返回url地址
        String url = aliOSSUtil.uploadFile(originalFilename, file.getInputStream());
        return ApiResult.success(url);
    }

    @PostMapping("/deleteOSS")
    @Operation(summary = "OSS文件删除")
    public ApiResult<String> delete(@RequestParam("fileUrl") String fileUrl) {
        try {
            // 调用工具类删除文件
            aliOSSUtil.deleteFileByUrl(fileUrl);
            return ApiResult.success("文件删除成功");
        } catch (Exception e) {
            // 处理异常并返回失败结果
            return ApiResult.error("文件删除失败: " + e.getMessage());
        }
    }

}

 

参考链接:https://www.bilibili.com/video/BV14z4y1N7pg?p=36&vd_source=714b8a2323b700f15d0b7b31226f1d5d 

 

二、MinIO存储的实现步骤

  1. MinIO在Linux的安装步骤(通过Docker命令安装到容器中)

    docker启动minio命令:

docker run -p 9000:9000 -p 9001:9001 --name minio \
-v /mydata/minio/data:/data \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
--restart always \
-d minio/minio server /data --console-address ":9001"
命令解释
 docker run -p 9000:9000 -p 9001:9001 --name minio \     启动命令,映射9000和9001端口,命名为minio
-v /mydata/minio/data:/data \                           挂载数据到/mydata/minio/data
-e MINIO_ROOT_USER=minioadmin \                         设置用户名为minioadmin
-e MINIO_ROOT_PASSWORD=minioadmin \                     设置密码为minioadmin
--restart always \                                      设置开机自动启动容器
-d minio/minio server /data --console-address ":9001"   后台启动,

  2. 登录MinIO的控制页面 地址为 虚拟机/服务器ip:9001  示例:192.168.115.136:9001

 输入上面定义的用户名和密码,此处为 minioadmin 

  

  3. 创建存储桶

  
  

  4. 将存储桶权限设置为公开(默认权限为私密)

  

  5. 获取API访问凭证
  

  6. 在application.yml中编写配置文件   注意参数一一对应且有效

#minio配置
minio:
  url: http://192.168.115.136:9000  #访问地址,对象存储服务的URL
  access-key: HLKXF1QG9TMM3LDOCY5P #Access key账户 写账号也可以
  secret-key: x+u8W90DaRFT1qOpcsqlv7pmVgONfXl9oGylYko8 #Secret key密码
  bucket-name: test-bucket # 桶名称
#  expire: 7200 # 过期时间

  7. 编写配置类

MinIOConfig
 package com.zxd.MinIO;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @Auther: Zxd
 * @Date: 2024/07/12  20:58
 * @Description:
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIOConfig {

    private String url;
    private String accessKey;
    private String secretKey;
    private String bucketName;

}

  8. 编写Oss工具类

MinIOUtil
package com.zxd.MinIO;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.errors.MinioException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * @Author: Zxd
 * @Date: 2024/07/12  14:47
 * @Description: MinIO工具类
 */
@Component
public class MinIOUtil {

    @Autowired
    private MinIOConfig minioConfig;

    /**
     * 上传文件到MinIO。
     *
     * @param originalFilename 原始文件名,包含文件扩展名。
     * @param in 文件的输入流。
     * @return 上传后的文件URL。
     * @throws Exception 如果上传过程中发生错误。
     * @description 存入时自动在文件名后面加四位字符,避免文件名重复并且可以根据不同文件的后缀放入不同的文件夹下,具体可以修改工具类中getDirectoryByExtension方法
     */
    public String uploadFile(String originalFilename, InputStream in) throws Exception {
        // 从原始文件名中提取文件扩展名
        String fileExtension = getFileExtension(originalFilename);
        // 根据文件扩展名决定文件存储的目录
        String directory = getDirectoryByExtension(fileExtension);

        // 生成唯一ID,用于文件名的一部分(在文件名后面-四位随机字符),以避免文件名冲突
        String uniqueId = UUID.randomUUID().toString().substring(0, 4); // 取UUID的前4个字符
        // 构造新文件名,原始文件名加上唯一ID和原始文件扩展名
        String newFileName = originalFilename.substring(0, originalFilename.lastIndexOf('.')) + "-" + uniqueId + originalFilename.substring(originalFilename.lastIndexOf('.'));

        // 构造新的objectName,添加目录
        String newObjectName = directory + "/" + newFileName;

        // 创建MinIO客户端实例
        MinioClient minioClient = MinioClient.builder()
                .endpoint(minioConfig.getUrl())
                .credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey())
                .build();
        String url = "";
        try {
            // 准备上传文件
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(newObjectName)
                    .stream(in, in.available(), -1)
                    .build();

            // 执行文件上传操作
            minioClient.putObject(putObjectArgs);

            // URL编码处理
            // 只对文件名部分进行编码
            String encodedFileName = URLEncoder.encode(newFileName, StandardCharsets.UTF_8.toString()).replace("+", "%20");
            // 不对路径中的斜杠进行编码
            String encodedObjectName = newObjectName.substring(0, newObjectName.lastIndexOf('/') + 1) + encodedFileName;

            // 构造上传后文件的URL
            url = minioConfig.getUrl() + "/" + minioConfig.getBucketName() + "/" + encodedObjectName;
        } catch (MinioException e) {
            // 处理MinIO异常,打印错误信息
            System.out.println("Caught an MinioException, which means your request made it to MinIO, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + e.getMessage());
        } finally {
            // 关闭输入流,释放资源
            if (in != null) {
                in.close();
            }
        }
        // 返回上传后的文件URL
        return url;
    }

    /**
     * 获取文件名的扩展名。
     *
     * 该方法通过查找字符串中最后一个'.'的位置来确定文件的扩展名。如果找到了'.',则返回'.'后面的部分,
     * 并将其转换为小写;如果未找到'.',则返回空字符串。
     *
     * @param objectName 待处理的文件名或路径名字符串。
     * @return 文件名的扩展名,以小写形式表示,如果不存在扩展名则返回空字符串。
     */
    private String getFileExtension(String objectName) {
        int dotIndex = objectName.lastIndexOf('.');
        return dotIndex != -1 ? objectName.substring(dotIndex + 1).toLowerCase() : "";
    }

    /**
     * 根据文件扩展名获取文件目录类别。
     *
     * 此方法通过文件的扩展名来确定文件的类型,并返回相应的目录类别字符串。
     * 支持的文件类型包括音频、视频、图片、文档、压缩文件、代码、数据库、配置、设计和其他类型。
     *
     * @param extension 文件的扩展名(不包含点号)
     * @return 对应的目录类别字符串,如"audio"、"video"、"image"等。
     */
    private String getDirectoryByExtension(String extension) {
        // 根据文件扩展名确定文件类型并返回相应的目录类别
        switch (extension) {
            // 音频文件类型
            case "mp3":
            case "wav":
            case "aac":
            case "ogg":
            case "flac":
            case "m4a":
            case "wma":
                return "audio";

            // 视频文件类型
            case "mp4":
            case "avi":
            case "mov":
            case "mkv":
            case "flv":
            case "wmv":
            case "webm":
            case "mpg":
            case "mpeg":
            case "rmvb":
                return "video";

            // 图片文件类型
            case "png":
            case "jpg":
            case "jpeg":
            case "gif":
            case "bmp":
            case "tiff":
            case "ico":
            case "svg":
                return "image";

            // 文档文件类型
            case "doc":
            case "docx":
            case "ppt":
            case "pptx":
            case "xls":
            case "xlsx":
            case "pdf":
            case "txt":
            case "md":
            case "rtf":
                return "word";

            // 压缩文件类型
            case "zip":
            case "rar":
            case "gz":
            case "tar":
                return "zip";

            // 代码文件类型
            case "java":
            case "py":
            case "js":
            case "html":
            case "htm":
            case "css":
            case "cpp":
            case "h":
            case "cs":
                return "code";

            // 数据库文件类型
            case "sqlite":
            case "db":
            case "sql":
                return "databases";

            // 配置文件类型
            case "ini":
            case "config":
            case "yaml":
            case "json":
            case "log":
            case "sh":
            case "bat":
                return "configurations";

            // 设计文件类型
            case "psd":
            case "ai":
            case "sketch":
                return "designs";

            // 默认情况,对于未识别的扩展名
            default:
                return "others";
        }
    }

    /**
     * 通过URL删除存储桶内的数据。
     *
     * @param fileUrl 文件的URL地址。
     * @throws Exception 如果删除过程中发生错误。
     */
    public void deleteFileByUrl(String fileUrl) throws Exception {
        // URL解码
        String decodedUrl = URLDecoder.decode(fileUrl, StandardCharsets.UTF_8.toString());

        // 提取bucket名称和对象名称
        String bucketName = minioConfig.getBucketName();
        String objectName = decodedUrl.substring(decodedUrl.indexOf(bucketName) + bucketName.length() + 1);

        // 创建MinIO客户端实例
        MinioClient minioClient = MinioClient.builder()
                .endpoint(minioConfig.getUrl())
                .credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey())
                .build();

        try {
            // 执行文件删除操作
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build();
            minioClient.removeObject(removeObjectArgs);
        } catch (MinioException e) {
            // 处理MinIO异常,打印错误信息
            System.out.println("Caught an MinioException, which means your request made it to MinIO, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + e.getMessage());
        }
    }

}

  9. 图片上传controller层示例 

FileUploadMinIOController
package com.zxd.MinIO;

import com.zxd.pojo.ApiResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * @Auther: Zxd
 * @Date: 2024/07/12  10:15
 * @Description:
 */

@RestController
@Tag(name = "文件上传")
public class FileUploadMinIOController {

    @Autowired
    private MinIOUtil minIOUtil;

    @PostMapping("/uploadMinIO")
    @Operation(summary = "MinIO文件上传")
    public ApiResult<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
        // 把文件内容存储到阿里云OSS上
        // 获取文件名
        String originalFilename = file.getOriginalFilename();
        // 调用工具类(文件名,文件数据流)上传到阿里云并返回url地址
        String url = minIOUtil.uploadFile(originalFilename, file.getInputStream());
        return ApiResult.success(url);
    }

    @PostMapping("/deleteMinIO")
    @Operation(summary = "MinIO文件删除")
    public ApiResult<String> delete(@RequestParam("fileUrl") String fileUrl) {
        try {
            // 调用工具类删除文件
            minIOUtil.deleteFileByUrl(fileUrl);
            return ApiResult.success("文件删除成功");
        } catch (Exception e) {
            // 处理异常并返回失败结果
            return ApiResult.error("文件删除失败: " + e.getMessage());
        }
    }

}

 

参考链接:https://blog.csdn.net/qq_40623672/article/details/140634694

 

三、配置文件的编写

1. 在application.yml里面配置需要的参数

2. pom.xml文件引入配置文件依赖  不然 @ConfigurationProperties(prefix = "aliyun.oss") 会报红不生效

        <!-- 配置生成配置文件处理器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

3. 使用配置文件里面的信息

  方法一:适用于参数多  使用实体类和 @Autowired 引入配置类参数

      1)编写config类:注意 @ConfigurationProperties(prefix = "aliyun.oss")   要与第一步yml文件里面的路径匹配

        2)使用@Autowired注解,哪里需要就注入引用

  方法二:适用于参数少直接使用 @Value("${aliyun.oss.endpoint}") 引入配置类参数(需要注意路径)

   

   

posted @ 2024-07-13 15:15  xd99  阅读(16)  评论(0编辑  收藏  举报