diff --git a/README.md b/README.md index 2bbc231..c06653b 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ A downloader for articles from yuque(语雀知识库同步工具) "lastGeneratePath": "lastGeneratePath.log", "imgCdn": { "enabled": false, + "concurrency": 0, "imageBed": "qiniu", "host": "", "bucket": "", @@ -113,6 +114,7 @@ imgCdn 语雀图片转图床配置说明 | 参数名 | 含义 | 默认值 | |-----------|-----------------------------------------------------------------------------------------|---------| | enabled | 是否开启 | false | +| concurrency | 上传图片并发数, 0代表无限制,使用github图床时,并发问题严重,建议设置为1 | 0 | | imageBed | 选择将图片上传的图床
目前支持腾讯云(cos)、阿里云(oss)和七牛云(qiniu),又拍云(upyun),Github图床(github)
默认使用七牛云 | 'qiniu' | | host | 使用七牛云/又拍云图床时,需要指定CDN域名前缀 | | | bucket | 图床的bucket名称 | - | @@ -233,6 +235,11 @@ DEBUG=yuque-hexo.* yuque-hexo sync # Changelog +### v1.9.5 +- 修复腾讯云图床/Github图床上传问题 +- 图片上传失败时,取消停止进程 +- 新增图片上传时的并发数concurrency,使用Github图床时,并发问题严重,建议设置为1 + ### v1.9.4 - 🔥 新增GitHub图床和又拍云图床 diff --git a/config.js b/config.js index 71a1d09..b72ce1e 100644 --- a/config.js +++ b/config.js @@ -20,6 +20,7 @@ const defaultConfig = { onlyPublished: false, onlyPublic: false, imgCdn: { + concurrency: 0, enabled: false, imageBed: 'qiniu', host: '', diff --git a/lib/Downloader.js b/lib/Downloader.js index bf242bb..2ca45da 100644 --- a/lib/Downloader.js +++ b/lib/Downloader.js @@ -210,10 +210,21 @@ class Downloader { const { _cachedArticles, postBasicPath } = this; mkdirp.sync(postBasicPath); out.info(`create posts directory (if it not exists): ${postBasicPath}`); - const promiseList = _cachedArticles.map(async item => { - return await this.generatePost(item); + const promiseList = _cachedArticles.map(item => { + return async () => { + return await this.generatePost(item); + }; + }); + // 并发数 + const concurrency = this.config.imgCdn.concurrency || promiseList.length; + const queue = new Queue({ concurrency }); + queue.push(...promiseList); + await new Promise((resolve, reject) => { + queue.start(function(err) { + if (err) return reject(err); + resolve(); + }); }); - await Promise.all(promiseList); } // 文章下载 => 增量更新文章到缓存 json 文件 => 全量生成 markdown 文章 diff --git a/package.json b/package.json index 9526806..86b9f17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yuque-hexo", - "version": "1.9.4", + "version": "1.9.5", "description": "A downloader for articles from yuque", "main": "index.js", "scripts": { diff --git a/util/imageBeds/cos.js b/util/imageBeds/cos.js index 7f65eb6..0c2d424 100644 --- a/util/imageBeds/cos.js +++ b/util/imageBeds/cos.js @@ -3,7 +3,6 @@ // 腾讯云图床 const COS = require('cos-nodejs-sdk-v5'); const out = require('../../lib/out'); -const { transformRes } = require('../index'); const secretId = process.env.SECRET_ID; const secretKey = process.env.SECRET_KEY; @@ -40,7 +39,6 @@ class CosClient { }); return `https://${this.config.bucket}.cos.${this.config.region}.myqcloud.com/${this.config.prefixKey}/${fileName}`; } catch (e) { - out.warn(`检查图片信息时出错: ${transformRes(e)}`); return ''; } } @@ -63,8 +61,8 @@ class CosClient { }); return `https://${res.Location}`; } catch (e) { - out.error(`上传图片失败,请检查: ${transformRes(e)}`); - process.exit(-1); + out.warn(`上传图片失败,请检查: ${e}`); + return ''; } } } diff --git a/util/imageBeds/github.js b/util/imageBeds/github.js index 78b1d9c..9926d5f 100644 --- a/util/imageBeds/github.js +++ b/util/imageBeds/github.js @@ -51,6 +51,10 @@ class GithubClient { Authorization: `token ${secretKey}`, }, }); + if (result.status === 409) { + out.warn('由于github并发问题,图片上传失败'); + return ''; + } if (result.status === 200 || result.status === 201) { if (this.config.host) { return `${this.config.host}/gh/${secretId}/${this.config.bucket}/${this.config.prefixKey}/${fileName}`; @@ -96,11 +100,8 @@ class GithubClient { const base64File = imgBuffer.toString('base64'); const imgUrl = await this._fetch('PUT', fileName, base64File); if (imgUrl) return imgUrl; - out.error('上传图片失败,请检查'); - process.exit(-1); } catch (e) { - out.error(`上传图片失败,请检查: ${transformRes(e)}`); - process.exit(-1); + out.warn(`上传图片失败,请检查: ${e}`); } } } diff --git a/util/imageBeds/oss.js b/util/imageBeds/oss.js index cf4c2fb..aeed5ea 100644 --- a/util/imageBeds/oss.js +++ b/util/imageBeds/oss.js @@ -57,8 +57,8 @@ class OssClient { const res = await this.imageBedInstance.put(`${this.config.prefixKey}/${fileName}`, imgBuffer); return res.url; } catch (e) { - out.error(`上传图片失败,请检查: ${transformRes(e)}`); - process.exit(-1); + out.warn(`上传图片失败,请检查: ${e}`); + return ''; } } } diff --git a/util/imageBeds/qiniu.js b/util/imageBeds/qiniu.js index d3d7df7..199149c 100644 --- a/util/imageBeds/qiniu.js +++ b/util/imageBeds/qiniu.js @@ -72,14 +72,14 @@ class QiniuClient { this.formUploader.put(this.uploadToken, `${this.config.prefixKey}/${fileName}`, imgBuffer, this.putExtra, (respErr, respBody, respInfo) => { if (respErr) { - out.error(`上传图片失败,请检查: ${transformRes(respErr)}`); - process.exit(-1); + out.warn(`上传图片失败,请检查: ${transformRes(respErr)}`); + resolve(''); } if (respInfo.statusCode === 200) { resolve(`${this.config.host}/${this.config.prefixKey}/${fileName}`); } else { - out.error(`上传图片失败,请检查: ${transformRes(respInfo)}`); - process.exit(-1); + out.warn(`上传图片失败,请检查: ${transformRes(respInfo)}`); + resolve(''); } }); }); diff --git a/util/imageBeds/upyun.js b/util/imageBeds/upyun.js index a6779ee..57b7b85 100644 --- a/util/imageBeds/upyun.js +++ b/util/imageBeds/upyun.js @@ -3,7 +3,6 @@ // 又拍云图床 const upyun = require('upyun'); const out = require('../../lib/out'); -const { transformRes } = require('../index'); const secretId = process.env.SECRET_ID; const secretKey = process.env.SECRET_KEY; @@ -47,7 +46,7 @@ class UPClient { } return ''; } catch (e) { - out.error(`上传图片失败,请检查: ${transformRes(e)}`); + out.warn(`上传图片失败,请检查: ${e}`); return ''; } } @@ -65,11 +64,11 @@ class UPClient { if (res) { return `${this.config.host}/${this.config.prefixKey}/${fileName}`; } - out.error('上传图片失败,请检查又拍云配置'); - process.exit(-1); + out.warn('上传图片失败,请检查又拍云配置'); + return ''; } catch (e) { - out.error(`上传图片失败,请检查: ${transformRes(e)}`); - process.exit(-1); + out.warn(`上传图片失败,请检查: ${e}`); + return ''; } } } diff --git a/util/img2cdn.js b/util/img2cdn.js index b929b5a..5fdf01c 100644 --- a/util/img2cdn.js +++ b/util/img2cdn.js @@ -5,6 +5,8 @@ const getEtag = require('../lib/qetag'); const config = require('../config'); const out = require('../lib/out'); const ImageBed = require('./imageBeds'); +const Queue = require('queue'); +const lodash = require('lodash'); const imageBed = config.imgCdn.enabled ? ImageBed.getInstance(config.imgCdn) : null; @@ -83,49 +85,67 @@ async function img2Cdn(article) { // 1。从文章中获取语雀的图片URL列表 const matchYuqueImgUrlList = article.body.match(imageUrlRegExp); if (!matchYuqueImgUrlList) return article; - const promiseList = matchYuqueImgUrlList.map(async matchYuqueImgUrl => { - // 获取真正的图片url - const yuqueImgUrl = getImgUrl(matchYuqueImgUrl); - // 2。将图片转成buffer - const imgBuffer = await img2Buffer(yuqueImgUrl); - if (!imgBuffer) { - return { - originalUrl: matchYuqueImgUrl, - yuqueRealImgUrl: yuqueImgUrl, - url: yuqueImgUrl, - }; - } - // 3。根据buffer文件生成唯一的hash文件名 - const fileName = await getFileName(imgBuffer, yuqueImgUrl); - try { - // 4。检查图床是否存在该文件 - let url = await imageBed.hasImage(fileName); - let exists = true; - // 5。如果图床已经存在,直接替换;如果图床不存在,则先上传到图床,再将原本的语雀url进行替换 - if (!url) { - url = await imageBed.uploadImg(imgBuffer, fileName); - exists = false; + const promiseList = matchYuqueImgUrlList.map(matchYuqueImgUrl => { + return async () => { + // 获取真正的图片url + const yuqueImgUrl = getImgUrl(matchYuqueImgUrl); + // 2。将图片转成buffer + const imgBuffer = await img2Buffer(yuqueImgUrl); + if (!imgBuffer) { + return { + originalUrl: matchYuqueImgUrl, + yuqueRealImgUrl: yuqueImgUrl, + url: yuqueImgUrl, + }; } - return { - originalUrl: matchYuqueImgUrl, - yuqueRealImgUrl: yuqueImgUrl, - url, - exists, - }; - } catch (e) { - out.error(`访问图床出错,请检查配置: ${e}`); - process.exit(-1); - } + // 3。根据buffer文件生成唯一的hash文件名 + const fileName = await getFileName(imgBuffer, yuqueImgUrl); + try { + // 4。检查图床是否存在该文件 + let url = await imageBed.hasImage(fileName); + let exists = true; + // 5。如果图床已经存在,直接替换;如果图床不存在,则先上传到图床,再将原本的语雀url进行替换 + if (!url) { + url = await imageBed.uploadImg(imgBuffer, fileName); + exists = false; + } + return { + originalUrl: matchYuqueImgUrl, + yuqueRealImgUrl: yuqueImgUrl, + url, + exists, + }; + } catch (e) { + out.error(`访问图床出错,请检查配置: ${e}`); + return { + yuqueRealImgUrl: yuqueImgUrl, + url: '', + }; + } + }; + }); + // 并发数 + const concurrency = config.imgCdn.concurrency || promiseList.length; + const queue = new Queue({ concurrency, results: [] }); + queue.push(...promiseList); + await new Promise(resolve => { + queue.start(() => { + resolve(); + }); }); - const urlList = await Promise.all(promiseList); + const _urlList = queue.results; + const urlList = lodash.flatten(_urlList); + urlList.forEach(function(url) { - if (url) { + if (url.url) { article.body = article.body.replace(url.originalUrl, `![](${url.url})`); if (url.exists) { out.info(`图片已存在 skip: ${url.url}`); } else { out.info(`replace ${url.yuqueRealImgUrl} to ${url.url}`); } + } else { + out.warn(`图片替换失败,将使用原url: ${url.yuqueRealImgUrl}`); } }); return article;