SpringBoot 集成 OSS 对象存储

一、简介

阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,可提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。

OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

您可以使用阿里云提供的API、SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准存储(Standard)作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问存储(Infrequent Access)、归档存储(Archive)、冷归档存储(Cold Archive)作为不经常访问数据的存储方式。

二、概念

  • 存储类型(Storage Class)

    OSS提供标准、低频访问、归档、冷归档四种存储类型,全面覆盖从热到冷的各种数据存储场景。其中标准存储类型提供高持久、高可用、高性能的对象存储服务,能够支持频繁的数据访问;低频访问存储类型适合长期保存不经常访问的数据(平均每月访问频率1到2次),存储单价低于标准类型;归档存储类型适合需要长期保存(建议半年以上)的归档数据;冷归档存储适合需要超长时间存放的极冷数据。更多信息,请参见存储类型介绍

  • 存储空间(Bucket)

    存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。存储空间具有各种配置属性,包括地域、访问权限、存储类型等。您可以根据实际需求,创建不同类型的存储空间来存储不同的数据。

  • 对象(Object)

    对象是OSS存储数据的基本单元,也被称为OSS的文件。对象由元信息(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的Key来标识。对象元信息是一组键值对,表示了对象的一些属性,例如最后修改时间、大小等信息,同时您也可以在元信息中存储一些自定义的信息。

  • 地域(Region)

    地域表示OSS的数据中心所在物理位置。您可以根据费用、请求来源等选择合适的地域创建Bucket。更多信息,请参见OSS已开通的地域

  • 访问域名(Endpoint)

    Endpoint表示OSS对外服务的访问域名。OSS以HTTP RESTful API的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。更多信息,请参见各个Region对应的Endpoint

  • 访问密钥(AccessKey)

    AccessKey简称AK,指的是访问身份验证中用到的AccessKey ID和AccessKey Secret。OSS通过使用AccessKey ID和AccessKey Secret对称加密的方法来验证某个请求的发送者身份。AccessKey ID用于标识用户;AccessKey Secret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,必须保密。关于获取AccessKey的方法,请参见获取AccessKey

三、操作

  • 创建Bucket

    在上传文件(Object)到OSS之前,您需要创建一个用于存储文件的Bucket。Bucket具有各种配置属性,包括地域、访问权限以及其他元数据。创建Bucket的具体操作,请参见创建存储空间

  • 上传文件

    Bucket创建完成后,您可以通过多种方式上传不同大小的文件。有关上传文件的具体操作,请参见上传文件

  • 下载文件

    文件上传完成后,您可以将文件下载至浏览器默认路径或本地指定路径。有关下载文件的具体操作,请参见下载文件

  • 列举文件

    当您的Bucket内存储了大量的文件后,您可以选择列举Bucket内的全部或部分文件。有关列举文件的具体操作,请参见列举文件

  • 删除文件

    当您不再需要保留上传的文件时,您可以手动删除单个或多个文件,也可以通过配置生命周期规则自动删除单个或多个文件。有关删除文件的具体操作,请参见删除文件

 四、代码

1、目录结构

2、OSSConfig

package com.llj.config;

import java.io.IOException;

/**
 * @PakcageName:com.llj.config
 * @ClassName:OSSConfig
 * @Description: OSS配置类
 * @Author:liulianjia
 * @Date:2021/2/2 16:29
 */
public class OSSConfig {
    private  String endpoint;  		//连接区域地址
    private  String accessKeyId;  	//连接keyId
    private  String accessKeySecret;//连接秘钥
    private  String bucketName;  	//需要存储的bucketName
    private  String picLocation;  	//图片保存路径

    public OSSConfig() {
        try {
            this.endpoint = SystemConfig.getConfigResource("endpoint");
            this.bucketName = SystemConfig.getConfigResource("bucketName");
            this.picLocation = SystemConfig.getConfigResource("picLocation");
            this.accessKeyId = SystemConfig.getConfigResource("accessKeyId");
            this.accessKeySecret = SystemConfig.getConfigResource("accessKeySecret");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getEndpoint() {
        return endpoint;
    }
    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }
    public String getAccessKeyId() {
        return accessKeyId;
    }
    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }
    public String getAccessKeySecret() {
        return accessKeySecret;
    }
    public void setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }
    public String getBucketName() {
        return bucketName;
    }
    public void setBucketName(String bucketName) {
        this.bucketName = bucketName;
    }
    public String getPicLocation() {
        return picLocation;
    }
    public void setPicLocation(String picLocation) {
        this.picLocation = picLocation;
    }

}

 3、SystemConfig

