由于简单上传视频、大文件响应速度很慢,经常出现超时请求失败的问题,改写OSS文档提供的例子,写成适合前后端联调的接口来使用,如有不足还请指教。
准备工作
首先第一步我们得先去阿里云创建一个桶,也是存放文件的地方。
创建完在列表页点进刚创建的桶,查看概览
现在我们得到两个数据 endPoint bucketName
还有两个accessKeyId accessKeySecret需要再AccessKey管理获取,在之前的短信功能有讲过这里不再赘述。 SpringBoot接入阿里云短信服务,实现登录验证功能
后端部分
整个流程如下
在配置文件中配置刚刚获取到的四条信息
oss:
endPoint: https://oss-cn-beijing.aliyuncs.com
accessKeyId: LTAIxxxxxxxxxx2Ej
accessKeySecret: Guyxxxxxxxxxxxx3XG
bucketName: xxxxxxx
pom引入
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
开始编写工具类
import cn.hutool.core.bean.BeanUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.benjamin.blog.Vo.FileVo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
@Data
@Component
@ConfigurationProperties(prefix = "oss")
public class AliOSSUtils {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
public HashMap getUploadId(String f){
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
String fileName = getFileName(f);
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileName);
// 如果需要在初始化分片时设置请求头,请参考以下示例代码。
HashMap<String, String> map = new HashMap<>();
map.put("fileName", fileName);
map.put("uploadId", ossClient.initiateMultipartUpload(request).getUploadId());
return map;
}
public Map chunkUpload(String uploadId,int partNumber,MultipartFile partFile,String fileName){
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
InputStream inputStream = null;
try {
inputStream = partFile.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(fileName);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(inputStream);
uploadPartRequest.setPartSize(partFile.getSize());
uploadPartRequest.setPartNumber(partNumber);
UploadPartResult result = ossClient.uploadPart(uploadPartRequest);
// 关闭输入流和OSS客户端
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
ossClient.shutdown();
// 上传成功后,可以返回分片的ETag给前端,用于后续的分片合并
Map<String, Object> map = BeanUtil.beanToMap(result.getPartETag());
map.put("partCRC", result.getPartETag().getPartCRC().toString());
return map;
}
public HashMap completeUpload(FileVo fileVo){
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
List<PartETag> ownPartETag = new ArrayList<>();
fileVo.getPartETags().forEach(
partETag -> {
PartETag etag = new PartETag(partETag.getPartNumber(), partETag.geteTag(), partETag.getPartCRC(), partETag.getPartSize());
ownPartETag.add(etag);
}
);
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, fileVo.getFileName(), fileVo.getUploadId(), ownPartETag);
CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
ossClient.shutdown();
HashMap<String, String> map = new HashMap<>();
map.put("url", completeMultipartUploadResult.getLocation());
return map;
}
/**
* 实现上传图片到OSS
*/
public HashMap upload(MultipartFile file) throws IOException {
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
InputStream inputStream = file.getInputStream();
String fileName = getFileName(file);
//上传文件到 OSS
ossClient.putObject(bucketName, fileName, inputStream);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
HashMap<String, String> map = new HashMap<>();
map.put("url", url);
return map;
}
public String getFileName(MultipartFile file) throws IOException{
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
// 增加文件结构,按年月日区分文件
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(new Date());
return "image"+ "/" + date + "/" + fileName;
}
public String getFileName(String f){
// 增加文件结构,按年月日区分文件
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(new Date());
return "image"+ "/" + date + "/" + f;
}
}
首先生成uploadId,再进行分片上传,最后将分片进行合并完成上传。
Controller层
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {
@Autowired
private AliOSSUtils aliOSSUtils;
@PostMapping("/getUploadId")
public Result getUploadId(@RequestPart("fileName") String fileName){
return Result.success(aliOSSUtils.getUploadId(fileName));
}
@PostMapping("/chunkUpload")
public Result chunkUpload(@RequestParam("file")MultipartFile file,
@RequestParam("partNumber") Integer partNumber,
@RequestParam("uploadId") String uploadId,
@RequestParam("fileName") String fileName
){
return Result.success(aliOSSUtils.chunkUpload(uploadId,partNumber,file,fileName));
}
@PostMapping("/completeUpload")
public Result completeUpload(@RequestBody FileVo fileVo){
return Result.success(aliOSSUtils.completeUpload(fileVo));
}
}
前端部分
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB
export function useUpload() {
const [fileList, setFileList] = useState<UploadFile[]>([]);
const uploadFile = () => { };
const uploadFileChunk = (file: File) => {
// eslint-disable-next-line no-async-promise-executor
return new Promise<{ url: string }>(async (resolve) => {
const fileSize = file.size;
const form = new FormData();
form.append('file', file);
const res = await getUploadId(form);
const { uploadId, fileName } = res;
const totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
const chunkList = [];
// 上传文件分片
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = (i + 1) * CHUNK_SIZE;
const chunk = file.slice(start, end); // 切割分片
// 创建FormData对象,用于传输分片数据
const formData = new FormData();
formData.append('file', chunk);
formData.append('uploadId', uploadId);
formData.append('fileName', fileName);
formData.append('partNumber', (i + 1) as any);
chunkList.push(chunkUpload(formData));
}
const partETags = (await Promise.all(chunkList));
const params = {
uploadId,
fileName,
partETags
};
completeUpload(params).then((url) => {
resolve(url);
});
});
};
return {
uploadFile,
uploadFileChunk,
fileList,
setFileList
};
}
前端这里写成了hooks,React、Vue应该都可以用上
结语
如果觉得有用,还请点个免费的赞吧~