什么是OSS

我们可以理解为就是一个资源服务器,在这之前我也尝试过Nginx当静态资源服务器,但效果比较一般,为什么选择阿里云OSS,只是因为最近刚好公司用到了,所以就接入了,还有其他的比如七牛云,腾讯云等等。

axios oss上传 oss如何上传和下载文件_axios oss上传

创建 Bucket

Bucket 就是存储空间,我们的文件放在那里。具体操作如下:

1、点击进入阿里云OSS,在 Bucket 列表创建自己的存储空间。

axios oss上传 oss如何上传和下载文件_java_02

2、填写空间名,选择地域节点等操作。

axios oss上传 oss如何上传和下载文件_存储空间_03

我们的存储空间创建好了,下一步就是集成到我们的项目当中。

项目集成

在阿里云的官方文档中提供了不同语言SDK示例,可自行参考。

axios oss上传 oss如何上传和下载文件_存储空间_04

场景

在操作之前,先讲一讲我使用的场景。

在我负责的模块需要提供一个上传文件的服务,从消息队列中去读取数据,然后把数据转为 excel 文件在上传到 oss 中去,并返回一个文件的路径。当然,不只是上传 excel ,任何文件都可以。

maven 依赖

首先是 oss 的依赖,我用的是官方文档中提供的版本:

<dependency>
	<groupId>com.aliyun.oss</groupId>
	<artifactId>aliyun-sdk-oss</artifactId>
	<version>3.10.2</version>
</dependency>

因为涉及到 excel 的生成,我直接使用开源的 easypoi,对于 poi 操作有一大堆开源的,如果不是需要定制开发,我一般都是用开源的,还有阿里的 easyexcel 也是不错的。

<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-base</artifactId>
	<version>4.1.0</version>
</dependency>
<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-web</artifactId>
	<version>4.1.0</version>
</dependency>
<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-annotation</artifactId>
	<version>4.1.0</version>
</dependency>

还用到了一个工具类Hutool,Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率。你想要的他都有。

<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.7.13</version>
</dependency>

工具类

1、根据官方提供的文档封装的工具类。

再此之前你需要获得这几项配置:

  • endpoint,地域节点
  • accessKeyId,秘钥Id
  • accessKeySecret,秘钥
  • bucketName,存储空间名称
  • url,Bucket域名(存储空间名称和地域节点的组合)
import cn.hutool.core.date.DateUtil;
import com.fanryes.vanadium.cloud.exception.OssFileException;
import org.apache.commons.lang3.StringUtils;

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

/**
 * 阿里云oss文件上传
 *
 * @author ayue
 * @date 2021/9/26 10:47
 */
public class AliyunOssApiClient {
    /**
     * 地域节点
     */
    private String endpoint = "你的endpoint";
    /**
     * accessKeyId
     */
    private String accessKeyId = "你的accessKeyId";
    /**
     * accessKeySecret
     */
    private String accessKeySecret = "你的accessKeySecret";
    /**
     * Bucket 域名
     */
    private String url = "你的Bucket 域名";
    /**
     * Bucket名称
     */
    private String bucketName = "你的Bucket名称";

	/**
     * 组合的方式
     */
    private OssApi ossApi;

    /**
     * 初始化 OssApi
     *
     * @return
     */
    public AliyunOssApiClient init() {
        ossApi = new OssApi(this.endpoint, this.accessKeyId, this.accessKeySecret);
        this.url = url;
        this.bucketName = bucketName;
        return this;
    }

