什么是OSS
我们可以理解为就是一个资源服务器,在这之前我也尝试过Nginx当静态资源服务器,但效果比较一般,为什么选择阿里云OSS,只是因为最近刚好公司用到了,所以就接入了,还有其他的比如七牛云,腾讯云等等。
创建 Bucket
Bucket 就是存储空间,我们的文件放在那里。具体操作如下:
1、点击进入阿里云OSS,在 Bucket 列表创建自己的存储空间。
2、填写空间名,选择地域节点等操作。
我们的存储空间创建好了,下一步就是集成到我们的项目当中。
项目集成
在阿里云的官方文档中提供了不同语言SDK示例,可自行参考。
场景
在操作之前,先讲一讲我使用的场景。
在我负责的模块需要提供一个上传文件的服务,从消息队列中去读取数据,然后把数据转为 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
注意事项
在实际操作过程中,你可以按照自己需求去实现自己的工具类,主要参考OssApi
这个类,这里讲讲提供的uploadFileExpired
和uploadFile
方法,其他的可自行查看,都有注释。
1、uploadFileExpired
返回的是一个有过期时间的连接,并且是有权限才能访问的,在控制台详情可以看到:
其主要是通过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权限:
如果你在没有开启的情况下调用该方法,结果可能就是这样:
还有一个需要注意的是,上面每个方法执行完成之后都是把OSSClient
给关闭了,即调用了OssClient.shutdown()
,相当于连接关闭,所以你不能连续的执行不同方法,除非你在shutdown
之前,不然会出现以下情况。