Skip to content

支持上传文件夹 #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 82 additions & 21 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const path = require('path');
require('winston-daily-rotate-file');
const ProgressBar = require('progress');
const BlueBirdPromise = require("bluebird");
const glob = require('glob');

const logger = require('../lib/log');
const { DEFAULT_CHUNK_SIZE, MAX_CHUNK } = require('../lib/constants');
Expand All @@ -33,7 +34,7 @@ process.on('uncaughtException', error => {
logger.error(error.stack);
})

const upload = async (filePath, parts = []) => {
const upload = async (filePath, parts = [], requestUrl) => {
const bar = new ProgressBar(':bar [:current/:total] :percent ', { total: totalChunk });
const uploadChunk = async (currentChunk, currentChunkIndex, parts, isRetry) => {
if (parts.some(({ partNumber, size }) => partNumber === currentChunkIndex && size === currentChunk.length)) {
Expand All @@ -47,7 +48,7 @@ const upload = async (filePath, parts = []) => {
version,
partNumber: currentChunkIndex,
size: currentChunk.length,
currentChunk
currentChunk
}, {
headers: {
'Content-Type': 'application/octet-stream'
Expand Down Expand Up @@ -75,14 +76,14 @@ const upload = async (filePath, parts = []) => {
}
}

console.log(`\n开始上传\n`)
logger.info('开始上传')
console.log(`\n开始上传 (${filePath})\n`);
logger.info(`开始上传 (${filePath})`);

try {

const chunkIndexs = new Array(totalChunk).fill("").map((_,index) => index+1)
const chunkIndexs = new Array(totalChunk).fill("").map((_, index) => index + 1)

await BlueBirdPromise.map(chunkIndexs,(currentChunkIndex)=>{
await BlueBirdPromise.map(chunkIndexs, (currentChunkIndex) => {
const start = (currentChunkIndex - 1) * chunkSize;
const end = ((start + chunkSize) >= fileSize) ? fileSize : start + chunkSize - 1;
const stream = fs.createReadStream(filePath, { start, end })
Expand Down Expand Up @@ -113,9 +114,9 @@ const upload = async (filePath, parts = []) => {





const merge = async () => {

const merge = async () => {
console.log(chalk.cyan('正在合并分片,请稍等...'))
return await _mergeAllChunks(requestUrl, {
version,
Expand All @@ -126,7 +127,7 @@ const upload = async (filePath, parts = []) => {
Authorization
});
}


try {
const res = await withRetry(merge, 3, 500);
Expand All @@ -140,11 +141,11 @@ const upload = async (filePath, parts = []) => {
return;
}

console.log(chalk.green(`\n上传完毕\n`))
console.log(chalk.green(`\n上传完毕 (${filePath})\n`))
logger.info('************************ 上传完毕 ************************')
}

const getFileMD5Success = async (filePath) => {
const getFileMD5Success = async (filePath, requestUrl) => {
try {
const res = await _getExistChunks(requestUrl, {
fileSize,
Expand All @@ -160,10 +161,10 @@ const getFileMD5Success = async (filePath) => {

// 上传过一部分
if (Array.isArray(res.data.parts)) {
await upload(filePath, res.data.parts);
await upload(filePath, res.data.parts, requestUrl);
} else {
// 未上传过
await upload(filePath);
await upload(filePath, [], requestUrl);
}
} catch (error) {
logger.error(error.message);
Expand All @@ -173,20 +174,20 @@ const getFileMD5Success = async (filePath) => {
}
}

const getFileMD5 = async (filePath) => {
const getFileMD5 = async (filePath, requestUrl) => {
totalChunk = Math.ceil(fileSize / DEFAULT_CHUNK_SIZE);
if (totalChunk > MAX_CHUNK) {
chunkSize = Math.ceil(fileSize / MAX_CHUNK);
totalChunk = Math.ceil(fileSize / chunkSize);
}
const spark = new SparkMD5.ArrayBuffer();
try {
console.log(`\n开始计算 MD5\n`)
logger.info('开始计算 MD5')
console.log(`\n开始计算 MD5 (${filePath})\n`);
logger.info(`开始计算 MD5 (${filePath})`);

const bar = new ProgressBar(':bar [:current/:total] :percent ', { total: totalChunk });
await new Promise(resolve => {
stream = fs.createReadStream(filePath, { highWaterMark: chunkSize })
stream = fs.createReadStream(filePath, { highWaterMark: chunkSize });
stream.on('data', chunk => {
bar.tick();
spark.append(chunk)
Expand All @@ -198,7 +199,7 @@ const getFileMD5 = async (filePath) => {
md5 = spark.end();
spark.destroy();
console.log(`\n文件 MD5:${md5}\n`)
await getFileMD5Success(filePath);
await getFileMD5Success(filePath, requestUrl);
resolve();
})
}).catch(error => {
Expand All @@ -212,14 +213,70 @@ const getFileMD5 = async (filePath) => {
}
}

const uploadFile = async (filePath, size, requestUrl) => {
fileSize = size;
await getFileMD5(filePath, requestUrl);
md5 = '';
uploadId = '';
fileSize = 0;
chunkSize = DEFAULT_CHUNK_SIZE;
totalChunk = 0;
}

const uploadDir = async (dir) => {
let files = [];
try {
files = await new Promise((resolve, reject) => {
glob("**/**", {
cwd: dir,
root: dir
}, function (error, files = []) {
if (error) {
reject(error);
} else {
resolve(files)
}
})
});
} catch (error) {
if (error) {
console.log(chalk.red((error.response && error.response.data) || error.message));
logger.error(error.message);
logger.error(error.stack);
process.exit(1);
} else {
resolve(files)
}
}


for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.lstatSync(filePath);
const isDirectory = stat.isDirectory();
if (!isDirectory) {
const url = new URL(`chunks/${dir.split(path.sep).pop()}/${file}`, requestUrl.endsWith('/') ? requestUrl : `${requestUrl}/`).toString();
await uploadFile(filePath, stat.size, url);
console.log('************************ **** ************************');
logger.info('************************ **** ************************');
}
}
}

const beforeUpload = async (filePath) => {
const isUploadDir = argv.dir;
let fSize = 0;
try {
const stat = fs.lstatSync(filePath);
if (stat.isDirectory()) {
const isDirectory = stat.isDirectory();
if (isDirectory && !isUploadDir) {
console.log(chalk.red(`\n${filePath}不合法,需指定一个文件\n`))
process.exit(1);
} else if (!isDirectory && isUploadDir) {
console.log(chalk.red(`\n${filePath}不合法,需指定一个文件夹\n`))
process.exit(1);
}
fileSize = stat.size;
fSize = stat.size;
} catch (error) {
if (error.code === 'ENOENT') {
console.log(chalk.red(`未找到 ${filePath}`));
Expand All @@ -230,7 +287,11 @@ const beforeUpload = async (filePath) => {
}
process.exit(1);
}
await getFileMD5(filePath);
if (isUploadDir) {
await uploadDir(filePath);
} else {
await uploadFile(filePath, fSize, requestUrl);
}
}

const onUpload = (_username, _password) => {
Expand Down
11 changes: 9 additions & 2 deletions lib/argv.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const argv = require('yargs')
.usage('用法: coding-generic --username=<USERNAME>[:PASSWORD] --path=<FILE.EXT> --registry=<REGISTRY>')
.usage('上传文件: coding-generic --username=<USERNAME>[:PASSWORD] --path=<FILE.EXT> --registry=<REGISTRY>')
.usage('上传文件夹: coding-generic --username=<USERNAME>[:PASSWORD] --dir --path=<FOLDER> --registry=<REGISTRY>')
.options({
username: {
alias: 'u',
Expand All @@ -21,12 +22,18 @@ const argv = require('yargs')
describe: '上传分块并行数',
demandOption: true,
default: 5,
},
dir: {
alias: 'd',
describe: '上传文件夹',
boolean: true,
}
})
.alias('version', 'v')
.help('h')
.alias('h', 'help')
.example('coding-generic --username=coding@coding.com:123456 --path=./test.txt --registry="https://codingcorp-generic.pkg.coding.net/project-name/generic-repo/chunks/test.txt?version=latest"')
.example('上传文件: coding-generic --username=coding@coding.com:123456 --path=./test.txt --registry="https://codingcorp-generic.pkg.coding.net/project-name/generic-repo/chunks/test.txt?version=latest"')
.example('上传文件夹: coding-generic --username=coding@coding.com:123456 --dir --path=./dirname --registry="https://codingcorp-generic.pkg.coding.net/project-name/generic-repo?version=latest"')
.argv;

module.exports = argv;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "coding-generic",
"version": "1.2.6",
"version": "1.2.7",
"description": "",
"main": "index.js",
"bin": {
Expand All @@ -15,6 +15,7 @@
"chalk": "^4.1.0",
"cos-nodejs-sdk-v5": "^2.8.2",
"form-data": "^3.0.0",
"glob": "^8.0.3",
"progress": "^2.0.3",
"prompts": "^2.3.2",
"spark-md5": "^3.0.1",
Expand Down
75 changes: 65 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,17 @@ aws4@^1.8.0:
resolved "https://registry.npm.taobao.org/aws4/download/aws4-1.10.1.tgz?cache=0&sync_timestamp=1597236947743&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faws4%2Fdownload%2Faws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
integrity sha1-4eguTz6Zniz9YbFhKA0WoRH4ZCg=

axios@^0.20.0:
version "0.20.0"
resolved "https://registry.npm.taobao.org/axios/download/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd"
integrity sha1-BXujDwSIRpSZOozQf6OUz/EcUL0=
axios@^0.21.1:
version "0.21.4"
resolved "https://mirrors.tencent.com/npm/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.10.0"
follow-redirects "^1.14.0"

balanced-match@^1.0.0:
version "1.0.2"
resolved "https://mirrors.tencent.com/npm/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==

bcrypt-pbkdf@^1.0.0:
version "1.0.2"
Expand All @@ -89,6 +94,13 @@ bluebird@^3.7.2:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==

brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://mirrors.tencent.com/npm/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"

caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
Expand Down Expand Up @@ -310,10 +322,10 @@ fn.name@1.x.x:
resolved "https://registry.npm.taobao.org/fn.name/download/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha1-JsrYAXlnrqhzG8QpYdBKPVmIrMw=

follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.13.0.tgz?cache=0&sync_timestamp=1597057976909&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha1-tC6Nk6Kn7qXtiGM2dtZZe8jjhNs=
follow-redirects@^1.14.0:
version "1.15.2"
resolved "https://mirrors.tencent.com/npm/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==

forever-agent@~0.6.1:
version "0.6.1"
Expand All @@ -338,6 +350,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"

fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://mirrors.tencent.com/npm/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=

get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npm.taobao.org/get-caller-file/download/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
Expand All @@ -350,6 +367,17 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"

glob@^8.0.3:
version "8.0.3"
resolved "https://mirrors.tencent.com/npm/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e"
integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^5.0.1"
once "^1.3.0"

har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
Expand Down Expand Up @@ -377,7 +405,15 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"

inherits@^2.0.3, inherits@~2.0.3:
inflight@^1.0.4:
version "1.0.6"
resolved "https://mirrors.tencent.com/npm/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"

inherits@2, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=
Expand Down Expand Up @@ -510,6 +546,13 @@ mimic-fn@^3.0.0:
resolved "https://registry.npm.taobao.org/mimic-fn/download/mimic-fn-3.1.0.tgz?cache=0&sync_timestamp=1596095644798&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmimic-fn%2Fdownload%2Fmimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74"
integrity sha1-ZXVRRbvz42lUuUnBZFBCdFHVynQ=

minimatch@^5.0.1:
version "5.1.0"
resolved "https://mirrors.tencent.com/npm/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7"
integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==
dependencies:
brace-expansion "^2.0.1"

moment@^2.11.2:
version "2.29.1"
resolved "https://registry.npm.taobao.org/moment/download/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
Expand All @@ -530,6 +573,13 @@ object-hash@^2.0.1:
resolved "https://registry.npm.taobao.org/object-hash/download/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea"
integrity sha1-0S2wROA80so9d8BXDYciWwLh5uo=

once@^1.3.0:
version "1.4.0"
resolved "https://mirrors.tencent.com/npm/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"

one-time@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/one-time/download/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
Expand Down Expand Up @@ -868,6 +918,11 @@ wrap-ansi@^7.0.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrappy@1:
version "1.0.2"
resolved "https://mirrors.tencent.com/npm/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=

xml2js@^0.4.19:
version "0.4.23"
resolved "https://registry.npm.taobao.org/xml2js/download/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
Expand Down