1、使用ssh的好处
通过使用SSH,你可以把所有传输的数据进行加密,这样"中间人"这种攻击方式就不可能实现了,而且也能够防止DNS欺骗和IP欺骗。使用SSH,还有一个额外的好处就是传输的数据是经过压缩的,所以可以加快传输的速度。
所谓“中间人”的攻击方式, 就是“中间人”冒充真正的服务器接收你传给服务器的数据,然后再冒充你把数据传给真正的服务器。服务器和你之间的数据传送被“中间人”一转手做了手脚之后,就会出现很严重的问题。
2、上传和下载要解决的两个问题:
- 上传(下载)的实现过程
- 在上传(下载)的过程中,前端需要知道上传的进度,更新进度条;在上传(下载)结束后,前端需要知道上传已经结束,更新上传数量
3、上传(下载)过程:
1)上传前的操作
在上传之前,我们要获取这几个参数:
- 被上传的文件在本地电脑的存储路径(绝对路径,包含文件名及后缀);
- 要上传的目的地的路径(远程端的路径,包含文件名及后缀),存到哪里;
- 一个用于上传的server对象,其中包括ip、port、username、password
2)调用服务端上传方法
在前端调用 Meteor.call,通知服务端相应的上传方法,并把上述参数传给服务端,去做上传
3)服务端执行上传(下载)
1、实现上传(下载)我们需要借助一个依赖包ssh2-sftp-client。
所以先下载它。
npm install ssh2-sftp-client
2、引入
let sftpClient = require('ssh2-sftp-client');
3、创建一个sftp对象
let sftp = new sftpClient();
4、上传的过程
sftp.connect({
host: '127.0.0.1',
port: '8080',
username: 'username',
password: '******'
}).then(() => {
return sftp.fastPut(localPath,remotePath, {step: (total_transferred, chunk, total) => {
//每小段上传完成后的回调
}})
})
.then(data => {
console.log( '上传结束',data);
sftp.end();
}).catch(err => {
console.log('上传出现错误',err);
sftp.end();
});
- 1)建立ssh连接,调用sftp.connect(server)方法,并传入server对象
- 2)执行上传,sftp.connect()返回一个Promise对象,在第一个then中,调用sftp.fastPut执行上传文件的操作
fastPut方法只是其中的一种上传方法,ssh包还提供有其它的上传方法。
fastPut(destFile, srcFile, options) ==> string
fastPut方法共有三个参数
- destFile为远程文件的路径(尾)
- srcFile为本地的路径(源)
- options为配置对象
options共有四个属性,其中step属性的值是一个方法。文件上传操作并不是一次性完成的,而是将一个大文件分成一小块一小块上传,所以每一小块上传完成后就调用一次这个方法。
在上传过程中会多次调用这个step方法。
step方法共有三个参数
- total_transferred,已经上传了多少k(包含本次上传的)
- chunk,本次上传了多少k
- total,此文件一共有多少k
存在的bug
上传过程中多次调用的step方法,其参数total_transferred,有时并不是按照递增执行的,
例如:按照正常情况,total_transferred的值应该是这样:…0.7,0.8,0.9,1
但有时它会是这样:…0.7,1,0.8,0.9,不靠谱。
- 3)当全部上传完成后,就执行第二个then中的回调方法。
- 回调的参数data是一个字符串,告诉你上传的结果,文件从哪上传到哪成功了。例如‘ E:\work\sg2148_MPanelCtl\img\Diagonal-45FoV-left.bmp was successfully uploaded to /opt/seeing/app/rgb_config/images/Diagonal-45FoV-left.bmp!’
在里面调用end方法,结束ssh - 4)当catch到error时也end结束ssh。
5、下载的过程
下载的过程同上传的过程类似
let sftp = new sftpClient();
sftp.connect(server).then(() => {
return sftp.fastGet(srcFile, destFile,{step: (total_transferred, chunk, total,) => {
//每小段下载完成后的回调
}});
}).then(() =>{
console.log("下载结束");
sftp.end();
}).catch((err) => {
console.log('下载出现错误',err);
sftp.end();
});
下载使用的是sftp.fastGet方法,并且其参数的顺序与上传不同
第一个参数是远程的路径(源)
第二个参数是本地的路径(尾)
总之无论上传还是下载方法,其第一个参数都是从哪里的地址,第二个参数是到哪里的地址。
4、前端如何知道每个文件上传的进度
上传一个文件是分多块上传的,每上传一小块就调用一次step函数。并且step函数的参数中含有已经上传完成的体积大小。
1、第一种方法,前端轮询数据库
在服务端每次调用step函数时,都将上传的进度更新到数据库;然后在前端向数据库轮询,当上传完成时resolve出去。
但我在项目中发现,服务端更新数据库有时会存在bug,数据更新不上,即使改成递归的更新数据库(即更新数据库不成功,就再次更新,直到更新成功为之),但还是偶尔有问题。
轮询函数
- 查询一次数据库获取传输情况的数据
- 没查到递归调用
- 根据状态做不同操作
- 如果还是正在传输,那么递归调用
async sftpSchedule(server, src, dest, successCb, failCb) {
let sftpInfo = await CommonMethods.meteorCall(
"querySftpInfo",
server,
src,
dest
);
let status = sftpInfo.status;
if (status === 1) {//传输完成
successCb();
} else if (status === -1) {//传输失败
failCb();
} else if (status === 0) {//传输正在进行
let percentComplete =
sftpInfo.total > 0 ? sftpInfo.total_transferred / sftpInfo.total : 0;
let progress = parseInt(percentComplete * 100);//进度
setTimeout(() => {
this.sftpSchedule(server, src, dest, successCb, failCb);
}, 500);
} else {
setTimeout(() => {
this.sftpSchedule(server, src, dest, successCb, failCb);
}, 500);
}
}
2、第二种,使用socket通知
服务端在step函数中向前端发消息,在传输完成时向前端发消息,在传输遇到错误时向前端发消息;前端根据这些消息做相应的处理。具体操作已在另一篇文章详述。