前言
作為一名程式設計師,經常需要下載一些程式設計相關的環境,而國內的網路環境大家都知道,有的檔案用瀏覽器是下載不動的,於是我有了利用github下載檔案的想法。
我的demo專案地址:https://github.com/bobowire/wireboy.remote.download
參考資料:
- NodeJS使用node-fetch下載檔案並顯示下載進度示例:https://www.jianshu.com/p/4b58711cb72a
- nodejs——傳送郵件(帶附件):https://www.cnblogs.com/yourstars/p/6728931.html
覺得好玩的,大家可以點個贊~
下載加速原理
使用github的Action遠端執行檔案下載(下載qt環境速度可以達到3mb/s),然後將下載的檔案進行分片,每片15mb,分片後的檔案以郵件附件的方式傳送到國內郵箱,我們通過下載郵箱中的附件,將分片的附件合併成完整檔案,從而實現不翻牆、不用下載器也能下載國外檔案的目的。
簡單點說
- github遠端下載
- 檔案分片
- 通過郵箱發到國內
- 對附件進行合併
使用方法
- 新建github專案
- 建立js檔案download.js檔案,內容請查閱後文
- 建立workflows/RunDownload.yml檔案,內容請查閱後文
- 修改download.js中的fileURL 變數值,此為檔案url地址
- 在專案github->settings->Secrets中,點選右上方“new responsitory secret”按鈕,新增"EMAILPASS","SENDEMAIL","TOEMAIL"變數(授權碼、傳送郵箱、目標郵箱)
- 以上全部完成後,我們每次修改download.js檔案的fileURL地址,github都會自動進行一次下載。原理請自行百度“github action”。
注意:
- 授權碼(EMAILPASS)是指“郵箱第三方登入授權碼”,如何獲取授權碼,以QQ郵箱為例,請點選:http://jingyan.baidu.com/article/fedf0737af2b4035ac8977ea.html
github Action檔案(RunDownload.yml)
name: Github wireboy.remote.download
on:
push:
branches:
- main
schedule:
- cron: '* * * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout codes
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Run
run: npm install
- run: node download.js
env:
EMAILPASS: ${{ secrets.EMAILPASS }}
SENDEMAIL: ${{ secrets.SENDEMAIL }}
TOEMAIL: ${{ secrets.TOEMAIL }}
原始碼(download.js)
const fetch = require("node-fetch");
const fs = require("fs");
const path = require("path");
const progressStream = require('progress-stream');
const nodemailer = require('nodemailer');
//下載 的檔案 地址 (https://nodejs.org/dist/v12.18.3/node-v12.18.3-x64.msi)
let fileURL = 'https://nodejs.org/dist/v12.18.3/node-v12.18.3-x64.msi';
//下載儲存的檔案路徑
let fileSavePath = path.join(__dirname, path.basename(fileURL));
//快取檔案路徑
let tmpFileSavePath = fileSavePath + ".tmp";
//建立寫入流
const fileStream = fs.createWriteStream(tmpFileSavePath).on('error', function (e) {
console.error('error==>', e)
}).on('ready', function () {
console.log("開始下載:", fileURL);
}).on('finish', function () {
//下載完成後重新命名檔案
fs.renameSync(tmpFileSavePath, fileSavePath);
console.log('檔案下載完成:', fileSavePath);
const readstream = fs.createReadStream(fileSavePath);
let i = 0;
console.time('readtime');
let patchIndex = 0;
readstream.on('readable', () => {
{
console.log('start read');
let chunk = readstream.read(1024 * 1024 * 15);
while (null !== chunk) {
patchIndex = patchIndex + 1;
console.log('read times:'+patchIndex)
console.log(fileSavePath+'.email_'+patchIndex);
let emailFile = fs.createWriteStream(fileSavePath+'.email_'+patchIndex).on('finish',function(){
})
emailFile.write(chunk);
emailFile.end();
let msg = createEmailMessage(patchIndex+'_'+path.basename(fileURL),fileSavePath+'.email_'+patchIndex,path.basename(fileURL) + '(' + patchIndex + ')');
console.log('Send Mail ' + patchIndex + ' times');
console.log(path.basename(fileURL));
var transporter = createTransporter();
transporter.sendMail(msg, (error, info) => {
if (error) {
console.log('Error occurred');
console.log(error.message);
return;
}
console.log('Message sent successfully!');
console.log('Server responded with "%s"', info.response);
transporter.close();
});
chunk = readstream.read(1024 * 1024 * 10);
}
console.log('end read');
}
});
readstream.on('close', () => {
console.timeEnd('readtime');
});
});
//請求檔案
fetch(fileURL, {
method: 'GET',
headers: { 'Content-Type': 'application/octet-stream' },
// timeout: 100,
}).then(res => {
//獲取請求頭中的檔案大小資料
let fsize = res.headers.get("content-length");
//建立進度
let str = progressStream({
length: fsize,
time: 100 /* ms */
});
// 下載進度
str.on('progress', function (progressData) {
//不換行輸出
let percentage = Math.round(progressData.percentage) + '%';
console.log(percentage);
// process.stdout.write('\033[2J'+);
// console.log(progress);
/*
{
percentage: 9.05,
transferred: 949624,
length: 10485760,
remaining: 9536136,
eta: 42,
runtime: 3,
delta: 295396,
speed: 949624
}
*/
});
res.body.pipe(str).pipe(fileStream);
}).catch(e => {
//自定義異常處理
console.log(e);
});
var createTransporter = function(){
return nodemailer.createTransport({
service: 'qq',
auth: {
user: process.env.SENDEMAIL,//傳送者郵箱
pass: process.env.EMAILPASS //郵箱第三方登入授權碼
},
// logger: bunyan.createLogger({
// name: 'nodemailer'
// }),//列印日誌
debug: true
},{
from: process.env.SENDEMAIL,//傳送者郵箱
headers: {
'X-Laziness-level': 1000
}
});
}
console.log('SMTP Configured');
var createEmailMessage = function(filename,filepath,subject){
var message = {
// Comma separated lsit of recipients 收件人用逗號間隔
to: process.env.TOEMAIL,
// Subject of the message 資訊主題
subject: subject,
// plaintext body
text: '請查閱附件',
// Html body
html: '<p>下載檔案成功</p>',
// Apple Watch specific HTML body 蘋果手錶指定HTML格式
watchHtml: '<b>Hello</b> to myself',
// An array of attachments 附件
attachments: [
// String attachment
// {
// filename: 'notes.txt',
// content: 'Some notes about this e-mail',
// contentType: 'text/plain' // optional,would be detected from the filename 可選的,會檢測檔名
// },
// // Binary Buffer attchment
// {
// filename: 'image.png',
// content: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/' +
// '//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U' +
// 'g9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', 'base64'),
// cid: '00001' // should be as unique as possible 儘可能唯一
// },
// File Stream attachment
{
filename: filename,
path: filepath,
// cid: '00002' // should be as unique as possible 儘可能唯一
}
]
};
return message;
};