获取到文件下载输入流

后端

CosManager.java

       /**
     * 下载对象
     *
     * @param key 唯一键
     * @return 文件的字节数组
     */
    public byte[] getObject(String key)throws CosClientException, IOException {
        
      // 调用 COS 接口之前必须保证本进程存在一个 COSClient 实例,如果没有则创建
        COSClient cosClient = createCOSClient();
      
      //下载文件
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, key);
        COSObjectInputStream cosObjectInput = null;

        try {
            COSObject cosObject = cosClient.getObject(getObjectRequest);
            cosObjectInput = cosObject.getObjectContent();
        } catch (CosServiceException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载失败");
        }


        // 处理下载到的流
        // 这里是直接读取,按实际情况来处理
        byte[] bytes = null;
        try {
            bytes = IOUtils.toByteArray(cosObjectInput);

        } catch (IOException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "数据读取失败");
        } finally {
            // 用完流之后一定要调用 close()
            cosObjectInput.close();
        }

        // 在流没有处理完之前,不能关闭 cosClient
        // 确认本进程不再使用 cosClient 实例之后,关闭即可
        cosClient.shutdown();
        return bytes;
    }
    

CosController.java

      /**
     * 文件下载
     *
     * @param uploadFileRequest 文件对象
     * @param request           请求
     * @return 用户id
     */
    @Operation(summary = "文件下载")
    @PostMapping("/download")
    public BaseResponse<byte[]> download(@RequestBody UploadFileRequest uploadFileRequest, HttpServletRequest request) {

        String filepath = uploadFileRequest.getFilepath();
        userService.getLoginUser(request);
        String key = filepath.replace(HOST, "");
        String fileName = StringUtils.substringAfterLast(key, "/");
        String fileExtension = StringUtils.substringAfter(fileName, ".");

        try {

            boolean objectExists = cosClient.doesObjectExist(bucket, key);
            if (!objectExists) {
                // 如果对象不存在,返回相应的响应
                throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "对象不存在");
            }

            // 调用cosManager.download()下载文件
            byte[] data = cosManager.getObject(key);

            // 推断MIME类型
            MediaType mediaType;
            switch (fileExtension.toLowerCase()) {
                case "pdf":
                    mediaType = MediaType.APPLICATION_PDF;
                    break;
                case "jpg":
                    mediaType = MediaType.IMAGE_JPEG;
                    break;
                // 添加更多文件类型和对应的MediaType
                default:
                    mediaType = MediaType.APPLICATION_OCTET_STREAM;
                    break;
            }

            // 设置响应头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(mediaType);
            headers.setContentDispositionFormData("attachment", fileName);

            return ResultUtils.success(data, fileName, mediaType.getType());

        } catch (Exception e) {
            log.error("file download error, filepath = " + filepath, e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载失败:"+ e.getMessage());
        }

    }
    

前端

基于vue3 + ts + elementplus

接口

       /**
     * 文件下载
     * @param requestBody 
     * @returns BaseResponseByte<any> OK
     * @throws ApiError
     */
    public static download(
requestBody: UploadFileRequest,
): CancelablePromise<BaseResponseByte> {
        return __request(OpenAPI, {
            method: 'POST',
            url: '/cos/download',
            body: requestBody,
            mediaType: 'application/json',
        });
    }
    

使用

html

      <el-button type="primary" @click="downloadFile">下载</el-button>
    

ts

      function base64toFile(base64Data:any, filename:any, mimeType:any) {
  // 创建一个Base64字符串转换为Uint8Array的函数
  function base64ToArrayBuffer(base64:any) {
	const binaryString = window.atob(base64);
	const length = binaryString.length;
	const bytes = new Uint8Array(length);
	
	for (let i = 0; i < length; i++) {
	  bytes[i] = binaryString.charCodeAt(i);
	}
	
	return bytes;
  }
  
  // 将Base64数据转换为Uint8Array
  const uint8Array = base64ToArrayBuffer(base64Data);
  
  // 创建Blob对象
  const blob = new Blob([uint8Array], { type: mimeType });
  
  // 创建一个URL对象,用于文件下载
  const url = window.URL.createObjectURL(blob);
  
  // 创建一个链接元素并触发下载
  const a = document.createElement("a");
  a.href = url;
  a.download = filename || "file"; // 可以指定文件名,如果未提供默认为"file"
  document.body.appendChild(a);
  a.click();
  
  // 释放URL对象
  window.URL.revokeObjectURL(url);
}


function downloadFile({ $index, row }) {
  return new Promise((resolve, reject) => {
	CosControllerService.download({
	  filepath: row.key
	}).then(res => {
	  base64toFile(res.data, res.key, res.mimeType);
	 resolve(res.data)
	}).catch(error => {
	  reject(error)
	})
  })
}
    

通过路径链接下载

后端

官方文档介绍了 2 种文件下载方式。一种是直接下载 COS 的文件到后端服务器(适合服务器端处理文件),另一种是获取到文件下载输入流(适合返回给前端用户)。

参考官方文档:

  1. https://cloud.tencent.com/document/product/436/65937
  2. https://cloud.tencent.com/document/product/436/10199#.E4.B8.8B.E8.BD.BD.E5.AF.B9.E8.B1.A1

其实还有第三种“下载方式”,直接通过路径链接访问,适用于单一的、可以被用户公开访问的资源,比如用户头像、本项目中的代码生成器图片。

但是对于本项目中的代码生成器产物包文件,更建议通过后端服务器从 COS 下载文件并返回给前端,这样可以在后端限制只有登录用户才能下载。

CosManager

CosManager 中新增对象下载方法,根据对象的 key 获取存储信息:

      /**
 * 下载对象
 *
 * @param key 唯一键
 * @return
 */
public COSObject getObject(String key) {
    GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
    return cosClient.getObject(getObjectRequest);
}
    

CosController

核心流程是根据路径获取到 COS 文件对象,然后将文件对象转换为文件流,并写入到 Servlet 的 Response 对象中。注意要设置文件下载专属的响应头。

加载中...

声明

作者: liyao

版权:本博客所有文章除特别声明外,均采用CCBY-NC-SA4.O许可协议。转载请注明!

最后更新于 2026-02-18 18:09 history