file-uploader-cli源码修改
注:可以直接跳到最下面看需要修改的部分
最近由于Gitee的开源审查,导致其无法再作为图床使用。在对比了多家OSS服务后,决定使用京东云作为新图床,并使用
file-uploader-cli
来进行图片和文件的上传,其中Typora只使用其进行博客图片的上传。但查看了file-uploader-cli
对于京东云的相关配置,并且进行多次测试后,发现它只能将文件上传到图床的根目录中。而如果在bucket
参数中加入根目录中的文件夹,返回的值就会出错。这很不利于Typora在将图片自动转为网络地址后进行展示。查询解决办法无果后,只能将其源码进行修改。
Typora图片自动上传至博客图片文件夹
分析配置文件(具体配置可查看原作者的README)
file-uploader-cli
给京东云的配置参数如下
{
"bucket": "*",
"region": "*",
"accessKeyId": "*",
"secretAccessKey": "*"
}
其中bucket
是用来配置图床地址的,比如我的是rezc404-img-host
;region
是服务所在地,官网中的Bucket域名
也会写到,如cn-north-1
;后两个是京东云提供的访问京东云API的密钥,可以在官网自行添加和删除。
可以在任意位置编写完此JSON文件后使用fuc -t 配置类型 -c 文件名.json
,比如fuc -t jdcloud -c jdcloud.json
,此时打开file-uploader-cli
安装目录中的config.json
文件就会发现程序添加了一段配置
{
"default": "jdcloud",
"jdcloud": {
"bucket": "rezc404-img-host",
"region": "cn-east-2",
"accessKeyId": "*",
"secretAccessKey": "*"
}
}
其中jdcloud
对象中的参数就是之前配置的参数。此时随便找张截图,使用fuc 文件绝对路径
,就可以将文件上传至图床,并返回网络地址
现在我想将上传至图床中的对象进行分类,图片放一起,文件放一起,可又因为上传后京东云OSS服务不支持文件的移动,这就要求我们必须在上传时选好路径。
可以在配置bucket
时加上文件夹路径,比如 rezc404-img-host/blog-img
,这时虽然可以上传,但是返回的网络地址rezc404-img-host/blog-img.s3.cn-east-2.jdcloud-oss.com/test.png
却是错误的,这导致直接在Typora中设定自定义命令fuc
后自动更改的网络地址也是错误的。
而在网站中查看外链则是rezc404-img-host.s3.cn-east-2.jdcloud-oss.com/blog-img/test.png
可以看到文件夹路径在上传时是可以放在bucket
中,但最后网络地址的结构是Bucket域名/文件夹名/文件名
,所以并不能简单的在bucket
参数中加入文件夹。
那该如何将文件上传至指定的文件夹中,且返回地址也正确呢?
答:参考file-uploader-cli
关于github的配置,添加这个path参数
分析源码
查看关于jdcloud
的源代码jdcloud.js
#!/usr/bin/env node
const sharp = require('sharp')
const fs = require('fs')
const stream = require('stream')
const AWS = require('aws-sdk')
const path = require('path')
const FileType = require('file-type')
const mimeTypes = require('mime-types')
const { supportImages } = require('./util')
async function upload (config, fileList){
const { accessKeyId, secretAccessKey, region, bucket, webp, quality } = config //1.获取config.json中关于jdcloud的参数
const s3 = new AWS.S3()
s3.endpoint = `https://s3.${region}.jdcloud-oss.com`
s3.config.update({
accessKeyId,
secretAccessKey,
s3ForcePathStyle: true,
region,
signatureVersion: "v4"
})
async function put (filePath) {
const mime = (await FileType.fromFile(filePath) || {}).mime || mimeTypes.lookup(filePath)
try {
let name = null, data = null;
if(webp && supportImages.indexOf(path.extname(filePath)) > -1){
name = `${path.basename(filePath,path.extname(filePath))}.webp`
data = await sharp(filePath)
.webp({ quality: quality || 75 })
.toBuffer();
// 创建一个bufferstream
filePath = path.basename(filePath, path.extname(filePath)) + '.webp'
readableStream = new stream.PassThrough()
//将Buffer写入
readableStream.end(data)
}else{
name = path.basename(filePath)
data = fs.createReadStream(filePath)
}
var params = {
Body: data,
Bucket: bucket, //2.上传的地址为config中叫bucket的参数,在后面加入"/文件夹名"就行
ContentType: mime,
Key: name,
}
s3.putObject(params, function(err, data) {
if(err) console.log(err)
!err && console.log(`https://${bucket}.s3.${region}.jdcloud-oss.com/${name}`) //3.返回的网络地址格式,其中bucket是直接插入其中,所以如果将文件夹名也加入bucket中,那么返回的结果也将是错的
});
} catch (e) {
console.log(e);
}
}
for(let i = 0; i < fileList.length; i++){
await put(fileList[i])
}
}
module.exports = upload
修改源码
其中12行,42行和48行是重点:
-
12行中的变量对应的正是配置文件中的参数,所以只需要在其中添加一个路径参数
cloudPath
即可const { accessKeyId, secretAccessKey, cloudPath, region, bucket, webp, quality } = config
-
42行中
Bucket
为上传地址,value值中的bucket如果带上文件夹,就会传入图床中的文件夹,所以这里只需将value改为bucket + cloudPath,
即可
-
48行打印出来的是图片上传后的网络地址,不难看出如果
bucket
中带文件夹,返回的地址就不会正确,所以只需要把cloudPath
参数加在文件名前即可!err && console.log(`https://${bucket}.s3.${region}.jdcloud-oss.com${cloudPath}/${name}`)
-
最后只需要在配置文件中加入
cloudPath
即可(注:如果想放在根目录就只需要""
,如果要放入文件夹前面必须要加/
){ "default": "jdcloud", "jdcloud": { "bucket": "rezc404-img-host", "region": "cn-east-2", "cloudPath": "/blog-img", "accessKeyId": "*", "secretAccessKey": "*" } }
这时返回的地址就是正确的地址,在Typora中自动转换的路径也是正确的
博客中上传的图片会传入blog-img
文件夹中。如果我想向别的文件夹上传文件,比如博客的缩略图文件夹,那该怎么办呢?
答:多创建几个配置配置对象即可
不同文件上传至指定文件夹
分析源码
因为我目前只使用京东云作为图床,所以只需要添加另一段配置,然后使用同一个的上传代码,也就是jdcloud.js
就可以了。
配置文件
{
"default": "jdcloud",
"jdcloud": {
"bucket": "rezc404-img-host",
"region": "cn-east-2",
"cloudPath": "/blog-img",
"accessKeyId": "*",
"secretAccessKey": "*"
}
}
修改成如下
{
"default": "jdcloud-img",
"jdcloud-img": {
"bucket": "rezc404-img-host",
"region": "cn-east-2",
"cloudPath": "/blog-img",
"accessKeyId": "*",
"secretAccessKey": "*"
},
"jdcloud-thn": {
"bucket": "rezc404-img-host",
"region": "cn-east-2",
"cloudPath": "/blog-thumbnails",
"accessKeyId": "*",
"secretAccessKey": "*"
}
}
但此时因为index.js
中
#!/usr/bin/env node
const { program } = require('commander')
const helpOptions = require('./lib/core/help')
const generateConfig = require('./lib/core/generateConfig')
const pkg = require('./package.json')
const configOperate = require('./lib/core/configOperate')
const { readConfig, absolutePath, supportList } = require('./lib/util')
helpOptions()
program
.version(pkg.version)
.parse(process.argv);
configOperate()
const config = generateConfig()
const options = program.opts()
const type = options['type'] || readConfig()['default']
if(supportList.indexOf(type) > -1){ //const supportList = ['github', 'ali-oss', 'qiniu', 'ftp', 'cos', 'jdcloud']
if(options.length === 0 && program.args.length === 0){
console.log('please specify the file to upload')
return
}
const fileList = absolutePath(program.args)
if(fileList.length > 0 && (!config || Object.keys(config).length === 0)){
console.log("can't find configuration")
return
}
fileList.length > 0 && require(`./lib/${type}`)(config,fileList)
}
supportList
里没有jdcloud-img
和jdcloud-thn
,所以程序找不到上传的入口。
修改源码
解决办法有两种:
- 在
supportList
中添加这两个type
,并复制**\file-uploader-cli\lib**中的jdcloud.js
,改名成配置名jdcloud-img.js
jdcloud-thn.js
- 修改源码,将传入的
type
的”-“后的部分去掉
我选择了第二种,在19行后添加
const minusIndex = type.indexOf('-')
const typeStr = minusIndex > -1 ? type.substring(0, minusIndex) : type
并把下面的type
都换成typeStr
。
本来认为已经可以使用了,没想到第17行的generateConfig()
函数报错,进去查看
const { program } = require('commander')
const { updateFile, readOptionConfig, readConfig } = require('../util')
const generateConfig = () => {
let config = null
const options = program.opts()
// 如果指定配置 使用当前指定
if(options.config){
if(!options.type){
throw new Error('configuration needs to specify the type')
}else{
config = readOptionConfig(options.config)//具体函数在下面
updateFile(options.type, config)
}
}else if(options.type){
config = readConfig()[options.type]
}else{
const setting = readConfig()
config = setting[setting.default]
}
return config
}
module.exports = generateConfig
// 读取选项指定配置
const readOptionConfig = (file) => {
createConfigFile()
console.log(path.resolve(process.cwd(), file))
const config = JSON.parse(readFileSync(path.resolve(process.cwd(), file), 'utf8'));
return config
}
发现如果上传使用指定配置,程序会判断此配置中是否说明其种类,只有含有种类的配置程序才会在config.json
中找到它。但改写之后配置一没有种类,二在运行readOptionConfig()
函数中process.cwd()
返回的路径也是错误的。我认为反正我目前也只使用京东云这一种配置,就索性把判断种类这一步去掉,并使用最后一个else
中的逻辑
if(options.config){
const setting = readConfig()
config = setting[options.config]
}
这是返回的config
就是所选的配置,而且图片也成功上传
至此,对file-uploader-cli
的源码修改已经全部结束。
修改部分汇总
-
\file-uploader-cli\lib\jdcloud.js
#!/usr/bin/env node const sharp = require('sharp') const fs = require('fs') const stream = require('stream') const AWS = require('aws-sdk') const path = require('path') const FileType = require('file-type') const mimeTypes = require('mime-types') const { supportImages } = require('./util') async function upload(config, fileList) { const { accessKeyId, secretAccessKey, cloudPath, region, bucket, webp, quality } = config const s3 = new AWS.S3() s3.endpoint = `https://s3.${region}.jdcloud-oss.com` s3.config.update({ accessKeyId, secretAccessKey, s3ForcePathStyle: true, region, signatureVersion: "v4" }) async function put(filePath) { const mime = (await FileType.fromFile(filePath) || {}).mime || mimeTypes.lookup(filePath) try { let name = null, data = null; if (webp && supportImages.indexOf(path.extname(filePath)) > -1) { name = `${path.basename(filePath, path.extname(filePath))}.webp` data = await sharp(filePath) .webp({ quality: quality || 75 }) .toBuffer(); // 创建一个bufferstream filePath = path.basename(filePath, path.extname(filePath)) + '.webp' readableStream = new stream.PassThrough() //将Buffer写入 readableStream.end(data) } else { name = path.basename(filePath) data = fs.createReadStream(filePath) } var params = { Body: data, Bucket: bucket + cloudPath, ContentType: mime, Key: name, } s3.putObject(params, function (err, data) { if (err) console.log(err) !err && console.log(`https://${bucket}.s3.${region}.jdcloud-oss.com${cloudPath}/${name}`) }); } catch (e) { console.log(e); } } for (let i = 0; i < fileList.length; i++) { await put(fileList[i]) } } module.exports = upload
修改第12行,42行和48行
-
\config.json
{ "default": "jdcloud", "jdcloud": { "bucket": "rezc404-img-host", "region": "cn-east-2", "cloudPath": "/blog-img", "accessKeyId": "*", "secretAccessKey": "*" } }
添加第6行
-
\file-uploader-cli\index.js
#!/usr/bin/env node const { program } = require('commander') const helpOptions = require('./lib/core/help') const generateConfig = require('./lib/core/generateConfig') const pkg = require('./package.json') const configOperate = require('./lib/core/configOperate') const { readConfig, absolutePath, supportList } = require('./lib/util') helpOptions() program .version(pkg.version) .parse(process.argv); configOperate() const config = generateConfig() const options = program.opts() const type = options['type'] || readConfig()['default'] const minusIndex = type.indexOf('-') const typeStr = minusIndex > -1 ? type.substring(0, minusIndex) : type if (supportList.indexOf(typeStr) > -1) { if (options.length === 0 && program.args.length === 0) { console.log('please specify the file to upload') return } const fileList = absolutePath(program.args) if (fileList.length > 0 && (!config || Object.keys(config).length === 0)) { console.log("can't find configuration") return } fileList.length > 0 && require(`./lib/${typeStr}`)(config, fileList) }
添加第19,20行
-
\file-uploader-cli\lib\core\generateConfig.js
const { program } = require('commander') const { updateFile, readOptionConfig, readConfig } = require('../util') const generateConfig = () => { let config = null const options = program.opts() // 如果指定配置 使用当前指定 if(options.config){ const setting = readConfig() config = setting[options.config] }else if(options.type){ config = readConfig()[options.type] }else{ const setting = readConfig() config = setting[setting.default] } return config } module.exports = generateConfig
修改第8行后
if
里的代码