


















说明:
1. 前端将文件分片加密后上传,后端存储加密数据和索引
2. 下载时前端获取索引,逐片下载解密并合并成原文件
3. 加密方式是 AES-256 ,调用Crypto库前端执行的;
4.就一个演示案例,没有写后台,就两个文件,前端html,后端php;
5.文件上传在“uploads”目录内,可以打开php文件自己修改;
6.主要用途就是减少网络波动导致文件传输不稳定,与服务器上传文件大小限制;
7.传输实时显示进度条,使用的是4线程并发传输,局域网测试可以跑满带宽;
代码:
index.html
<!--
Title:文件二进制分片加密传输
Blog:https://www.ximi.me
Author:希米
Time:2036-06-19
说明:
- 前端将文件分片加密后上传,后端存储加密数据和索引
- 下载时前端获取索引,逐片下载解密并合并成原文件
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>文件二进制分片加密传输</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
</head>
<body class="bg-gray-100 p-6">
<div class="max-w-2xl mx-auto space-y-6">
<h1 class="text-2xl font-bold text-center"> 文件二进制分片加密传输演示</h1>
<!-- 密钥 -->
<div class="bg-white p-4 rounded-xl">
<input id="key" type="password" placeholder="加密密钥"
class="w-full p-3 border rounded">
</div>
<!-- 上传 -->
<div class="bg-white p-4 rounded-xl">
<h2 class="font-bold mb-2">上传文件</h2>
<input type="file" id="file" class="mb-3">
<button onclick="upload()" class="bg-blue-500 text-white px-4 py-2 rounded">
开始上传
</button>
<div id="upStatus" class="text-sm mt-2 text-gray-600"></div>
<progress id="upProgress" value="0" max="100" class="w-full mt-2"></progress>
</div>
<!-- 下载 -->
<div class="bg-white p-4 rounded-xl">
<h2 class="font-bold mb-2">下载文件</h2>
<input id="fid" placeholder="文件ID" class="w-full p-2 border mb-3">
<button onclick="downloadFile()" class="bg-purple-500 text-white px-4 py-2 rounded">
开始下载
</button>
<div id="downStatus" class="text-sm mt-2 text-gray-600"></div>
<progress id="downProgress" value="0" max="100" class="w-full mt-2"></progress>
</div>
</div>
<script>
// ================= 工具 =================
function sha256(str){
return CryptoJS.SHA256(str).toString();
}
function safeJSON(t){
try{
return JSON.parse(t);
}catch(e){
console.log("❌ 后端返回:", t);
throw new Error("JSON解析失败");
}
}
// ================= 并发池 =================
async function asyncPool(limit, arr, fn){
const ret = []
const executing = []
for(const item of arr){
const p = Promise.resolve().then(()=>fn(item))
ret.push(p)
const e = p.then(()=>executing.splice(executing.indexOf(e),1))
executing.push(e)
if(executing.length >= limit){
await Promise.race(executing)
}
}
return Promise.all(ret)
}
// ================= 上传(并行版) =================
async function upload(){
const file = document.getElementById('file').files[0]
const key = document.getElementById('key').value
if(!file || !key) return alert("缺文件或密钥")
const status = document.getElementById('upStatus')
const bar = document.getElementById('upProgress')
const buf = await file.arrayBuffer()
const chunkSize = 256 * 1024
const chunks = []
const originalName = file.name
// ================= 切块 =================
for(let i=0;i<buf.byteLength;i+=chunkSize){
const slice = buf.slice(i, i+chunkSize)
const word = CryptoJS.lib.WordArray.create(slice)
const encrypted = CryptoJS.AES.encrypt(word, key).toString()
const name = Math.random().toString(36).slice(2) + ".bin"
chunks.push({
index: i,
name,
data: encrypted,
hash: sha256(encrypted)
})
}
const fileId = "f_" + Date.now()
let done = 0
const start = Date.now()
status.innerText = "并行上传中..."
// ================= 并行上传(核心) =================
await asyncPool(4, chunks, async (c)=>{
const fd = new FormData()
fd.append("id", fileId)
fd.append("name", c.name)
fd.append("chunk", c.data)
const res = await fetch("file.php?action=upload",{
method:"POST",
body:fd
})
const text = await res.text()
const data = safeJSON(text)
if(data.status !== "ok"){
throw new Error("upload fail")
}
done++
bar.value = (done / chunks.length) * 100
let speed = (done / ((Date.now()-start)/1000)).toFixed(2)
status.innerText = `上传 ${done}/${chunks.length} 速度:${speed}/s`
})
// ================= manifest =================
const manifest = {
originalName,
chunks: chunks.map(c=>({
name:c.name,
hash:c.hash
}))
}
const fd2 = new FormData()
fd2.append("id", fileId)
fd2.append("manifest", JSON.stringify(manifest))
await fetch("file.php?action=upload",{
method:"POST",
body:fd2
})
bar.value = 100
status.innerText = "上传完成 ID: " + fileId
}
// ================= 下载(并行版) =================
async function downloadFile(){
const id = document.getElementById('fid').value
const key = document.getElementById('key').value
const status = document.getElementById('downStatus')
const bar = document.getElementById('downProgress')
const res = await fetch("file.php?action=send&id="+id)
const text = await res.text()
const data = safeJSON(text)
if(data.status !== "ok"){
throw new Error(data.msg)
}
const manifest = data.manifest
const list = manifest.chunks
const originalName = manifest.originalName
status.innerText = "并行下载中..."
let done = 0
// ================= 并行下载 =================
const results = await asyncPool(4, list, async (item)=>{
const r = await fetch(`uploads/${id}/${item.name}`)
const enc = await r.text()
const decrypted = CryptoJS.AES.decrypt(enc, key)
const base64 = decrypted.toString(CryptoJS.enc.Base64)
const binary = atob(base64)
const arr = new Uint8Array(binary.length)
for(let i=0;i<binary.length;i++){
arr[i] = binary.charCodeAt(i)
}
done++
bar.value = (done / list.length) * 100
status.innerText = `下载 ${done}/${list.length}`
return {
index: list.indexOf(item),
data: arr
}
})
// ================= 排序重组 =================
results.sort((a,b)=>a.index-b.index)
let total = 0
results.forEach(r=> total += r.data.length)
const merged = new Uint8Array(total)
let offset = 0
for(const r of results){
merged.set(r.data, offset)
offset += r.data.length
}
// ================= 下载文件 =================
const blob = new Blob([merged])
const a = document.createElement("a")
a.href = URL.createObjectURL(blob)
a.download = originalName
a.click()
status.innerText = "下载完成"
bar.value = 100
}
</script>
</body>
</html>
file.php
<?php
$uploadDir = 'uploads/';
$action = $_GET['action'] ?? '';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
function out($data){
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
/**
* ======================
* upload
* ======================
*/
if($action === 'upload'){
$id = $_POST['id'] ?? null;
if(!$id){
$id = uniqid("f_");
}
$dir = $uploadDir . $id;
if(!is_dir($dir)){
mkdir($dir, 0755, true);
}
// chunk
if(isset($_POST['chunk']) && isset($_POST['name'])){
file_put_contents($dir . "/" . $_POST['name'], $_POST['chunk']);
out([
"status"=>"ok",
"type"=>"chunk",
"id"=>$id
]);
}
// manifest
if(isset($_POST['manifest'])){
file_put_contents($dir . "/manifest.json", $_POST['manifest']);
out([
"status"=>"ok",
"type"=>"manifest",
"id"=>$id
]);
}
out(["status"=>"error","msg"=>"invalid"]);
}
/**
* ======================
* download
* ======================
*/
if($action === 'send'){
$id = $_GET['id'] ?? '';
$dir = $uploadDir . $id;
$file = $dir . "/manifest.json";
if(!file_exists($file)){
out(["status"=>"error","msg"=>"not found"]);
}
$manifest = json_decode(file_get_contents($file), true);
out([
"status"=>"ok",
"manifest"=>$manifest,
"id"=>$id
]);
}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。