SpringBoot项目中集成阿里云对象存储服务SDK实现文件上传
0 引言
随着社交媒体、短视频的流行,企业中对图片、文档及短视频文件的上传需求越来越多,要求上传质量和体验也越来越高。之前,一些小公司可能会选择把客户上传后的文件存储在tomcat服务器中,或者搭建一个fastdfs文件服务器专门用于存储客户上传的文件,目前也开源市场上有流行的文件上传服务如minio供开发者选择使用。
把文化存储在tomcat服务器中时间一长后者用户上传的文件越来越多时无疑会造成服务器越累越多的服务器硬盘资源,无疑会拖累项目部署的服务器,造成宕机也有可能。而搭建fastdfs文件服务器工作量比较大、学习操作文件上传下载的API学习成本也比较高。而开源的minio服务虽然也是一个非常不错的选择,但是毕竟是开源的,没有专业人员负责维护,一旦上线后出现棘手的Bug的话需要开发人员自己去定位和修复bug。个人项目学习选择开源的minio服务作为文件服务器没问题,但是大公司一般不会作为第一选择。
随着阿里云对象存储服务的兴起,越来越多的企业选择使用对象存储服务来存储自己项目中的图片、文档和短视频。阿里云对象存储服务不仅价格实惠(每月只需5元)、而且提供了多种常用语言(如java、GO、python、PHP和Node.js)的API,上手也非常简单。本文就提供基于Java语言的SDK在Spring-Boot项目中实现文件上传到阿里云对象存储服务中的实战demo,希望对读者朋友们在面对工作中的开发任务时能有一定的帮助。
说明:本文以之前在本地启动成功过的Jeecg-boot项目为基础,具体可参考笔者之前发布过的文章改造jeecg-boot项目,解决启动报错,跑通开发环境!
1 引入阿里云对象存储服务SDK依赖
这里需要在Spring-Boot项目的Pom文件中引入阿里云的镜像仓库
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun Repository</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
引入的aliyun-sdk-oss为3.6.0版本,读者如果在IDEA中通过依赖下载不了,也可至阿里云maven镜像仓库通过文件搜索把jar包下载下来
然后在IDEA中点击File->Project Structure->Modules->Dependencies面板下面右上方的“+”选择第一项"Jars or directories"将Jar包导入到模块项目中依赖库中
也可以将下载下来的aliyun-sdk-oss-3.6.0-jar
包放到本地磁盘后,在jar包所在盘符打开dos命令窗口,执行以下mvn命令将jar包安装到本地maven仓库
mvn install:install-file -Dfile=aliyun-sdk-oss-3.6.0-jar -DgroupId=com.aliyun.oss -DartifactId=aliyun-sdk-oss -Dversion=3.6.0 -Dpackaging=jar
2 application.yml
配置文件中配置ak/sk及endPoint等信息
jeecg :
oss:
endpoint: oss-cn-guangzhou.aliyuncs.com #oss-cn-beijing.aliyuncs.com
accessKey: LT************C6f
secretKey: JR**********Fv3M
bucketName: jeecgboot2021
要拿到以上信息需要去阿里云控制台购买OSS服务,然后创建用户并授予用户想要的角色和权限,具体可参考我上一篇文章改造jeecg-boot项目,解决启动报错,跑通开发环境中**1.6 开通阿里云对象存储服务(OOS)**部分的内容。
3 配置OSSClient对象Bean及对象存储服务组件
(1) OSSClient对象Bean配置
在jeecg-boot-base-common模块项目下新建org.jeecg.common.configuration
包,并在该包下新建ThirdPartyClientBeansConfig
配置类
package org.jeecg.common.configuration;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ThirdPartyClientBeansConfig {
@Value("${jeecg.oss.endpoint}")
private String endPoint;
@Value("${jeecg.oss.accessKey}")
private String accessKeyId;
@Value("${jeecg.oss.secretKey}")
private String accessKeySecret;
@Bean
public OSSClient ossClient(){
OSSClient ossClient = new OSSClient(endPoint,
new DefaultCredentialProvider(accessKeyId,accessKeySecret),
new ClientConfiguration());
return ossClient;
}
}
(2)实现文件上传OSSClientService
组件配置
同样在org.jeecg.common.configuration
包下新建OSSClientService
类,通过注入OSSClient
bean实例实现文件上传业务逻辑
package org.jeecg.common.configuration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.PutObjectResult;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.oConvertUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDateTime;
@Service
public class OSSClientService {
@Autowired
private OSSClient ossClient;
@Value("${jeecg.oss.bucketName}")
private String bucketName;
@Value("${jeecg.oss.endpoint}")
private String endPoint;
private static final Logger logger = LoggerFactory.getLogger(OSSClientService.class);
/**
* 文件上传至阿里云OSS云服务器
* @param file 上传文件
* @param fileDir 上传目录
* @param customBucket 自定义桶名
* @return oss 中的相对文件路径
*/
public String upload(MultipartFile file, String fileDir, String customBucket){
StringBuilder fileUrl = new StringBuilder();
String newBucket = bucketName;
if(oConvertUtils.isNotEmpty(customBucket)){
newBucket = customBucket;
}
//判断桶是否存在,不存在则创建桶
if(!ossClient.doesBucketExist(newBucket)){
ossClient.createBucket(newBucket);
}
// 获取文件名
String orgName = file.getOriginalFilename();
orgName = CommonUtils.getFileName(orgName);
//新文件名为原文件连上时间戳字符串
LocalDateTime now = LocalDateTime.now();
String dateTimeStr = DateUtils.getLocalDateTimeStr(now,"yyyyMMddHHmmss");
String fileName = orgName.substring(0, orgName.lastIndexOf(".")) + "_" + dateTimeStr + orgName.substring(orgName.indexOf("."));
if (!fileDir.endsWith("/")) {
fileDir = fileDir.concat("/");
}
fileUrl = fileUrl.append(fileDir + fileName);
String downloadUrl = "https://" + newBucket + "." + endPoint + "/" + fileUrl;
try {
PutObjectResult result = ossClient.putObject(newBucket, fileUrl.toString(), file.getInputStream());
// 设置权限(公开读)
ossClient.setBucketAcl(newBucket, CannedAccessControlList.PublicRead);
if(result!=null){
logger.info("------OSS文件上传成功------" + fileUrl);
}
} catch (IOException e) {
logger.error("上传文件失败",e);
return null;
}
return downloadUrl;
}
}
以上DateUtils#getLocalDateTimeStr
为笔者在原DateUtils
工具类中新添加的方法,具体实现如下:
/**
* 本地日期时间转日期格式字符串
* @param localDateTime
* @param pattern
* @return dateFormat
*/
public static String getLocalDateTimeStr(LocalDateTime localDateTime,String pattern){
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
String dateFormat = localDateTime.format(dateTimeFormatter);
return dateFormat;
}
4 完成文件上传控制器接口方法
在jeecg-boot-module-system
子项目下org.jeecg.modules.system.controller.SysUploadController
类中添加文件上传至阿里云对象存储服务接口方法
@Slf4j
@RestController
@RequestMapping("/sys/upload")
public class SysUploadController {
//...省略类中原有其他属性和方法
@Autowired
private OSSClientService ossClientService;
@PostMapping(value = "/uploadOss")
public Result<String> ossClientUpload(HttpServletRequest request,@RequestParam(name="fileDir") String fileDir){
Result<String> result = new Result<>();
//如果通过前端 enctype="multipart/form-data"的form表单提交请求,则可以直接使用MultipartFile接收上传文件,这里因为使用postman测试所以使用转换后的MultipartHttpServletRequest类对象获取请求中的文件对象
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
MultipartFile file = multipartRequest.getFile("file");// 获取上传文件对象
String downloadUrl = ossClientService.upload(file,fileDir,null);
log.info("downloadUrl={}",downloadUrl);
if(downloadUrl==null){
result.setCode(CommonConstant.SC_INTERNAL_SERVER_ERROR_500);
result.setMessage("上传文件失败");
}else{
result.setCode(CommonConstant.SC_OK_200);
result.setMessage("上传成功");
result.setResult(downloadUrl);
}
return result;
}
}
说明:如果读者是单独新建一个spring-boot-web项目的话以上配置类可以写在项目对应的configuration、service和controller对应的包中即可
5 测试效果体验
在本地启动jeecg-boot项目方法之前需要事先在启动mysql服务和redis服务,然后在IDEA中以debug的方式启动
JeecgApplication
启动类(如果读者是单独新建一个spring-boot-web项目的话则无需事先启动Mysql和redis服务)
(1)放开接口权限验证拦截
为了方便直接在postman上测试文件上传效果,需要先放开对这个接口的权限验证拦截
在jeecg-boot-mudule-system
子项目中的org.jeecg.config.ShiroConfig
类中的public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager)
方法中添加下面这行代码
filterChainDefinitionMap.put("/sys/upload/uploadOss","anon"); //oss上传文件,postman测试时放开
(2)给阿里云对象存储服务管理用户授予操作桶的读写权限
如果通过endPoint、accessKeyId和secretAccessKey等参数实例化OSSClient
bean对象后,其对应用户没有写存储桶的权限的话,上传文件过程中会直接报下面这种访问存储桶被拒绝的异常,导致上传文件失败
解决的办法是: 在阿里云控制台中的对象存储->{用户}->权限管理->访问控制RAM->点击前往控制台,在RAM控制/用户界面点击已有的用户进入如下图所示的界面
在目标用户(项目accessKeyId对应的用户)一行点击右侧的添加权限按钮,给授权主体添加AliyunOSSFullAccess权限策略,然后点击下面的确定按钮完成给用户权限策略的添加
这样在文件上传的过程中就不会报权限被拒绝的异常了。
(3)postman上传文件测试
为了方便测试效果这里使用post对文件上传接口进行测试
注意请求的Body参数类型为form-data,file参数类型为File,然后点击右侧的“Select Files"完成选择本地文件上传
上传成功后的返回信息中result参数为文件上传成功后的下载地址
进入阿里云控制台中的对象存储/{用户}/文件管理界面,点击右侧的文件夹myFile即可看到上传成功的文件
以上是笔者利用写的这个接口通过postman调用将一些附件上传到阿里云对象存储服务后的效果图。
全文完,本文首发个人微信公众好“阿福谈java技术栈”。更多实战文章,请读者关注笔者的个人微信公众号。
声明:原创不易,转载请贴上原创博客地址和作者,谢谢!