ຂ້າມໄປຫາເນື້ອຫາຫຼັກ

ການອັບໂຫຼດແບບ Chunked

ອັບໂຫຼດໄຟລ໌ໃຫຍ່ໂດຍການແບ່ງເປັນສ່ວນນ້ອຍໆ. ອັນນີ້ຊ່ວຍໃຫ້:

  • ອັບໂຫຼດໄຟລ໌ທີ່ໃຫຍ່ກວ່າຂີດຈຳກັດຂອງເຊີບເວີ
  • ສືບຕໍ່ການອັບໂຫຼດຫຼັງຈາກ network ລົ້ມເຫຼວ
  • ການຕິດຕາມຄວາມຄືບໜ້າທີ່ດີກວ່າ
  • ຫຼຸດຜ່ອນການໃຊ້ໜ່ວຍຄວາມຈຳ

ການອັບໂຫຼດ Chunked ພື້ນຖານ

import { useDropup, createChunkedUploader } from '@samithahansaka/dropup';

function ChunkedUploader() {
const { files, actions, state, getDropProps, getInputProps } = useDropup({
upload: createChunkedUploader({
url: '/api/upload/chunk',
chunkSize: 5 * 1024 * 1024, // 5MB chunks
}),
});

return (
<div>
<div {...getDropProps()} style={styles.dropzone}>
<input {...getInputProps()} />
<p>ວາງໄຟລ໌ໃຫຍ່ທີ່ນີ້ - ມັນຈະອັບໂຫຼດເປັນສ່ວນໆ</p>
</div>

{files.map(file => (
<div key={file.id} style={styles.fileItem}>
<span>{file.name}</span>
<span>{(file.size / 1024 / 1024).toFixed(1)} MB</span>

{file.status === 'uploading' && (
<div style={styles.progressBar}>
<div
style={{
...styles.progress,
width: `${file.progress}%`,
}}
/>
</div>
)}

<span>{file.status}</span>
</div>
))}

<button
onClick={() => actions.upload()}
disabled={state.isUploading}
>
ອັບໂຫຼດ
</button>
</div>
);
}

const styles = {
dropzone: {
border: '2px dashed #ccc',
borderRadius: 8,
padding: 40,
textAlign: 'center' as const,
marginBottom: 20,
},
fileItem: {
display: 'flex',
alignItems: 'center',
gap: 12,
padding: 12,
borderBottom: '1px solid #eee',
},
progressBar: {
flex: 1,
height: 8,
backgroundColor: '#eee',
borderRadius: 4,
overflow: 'hidden',
},
progress: {
height: '100%',
backgroundColor: '#4caf50',
transition: 'width 0.2s',
},
};

ຕົວເລືອກການອັບໂຫຼດ Chunked

createChunkedUploader({
// ຈຳເປັນ
url: '/api/upload/chunk',

// ການຕັ້ງຄ່າທາງເລືອກ
chunkSize: 5 * 1024 * 1024, // 5MB (ຄ່າເລີ່ມຕົ້ນ)
concurrency: 3, // chunks ພ້ອມກັນ (ຄ່າເລີ່ມຕົ້ນ: 1)
retries: 3, // ລອງໃໝ່ chunks ທີ່ລົ້ມເຫຼວ (ຄ່າເລີ່ມຕົ້ນ: 3)

// Headers ສຳລັບທຸກ chunk requests
headers: {
'Authorization': 'Bearer token',
},

// Metadata ຂອງ chunk ແບບກຳນົດເອງ
getChunkMeta: (file, chunkIndex, totalChunks) => ({
fileId: file.id,
fileName: file.name,
chunkIndex,
totalChunks,
}),
});

ການຈັດການຝ່າຍ Server

ເຊີບເວີຂອງທ່ານຕ້ອງຈັດການການອັບໂຫຼດ chunks ແລະປະກອບພວກມັນຄືນ.

ຕົວຢ່າງ: Node.js/Express

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');

const app = express();
const upload = multer({ dest: 'chunks/' });

const uploadState = new Map();

app.post('/api/upload/chunk', upload.single('chunk'), (req, res) => {
const { fileId, fileName, chunkIndex, totalChunks } = req.body;

// ເກັບຂໍ້ມູນ chunk
if (!uploadState.has(fileId)) {
uploadState.set(fileId, {
fileName,
totalChunks: parseInt(totalChunks),
chunks: [],
});
}

const state = uploadState.get(fileId);
state.chunks.push({
index: parseInt(chunkIndex),
path: req.file.path,
});

// ກວດສອບວ່າຮັບ chunks ທັງໝົດແລ້ວຫຼືບໍ່
if (state.chunks.length === state.totalChunks) {
// ຈັດລຳດັບ chunks ຕາມ index
state.chunks.sort((a, b) => a.index - b.index);

// ລວມ chunks
const finalPath = path.join('uploads', fileName);
const writeStream = fs.createWriteStream(finalPath);

for (const chunk of state.chunks) {
const data = fs.readFileSync(chunk.path);
writeStream.write(data);
fs.unlinkSync(chunk.path); // ລ້າງ chunk
}

writeStream.end();
uploadState.delete(fileId);

return res.json({
complete: true,
url: `/uploads/${fileName}`,
});
}

res.json({
complete: false,
received: state.chunks.length,
total: state.totalChunks,
});
});

tus Protocol

ສຳລັບການອັບໂຫຼດທີ່ສືບຕໍ່ໄດ້ທີ່ເຂັ້ມແຂງ, ໃຊ້ tus protocol:

