文章目录
为解决服务端IO压力,将文件服务转移至阿里云OSS,先丢一个 阿里云OSS官网链接。 上传及下载流程为:将文件上传至OSS,并拿到文件对应的key。根据获取key,再从OSS获取文件临时URL(安全性考虑,加上了url有效期)。
突然想起好久没写博客了,正好上来记录一二~ 关于代码中的oss配置,想着写成一个统一配置bean,这样就不用在每一个使用的地方都进行设值,但由于本人太懒了等后期再倒腾吧~
使用手册
Step1:加入maven依赖及配置文件中配置秘钥等
Step2:定义配置bean与OSS工具类
Step3:定义UploadController和DownloadController
maven依赖及环境配置
- 在pom.xml中加入依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
- 在bootstraps.yml(或application.yml等)中配置阿里云秘钥等
alicloud:
access-key: xxxxx
secret-key: xxxx
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
定义配置bean及OSS工具类
- 声明配置bean
/**
* 阿里云OSS配置
*/
@Data
public class AliyunOssItem {
private String accessKey;
private String sceretKey;
private String endpoint;
/**
* 存储空间名称
*/
private String bucketName;
/**
* 上传目录
*/
private String uploadDir;
/**
*
* @param accessKey
* @param sceretKey
* @param endpoint
* @param bucketName 存储空间名称
* @param uploadDir 上传目录
*/
public AliyunOssItem(String accessKey, String sceretKey, String endpoint, String bucketName, String uploadDir) {
this.accessKey = accessKey;
this.sceretKey = sceretKey;
this.endpoint = endpoint;
this.bucketName = bucketName;
this.uploadDir = uploadDir;
}
/**
*
* @param accessKey
* @param sceretKey
* @param endpoint
* @param bucketName 存储空间名称
*/
public AliyunOssItem(String accessKey, String sceretKey, String endpoint, String bucketName) {
this.accessKey = accessKey;
this.sceretKey = sceretKey;
this.endpoint = endpoint;
this.bucketName = bucketName;
}
}
- OSS工具类
/**
* 阿里云OSS上传和下载文件工具
*/
@Slf4j
@UtilityClass
public class AliyunOssUtil {
/**
* 返回文件在OSS存储的key
* @param file 上传文件
* @param item OSS配置bean
* @param ossClient
* @return
*/
public static String upload2OSS(MultipartFile file, AliyunOssItem item) {
OSS ossClient = new OSSClientBuilder().build(item.getEndpoint(), item.getAccessKey(), item.getSceretKey());
String bucketName = item.getBucketName();
String fileName = file.getOriginalFilename();// 文件名
String uploadDir = item.getUploadDir();// 目录名
String uploadPath = "";// 保存文件路径名称
InputStream uploadInputStrem = null;
if (StringUtils.isNotEmpty(uploadDir)) {
uploadDir = uploadDir.substring(0, uploadDir.length()).replaceAll("\\\\", "/") + "/";
}
try {
ensureBucket(item.getBucketName(), ossClient);
// 获取上传文件后缀名
String fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
String md5 = MD5.getMessageDigest(file.getBytes());
uploadPath = String.format("%1$s%2$s%3$s", uploadDir, md5, fileSuffix);
// 创建上传Object的Metadata。ObjectMetaData是用户对该object的描述
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(file.getSize());
objectMetadata.setCacheControl("no-cache");
objectMetadata.setContentEncoding("utf-8");
objectMetadata.setContentType(getcontentType(file, fileSuffix));// 获取文件类型
objectMetadata.setContentDisposition("attachment;filename=" + fileName + fileSuffix);
uploadInputStrem = file.getInputStream(); // 文件输入流
// 上传文件
log.debug("正在上传文件到OSS...");
ossClient.putObject(bucketName, uploadPath, uploadInputStrem, objectMetadata);
log.debug("上传文件完成...");
return uploadPath;
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
try {
if (uploadInputStrem != null) {
uploadInputStrem.close();// 关闭文件流
}
} catch (IOException e) {
e.printStackTrace();
}
// 关闭OSSClient。
ossClient.shutdown();
}
return null;
}
/**
* 判断bucket存储空间是否已建立 若未建立,先创建一个Bucket
*/
private void ensureBucket(String bucketName, OSS ossClient) throws OSSException {
// 判断bucket是否存在
boolean exists = ossClient.doesBucketExist(bucketName);
if (!exists) {
// log.error("bucket不存在,新建当前bucket:{}",BUCKETNAME);
ossClient.createBucket(bucketName);
}
}
/**
* 根据key,从OSS上获取图片临时url
* @param item OSS配置
* @param key
* @param expiration 过期时间(单位s)
* @return
*/
public String downFromOSS(AliyunOssItem item, String key, int expiration) {
OSS ossClient = new OSSClientBuilder().build(item.getEndpoint(), item.getAccessKey(), item.getSceretKey());
try {
if (StringUtils.isNotEmpty(key)) {
// 设置URL过期时间
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
// 生成URL
URL url = ossClient.generatePresignedUrl(item.getBucketName(), key, expirationDate);
if (null != url) {
return url.toString();
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
//关闭
ossClient.shutdown();
}
return null;
}
/**
* 判断OSS服务文件上传时文件的contentType
*
* @param file 上传文件
* @param FilenameExtension 文件后缀
* @return String
*/
private String getcontentType(MultipartFile file, String FilenameExtension) {
if (FilenameExtension.equalsIgnoreCase(".bmp")) {
return "image/bmp";
}
if (FilenameExtension.equalsIgnoreCase(".gif")) {
return "image/gif";
}
if (FilenameExtension.equalsIgnoreCase(".jpeg") || FilenameExtension.equalsIgnoreCase(".jpg")
|| FilenameExtension.equalsIgnoreCase(".png") || FilenameExtension.equalsIgnoreCase(".jpz")) {
return "image/jpeg";
}
if (FilenameExtension.equalsIgnoreCase(".html") || FilenameExtension.equalsIgnoreCase(".htm")
|| FilenameExtension.equalsIgnoreCase(".hts")) {
return "text/html";
}
if (FilenameExtension.equalsIgnoreCase(".txt")) {
return "text/plain";
}
if (FilenameExtension.equalsIgnoreCase(".vsd")) {
return "application/vnd.visio";
}
if (FilenameExtension.equalsIgnoreCase(".pptx") || FilenameExtension.equalsIgnoreCase(".ppt")) {
return "application/vnd.ms-powerpoint";
}
if (FilenameExtension.equalsIgnoreCase(".docx") || FilenameExtension.equalsIgnoreCase(".doc")) {
return "application/msword";
}
if (FilenameExtension.equalsIgnoreCase(".xml")) {
return "text/xml";
}
if (FilenameExtension.equalsIgnoreCase(".xls")) {
return "application/vnd.ms-excel";
}
if (FilenameExtension.equalsIgnoreCase(".xlsx")) {
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
}
if (FilenameExtension.equalsIgnoreCase(".zip")) {
return "application/zip";
}
return file.getContentType();
}
定义UploadController和DownloadController
- 定义UploadController
/**
* 后台文件上传接口
*
*/
@Slf4j
@Controller
public class UploadController {
/** 存储空间名称 */
private static final String BUCKETNAME = "bucket-private";
private static final String ROOT_DIR = "root";
@Value("${spring.cloud.alicloud.access-key}")
private String accessKeyId;
@Value("${spring.cloud.alicloud.secret-key}")
private String sceretKey;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
/**
* 上传至OSS
* @param file
* @return
* @throws Exception
*/
@ResponseBody
@PostMapping({ "/upload2OSS"})
public ResponseVo<?> upload2OSS(@RequestParam(value = "file", required = false) MultipartFile file) throws Exception {
if (null == file || file.isEmpty()) {
throw new UploadFileNotFoundException(UploadResponseVo.Error.FILENOTFOUND);
}
try {
SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd");
String dir = fmt.format(new Date());
AliyunOssItem item = new AliyunOssItem(accessKeyId, sceretKey, endpoint, BUCKETNAME, ROOT_DIR + "/" + dir);
String url = AliyunOssUtil.upload2OSS(file, item);
if (!StringUtils.isEmpty(url)) {
log.debug("上传文件至OSS后的url为:" + url);
return ResultUtil.success("上传成功", url);
}
} catch (Exception e) {
log.error(String.format("UploadController.upload%s", e));
throw e;
}
return ResultUtil.error("上传失败!");
}
}
- 定义DownloadController
/**
* 后台文件下载接口
*
*/
@Slf4j
@Controller
public class DownloadController {
/** 存储空间名称 */
private static final String BUCKETNAME = "ak-web-private";
@Value("${spring.cloud.alicloud.access-key}")
private String accessKeyId;
@Value("${spring.cloud.alicloud.secret-key}")
private String sceretKey;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.expiration}")
private Integer expiration;
@ResponseBody
@PostMapping({ "/download"})
public ResponseVo<?> downloadFromOSS(String key) throws Exception {
try {
AliyunOssItem item = new AliyunOssItem(accessKeyId, sceretKey, endpoint, BUCKETNAME);
String fileTmpUrl = AliyunOssUtil.downFromOSS(item, key, expiration);
if (!StringUtils.isEmpty(fileTmpUrl)) {
return ResultUtil.success("下载成功", fileTmpUrl);
}
} catch (Exception e) {
log.error(String.format("DownloadController.downloadFromOSS%s", e));
throw e;
}
return ResultUtil.error("下载失败!");
}
}
下载接口优化为返回重定向oss路径
/**
* 后台文件下载接口
*
*/
@Slf4j
@Controller
public class DownloadController {
/** 存储空间名称 */
private static final String BUCKETNAME = "ak-web-private";
@Value("${spring.cloud.alicloud.access-key}")
private String accessKeyId;
@Value("${spring.cloud.alicloud.secret-key}")
private String sceretKey;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.expiration}")
private Integer expiration;
@PostMapping({ "/download"})
public String downloadFromOSS(String key) throws Exception {
try {
AliyunOssItem item = new AliyunOssItem(accessKeyId, sceretKey, endpoint, BUCKETNAME);
String fileTmpUrl = AliyunOssUtil.downFromOSS(item, key, expiration);
if (!StringUtils.isEmpty(fileTmpUrl)) {
return ResultUtil.success("下载成功", fileTmpUrl);
}
return "redirect:" + fileTmpUrl;
} catch (Exception e) {
log.error(String.format("DownloadController.downloadFromOSS%s", e));
throw e;
}
}
}
遇到的问题
The bucket you visit is not belong to you
在网上找到的解决方案有:
第一种解决办法:url中主域名后面增加bucket目录。
第二种解决办法:在bucket管理内增加安全访问域名
但是我遇到的问题是由创建的OSS上传路径中携带了"//" 造成的~~
从OSS获取临时url是http协议的
查看ClientConfiguration源码,发现默认protocol是HTTP。
public class ClientConfiguration {
...
protected Protocol protocol = Protocol.HTTP;
}
但是因为客户端有微信小程序,规定访问的资源必须为https协议的,所以需要将临时url变更为https协议
解决方案:知道是配置的锅,当然得从配置上下功夫喽,所以创建OSSClient时加入配置!
/**
* 获取OSSClient
* @param item
* @return
*/
private OSS getOssClient(AliyunOssItem item) {
ClientBuilderConfiguration config = new ClientBuilderConfiguration();
config.setProtocol(Protocol.HTTPS);
return new OSSClientBuilder().build(item.getEndpoint(), item.getAccessKey(), item.getSceretKey(), config);
}