文件下载原创
# 获取到文件下载输入流
# 后端
# 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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 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());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 前端
基于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',
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 通过路径链接下载
# 后端
官方文档介绍了 2 种文件下载方式。一种是直接下载 COS 的文件到后端服务器(适合服务器端处理文件),另一种是获取到文件下载输入流(适合返回给前端用户)。
参考官方文档:
- https://cloud.tencent.com/document/product/436/65937
- 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);
}
2
3
4
5
6
7
8
9
10
# CosController
核心流程是根据路径获取到 COS 文件对象,然后将文件对象转换为文件流,并写入到 Servlet 的 Response 对象中。注意要设置文件下载专属的响应头。
同上,测试接口一定要加上管理员权限!防止任何用户随意上传文件。
测试文件下载接口代码如下:
/**
* 测试文件下载
*
* @param filepath
* @param response
* @return
*/
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
@GetMapping("/test/download/")
public void testDownloadFile(String filepath, HttpServletResponse response) throws IOException {
COSObjectInputStream cosObjectInput = null;
try {
COSObject cosObject = cosManager.getObject(filepath);
cosObjectInput = cosObject.getObjectContent();
// 处理下载到的流
byte[] bytes = IOUtils.toByteArray(cosObjectInput);
// 设置响应头
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + filepath);
// 写入响应
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
} catch (Exception e) {
log.error("file download error, filepath = " + filepath, e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载失败");
} finally {
if (cosObjectInput != null) {
cosObjectInput.close();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 前端
基于react + ts + Ant Design
# 使用 openAPI 工具生成接口
# 新增对象存储相关常量。
修改 constants/index.ts
文件,添加下列代码:
/**
* COS 访问地址
*/
export const COS_HOST = "https://yuzi-1256524210.cos.ap-shanghai.myqcloud.com";
2
3
4
# 开发页面
遵循 Flex 左右布局,左边上传文件,右边展示和下载文件。
对于文件上传,直接使用 Ant Design 的拖拽文件上传组件。
官方文档:https://ant.design/components/upload-cn#components-upload-demo-drag
使用 img
标签直接拼接图片地址并展示:
<div>文件地址:{COS_HOST + value}</div>
<Divider />
<img src={COS_HOST + value} height={280} />
2
3
使用 file-saver
库,可以下载后端返回的 blob 内容为文件。
先安装 file-saver
库:
npm install file-saver
npm i --save-dev @types/file-saver
2
完整下载文件代码如下:
<div>文件地址:{COS_HOST + value}</div>
<Divider />
<img src={COS_HOST + value} height={280} />
<Divider />
<Button
onClick={async () => {
const blob = await testDownloadFileUsingGet(
{
filepath: value,
},
{
responseType: 'blob',
},
);
// 使用 file-saver 来保存文件
const fullPath = COS_HOST + value;
saveAs(blob, fullPath.substring(fullPath.lastIndexOf('/') + 1));
}}
>
点击下载文件
</Button>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
由于后端下载文件接口不返回 code
状态码,所以需要修改响应拦截器,对于文件下载请求,直接返回 blob 对象。修改 requestConfig.ts
的部分代码如下:
// 响应拦截器
responseInterceptors: [
(response) => {
// 请求地址
const requestPath: string = response.config.url ?? '';
// 响应
const { data } = response as unknown as ResponseStructure;
if (!data) {
throw new Error('服务异常');
}
// 文件下载时,直接返回
if (requestPath.includes("download")) {
return response;
}
...
},
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 01
- element-plus多文件手动上传 原创11-03
- 02
- TrueLicense 创建及安装证书 原创10-25
- 03
- 手动修改迅捷配置 原创09-03
- 04
- 安装 acme.sh 原创08-29
- 05
- zabbix部署 原创08-20