django使用流响应文件, 浏览器会有下载进度条, 可以点击进行暂停, 继续
后端
@action(detail=False, methods=['get'], schema=AutoDocSchema())
def get_big_files(self, request):
def file_iterator(file_path, start_byte=0, chunk_size=256):
with open(file_path, 'rb') as f:
f.seek(start_byte)
while chunk := f.read(chunk_size):
yield chunk
file_path = '/home/chf/databases-bak/databaes-data-bak/backup/docker_image/mongo-express:latest'
# 检查文件是否存在
if not os.path.exists(file_path):
return Response({"detail": "File not found."}, status=status.HTTP_404_NOT_FOUND)
file_size = os.path.getsize(file_path)
# 获取 Range 请求头(如 "bytes=0-999")
range_header = request.headers.get('Range', None)
if range_header:
# 解析 Range 头
try:
byte_range = range_header.split('=')[1]
start_byte, end_byte = byte_range.split('-')
start_byte = int(start_byte)
end_byte = int(end_byte) if end_byte else file_size - 1
except (ValueError, IndexError):
return Response({"detail": "Invalid Range header format."}, status=status.HTTP_400_BAD_REQUEST)
else:
# 如果没有 Range 请求头,则从头开始返回文件
start_byte = 0
end_byte = file_size - 1
# 确保返回的字节范围不会超出文件大小
if start_byte > file_size:
start_byte = file_size - 1
if end_byte > file_size - 1:
end_byte = file_size - 1
# 设置流式响应
response = StreamingHttpResponse(file_iterator(file_path, start_byte, chunk_size=256), status=status.HTTP_206_PARTIAL_CONTENT)
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = f'attachment; filename="file.sql"'
response['Content-Length'] = str(end_byte - start_byte + 1)
response['Content-Range'] = f"bytes {start_byte}-{end_byte}/{file_size}"
response['Accept-Ranges'] = 'bytes' # 支持分段下载
# 返回部分文件数据
return response
前端js实现下载文件获取进度条
XMLHttpRequest由于安全问题, 无法支持暂停,的时候获取数据, 只那下载整个文件, 获取一些请求头信息,进度条, 文件大小等...
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件下载进度</title>
<style>
#progress-container {
width: 100%;
background-color: #f3f3f3;
height: 30px;
border-radius: 5px;
margin: 20px 0;
}
#progress-bar {
height: 100%;
width: 0;
background-color: green;
text-align: center;
line-height: 30px;
color: white;
border-radius: 5px;
}
</style>
</head>
<body>
<h1>下载文件</h1>
<button id="download-button">开始下载</button>
<div id="progress-container">
<div id="progress-bar">0%</div>
</div>
<script>
document.getElementById('download-button').addEventListener('click', function() {
const xhr = new XMLHttpRequest();
const url = 'http://192.168.1.80:8001/api/stock/get_big_files/'; // 你的API端点
xhr.open('GET', url, true);
xhr.setRequestHeader('authorization', 'Token bfd620b1d644dcf70c499423e441c083e0cefc98')
xhr.responseType = 'blob'; // 处理二进制数据
// 监听进度事件
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
updateProgressBar(percentComplete);
}
};
// 下载完成后
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = "文件.zip"; // 设置下载文件名
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(downloadUrl);
updateProgressBar(100); // 确保进度条达到100%
}
};
xhr.onerror = function() {
alert('下载失败');
};
xhr.send();
// 重置进度条
updateProgressBar(0);
});
// 更新进度条
function updateProgressBar(percent) {
const progressBar = document.getElementById('progress-bar');
progressBar.style.width = percent + '%';
progressBar.textContent = Math.round(percent) + '%';
}
</script>
</body>
</html>
js版本2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Download with Progress</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
#progress-container {
width: 100%;
background-color: #f3f3f3;
border-radius: 5px;
height: 20px;
margin-top: 20px;
display: none;
}
#progress-bar {
height: 100%;
width: 0%;
background-color: green;
border-radius: 5px;
}
#status {
margin-top: 10px;
}
button {
padding: 10px 20px;
margin-top: 20px;
font-size: 16px;
}
#pause-btn {
display: none;
}
</style>
</head>
<body>
<h1>Download File with Progress</h1>
<button id="startBtn">Start Download</button>
<button id="pauseBtn">Pause Download</button>
<button id="resumeBtn">Resume Download</button>
<div id="progress-container">
<div id="progress-bar"></div>
</div>
<p id="status"></p>
<script>
let xhr;
let downloadUrl = 'http://192.168.1.80:8001/api/stock/get_big_files/';
let isPaused = false;
let downloadedBytes = 0; // 已下载的字节数
let currentResponse = 0;
let currentBytes = 0;
let totalBytes = 0; // 文件总大小
let partialData = []; // 保存已下载的部分数据
const status = document.getElementById('status');
const startBtn = document.getElementById('startBtn');
const pauseBtn = document.getElementById('pauseBtn');
const resumeBtn = document.getElementById('resumeBtn');
function startDownload() {
xhr = new XMLHttpRequest();
xhr.open('GET', downloadUrl, true);
xhr.setRequestHeader('authorization', 'Token bfd620b1d644dcf70c499423e441c083e0cefc98')
xhr.responseType = 'arraybuffer';
// 设置进度回调
xhr.onprogress = function (event) {
if (event.lengthComputable) {
// console.log("downloadedBytes: ", event.loaded);
if (totalBytes == 0) {
totalBytes = event.total;
console.log("文件大小: ", totalBytes);
console.log(event);
}
currentBytes = event.loaded;
const percent = (downloadedBytes + event.loaded) / totalBytes * 100;
status.innerText = `Downloading... ${Math.round(percent)}%`;
}
let data = xhr.response;
if (data) {
partialData.push(data); // 保存每次接收到的数据
}
};
// 请求完成后处理
xhr.onload = function () {
console.log("请求完成");
if (xhr.status === 206) {
// 下载完成
let data = xhr.response;
downloadedBytes += data.byteLength;
// partialData.push(data); // 保存下载的数据
console.log("所有数据:", partialData);
// 合并所有已下载的数据
// 合并所有已下载的数据
let allData = new Uint8Array(downloadedBytes);
let offset = 0;
// 合并数据时,确保逐一拷贝每个部分
for (let i = 0; i < partialData.length; i++) {
if (partialData[i]) { // 确保数据有效
allData.set(new Uint8Array(partialData[i]), offset);
offset += partialData[i].byteLength; // 更新偏移量
}
}
document.getElementById('status').innerText = 'Download complete!';
// 将下载的二进制数据转为 Blob 对象
let blob = new Blob([allData], { type: 'application/octet-stream' });
// 创建一个临时的下载链接
let link = document.createElement('a');
link.href = URL.createObjectURL(blob); // 使用 Blob 创建一个临时链接
link.download = 'downloadedFile.sql'; // 设置下载的文件名
link.click(); // 模拟点击下载
}
};
// 开始请求时检查总文件大小
xhr.onloadstart = function (event) {
status.innerText = 'Starting download...';
};
xhr.onabort = function (event) {
downloadedBytes += currentBytes;
console.log('暂停下载进度:', downloadedBytes);
};
// 发送请求,使用 Range 请求头来限制下载的字节范围
if (downloadedBytes > 0) {
console.log("恢复下载: 从[", downloadedBytes, "]字节开始");
// 恢复下载时,设置 Range 请求头从上次停止的字节继续下载
xhr.setRequestHeader('Range', `bytes=${downloadedBytes}-`);
}
xhr.send();
}
function pauseDownload() {
if (xhr && xhr.readyState === XMLHttpRequest.LOADING) {
console.log(xhr.response);
xhr.abort(); // 暂停下载
isPaused = true;
}
}
function resumeDownload() {
startDownload(); // 重新发起请求,恢复下载
}
// 绑定暂停和恢复按钮
pauseBtn.addEventListener('click', pauseDownload);
resumeBtn.addEventListener('click', resumeDownload);
startBtn.addEventListener("click", startDownload)
</script>
</body>
</html>