1. 前言

家中有三条宽带,想通过搭建私有云盘,叠加上传和下载的速度。对比了多种私有云的系统,没找到满足的要求的,所以便萌生出搭建一个可宽带叠加的分布式私有云存储。

2. 各种私有云盘对比

可道云、Nextcloud、易有云:没有多节点。
Cloidreve:有从服务器,但只是用来离线下载的
Seafile:支持ceph分布式储存,只能单节点。
IPFS:去中心化,p2p,未来之星。网关被污染了,有点费硬盘。
Minio: 分布式储存,但传输时只有一个节点传输,别的节点负责加油。
Resilio Sync、微力备份(接近要求,但属于备份软件,比较占储存空间)
jigdo(实现不了,没找到部署教程)(大佬可是试试这个)

3.本云盘实现的功能

多节点
部署简单
文件秒传
断点续传
MD5值校验
minio存储(防止文件上传漏洞)
大文件下载(数据流的方式,内存占用小)
下载和上传的速度基本取决于宽带上传的下载的速度。

4. 界面

上传界面

文件浏览
在这里插入图片描述

4. 安装

4.1 负载均衡服务器

负责提供浏览界面,接收客户端的请求,返回文件的md5。


只需修改config.php

<?php
//添加节点地址
$nodes = ['http://ip1:80',"http://ip2:80"];
$dbh = new PDO('mysql:host=localhost;dbname=dbname', "dbuser", "password");
?>

4.1 储存节点

修改

########################需要修改########################
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

5. 代码说明

计算文件的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));
        }
    });
   
}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