    /**
     * 文件上传
     *
     * @param is         文件流
     * @param pathPrefix
     * @param suffix
     * @return Bucket读写权限未开放,返回带有过期时间的私有访问路径
     */
    public String uploadFileExpired(InputStream is, String pathPrefix, String suffix) {
        String prefix = pathPrefix.endsWith("/") ? pathPrefix : pathPrefix + "/";
        String fileName = prefix + DateUtil.format(new Date(), "yyyyMMddHHmmssSSS") + suffix;
        this.check();
        try {
            return ossApi.uploadFileExpired(is, fileName, bucketName);
        } catch (Exception e) {
            throw new OssFileException("文件上传失败:" + e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 文件上传
     *
     * @param is         文件流
     * @param pathPrefix
     * @param suffix
     * @return Bucket读写权限开放,不通过身份验证直接读 Bucket 中的数据
     */
    public String uploadFile(InputStream is, String pathPrefix, String suffix) {
        String prefix = pathPrefix.endsWith("/") ? pathPrefix : pathPrefix + "/";
        String fileName = prefix + DateUtil.format(new Date(), "yyyyMMddHHmmssSSS") + suffix;
        this.check();
        try {
            return ossApi.uploadFile(is, fileName, bucketName, endpoint);
        } catch (Exception e) {
            throw new OssFileException("文件上传失败:" + e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 下载到本地
     *
     * @param fileName 需要下载的文件,填写不包含Bucket名称在内的文件完整路径
     * @param path     下载到本地的路径
     */
    public void downloadLocalFile(String fileName, String path) {
        this.check();
        try {
            ossApi.downloadLocalFile(fileName, path, bucketName);
        } catch (Exception e) {
            throw new OssFileException("文件下载失败:" + e.getMessage());
        }
    }


    /**
     * 删除文件(备用,可用作后续定时删除)
     *
     * @param path 文件路径
     * @return
     */
    public boolean removeFile(String path) {
        this.check();
        if (StringUtils.isEmpty(path)) {
            throw new OssFileException("删除文件失败:文件key为空");
        }

        try {
            this.ossApi.deleteFile(path, bucketName);
            return true;
        } catch (Exception e) {
            throw new OssFileException(e.getMessage());
        }
    }

    /**
     * 检查配置
     */
    public void check() {
        if (null == ossApi) {
            throw new OssFileException("尚未配置阿里云OSS,文件上传功能暂时不可用!");
        }
    }
}

2、由于我这里还有其他操作,所以把它的操作和实际业务操作分开了,单独提供了一个Api。

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateBucketRequest;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;
import com.aliyun.oss.model.ObjectPermission;
import com.aliyun.oss.model.StorageClass;
import com.fanryes.vanadium.cloud.domain.ObjectsRequest;
import com.fanryes.vanadium.cloud.exception.OssFileException;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

/**
 * 阿里云oss文件相关Api
 *
 * @author ayue
 * @date 2021/9/26 11:47
 */
public class OssApi {
    private OSSClient client;

    public OssApi(OSSClient client) {
        this.client = client;
    }

    public OssApi(String endpoint, String accessKeyId, String accessKeySecret) {
        this.client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 授权访问文件的URL
     *
     * @param fileName       待授权的文件名
     * @param bucketName     存储空间
     * @param expirationTime 授权失效时间,单位秒
     */
    public String authFile(String fileName, String bucketName, long expirationTime) {
        try {
            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] 无法授权访问文件的URL!Bucket不存在:" + bucketName);
            }
            if (!this.client.doesObjectExist(bucketName, fileName)) {
                throw new OssFileException("[阿里云OSS] 文件授权失败!文件不存在:" + bucketName + "/" + fileName);
            }
            // 设置URL过期时间为1小时
            Date expiration = new Date(new Date().getTime() + expirationTime * 1000);
            // 生成URL
            return this.client.generatePresignedUrl(bucketName, fileName, expiration).toString();
        } finally {
            this.shutdown();
        }
    }

    /**
     * 判断文件是否存在
     *
     * @param fileName   OSS中保存的文件名
     * @param bucketName 存储空间
     */
    public boolean isExistFile(String fileName, String bucketName) {
        try {
            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] Bucket不存在:" + bucketName);
            }
            return this.client.doesObjectExist(bucketName, fileName);
        } finally {
            this.shutdown();
        }
    }

    /**
     * 获取指定bucket下的文件的访问权限
     *
     * @param fileName   OSS中保存的文件名
     * @param bucketName 存储空间
     * @return
     */
    public ObjectPermission getFileAcl(String fileName, String bucketName) {
        try {
            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] 无法获取文件的访问权限!Bucket不存在:" + bucketName);
            }
            if (!this.client.doesObjectExist(bucketName, fileName)) {
                throw new OssFileException("[阿里云OSS] 无法获取文件的访问权限!文件不存在:" + bucketName + "/" + fileName);
            }
            return this.client.getObjectAcl(bucketName, fileName).getPermission();
        } finally {
            this.shutdown();
        }
    }

    /**
     * 获取文件列表
     *
     * @param bucketName 存储空间名
     * @param request    查询条件
     * @return 文件列表
     */
    public List<OSSObjectSummary> listFile(String bucketName, ObjectsRequest request) {
        try {
            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] 无法获取文件列表!Bucket不存在:" + bucketName);
            }
            ListObjectsRequest listRequest = new ListObjectsRequest(bucketName);
            if (null != request) {
                listRequest.withDelimiter(request.getDelimiter())
                        .withEncodingType(request.getEncodingType())
                        .withMarker(request.getMarker())
                        .withMaxKeys(request.getMaxKeys())
                        .withPrefix(request.getPrefix());
            }
            // 列举Object
            ObjectListing objectListing = this.client.listObjects(listRequest);
            return objectListing.getObjectSummaries();
        } finally {
            this.shutdown();
        }
    }

    /**
     * 修改指定bucket下的文件的访问权限
     *
     * @param fileName   OSS中保存的文件名
     * @param bucketName 保存文件的目标bucket
     * @param acl        权限
     */
    public void setFileAcl(String fileName, String bucketName, CannedAccessControlList acl) {
        try {
            boolean exists = this.client.doesBucketExist(bucketName);
            if (!exists) {
                throw new OssFileException("[阿里云OSS] 无法修改文件的访问权限!Bucket不存在:" + bucketName);
            }
            if (!this.client.doesObjectExist(bucketName, fileName)) {
                throw new OssFileException("[阿里云OSS] 无法修改文件的访问权限!文件不存在:" + bucketName + "/" + fileName);
            }
            this.client.setObjectAcl(bucketName, fileName, acl);
        } finally {
            this.shutdown();
        }
    }

    /**
     * 删除文件
     *
     * @param bucketName 保存文件的目标bucket
     * @param fileName   OSS中保存的文件名
     */
    public void deleteFile(String fileName, String bucketName) {
        try {
            boolean exists = this.client.doesBucketExist(bucketName);
            if (!exists) {
                throw new OssFileException("[阿里云OSS] 文件删除失败!Bucket不存在:" + bucketName);
            }
            if (!this.client.doesObjectExist(bucketName, fileName)) {
                throw new OssFileException("[阿里云OSS] 文件删除失败!文件不存在:" + bucketName + "/" + fileName);
            }
            this.client.deleteObject(bucketName, fileName);
        } finally {
            this.shutdown();
        }
    }

    /**
     * 创建存储空间
     *
     * @param bucketName 存储空间名称
     */
    public void createBucket(String bucketName) {
        try {
            boolean exists = this.client.doesBucketExist(bucketName);
            if (exists) {
                throw new OssFileException("[阿里云OSS] Bucket创建失败!Bucket名称[" + bucketName + "]已被使用!");
            }
            // -- 创建指定类型的Bucket,请使用Java SDK 2.6.0及以上版本。
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            // 设置bucket权限为公共读,默认是私有读写
            createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
            // 设置bucket存储类型为低频访问类型,默认是标准类型
            createBucketRequest.setStorageClass(StorageClass.IA);
            this.client.createBucket(createBucketRequest);
        } finally {
            this.shutdown();
        }
    }


    /**
     * 创建模拟文件夹本质上来说是创建了一个名字以“/”结尾的文件;<br>
     * 对于这个文件照样可以上传下载,只是控制台会对以“/”结尾的文件以文件夹的方式展示;<br>
     * 多级目录创建最后一级即可,比如dir1/dir2/dir3/,创建dir1/dir2/dir3/即可,dir1/、dir1/dir2/不需要创建;
     *
     * @param folder     目录名
     * @param bucketName 存储空间
     */
    public void createFolder(String folder, String bucketName) throws OssFileException {
        try {
            if (null == bucketName) {
                throw new OssFileException("[阿里云OSS] 尚未指定Bucket!");
            }

            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] 无法创建目录!Bucket不存在:" + bucketName);
            }
            folder = folder.endsWith("/") ? folder : folder + "/";
            this.client.putObject(bucketName, folder, new ByteArrayInputStream(new byte[0]));
        } finally {
            this.shutdown();
        }
    }

    /**
     * @param inputStream 待上传的文件流
     * @param fileName    文件名:最终保存到云端的文件名
     * @param bucketName  需要上传到的目标bucket
     */
    public String uploadFileExpired(InputStream inputStream, String fileName, String bucketName) {
        try {
            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] 无法上传文件!Bucket不存在:" + bucketName);
            }
            //上传文件
            this.client.putObject(bucketName, fileName, inputStream);
            //给文件授权,包含过期时间
            return this.authFile(fileName, bucketName, 1000);
        } finally {
            this.shutdown();
        }
    }

    /**
     * @param inputStream 待上传的文件流
     * @param fileName    文件名:最终保存到云端的文件名
     * @param bucketName  需要上传到的目标bucket
     */
    public String uploadFile(InputStream inputStream, String fileName, String bucketName, String endpoint) {
        try {
            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] 无法上传文件!Bucket不存在:" + bucketName);
            }
            //上传文件
            this.client.putObject(bucketName, fileName, inputStream);
            return "https://" + bucketName + "." + endpoint + "/" + fileName;
        } finally {
            this.shutdown();
        }
    }

    /**
     * 下载到本地
     *
     * @param fileName   需要下载的文件,填写不包含Bucket名称在内的文件完整路径
     * @param path       下载到本地的路径
     * @param bucketName 下载的目标bucket
     */
    public void downloadLocalFile(String fileName, String path, String bucketName) {
        try {
            if (!this.client.doesBucketExist(bucketName)) {
                throw new OssFileException("[阿里云OSS] 无法上传文件!Bucket不存在:" + bucketName);
            }
            // 下载Object到本地文件,并保存到指定的本地路径中。如果指定的本地文件存在会覆盖,不存在则新建。
            // 如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。
            this.client.getObject(new GetObjectRequest(bucketName, fileName), new File(path));
        } finally {
            this.shutdown();
        }
    }

    private void shutdown() {
        this.client.shutdown();
    }
}