package com.llj.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * @PakcageName:com.llj.config
 * @ClassName:SystemConfig
 * @Description: 读取config.properties配置文件
 * @Author:liulianjia
 * @Date:2021/2/2 16:27
 */
public class SystemConfig {
    private static final String CONFIG_PROPERTIES="config.properties";

    public static String getConfigResource(String key) throws IOException {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Properties properties = new Properties();
        InputStream in = loader.getResourceAsStream(CONFIG_PROPERTIES);
        properties.load(in);
        String value = properties.getProperty(key);
        // 编码转换,从ISO-8859-1转向指定编码
        value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
        in.close();
        return value;
    }

}

4、OSSUtil

package com.huapu.eng.common.utils;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.huapu.eng.common.config.OSSConfig;
import org.apache.commons.io.FileUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * @PakcageName:com.llj.util
 * @ClassName:OssUtil
 * @Description: OSS工具类
 * @Author:liulianjia
 * @Date:2021/2/2 16:34
 */
public class OSSUtil {
    private static OSSConfig config = null;

    /**
     * @param fileUrl 需要删除的文件url
     * @return boolean 是否删除成功
     * @MethodName: deleteFile
     * @Description: 单文件删除
     */
    public static boolean deleteFile(String fileUrl) {
        config = config == null ? new OSSConfig() : config;

        //根据url获取bucketName
        String bucketName = OSSUtil.getBucketName(fileUrl);
        //根据url获取fileName
        String fileName = OSSUtil.getFileName(fileUrl);
        if (bucketName == null || fileName == null) {
            return false;
        }
        OSSClient ossClient = null;
        try {
            ossClient = new OSSClient(config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
            GenericRequest request = new DeleteObjectsRequest(bucketName).withKey(fileName);
            ossClient.deleteObject(request);
        } catch (Exception oe) {
            oe.printStackTrace();
            return false;
        } finally {
            ossClient.shutdown();
        }
        return true;
    }

    /**
     * @param fileUrls 需要删除的文件url集合
     * @return int 成功删除的个数
     * @MethodName: batchDeleteFiles
     * @Description: 批量文件删除(较快):适用于相同endPoint和BucketName
     */
    public static int deleteFile(List<String> fileUrls) {
        //成功删除的个数
        int deleteCount = 0;
        //根据url获取bucketName
        String bucketName = OSSUtil.getBucketName(fileUrls.get(0));
        //根据url获取fileName
        List<String> fileNames = OSSUtil.getFileName(fileUrls);
        if (bucketName == null || fileNames.size() <= 0) {
            return 0;
        }
        OSSClient ossClient = null;
        try {
            ossClient = new OSSClient(config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
            DeleteObjectsRequest request = new DeleteObjectsRequest(bucketName).withKeys(fileNames);
            DeleteObjectsResult result = ossClient.deleteObjects(request);
            deleteCount = result.getDeletedObjects().size();
        } catch (OSSException oe) {
            oe.printStackTrace();
            throw new RuntimeException("OSS服务异常:", oe);
        } catch (ClientException ce) {
            ce.printStackTrace();
            throw new RuntimeException("OSS客户端异常:", ce);
        } finally {
            ossClient.shutdown();
        }
        return deleteCount;

    }

    /**
     * @param fileUrls 需要删除的文件url集合
     * @return int 成功删除的个数
     * @MethodName: batchDeleteFiles
     * @Description: 批量文件删除(较慢):适用于不同endPoint和BucketName
     */
    public static int deleteFiles(List<String> fileUrls) {
        int count = 0;
        for (String url : fileUrls) {
            if (deleteFile(url)) {
                count++;
            }
        }
        return count;
    }

    /**
     * @param file
     * @return String
     * @MethodName: putObject
     * @Description: 上传文件
     */
    public static String putObject(MultipartFile file) throws IOException {
        //默认null
        String url = null;
        OSSClient ossClient = null;
        try {
            config = config == null ? new OSSConfig() : config;
            String fileName = file.getOriginalFilename();
            String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
            fileName = config.getPicLocation() + UUID.randomUUID().toString().toUpperCase().replace("-", "") + "." + fileType;
            ossClient = new OSSClient(config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
            //MultipartFile 转 File
            File f = new File(Objects.requireNonNull(file.getOriginalFilename()));
            FileUtils.copyInputStreamToFile(file.getInputStream(), f);
            InputStream input = new FileInputStream(f);
            // 创建上传Object的Metadata
            ObjectMetadata meta = new ObjectMetadata();
            // 设置上传内容类型
            meta.setContentType(OSSUtil.contentType(fileType));
            // 被下载时网页的缓存行为
            meta.setCacheControl("no-cache");
            //创建上传请求
            PutObjectRequest request = new PutObjectRequest(config.getBucketName(), fileName, input, meta);
            ossClient.putObject(request);
            url = config.getEndpoint().replaceFirst("http://", "http://" + config.getBucketName() + ".") + "/" + fileName;
            // 会在本地产生临时文件,用完后需要删除
            if (f.exists()) {
                f.delete();
            }
        } catch (OSSException | ClientException | FileNotFoundException oe) {
            oe.printStackTrace();
            return null;
        } finally {
            ossClient.shutdown();
        }
        return url;
    }

    /**
     * @param "FileType
     * @return String
     * @MethodName: contentType
     * @Description: 获取文件类型
     */
    private static String contentType(String fileType) {
        fileType = fileType.toLowerCase();
        String contentType = "";
        switch (fileType) {
            case "bmp":
                contentType = "image/bmp";
                break;
            case "gif":
                contentType = "image/gif";
                break;
            case "png":
            case "jpeg":
            case "jpg":
                contentType = "image/jpeg";
                break;
            case "html":
                contentType = "text/html";
                break;
            case "txt":
                contentType = "text/plain";
                break;
            case "vsd":
                contentType = "application/vnd.visio";
                break;
            case "ppt":
            case "pptx":
                contentType = "application/vnd.ms-powerpoint";
                break;
            case "doc":
            case "docx":
                contentType = "application/msword";
                break;
            case "xml":
                contentType = "text/xml";
                break;
            case "mp4":
                contentType = "video/mp4";
                break;
            default:
                contentType = "application/octet-stream";
                break;
        }
        return contentType;
    }

    /**
     * @param fileUrl 文件url
     * @return String bucketName
     * @MethodName: getBucketName
     * @Description: 根据url获取bucketName
     */
    private static String getBucketName(String fileUrl) {
        String http = "http://";
        String https = "https://";
        int httpIndex = fileUrl.indexOf(http);
        int httpsIndex = fileUrl.indexOf(https);
        int startIndex = 0;
        if (httpIndex == -1) {
            if (httpsIndex == -1) {
                return null;
            } else {
                startIndex = httpsIndex + https.length();
            }
        } else {
            startIndex = httpIndex + http.length();
        }
        int endIndex = fileUrl.indexOf(".oss-");
        return fileUrl.substring(startIndex, endIndex);
    }

    /**
     * @param fileUrl 文件url
     * @return String fileName
     * @MethodName: getFileName
     * @Description: 根据url获取fileName
     */
    public static String getFileName(String fileUrl) {
        String str = "aliyuncs.com/";
        int beginIndex = fileUrl.indexOf(str);
        if (beginIndex == -1) {
            return null;
        }
        return fileUrl.substring(beginIndex + str.length());
    }

    /**
     * @param "fileUrl 文件url
     * @return List<String>  fileName集合
     * @MethodName: getFileName
     * @Description: 根据url获取fileNames集合
     */
    private static List<String> getFileName(List<String> fileUrls) {
        List<String> names = new ArrayList<>();
        for (String url : fileUrls) {
            names.add(getFileName(url));
        }
        return names;
    }

}

 5、config.properties

endpoint = http://oss-cn-chengdu.aliyuncs.com #自己的oss访问地址
bucketName = llj-blog #自己的bucket名称
picLocation = main/
accessKeyId =  #key
accessKeySecret = #secret