import { useDropup } from '@samithahansaka/dropup';
import { createTusUploader } from '@samithahansaka/dropup/tus';

function TusUploader() {
const { files, actions, state, getDropProps, getInputProps } = useDropup({
upload: createTusUploader({
endpoint: 'https://tusd.tusdemo.net/files/',

// ການຕັ້ງຄ່າທາງເລືອກ
chunkSize: 5 * 1024 * 1024,
retryDelays: [0, 1000, 3000, 5000],

// Metadata ສຳລັບເຊີບເວີ
metadata: {
filetype: 'file.type',
filename: 'file.name',
},

// ສືບຕໍ່ຈາກ localStorage
storeFingerprintForResuming: true,
}),

onUploadComplete: (file) => {
console.log('tus ອັບໂຫຼດສຳເລັດ:', file.uploadedUrl);
},
});

return (
<div>
<div {...getDropProps()} style={styles.dropzone}>
<input {...getInputProps()} />
<p>ວາງໄຟລ໌ສຳລັບການອັບໂຫຼດທີ່ສືບຕໍ່ໄດ້</p>
</div>

{files.map(file => (
<div key={file.id} style={styles.fileItem}>
<span>{file.name}</span>
<span>{file.progress}%</span>
<span>{file.status}</span>
</div>
))}

<button onClick={() => actions.upload()}>
ອັບໂຫຼດ
</button>
</div>
);
}

ຢຸດຊົ່ວຄາວ ແລະສືບຕໍ່

ກັບການອັບໂຫຼດ chunked, ທ່ານສາມາດຢຸດຊົ່ວຄາວ ແລະສືບຕໍ່ໄດ້:

function PausableUploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createChunkedUploader({
url: '/api/upload/chunk',
}),
});

return (
<div>
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>ວາງໄຟລ໌ທີ່ນີ້</p>
</div>

{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
<span>{file.progress}%</span>
<span>{file.status}</span>

{file.status === 'uploading' && (
<button onClick={() => actions.cancel(file.id)}>
ຢຸດຊົ່ວຄາວ
</button>
)}

{file.status === 'paused' && (
<button onClick={() => actions.retry([file.id])}>
ສືບຕໍ່
</button>
)}
</div>
))}

<button onClick={() => actions.upload()}>
ເລີ່ມທັງໝົດ
</button>
</div>
);
}

ການຕິດຕາມຄວາມຄືບໜ້າກັບ Chunks

import { useState } from 'react';

function DetailedChunkProgress() {
const [chunkProgress, setChunkProgress] = useState<Map<string, number[]>>(
new Map()
);

const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createChunkedUploader({
url: '/api/upload/chunk',
chunkSize: 1024 * 1024, // 1MB chunks ສຳລັບການເບິ່ງເຫັນ

onChunkProgress: (file, chunkIndex, progress) => {
setChunkProgress(prev => {
const next = new Map(prev);
const fileProgress = next.get(file.id) || [];
fileProgress[chunkIndex] = progress;
next.set(file.id, fileProgress);
return next;
});
},
}),
});

return (
<div>
<div {...getDropProps()} style={styles.dropzone}>
<input {...getInputProps()} />
<p>ວາງໄຟລ໌ທີ່ນີ້</p>
</div>

{files.map(file => {
const chunks = chunkProgress.get(file.id) || [];
const totalChunks = Math.ceil(file.size / (1024 * 1024));

return (
<div key={file.id} style={styles.fileCard}>
<p>{file.name}</p>

{/* ການສະແດງຄວາມຄືບໜ້າຂອງ chunks */}
<div style={styles.chunkGrid}>
{Array.from({ length: totalChunks }).map((_, i) => (
<div
key={i}
style={{
...styles.chunkBlock,
backgroundColor: chunks[i] === 100
? '#4caf50'
: chunks[i] > 0
? '#8bc34a'
: '#eee',
}}
title={`Chunk ${i + 1}: ${chunks[i] || 0}%`}
/>
))}
</div>

<p>ໂດຍລວມ: {file.progress}%</p>
</div>
);
})}

<button onClick={() => actions.upload()}>
ອັບໂຫຼດ
</button>
</div>
);
}

const styles = {
dropzone: {
border: '2px dashed #ccc',
padding: 40,
textAlign: 'center' as const,
marginBottom: 20,
},
fileCard: {
padding: 16,
border: '1px solid #eee',
borderRadius: 8,
marginBottom: 12,
},
chunkGrid: {
display: 'flex',
flexWrap: 'wrap' as const,
gap: 4,
margin: '12px 0',
},
chunkBlock: {
width: 20,
height: 20,
borderRadius: 4,
transition: 'background-color 0.2s',
},
};

ຄຳແນະນຳຂະໜາດ Chunk

ຂະໜາດໄຟລ໌ຂະໜາດ Chunk ທີ່ແນະນຳ
< 10 MBບໍ່ຈຳເປັນຕ້ອງ chunking
10-100 MB5 MB
100 MB - 1 GB10 MB
> 1 GB20-50 MB
ປະສິດທິພາບ

Chunks ໃຫຍ່ = requests ໜ້ອຍ, ແຕ່ໃຊ້ເວລາດົນກວ່າໃນການກູ້ຄືນເມື່ອລົ້ມເຫຼວ. Chunks ນ້ອຍ = overhead ຫຼາຍ, ແຕ່ສາມາດສືບຕໍ່ໄດ້ດີກວ່າ.