家中有三条宽带,想通过搭建私有云盘,叠加上传和下载的速度。对比了多种私有云的系统,没找到满足的要求的,所以便萌生出搭建一个可宽带叠加的分布式私有云存储。
可道云、Nextcloud、易有云:没有多节点。
Cloidreve:有从服务器,但只是用来离线下载的
Seafile:支持ceph分布式储存,只能单节点。
IPFS:去中心化,p2p,未来之星。网关被污染了,有点费硬盘。
Minio: 分布式储存,但传输时只有一个节点传输,别的节点负责加油。
Resilio Sync、微力备份(接近要求,但属于备份软件,比较占储存空间)
jigdo(实现不了,没找到部署教程)(大佬可是试试这个)
√ 多节点
√ 部署简单
√ 文件秒传
√ 断点续传
√ MD5值校验
√ minio存储(防止文件上传漏洞)
√ 大文件下载(数据流的方式,内存占用小)
√ 下载和上传的速度基本取决于宽带上传的下载的速度。
上传界面
负责提供浏览界面,接收客户端的请求,返回文件的md5。
//添加节点地址
$nodes = ['http://ip1:80',"http://ip2:80"];
$dbh = new PDO('mysql:host=localhost;dbname=dbname', "dbuser", "password");
?>
修改
########################需要修改########################
minioClient = Minio('ip:9000',
access_key='minio_key',
secret_key='minio_secret',
secure=False)
#如果是采用https则,secure=True。
buckets = "mydisk"
########################需要修改########################
运行
//运行minio
/home/minio/app/minio server /home/minio/data --config-dir /home/minio/config --console-address :42606 --address :9000
//运行node.py
python3 node.py
计算文件的MD5 ,获取切片
/**
* 计算文件的MD5 ,获取切片
* @param file 文件
* @param chunkSize 分片大小
* @returns Promise
*/
function md5(file, chunkSize = 1024*1024*1) {
return new Promise((resolve, reject) => {
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
let chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
let spark = new SparkMD5.ArrayBuffer();
let fileReader = new FileReader();
var slices = [];
fileReader.onload = function(e) {
spark.append(e.target.result);
currentChunk++;
num = 100*currentChunk/chunks;
num = num.toFixed(2);
jindutiao(num,"计算哈希","#7A0BE2")
if (currentChunk < chunks) {
loadNext();
} else {
slices.push(spark.end());
resolve(slices);
}
};
// fileReader.onerror = function(e) {
// reject(e);
// };
function loadNext() {
let start = currentChunk * chunkSize;
let end = start + chunkSize;
if (end > file.size){
end = file.size;
}
slice = blobSlice.call(file, start, end);
slices.push(slice);
fileReader.readAsArrayBuffer(slice);
}
loadNext();
});
}
上传片段
//上传片段
function sendChunk(id, chunks,offset,name){
// 逐个提交
// 用于保证ajax发送完毕
var task=offset.length;
var alllen = chunks.length ;
chunks.forEach(function(chunk, index){
if (offset.length > 0) {text = "断点续传";}
if (offset.length === 0){text = "文件上传";}
if (offset.indexOf(index.toString()) >= 0) {
return;
}
var formData = new FormData();
formData.append('fileId', id);
formData.append('myFileChunk', chunk);
formData.append('chunkIndex', index);
url = nodes[index%nodes.length]+"/success";
console.log(url);
$.ajax({
type: "POST",
url: url,
data: formData,
contentType: false,
processData: false,
async: true,
success: function(done){
console.log("a00" + done + "a00");
if(done == 200){
task =task + 1;
num = 100*task/alllen ;
num = num.toFixed(2);
console.log(index + "已完成" + num.toString() + "%");
jindutiao(num,text)
}
}
})
})
}
进度条
//进度条
function jindutiao(ini,text,color="pink") {
let texth = document.getElementById("text");
let cont = document.getElementById("cont");
let container = document.getElementById("container");
cont.style.backgroundColor = color;
container.style.display = 'block';
cont.style.width = ini + "%";
if(ini>=100){
text = text + "成功";
ini = 100.00;
}
texth.innerText = text + ": " + ini + "%";
}
下载文件,二进制的方式。
//下载文件,二进制的方式。
function getBinaryContent(url , i) {
//console.log("getBinaryContent : " + url);
return new Promise((resolve, reject) => {
try {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer"; // 设置返回的类型为arraybuffer
xhr.onload = function () {
resolve({
index: i, // 文件块的索引
buffer: xhr.response, // 范围请求对应的数据
});
};
xhr.onprogress=function(event){
if(event.lengthComputable) {
var percentComplete = event.loaded / event.total*100;
percentComplete = percentComplete.toFixed(2);
//console.log("当前下载进度: " + event.loaded + "," + event.total , percentComplete);
//updateProgress(percentComplete * 100);
}
}
xhr.send();
} catch (err) {
reject(new Error(err));
}
});
}
多进程
//多进程
//poolLimit 代表进程池的数量
async function download( urls, poolLimit = 2 ) {
// // Abort the download stream when leaving the page
// window.isSecureContext && window.addEventListener('beforeunload', evt => {
// writer.abort()
// })
const fileStream= streamSaver.createWriteStream(filename);
const writer = fileStream.getWriter();
const all_part_num = Object.keys(urls).length;
const pools = oneArrToTwoArr([...new Array(all_part_num).keys()],poolLimit*2);
const MD5 = new SparkMD5();
//分组下载
for (let pool of pools) {
var results = await asyncPool(
poolLimit,
pool,
(i) => {
//console.log(urls[i])
ini = (i+1)/all_part_num*100;
ini = ini.toFixed(2)
jindutiao(ini,"文件下载")
//异步下载,返回到results
return getBinaryContent(urls[i], i);
}
);
//文件数据 格式转换,转换为数组的类型
var sortedBuffers = results.map((item) => new Uint8Array(item.buffer));
for (let array of sortedBuffers) {
writer.write(array)
}
}
writer.close();
}
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = []; // 存储所有的异步任务
const executing = []; // 存储正在执行的异步任务
for (const item of array) {
// 调用iteratorFn函数创建异步任务
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p); // 保存新的异步任务
// 当poolLimit值小于或等于总任务个数时,进行并发控制
if (poolLimit <= array.length) {
// 当任务完成后,从正在执行的任务数组中移除已完成的任务
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e); // 保存正在执行的异步任务
if (executing.length >= poolLimit) {
await Promise.race(executing); // 等待较快的任务执行完成
}
}
}
return Promise.all(ret);
}
下载文件,二进制的方式。
//下载文件,二进制的方式。
function getBinaryContent(url , i) {
//console.log("getBinaryContent : " + url);
return new Promise((resolve, reject) => {
try {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer"; // 设置返回的类型为arraybuffer
xhr.onload = function () {
resolve({
index: i, // 文件块的索引
buffer: xhr.response, // 范围请求对应的数据
});
};
xhr.onprogress=function(event){
if(event.lengthComputable) {
var percentComplete = event.loaded / event.total*100;
percentComplete = percentComplete.toFixed(2);
//console.log("当前下载进度: " + event.loaded + "," + event.total , percentComplete);
//updateProgress(percentComplete * 100);
}
}
xhr.send();
} catch (err) {
reject(new Error(err));
}
});
}