3、由于我们自定义了Api,所以提供一个自定义的异常。

/**
 * 阿里云oss文件自定义异常
 *
 * @author ayue
 * @date 2021/9/26 11:50
 */
public class OssFileException extends RuntimeException {
    public OssFileException(String message) {
        super(message);
    }

    public OssFileException(String message, Throwable cause) {
        super(message, cause);
    }
}

4、因为需要把业务数据转为一个excel,但实际上我们在把数据转为 Workbook 的时候只需要获取到流就行了,所以需要把生成的 excel 转为流。

/**
 * excel工具类
 *
 * @author ayue
 * @date 2021/9/26 16:48
 */
public class ExcelUtils {

    /**
     * Workbook转为输入流
     * @param workbook
     * @return
     */
    public static InputStream workbookConvertorStream(Workbook workbook) {
        InputStream in = null;
        try {
            //临时缓冲区
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            //创建临时文件
            workbook.write(out);
            byte[] bookByteAry = out.toByteArray();
            in = new ByteArrayInputStream(bookByteAry);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return in;
    }
}

测试

这里准备一个虚假数据来测试。

实体类:

import cn.afterturn.easypoi.excel.annotation.Excel;

import java.io.Serializable;
import java.util.Date;

public class StudentEntity implements Serializable {
    /**
     * id
     */
    private String id;
    /**
     * 学生姓名
     */
    @Excel(name = "学生姓名", height = 20, width = 30, isImportField = "true_st")
    private String name;
    /**
     * 学生性别
     */
    @Excel(name = "学生性别", replace = {"男_1", "女_2"}, suffix = "生", isImportField = "true_st")
    private int sex;

