阿里云OSS简单上传、分片上传前后端功能实现

1,260 阅读1分钟

由于简单上传视频、大文件响应速度很慢,经常出现超时请求失败的问题,改写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应该都可以用上

结语

如果觉得有用,还请点个免费的赞吧~