参考资料:Java操作阿里云OSS操作官方文档
学会看文档,并实际运用也是一种习惯和技能
下面就来简单入门一下,用当下比较热门的Springboot 去操作阿里云OSS文件存储。
1.需求
(没踩过下面的坑的小伙伴可以直接跳过这一章节)
问题简述
首先,我在之前自己做一些开源小项目案例中遇到一些文件上传下载的问题,比如在本机文件上传和下载都可以正常使用,通过将文件上传到Springboot项目的根目录下,按日期分文件夹,文件访问也很方便,可以直接返回文件相对路径地址,并直接可以访问。
问题
然而,这种方式存在弊端,因为当项目打包(jar包)部署阿里云学生机后,出现类似io.NotFoundException...(No Such Directory)
的问题,,而如果打war包部署到tomcat则没问题,可以正常使用,经过排查很久,找出问题所在:
因为jar打包封装后是不能改变其内部目录结构的,也就是说,按日期分类的文件上传文件夹,如果当需要创建新日期的文件夹的时候,是无法在jar包中新增文件夹的,这时候就会出现IO异常问题。而对于放在tomcat中的war包,当tomcat运行的时候会自动解压war包,其在服务器上是存在真实路径的。
解决方案
- 方案一:我在网上找了一种方法,是通过打完jar包部署后,给springboot项目static下的文件上传文件夹单独分离出来(相当于是以相对路径换绝对路径),访问的时候直接相当通过服务器上和jar包同级目录下新建一个文件上传文件夹。
- 方案二:直接将文件上传到服务器指定路径下的文件上传位置,这种方式也相当于直接使用绝对路径。
- 方案三:在服务器上使用FastDFS和Nginx搭建分布式文件存储,这种方式比较复杂,而且学生及本来内存和带宽就小,在自己电脑的虚拟机可以试试这种方案,还是挺好用的,学生服务器就算了。
- 方案四:就是直接将文件上传到阿里云OSS文件存储系统上
2. 阿里云OSS购买和配置
这个比较简单,给大家推荐一篇博文自己了解下阿里云oss购买和配置,也可以参考阿里云OSS官方文档。
3. Springboot操作OSS
创建一个spring boot项目,pom文件需要引入依赖:
pom.xml
<dependencies>
<!-- 个人版本踩坑: 不加这个依赖的话,当在配置类中 使用@ConfigurationProperties(prefix = "aliyun")注解时, 我这个版本的spring boot会提示有问题 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- swagger ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- thymeleaf 可不加,个人习惯性引入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 热部署,看个人习惯 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 小辣椒插件,推荐使用,可以节省javaBean的setter/getter,还可以使用链式调用 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- fastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- aliyun-oos -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<!-- apache-common-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
包结构很简单:
我们使用自己添加的application-aliyun-oss.properties配置文件,去配置OSS相关信息,之所以不在application.yml 中配置,看个人习惯了,因为自定义的配置属性还是提出来配比较好,没必要所有的都配到application.yml(properties)中去。
application-aliyun-oss.properties
# 文件上传大小限制
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=1000MB
# 地域节点
aliyun.endPoint=oss-cn-beijing.aliyuncs.com
# Bucket 域名
aliyun.urlPrefix=http://csp-xxxx.oss-cn-beijing.aliyuncs.com/
# accessKey Id
aliyun.accessKeyId=LTAI4XXXXXXXzqD1saGFZ
# accessKey Secret
aliyun.accessKeySecret=2WjxNXXXXXXXX4f2bREc
# 你的Bucket名称
aliyun.bucketName=csp-xxxx
# 目标文件夹
aliyun.fileHost=files
config包下的相关配置类
AliyunOssConfig.java
// 声明配置类,放入Spring容器
@Configuration
// 指定配置文件位置
@PropertySource(value = { "classpath:application-aliyun-oss.properties"})
// 指定配置文件中自定义属性前缀
@ConfigurationProperties(prefix = "aliyun")
@Data// lombok
@Accessors(chain = true)// 开启链式调用
public class AliyunOssConfig {
private String endPoint;// 地域节点
private String accessKeyId;
private String accessKeySecret;
private String bucketName;// OSS的Bucket名称
private String urlPrefix;// Bucket 域名
private String fileHost;// 目标文件夹
// 将OSS 客户端交给Spring容器托管
@Bean
public OSS OSSClient() {
return new OSSClient(endPoint, accessKeyId, accessKeySecret);
}
}
Swagger2Config.java
@Configuration
@EnableSwagger2// 开启swagger2
public class Swagger2Config {
@Bean
public Docket webApiConfig() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
private ApiInfo webApiInfo() {
return new ApiInfoBuilder()
.title("SpringBoot整合OSS-API文档")
.description("阿里云OSS-文件上传下载测试")
.version("1.0")
.contact(new Contact("CSP", "https://blog.csdn.net/weixin_43591980", ""))
.build();
}
}
定义一个关于执行状态结果的枚举类
public enum StatusCode {
SUCCESS("success",200),ERROR("error",500);
private String msg;
private Integer code;
StatusCode(String msg,Integer code){
this.msg = msg;
this.code = code;
}
StatusCode(Integer code){
this.code = code;
}
StatusCode(String msg){
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
service层
在service使用ossClient操作阿里云OSS,进行上传、下载、删除、查看所有文件等操作,同时可以将图片的url进行入库操作:
FileUploadService.java
@Service("fileUploadService")
public class FileUploadService {
// 允许上传文件(图片)的格式
private static final String[] IMAGE_TYPE = new String[]{ ".bmp", ".jpg",
".jpeg", ".gif", ".png"};
@Autowired
private OSS ossClient;// 注入阿里云oss文件服务器客户端
@Autowired
private AliyunOssConfig aliyunOssConfig;// 注入阿里云OSS基本配置类
public String upload(MultipartFile uploadFile) {
// 获取oss的Bucket名称
String bucketName = aliyunOssConfig.getBucketName();
// 获取oss的地域节点
String endpoint = aliyunOssConfig.getEndPoint();
// 获取oss的AccessKeySecret
String accessKeySecret = aliyunOssConfig.getAccessKeySecret();
// 获取oss的AccessKeyId
String accessKeyId = aliyunOssConfig.getAccessKeyId();
// 获取oss目标文件夹
String filehost = aliyunOssConfig.getFileHost();
// 返回图片上传后返回的url
String returnImgeUrl = "";
// 校验图片格式
boolean isLegal = false;
for (String type : IMAGE_TYPE) {
if (StringUtils.endsWithIgnoreCase(uploadFile.getOriginalFilename(), type)) {
isLegal = true;
break;
}
}
if (!isLegal) { // 如果图片格式不合法
return StatusCode.ERROR.getMsg();
}
// 获取文件原名称
String originalFilename = uploadFile.getOriginalFilename();
// 获取文件类型
String fileType = originalFilename.substring(originalFilename.lastIndexOf("."));
// 新文件名称
String newFileName = UUID.randomUUID().toString() + fileType;
// 构建日期路径, 例如:OSS目标文件夹/2020/10/31/文件名
String filePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
// 文件上传的路径地址
String uploadImgeUrl = filehost + "/" + filePath + "/" + newFileName;
// 获取文件输入流
InputStream inputStream = null;
try {
inputStream = uploadFile.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
ObjectMetadata meta = new ObjectMetadata();
meta.setContentType("image/jpg");
//文件上传至阿里云OSS
ossClient.putObject(bucketName, uploadImgeUrl, inputStream, meta);
// 获取文件上传后的图片返回地址
returnImgeUrl = "http://" + bucketName + "." + endpoint + "/" + uploadImgeUrl;
return returnImgeUrl;
}
public String download(String fileName, HttpServletResponse response) throws UnsupportedEncodingException {
// // 设置响应头为下载
// response.setContentType("application/x-download");
// // 设置下载的文件名
// response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
// response.setCharacterEncoding("UTF-8");
// 文件名以附件的形式下载
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 获取oss的Bucket名称
String bucketName = aliyunOssConfig.getBucketName();
// 获取oss目标文件夹
String filehost = aliyunOssConfig.getFileHost();
// 日期目录
// 注意,这里虽然写成这种固定获取日期目录的形式,逻辑上确实存在问题,但是实际上,filePath的日期目录应该是从数据库查询的
String filePath = new DateTime().toString("yyyy/MM/dd");
String fileKey = filehost + "/" + filePath + "/" + fileName;
// ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
OSSObject ossObject = ossClient.getObject(bucketName, fileKey);
try {
// 读取文件内容。
InputStream inputStream = ossObject.getObjectContent();
BufferedInputStream in = new BufferedInputStream(inputStream);// 把输入流放入缓存流
ServletOutputStream outputStream = response.getOutputStream();
BufferedOutputStream out = new BufferedOutputStream(outputStream);// 把输出流放入缓存流
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
if (out != null) {
out.flush();
out.close();
}
if (in != null) {
in.close();
}
return StatusCode.SUCCESS.getMsg();
} catch (Exception e) {
return StatusCode.ERROR.getMsg();
}
}
public String delete(String fileName) {
// 获取oss的Bucket名称
String bucketName = aliyunOssConfig.getBucketName();
// 获取oss的地域节点
String endpoint = aliyunOssConfig.getEndPoint();
// 获取oss的AccessKeySecret
String accessKeySecret = aliyunOssConfig.getAccessKeySecret();
// 获取oss的AccessKeyId
String accessKeyId = aliyunOssConfig.getAccessKeyId();
// 获取oss目标文件夹
String filehost = aliyunOssConfig.getFileHost();
// 日期目录
// 注意,这里虽然写成这种固定获取日期目录的形式,逻辑上确实存在问题,但是实际上,filePath的日期目录应该是从数据库查询的
String filePath = new DateTime().toString("yyyy/MM/dd");
try {
// 建议在方法中创建OSSClient 而不是使用@Bean注入,不然容易出现Connection pool shut down
OSSClient ossClient = new OSSClient(endpoint,
accessKeyId, accessKeySecret);
// 根据BucketName,filetName删除文件
// 删除目录中的文件,如果是最后一个文件fileoath目录会被删除。
String fileKey = filehost + "/" + filePath + "/" + fileName;
ossClient.deleteObject(bucketName, fileKey);
try {
} finally {
ossClient.shutdown();
}
System.out.println("文件删除!");
return StatusCode.SUCCESS.getMsg();
} catch (Exception e) {
e.printStackTrace();
return StatusCode.ERROR.getMsg();
}
}
}
controller层
controller提供测试接口
@Api(description = "阿里云OSS文件上传、下载、删除API")
@RequestMapping("api/pri/file")
@RestController
public class OssFileController {
@Autowired
private FileUploadService fileUploadService;
@ApiOperation(value = "文件上传")
@PostMapping("upload")
public JSONObject upload(@RequestParam("file") MultipartFile file) {
JSONObject jsonObject = new JSONObject();
if (file != null) {
String returnFileUrl = fileUploadService.upload(file);
if (returnFileUrl.equals("error")) {
jsonObject.put("error", "文件上传失败!");
return jsonObject;
}
jsonObject.put("success", "文件上传成功!");
jsonObject.put("returnFileUrl", returnFileUrl);
return jsonObject;
} else {
jsonObject.put("error", "文件上传失败!");
return jsonObject;
}
}
@ApiOperation(value = "文件下载")
@GetMapping(value = "download/{fileName}")
public JSONObject download(@PathVariable("fileName") String fileName, HttpServletResponse response) throws Exception {
JSONObject jsonObject = new JSONObject();
String status = fileUploadService.download(fileName, response);
if (status.equals("error")) {
jsonObject.put("error", "文件下载失败!");
return jsonObject;
} else {
jsonObject.put("success", "文件下载成功!");
return jsonObject;
}
}
@ApiOperation(value = "文件删除")
@GetMapping("/delete/{fileName}")
public JSONObject DeleteFile(@PathVariable("fileName") String fileName) {
JSONObject jsonObject = new JSONObject();
String status = fileUploadService.delete(fileName);
if (status.equals("error")) {
jsonObject.put("error", "文件删除失败!");
return jsonObject;
} else {
jsonObject.put("success", "文件删除成功!");
return jsonObject;
}
}
}
4.运行项目测试API接口
本机访问:http://localhost:8083/swagger-ui.html
测试上传:
结果如图:
如果说明上传成功,我们来看一下这个链接能不能访问到:
上传成功!
如果文章对您有帮助,三连一波万分感谢!