切换主题
📥 文件下载接口
本文档介绍文件下载相关的所有接口,支持多种下载方式和在线预览。
功能概述
文件下载接口提供了灵活的文件访问方式,支持:
- ✅ 多种下载方式(路径参数、查询参数)
- ✅ 自定义下载文件名
- ✅ 在线预览(图片、PDF等)
- ✅ 业务类型隔离
- ✅ 流式传输,支持大文件
- ✅ HTTP Range请求(断点续传)
- ✅ 分片下载接口(大文件优化)
- ✅ 自动路径清理(去除多余斜杠)
下载流程
下载方式
方式对比
| 方式 | URL格式 | 适用场景 | 推荐度 |
|---|---|---|---|
| 路径参数 | /download/{业务类型}/{文件路径} | 标准下载,URL简洁 | ⭐⭐⭐⭐⭐ |
| 查询参数 | /download/{业务类型}?filePath=xxx | 特殊字符路径 | ⭐⭐⭐⭐ |
| 默认下载 | /download?filePath=xxx | 内部系统 | ⭐⭐⭐ |
| HTTP Range | /download/{业务类型}/{文件路径} + Range头 | 断点续传、分片下载 | ⭐⭐⭐⭐⭐ |
| 分片下载接口 | /download/chunk/{业务类型}/{文件路径} | 大文件分片下载 | ⭐⭐⭐⭐⭐ |
接口说明
1️⃣ 路径参数下载(推荐)
通过URL路径指定业务类型和文件路径,URL更简洁直观。
接口信息
GET /download/{业务类型}/{文件路径}1
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|---|
业务类型 | Path | String | 是 | 业务类型代码 | video |
文件路径 | Path | String | 是 | 文件存储路径 | 2024/08/25/file.mp4 |
fileName | Query | String | 否 | 自定义下载文件名 | 我的视频.mp4 |
请求示例
bash
# 基础下载
curl -O "http://localhost:9830/download/video/2024/08/25/23/abc123.mp4"
# 自定义文件名
curl -O "http://localhost:9830/download/video/2024/08/25/23/abc123.mp4?fileName=我的视频.mp4"1
2
3
4
5
2
3
4
5
javascript
// 直接下载
window.location.href = '/download/video/2024/08/25/23/abc123.mp4';
// 自定义文件名
const downloadUrl = '/download/video/2024/08/25/23/abc123.mp4?fileName='
+ encodeURIComponent('我的视频.mp4');
window.location.href = downloadUrl;1
2
3
4
5
6
7
2
3
4
5
6
7
html
<!-- 直接链接 -->
<a href="/download/video/2024/08/25/23/abc123.mp4" download>下载视频</a>
<!-- 自定义文件名 -->
<a href="/download/video/2024/08/25/23/abc123.mp4?fileName=我的视频.mp4" download>
下载视频
</a>
<!-- 在线预览(图片) -->
<img src="/download/image/2024/08/25/23/photo.jpg" alt="图片">
<!-- 在线预览(PDF) -->
<iframe src="/download/document/2024/08/25/23/doc.pdf" width="100%" height="600px"></iframe>1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
响应说明
成功响应:
- Status:
200 OK或206 Partial Content(Range请求) - Headers:
Content-Type: 根据文件类型自动设置Content-Disposition:inline; filename*=utf-8''{编码后的文件名}Content-Length: 文件大小(完整下载)或分片大小(Range请求)Content-Range:bytes {start}-{end}/{total}(仅Range请求时返回)Accept-Ranges:bytes(表示支持Range请求)
- Body: 文件二进制流
Range请求响应(206 Partial Content):
- Status:
206 Partial Content - Headers:
Content-Range:bytes 0-10485759/52428800(示例:下载前10MB,文件总大小50MB)Content-Length:10485760(分片大小)
常见Content-Type:
| 文件类型 | Content-Type |
|---|---|
| 图片 (jpg/png) | image/jpeg, image/png |
application/pdf | |
| 视频 (mp4) | video/mp4 |
| 文档 (docx) | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
| 文本 (txt) | text/plain |
2️⃣ 查询参数下载
通过查询参数指定文件路径,适合路径包含特殊字符的场景。
接口信息
GET /download/{业务类型}?filePath={文件路径}1
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|---|
业务类型 | Path | String | 是 | 业务类型代码 | document |
filePath | Query | String | 是 | 文件存储路径 | /2024/08/25/file.pdf |
fileName | Query | String | 否 | 自定义下载文件名 | 报告.pdf |
请求示例
bash
# 基础下载
curl -O "http://localhost:9830/download/document?filePath=/2024/08/25/23/report.pdf"
# 自定义文件名
curl -O "http://localhost:9830/download/document?filePath=/2024/08/25/23/report.pdf&fileName=报告.pdf"1
2
3
4
5
2
3
4
5
javascript
// 使用fetch下载
async function downloadFile(businessType, filePath, fileName) {
const params = new URLSearchParams({
filePath: filePath,
...(fileName && { fileName })
});
const response = await fetch(`/download/${businessType}?${params}`);
const blob = await response.blob();
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName || filePath.split('/').pop();
a.click();
window.URL.revokeObjectURL(url);
}
// 使用示例
downloadFile('document', '/2024/08/25/23/report.pdf', '报告.pdf');1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3️⃣ 默认下载接口
不区分业务类型的通用下载接口,适用于内部系统。
接口信息
GET /download?filePath={文件路径}1
安全提醒
此接口不进行业务类型校验,建议:
- 仅在内网环境使用
- 添加额外的权限控制
- 不要在生产环境直接暴露
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|---|
filePath | Query | String | 是 | 文件存储路径 | /2024/08/25/file.pdf |
fileName | Query | String | 否 | 自定义下载文件名 | 文件.pdf |
4️⃣ HTTP Range请求(断点续传)
支持HTTP标准Range请求,实现断点续传和分片下载。
接口信息
GET /download/{业务类型}/{文件路径}
Headers: Range: bytes={start}-{end}1
2
2
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|---|
业务类型 | Path | String | 是 | 业务类型代码 | video |
文件路径 | Path | String | 是 | 文件存储路径 | 2024/08/25/file.mp4 |
Range | Header | String | 否 | 字节范围 | bytes=0-10485759 |
Range请求格式
bytes=0-499: 下载前500字节bytes=500-999: 下载第500-999字节bytes=500-: 从第500字节下载到文件末尾bytes=-500: 下载最后500字节
请求示例
bash
# 下载前10MB
curl -H "Range: bytes=0-10485759" \
"http://localhost:9830/download/video/2024/08/25/23/large.mp4" \
-o chunk1.mp4
# 下载第10-20MB
curl -H "Range: bytes=10485760-20971519" \
"http://localhost:9830/download/video/2024/08/25/23/large.mp4" \
-o chunk2.mp4
# 从第10MB下载到文件末尾
curl -H "Range: bytes=10485760-" \
"http://localhost:9830/download/video/2024/08/25/23/large.mp4" \
-o rest.mp41
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
javascript
// 使用Range请求下载指定范围
async function downloadRange(url, start, end) {
const response = await fetch(url, {
headers: {
'Range': `bytes=${start}-${end}`
}
});
if (response.status === 206) {
// 206 Partial Content
const blob = await response.blob();
return blob;
} else if (response.status === 200) {
// 完整文件
const blob = await response.blob();
return blob;
} else {
throw new Error(`下载失败: ${response.status}`);
}
}
// 使用示例:下载前10MB
const chunk = await downloadRange(
'/download/video/2024/08/25/23/large.mp4',
0,
10485759
);1
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
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
go
// 使用Range请求下载分片
func downloadRange(url string, start, end int64) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
client := &http.Client{Timeout: 60 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 && resp.StatusCode != 206 {
return nil, fmt.Errorf("HTTP错误: %d", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
5️⃣ 分片下载接口
专门用于大文件分片下载的接口,提供更精确的控制和更好的性能。
5.1 获取分片下载信息
获取文件大小、建议分片大小、总分片数等信息。
接口信息
GET /download/chunk/info/{业务类型}/{文件路径}
GET /download/chunk/info/{业务类型}?filePath={文件路径}
GET /download/chunk/info?filePath={文件路径}1
2
3
2
3
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|---|
业务类型 | Path | String | 是 | 业务类型代码 | video |
文件路径 | Path/Query | String | 是 | 文件存储路径 | 2024/08/25/file.mp4 |
fileName | Query | String | 否 | 自定义文件名 | 我的视频.mp4 |
响应示例
json
{
"code": 0,
"successful": true,
"msg": "成功",
"data": {
"fileSize": 52428800,
"chunkSize": 5242880,
"totalChunks": 10,
"fileName": "large-video.mp4"
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
请求示例
bash
# 获取文件信息
curl "http://localhost:9830/download/chunk/info/video/2024/08/25/23/large.mp4"
# 使用查询参数
curl "http://localhost:9830/download/chunk/info/video?filePath=2024/08/25/23/large.mp4"1
2
3
4
5
2
3
4
5
5.2 分片下载
下载指定字节范围的文件分片。
接口信息
GET /download/chunk/{业务类型}/{文件路径}?start={起始字节}&end={结束字节}
GET /download/chunk/{业务类型}?filePath={文件路径}&start={起始字节}&end={结束字节}
GET /download/chunk?filePath={文件路径}&start={起始字节}&end={结束字节}1
2
3
2
3
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|---|
业务类型 | Path | String | 是 | 业务类型代码 | video |
文件路径 | Path/Query | String | 是 | 文件存储路径 | 2024/08/25/file.mp4 |
start | Query | Int64 | 是 | 起始字节位置(从0开始) | 0 |
end | Query | Int64 | 是 | 结束字节位置(包含) | 5242879 |
fileName | Query | String | 否 | 自定义文件名 | 我的视频.mp4 |
响应说明
- Status:
200 OK或206 Partial Content - Headers:
Content-Type: 根据文件类型自动设置Content-Range:bytes {start}-{end}/{total}Content-Length: 分片大小
- Body: 文件分片二进制流
请求示例
bash
# 下载第1个分片(0-5MB)
curl "http://localhost:9830/download/chunk/video/2024/08/25/23/large.mp4?start=0&end=5242879" \
-o chunk1.mp4
# 下载第2个分片(5-10MB)
curl "http://localhost:9830/download/chunk/video/2024/08/25/23/large.mp4?start=5242880&end=10485759" \
-o chunk2.mp41
2
3
4
5
6
7
2
3
4
5
6
7
完整分片下载示例
javascript
// 完整的分片下载实现
async function downloadFileInChunks(businessType, filePath, savePath) {
// 1. 获取文件信息
const infoUrl = `/download/chunk/info/${businessType}/${filePath}`;
const infoResponse = await fetch(infoUrl);
const infoData = await infoResponse.json();
if (!infoData.successful) {
throw new Error(infoData.msg);
}
const { fileSize, chunkSize, totalChunks } = infoData.data;
console.log(`文件大小: ${fileSize} bytes, 分片数: ${totalChunks}`);
// 2. 下载所有分片
const chunks = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize - 1, fileSize - 1);
const chunkUrl = `/download/chunk/${businessType}/${filePath}?start=${start}&end=${end}`;
const chunkResponse = await fetch(chunkUrl);
const chunkBlob = await chunkResponse.blob();
chunks.push(chunkBlob);
console.log(`已下载分片 ${i + 1}/${totalChunks}`);
}
// 3. 合并分片
const fullBlob = new Blob(chunks);
// 4. 保存文件
const url = window.URL.createObjectURL(fullBlob);
const a = document.createElement('a');
a.href = url;
a.download = savePath;
a.click();
window.URL.revokeObjectURL(url);
}
// 使用示例
downloadFileInChunks('video', '2024/08/25/23/large.mp4', 'large-video.mp4');1
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
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
使用示例
场景1: 图片在线预览
html
<!-- 直接使用img标签 -->
<img src="/download/image/2024/08/25/23/photo.jpg"
alt="照片"
style="max-width: 100%;">
<!-- 使用背景图 -->
<div style="background-image: url('/download/image/2024/08/25/23/bg.jpg');">
</div>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
场景2: PDF在线预览
html
<!-- 使用iframe -->
<iframe src="/download/document/2024/08/25/23/report.pdf"
width="100%"
height="800px"
frameborder="0">
</iframe>
<!-- 使用embed -->
<embed src="/download/document/2024/08/25/23/report.pdf"
type="application/pdf"
width="100%"
height="800px">1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
场景3: 视频播放
html
<!-- 使用video标签 -->
<video controls width="100%">
<source src="/download/video/2024/08/25/23/movie.mp4" type="video/mp4">
您的浏览器不支持视频播放
</video>1
2
3
4
5
2
3
4
5
场景4: 文件下载(带进度)
javascript
async function downloadWithProgress(url, fileName) {
const response = await fetch(url);
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0;
const chunks = [];
while(true) {
const {done, value} = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
// 更新进度
const progress = (receivedLength / contentLength * 100).toFixed(1);
console.log(`下载进度: ${progress}%`);
updateProgressBar(progress);
}
// 合并chunks并下载
const blob = new Blob(chunks);
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(downloadUrl);
}
// 使用示例
downloadWithProgress(
'/download/video/2024/08/25/23/large-video.mp4',
'大视频.mp4'
);1
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
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
场景5: 断点续传下载
javascript
// 实现断点续传功能
class ResumeDownloader {
constructor(url, fileName) {
this.url = url;
this.fileName = fileName;
this.downloadedSize = 0;
this.totalSize = 0;
this.chunks = [];
}
async getFileSize() {
const response = await fetch(this.url, { method: 'HEAD' });
this.totalSize = parseInt(response.headers.get('Content-Length') || '0');
return this.totalSize;
}
async downloadChunk(start, end) {
const response = await fetch(this.url, {
headers: {
'Range': `bytes=${start}-${end}`
}
});
if (response.status === 206) {
const blob = await response.blob();
this.chunks.push({ start, blob });
this.downloadedSize += blob.size;
return blob;
}
throw new Error('Range请求失败');
}
async resume() {
// 从本地存储恢复下载进度
const saved = localStorage.getItem(`download_${this.fileName}`);
if (saved) {
const data = JSON.parse(saved);
this.downloadedSize = data.downloadedSize;
this.chunks = data.chunks || [];
}
if (this.totalSize === 0) {
await this.getFileSize();
}
// 继续下载剩余部分
if (this.downloadedSize < this.totalSize) {
const start = this.downloadedSize;
const end = this.totalSize - 1;
await this.downloadChunk(start, end);
}
// 合并并保存
this.saveFile();
}
saveFile() {
const sortedChunks = this.chunks.sort((a, b) => a.start - b.start);
const blobs = sortedChunks.map(c => c.blob);
const fullBlob = new Blob(blobs);
const url = window.URL.createObjectURL(fullBlob);
const a = document.createElement('a');
a.href = url;
a.download = this.fileName;
a.click();
window.URL.revokeObjectURL(url);
// 清除本地存储
localStorage.removeItem(`download_${this.fileName}`);
}
}
// 使用示例
const downloader = new ResumeDownloader(
'/download/video/2024/08/25/23/large.mp4',
'large-video.mp4'
);
await downloader.resume();1
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
场景6: 批量下载
javascript
async function batchDownload(files) {
for (const file of files) {
const { businessType, filePath, fileName } = file;
const url = `/download/${businessType}/${filePath}?fileName=${encodeURIComponent(fileName)}`;
// 创建隐藏的iframe进行下载
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
// 延迟避免浏览器阻止
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// 使用示例
batchDownload([
{ businessType: 'document', filePath: '2024/08/25/23/file1.pdf', fileName: '文件1.pdf' },
{ businessType: 'document', filePath: '2024/08/25/23/file2.pdf', fileName: '文件2.pdf' },
{ businessType: 'document', filePath: '2024/08/25/23/file3.pdf', fileName: '文件3.pdf' }
]);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
最佳实践
✅ 推荐做法
1. 接口选择
javascript
// ✅ 推荐:使用路径参数(URL简洁)
const url = `/download/video/2024/08/25/23/movie.mp4`;
// ⚠️ 备选:查询参数(特殊字符路径)
const url = `/download/video?filePath=${encodeURIComponent(complexPath)}`;
// ❌ 不推荐:默认接口(生产环境)
const url = `/download?filePath=/2024/08/25/23/file.pdf`;1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
2. 文件名处理
javascript
// ✅ 正确:URL编码中文文件名
const fileName = encodeURIComponent('我的文档.pdf');
const url = `/download/document/2024/08/25/23/abc.pdf?fileName=${fileName}`;
// ❌ 错误:不编码中文
const url = `/download/document/2024/08/25/23/abc.pdf?fileName=我的文档.pdf`;1
2
3
4
5
6
2
3
4
5
6
3. 错误处理
javascript
async function safeDownload(url, fileName) {
try {
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) {
throw new Error('文件不存在');
} else if (response.status === 403) {
throw new Error('无访问权限');
} else {
throw new Error(`下载失败: ${response.status}`);
}
}
const blob = await response.blob();
// ... 下载逻辑
} catch (error) {
console.error('下载错误:', error);
alert(error.message);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
4. 大文件下载
javascript
// ✅ 使用流式下载,避免内存溢出
async function downloadLargeFile(url, fileName) {
const response = await fetch(url);
const reader = response.body.getReader();
// 使用流式API
const stream = new ReadableStream({
start(controller) {
function push() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
controller.enqueue(value);
push();
});
}
push();
}
});
const blob = await new Response(stream).blob();
// ... 下载逻辑
}
// ✅ 推荐:使用分片下载接口(性能更好)
async function downloadLargeFileWithChunks(businessType, filePath, fileName) {
// 1. 获取文件信息
const info = await fetch(`/download/chunk/info/${businessType}/${filePath}`)
.then(r => r.json());
if (!info.successful) {
throw new Error(info.msg);
}
const { fileSize, chunkSize, totalChunks } = info.data;
const chunks = [];
// 2. 并发下载分片(限制并发数)
const concurrency = 3; // 最多3个并发
for (let i = 0; i < totalChunks; i += concurrency) {
const batch = [];
for (let j = 0; j < concurrency && i + j < totalChunks; j++) {
const chunkIndex = i + j;
const start = chunkIndex * chunkSize;
const end = Math.min(start + chunkSize - 1, fileSize - 1);
batch.push(
fetch(`/download/chunk/${businessType}/${filePath}?start=${start}&end=${end}`)
.then(r => r.blob())
.then(blob => ({ index: chunkIndex, blob }))
);
}
const results = await Promise.all(batch);
chunks.push(...results);
console.log(`已下载 ${Math.min(i + concurrency, totalChunks)}/${totalChunks} 分片`);
}
// 3. 按顺序合并分片
chunks.sort((a, b) => a.index - b.index);
const fullBlob = new Blob(chunks.map(c => c.blob));
// 4. 保存文件
const url = window.URL.createObjectURL(fullBlob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
}1
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
⚠️ 注意事项
| 项目 | 说明 |
|---|---|
| 路径安全 | 避免路径遍历攻击,不要使用 .. 等特殊字符。服务端会自动清理多余的斜杠 |
| 文件名编码 | 中文文件名必须进行URL编码 |
| CORS | 跨域下载需要配置CORS响应头 |
| 缓存控制 | 根据文件类型设置合适的缓存策略 |
| 大文件 | 大文件建议使用分片下载接口,支持并发下载,性能更好 |
| 断点续传 | 使用HTTP Range请求或分片下载接口实现断点续传 |
| 路径格式 | 路径会自动清理,支持 /path/to/file 或 path/to/file 格式 |
| 权限控制 | 生产环境建议添加下载权限验证 |
| 日志记录 | 记录下载日志,便于审计和问题排查 |
| 并发控制 | 分片下载时建议限制并发数(3-5个),避免服务器压力过大 |
错误码说明
| HTTP状态码 | 说明 | 常见原因 | 解决方案 |
|---|---|---|---|
200 | 成功 | - | - |
206 | 部分内容 | Range请求成功 | - |
400 | 请求参数错误 | 缺少必填参数、参数格式错误 | 检查请求参数 |
403 | 无访问权限 | 业务类型不匹配、权限不足 | 确认业务类型和权限配置 |
404 | 文件不存在 | 文件路径错误、文件已删除 | 检查文件路径是否正确 |
416 | 范围请求不满足 | Range请求的范围无效 | 检查Range参数是否正确 |
500 | 服务器错误 | 存储服务异常、配置错误 | 查看服务器日志 |
常见问题
Q: 如何实现文件下载而不是在线预览?
A: 有两种方式:
- 使用HTML5的download属性:
html
<a href="/download/document/2024/08/25/23/file.pdf" download>下载文件</a>1
- 服务端设置Content-Disposition为attachment(需要后端支持)
Q: 下载大文件时如何显示进度?
A: 使用Fetch API的流式读取:
javascript
const response = await fetch(url);
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0;
while(true) {
const {done, value} = await reader.read();
if (done) break;
receivedLength += value.length;
const progress = (receivedLength / contentLength * 100).toFixed(1);
updateProgress(progress);
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Q: 如何处理中文文件名?
A: 使用URL编码:
javascript
const fileName = '我的文档.pdf';
const encodedName = encodeURIComponent(fileName);
const url = `/download/document/path/to/file.pdf?fileName=${encodedName}`;1
2
3
2
3
Q: 如何实现断点续传下载?
A: 有两种方式:
方式1: 使用HTTP Range请求头(推荐)
javascript
const response = await fetch(url, {
headers: {
'Range': `bytes=${startByte}-${endByte}`
}
});
if (response.status === 206) {
// 206 Partial Content
const blob = await response.blob();
// 处理分片数据
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
方式2: 使用分片下载接口
javascript
// 获取文件信息
const info = await fetch(`/download/chunk/info/${businessType}/${filePath}`)
.then(r => r.json());
// 下载指定范围的分片
const chunk = await fetch(
`/download/chunk/${businessType}/${filePath}?start=${start}&end=${end}`
).then(r => r.blob());1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
服务端已完全支持Range请求和分片下载。
Q: 大文件下载如何优化性能?
A: 推荐使用分片下载接口,支持并发下载:
javascript
// 1. 获取文件信息
const info = await fetch(`/download/chunk/info/video/large.mp4`)
.then(r => r.json());
// 2. 并发下载分片(限制并发数)
const chunks = [];
const concurrency = 3;
for (let i = 0; i < info.data.totalChunks; i += concurrency) {
const batch = [];
for (let j = 0; j < concurrency && i + j < info.data.totalChunks; j++) {
const idx = i + j;
const start = idx * info.data.chunkSize;
const end = Math.min(start + info.data.chunkSize - 1, info.data.fileSize - 1);
batch.push(
fetch(`/download/chunk/video/large.mp4?start=${start}&end=${end}`)
.then(r => r.blob())
.then(blob => ({ index: idx, blob }))
);
}
chunks.push(...await Promise.all(batch));
}
// 3. 按顺序合并
chunks.sort((a, b) => a.index - b.index);
const fullBlob = new Blob(chunks.map(c => c.blob));1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
优势:
- 支持并发下载,速度更快
- 可以暂停和恢复
- 内存占用更小
Q: 路径中的多余斜杠会被处理吗?
A: 是的,服务端会自动清理路径:
/demo-files/2026/01/file.pptx→demo-files/2026/01/file.pptx//demo-files///2026//file.pptx→demo-files/2026/file.pptx/demo-files/file.pptx/→demo-files/file.pptx
无需手动处理,直接使用即可。
Q: 跨域下载如何处理?
A: 需要服务端配置CORS响应头:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: Range1
2
3
2
3
或者使用代理服务器转发请求。