1、引入依赖
<dependencies>
<!-- 阿里云oss依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.9.1</version>
</dependency>
<!-- 日期工具栏依赖 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
2、在yml中进行配置
aliyun:
endpoint: oss-cn-beijing.aliyuncs.com # 地域节点
keyid: # AccessKey
keysecret: # AccessSecret
bucketname: ssyx-hxd # 桶名称
3、代码
controller
package com.atguigu.ssyx.product.controller;
import com.atguigu.ssyx.common.result.Result;
import com.atguigu.ssyx.product.service.FileUploadService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* @BelongsProject: guigu-ssyx-parent
* @BelongsPackage: com.atguigu.ssyx.product.controller
* @Author: 栋哥
* @CreateTime: 2024-03-19 22:03
* @Description: TODO
* @Version: 1.0
*/
@RestController
@RequestMapping("/admin/product")
@Api(tags = "文件上传")
@CrossOrigin
public class FileUploadController {
@Autowired
private FileUploadService fileUploadService;
@ApiOperation("图片上传")
@PostMapping("fileUpload")
public Result fileUpload(MultipartFile file){
String url = fileUploadService.uploadFile(file);
return Result.ok(url);
}
}
service
package com.atguigu.ssyx.product.service;
import org.springframework.web.multipart.MultipartFile;
public interface FileUploadService {
String uploadFile(MultipartFile file);
}
impl
注意事项:
- 文件名需要进行重命名,防止文件覆盖。
- 可以根据日期进行文件名分组处理。
package com.atguigu.ssyx.product.service.impl;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.atguigu.ssyx.product.service.FileUploadService;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
/**
* @BelongsProject: guigu-ssyx-parent
* @BelongsPackage: com.atguigu.ssyx.acl.service.impl
* @Author: 栋哥
* @CreateTime: 2024-03-19 22:04
* @Description: TODO
* @Version: 1.0
*/
@Service
public class FileUploadServiceImpl implements FileUploadService {
@Value("${aliyun.endpoint}")
private String endPoint;
@Value("${aliyun.keyid}")
private String accessKey;
@Value("${aliyun.keysecret}")
private String secreKey;
@Value("${aliyun.bucketname}")
private String bucketName;
@Override
public String uploadFile(MultipartFile file) {
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
// 重命名防止重名覆盖
String objectName = file.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
objectName = uuid + objectName;
// 对上传的文件根据年月日进行分组处理
String timeUrl = new DateTime().toString("yyyy/MM/dd");
objectName = timeUrl + "/" +objectName;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKey,secreKey);
try {
// 得到文件输入流
InputStream inputStream = file.getInputStream();
// 创建PutObjectRequest对象。
// 1.buck名称 2.上传文件路径 + 名称 3.文件输入流
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
//设置有响应值
putObjectRequest.setProcess("true");
// 创建PutObject请求。
PutObjectResult result = ossClient.putObject(putObjectRequest);
// 解决响应中文乱码问题
return URLDecoder.decode(result.getResponse().getUri(), StandardCharsets.UTF_8.toString());
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return null;
}
}
优化
方案一
创建线程池,在实现类方法上加==@Async==注解。
结果:失败
原因:
1. 首先前端传递过来的文件,会存储到临时文件夹中,即类似这样的一个路径。C:\Users\86189\logs\csp\upload_4330f731_2ae0_4955_a484_5862fd4530a2_00000009.tmp (系统找不到指定的文件。)
开启异步执行的时候,主线程结束,临时文件就会被清空了,子线程获取不到临时文件,所以会报错。
解决方案:
1. 在开启异步之前,需要把MultipartFile(file)转换为流来进行操作即可file.getInputStream()。
方案二
在controller中获取到图片的 inputStream 流,将流对象和重命名后的文件名,传递给方法。这样就能在开启@Async注解前获取到inputStream
结果:失败
原因: 需要获取结果返回值,但使用@Async开启子线程,主线程已经返回给前端,所以获取不到返回的图片路径。
解决方案:使用Future返回值 调用get()方法,阻塞主线程。返回给前端对象。(不知道为啥,没起作用,所以才有最终方案)
最终方案
使用 CompletableFuture 调用get()方发阻塞主线程。等待子线程返回的结果。
@ApiOperation("图片上传")
@PostMapping("fileUpload")
public Result fileUpload(MultipartFile file){
try {
//启动异步线程
CompletableFuture<String> uCompletableFuture = CompletableFuture.supplyAsync(() -> {
return fileUploadService.uploadFile(file);
});
// get() 会阻塞主线程,等待异步方法的执行结果。
return Result.ok(uCompletableFuture.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
}