    @Excel(name = "出生日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20)
    private Date birthday;

    @Excel(name = "进校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd")
    private Date registrationDate;

    public StudentEntity() {
    }

    public StudentEntity(String id, String name, int sex, Date birthday, Date registrationDate) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.birthday = birthday;
        this.registrationDate = registrationDate;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Date getRegistrationDate() {
        return registrationDate;
    }

    public void setRegistrationDate(Date registrationDate) {
        this.registrationDate = registrationDate;
    }
}

测试类:

public class TeatClient {

    public static void main(String[] args) {
        //模拟数据
        List<StudentEntity> list = new ArrayList<>();
        list.add(new StudentEntity("1", "张三", 18, new Date(), new Date()));
        list.add(new StudentEntity("2", "李四", 19, new Date(), new Date()));
        list.add(new StudentEntity("3", "王五", 20, new Date(), new Date()));
        //HSSF_.xls, XSSF_.xlsx;
        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("计算机一班学生", "学生", "HSSF"), StudentEntity.class, list);
        InputStream inputStream = ExcelUtils.workbookConvertorStream(workbook);
        AliyunOssApiClient client = new AliyunOssApiClient().init();
        String url = client.uploadFile(inputStream, SocialInsuranceAction.REGISTRATION.name().toLowerCase(), ".xls");
        System.out.println(url);
    }
}

测试结果:

返回一个文件连接,并且在阿里云控制台也能看到生成的文件。

https://wyh1995.oss-cn-hangzhou.aliyuncs.com/registration/20210926193659754.xls

axios oss上传 oss如何上传和下载文件_存储空间_05

注意事项

在实际操作过程中,你可以按照自己需求去实现自己的工具类,主要参考OssApi这个类,这里讲讲提供的uploadFileExpireduploadFile方法,其他的可自行查看,都有注释。

1、uploadFileExpired返回的是一个有过期时间的连接,并且是有权限才能访问的,在控制台详情可以看到:

axios oss上传 oss如何上传和下载文件_存储空间_06

其主要是通过OSSClient的授权方法实现的:

/**
 * 授权访问文件的URL
 *
 * @param fileName       待授权的文件名
 * @param bucketName     存储空间
 * @param expirationTime 授权失效时间,单位秒
 */
public String authFile(String fileName, String bucketName, long expirationTime) {
    try {
        if (!this.client.doesBucketExist(bucketName)) {
            throw new OssApiException("[阿里云OSS] 无法授权访问文件的URL!Bucket不存在:" + bucketName);
        }
        if (!this.client.doesObjectExist(bucketName, fileName)) {
            throw new OssApiException("[阿里云OSS] 文件授权失败!文件不存在:" + bucketName + "/" + fileName);
        }
        // 设置URL过期时间为1小时
        Date expiration = new Date(new Date().getTime() + expirationTime * 1000);
        // 生成URL
        return this.client.generatePresignedUrl(bucketName, fileName, expiration).toString();
    } finally {
        this.shutdown();
    }
}

2、uploadFile返回的是没有过期时间的连接,并且是开放权限的,任何人都可以访问,前提是你需要开启 Bucket权限:

axios oss上传 oss如何上传和下载文件_云存储_07

如果你在没有开启的情况下调用该方法,结果可能就是这样:

axios oss上传 oss如何上传和下载文件_阿里云_08

还有一个需要注意的是,上面每个方法执行完成之后都是把OSSClient给关闭了,即调用了OssClient.shutdown(),相当于连接关闭,所以你不能连续的执行不同方法,除非你在shutdown之前,不然会出现以下情况。

axios oss上传 oss如何上传和下载文件_阿里云_09