Skip to content

Commit 2d14487

Browse files
authored
Merge pull request #107 from LetTTGACO/img2cos-master
feat(图床): 图片替换支持腾讯云图床、阿里云图床、七牛云图床
2 parents 0f13e1e + 92bc038 commit 2d14487

File tree

10 files changed

+351
-92
lines changed

10 files changed

+351
-92
lines changed

README.md

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ A downloader for articles from yuque(语雀知识库同步工具)
3838
- mac / linux: `YUQUE_TOKEN=xxx yuque-hexo sync`
3939
- windows: `set YUQUE_TOKEN=xxx && yuque-hexo sync`
4040

41-
### 配置 腾讯云对象存储TOKEN(可选)
41+
### 配置 图床TOKEN(可选)
4242
语雀的url存在防盗链的问题,直接部署可能导致图片无法加载。
43-
如果需要语雀URL上传到腾讯云的COS中并替换原链接,就需要配置上传密钥。
43+
如果需要语雀URL上传到图床中并替换原链接,就需要配置上传密钥。
44+
45+
访问图床的密钥管理获取密钥,然后传入密钥到yuque-hexo
46+
- 腾讯云[API密钥管理](https://console.cloud.tencent.com/cam/capi)
47+
- 阿里云[API密钥管理](https://ram.console.aliyun.com/manage/ak)
48+
- 七牛云[API密钥管理](https://portal.qiniu.com/user/key)
4449

45-
访问[API密钥管理](https://console.cloud.tencent.com/cam/capi) 获取密钥,然后传入密钥到yuque-hexo
4650
- 在设置YUQUE_TOKEN的基础上配置SECRET_ID和SECRET_KEY
4751
- 命令执行时传入环境变量
4852
- mac / linux: `YUQUE_TOKEN=xxx SECRET_ID=xxx SECRET_KEY=xxx yuque-hexo sync`
@@ -70,6 +74,8 @@ A downloader for articles from yuque(语雀知识库同步工具)
7074
"lastGeneratePath": "lastGeneratePath.log",
7175
"imgCdn": {
7276
"enabled": false,
77+
"imageBed": "qiniu",
78+
"host": "",
7379
"bucket": "",
7480
"region": "",
7581
"prefixKey": ""
@@ -94,20 +100,38 @@ A downloader for articles from yuque(语雀知识库同步工具)
94100
| imgCdn | 语雀图片转CDN配置 | |
95101
> slug 是语雀的永久链接名,一般是几个随机字母。
96102
97-
imgCdn 语雀图片转COS(对象存储)配置说明
103+
imgCdn 语雀图片转图床配置说明
98104

99-
注意:开启后会将匹配到的所有的图片都上传到COS
105+
注意:开启后会将匹配到的所有的图片都上传到图床
100106

101107
| 参数名 | 含义 | 默认值 |
102108
| ------------- | ------------------------------------ | -------------------- |
103109
| enabled | 是否开启 | false |
104-
| bucket | 腾讯COS的bucket名称 | - |
105-
| region | 腾讯COS的region(地域名称) | - |
110+
| imageBed | 选择将图片上传的图床,目前支持腾讯云(cos)、阿里云(oss)和七牛云(qiniu),默认使用七牛云 | 'qiniu' |
111+
| host | 使用七牛云图床时,需要指定CDN域名前缀
112+
| bucket | 图床的bucket名称 | - |
113+
| region | 图床的的region | - |
106114
| prefixKey | 文件前缀 | - |
107-
> prefixKey 说明
115+
116+
> host 说明
117+
>
118+
> 由于七牛云默认使用CND进行图片外链访问(默认提供30天的临时域名或者添加自定义CDN域名),所以需要指定访问的域名前缀
119+
> 例如:'host': `http://image.1874.cool`,域名后面不需要加斜杠
120+
121+
> bucket和region说明
122+
>
123+
> [获取腾讯云的bucket和region](https://console.cloud.tencent.com/cos/bucket),示例:{ bucket: "blog", region: "ap-guangzhou" }
124+
>
125+
> [获取阿里云的bucket和region](https://oss.console.aliyun.com/bucket),示例:{ bucket: "blog", region: "oss-cn-shenzhen" }
108126
>
109-
> 如果需要将图片上传到COS的根目录,那么prefixKey不用配置。
127+
> [获取七牛云的bucket(空间)和region(机房)](https://portal.qiniu.com/kodo/overview),示例:{ bucket: "blog", region: "Zone_z2" }
110128
>
129+
> 七牛云机房取值: 华东(Zone_z0)华北(Zone_z0)华南(Zone_z0)北美(Zone_z0)
130+
131+
> prefixKey 说明
132+
>
133+
> 如果需要将图片上传到bucket的根目录,那么prefixKey不用配置。
134+
>
111135
> 如果想上传到指定目录blog/image下,则需要配置prefixKey为"prefixKey": "blog/image"。
112136
>
113137
> 目录名前后都不需要加斜杠
@@ -149,7 +173,7 @@ DEBUG=yuque-hexo.* yuque-hexo sync
149173
```
150174

151175
## Best practice
152-
176+
- [语雀云端写作Hexo+Github Actions+COS持续集成](https://www.yuque.com/1874w/1874.cool/roeayv)
153177
- [Hexo 博客终极玩法:云端写作,自动部署](https://www.yuque.com/u46795/blog/dlloc7)
154178
- [Hexo:语雀云端写作 Github Actions 持续集成](https://www.zhwei.cn/hexo-github-actions-yuque/)
155179

@@ -179,10 +203,8 @@ DEBUG=yuque-hexo.* yuque-hexo sync
179203

180204
more detail
181205
```
182-
- 为什么选择腾讯COS作为图床:腾讯的COS费用相对便宜,对于博客来说是非常划算且方便的。
183-
当然,如果想用其他图床,可以参考源码中实现方式,自行修改配置。
184206

185-
- 如果遇到上传到语雀的图片无法加载的问题,可以考虑开启imgCdn配置或者参考这个处理方式 [#41](https://github.com/x-cold/yuque-hexo/issues/41)
207+
- - 如果遇到上传到语雀的图片无法加载的问题, 可以考虑开启imgCdn配置或者参考这个处理方式[yuque-hexo插件语雀图片防盗链限制的解决方案](https://1874.cool/osar7h/)或者参考这个处理方式 [#41](https://github.com/x-cold/yuque-hexo/issues/41)
186208

187209
# Example
188210

@@ -191,8 +213,11 @@ DEBUG=yuque-hexo.* yuque-hexo sync
191213

192214
# Changelog
193215

216+
### v1.9.0
217+
- 🔥 支持腾讯云/阿里云/七牛云图床链接替换语雀链接
218+
194219
### v1.8.0
195-
- 🔥 支持自定义的适配器 adapter,具体查看 [配置示例](https://github.com/x-cold/yuque-hexo/tree/master/test/custom-adapter-project),如果需要实现类似图床上传的功能,可以参考[文章](https://juejin.cn/post/6875192087705288718)
220+
- 🔥 支持自定义的适配器 adapter,具体查看 [配置示例](https://github.com/x-cold/yuque-hexo/tree/master/test/custom-adapter-project)
196221

197222
### v1.7.0
198223

adapter/hexo.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const ejs = require('ejs');
44
const Entities = require('html-entities').AllHtmlEntities;
55
const FrontMatter = require('hexo-front-matter');
66
const { formatDate, formatRaw } = require('../util');
7-
const img2Cos = require('../util/img2cdn');
7+
const img2Cdn = require('../util/img2cdn');
88
const config = require('../config');
99

1010

@@ -66,7 +66,7 @@ function parseMatter(body) {
6666
module.exports = async function(post) {
6767
// 语雀img转成自己的cdn图片
6868
if (config.imgCdn.enabled) {
69-
post = await img2Cos(post);
69+
post = await img2Cdn(post);
7070
}
7171
// matter 解析
7272
const parseRet = parseMatter(post.body);

adapter/markdown.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
const { formatRaw } = require('../util');
4-
const img2Cos = require('../util/img2cdn');
4+
const img2Cdn = require('../util/img2cdn');
55
const config = require('../config');
66

77
/**
@@ -13,7 +13,7 @@ const config = require('../config');
1313
module.exports = async function(post) {
1414
// 语雀img转成自己的cdn图片
1515
if (config.imgCdn.enabled) {
16-
post = await img2Cos(post);
16+
post = await img2Cdn(post);
1717
}
1818
const { body } = post;
1919
const raw = formatRaw(body);

config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const defaultConfig = {
2121
onlyPublic: false,
2222
imgCdn: {
2323
enabled: false,
24+
imageBed: 'qiniu',
25+
host: '',
2426
bucket: '',
2527
region: '',
2628
prefixKey: '',

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "yuque-hexo",
3-
"version": "1.8.0",
3+
"version": "1.9.0",
44
"description": "A downloader for articles from yuque",
55
"main": "index.js",
66
"scripts": {
@@ -26,6 +26,7 @@
2626
"homepage": "https://github.com/x-cold/yuque-hexo#readme",
2727
"dependencies": {
2828
"@yuque/sdk": "^1.1.1",
29+
"ali-oss": "6.8.0",
2930
"chalk": "^2.4.1",
3031
"common-bin": "^2.7.3",
3132
"cos-nodejs-sdk-v5": "^2.11.6",
@@ -39,6 +40,7 @@
3940
"mkdirp": "^1.0.0",
4041
"moment": "^2.22.2",
4142
"prettier": "^2.0.4",
43+
"qiniu": "^7.4.0",
4244
"queue": "^4.5.0",
4345
"rimraf": "^2.6.2",
4446
"superagent": "^7.0.2",

util/imageBeds/cos.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
3+
// 腾讯云图床
4+
const COS = require('cos-nodejs-sdk-v5');
5+
const out = require('../../lib/out');
6+
7+
const secretId = process.env.SECRET_ID;
8+
const secretKey = process.env.SECRET_KEY;
9+
10+
11+
class CosClient {
12+
constructor(config) {
13+
this.config = config;
14+
this.imageBedInstance = new COS({
15+
SecretId: secretId, // 身份识别ID
16+
SecretKey: secretKey, // 身份秘钥
17+
});
18+
}
19+
20+
static getInstance(config) {
21+
if (!this.instance) {
22+
this.instance = new CosClient(config);
23+
}
24+
return this.instance;
25+
}
26+
27+
/**
28+
* 检查图床是否已经存在图片,存在则返回url,不存在返回空
29+
*
30+
* @param {string} fileName 文件名
31+
* @return {Promise<string>} 图片url
32+
*/
33+
async hasImage(fileName) {
34+
try {
35+
await this.imageBedInstance.headObject({
36+
Bucket: this.config.bucket, // 存储桶名字(必须)
37+
Region: this.config.region, // 存储桶所在地域,必须字段
38+
Key: `${this.config.prefixKey}/${fileName}`, // 文件名 必须
39+
});
40+
return `https://${this.config.bucket}.cos.${this.config.region}.myqcloud.com/${this.config.prefixKey}/${fileName}`;
41+
} catch (e) {
42+
return '';
43+
}
44+
}
45+
46+
/**
47+
* 上传图片到图床
48+
*
49+
* @param {Buffer} imgBuffer 文件buffer
50+
* @param {string} fileName 文件名
51+
* @return {Promise<string>} 图床的图片url
52+
*/
53+
async uploadImg(imgBuffer, fileName) {
54+
try {
55+
const res = await this.imageBedInstance.putObject({
56+
Bucket: this.config.bucket, // 存储桶名字(必须)
57+
Region: this.config.region, // 存储桶所在地域,必须字段
58+
Key: `${this.config.prefixKey}/${fileName}`, // 文件名 必须
59+
StorageClass: 'STANDARD', // 上传模式(标准模式)
60+
Body: imgBuffer, // 上传文件对象
61+
});
62+
return `https://${res.Location}`;
63+
} catch (e) {
64+
out.error(`上传图片失败,请检查: ${e}`);
65+
process.exit(-1);
66+
}
67+
}
68+
}
69+
70+
module.exports = CosClient;

util/imageBeds/index.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use strict';
2+
3+
const CosClient = require('./cos');
4+
const OssClient = require('./oss');
5+
const QiniuClient = require('./qiniu');
6+
const out = require('../../lib/out');
7+
8+
// 目前已适配图床列表
9+
const imageBedList = [ 'qiniu', 'cos', 'oss' ];
10+
11+
class ImageBeds {
12+
constructor(config) {
13+
this.config = config;
14+
this.imageBedInstance = this.getImageBedInstance(config.imageBed);
15+
}
16+
17+
static getInstance(config) {
18+
if (!this.instance) {
19+
this.instance = new ImageBeds(config);
20+
}
21+
return this.instance;
22+
}
23+
24+
/**
25+
* 获取图床对象的实例
26+
*
27+
* @param {string} imageBed 图床类型: cos | oss
28+
* @return {any} 图床实例
29+
*/
30+
getImageBedInstance(imageBed) {
31+
if (!imageBedList.includes(imageBed)) {
32+
out.error(`imageBed配置错误,目前只支持${imageBedList.toString()}`);
33+
process.exit(-1);
34+
}
35+
switch (imageBed) {
36+
case 'cos':
37+
return CosClient.getInstance(this.config);
38+
case 'oss':
39+
return OssClient.getInstance(this.config);
40+
case 'qiniu':
41+
return QiniuClient.getInstance(this.config);
42+
default:
43+
return QiniuClient.getInstance(this.config);
44+
}
45+
}
46+
47+
/**
48+
* 检查图床是否已经存在图片,存在则返回url
49+
*
50+
* @param {string} fileName 文件名
51+
* @return {Promise<string>} 图片url
52+
*/
53+
async hasImage(fileName) {
54+
return await this.imageBedInstance.hasImage(fileName);
55+
}
56+
57+
/**
58+
* 上传图片到图床
59+
*
60+
* @param {Buffer} imgBuffer 文件buffer
61+
* @param {string} fileName 文件名
62+
* @return {Promise<string>} 图床的图片url
63+
*/
64+
async uploadImg(imgBuffer, fileName) {
65+
return await this.imageBedInstance.uploadImg(imgBuffer, fileName);
66+
}
67+
68+
}
69+
70+
module.exports = ImageBeds;
71+

util/imageBeds/oss.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
3+
// 阿里云图床
4+
const OSS = require('ali-oss');
5+
const out = require('../../lib/out');
6+
7+
const secretId = process.env.SECRET_ID;
8+
const secretKey = process.env.SECRET_KEY;
9+
10+
11+
class OssClient {
12+
constructor(config) {
13+
this.config = config;
14+
this.imageBedInstance = new OSS({
15+
bucket: config.bucket,
16+
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
17+
region: config.region,
18+
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
19+
accessKeyId: secretId,
20+
accessKeySecret: secretKey,
21+
});
22+
}
23+
24+
static getInstance(config) {
25+
if (!this.instance) {
26+
this.instance = new OssClient(config);
27+
}
28+
return this.instance;
29+
}
30+
31+
/**
32+
* 检查图床是否已经存在图片,存在则返回url,不存在返回空
33+
*
34+
* @param {string} fileName 文件名
35+
* @return {Promise<string>} 图片url
36+
*/
37+
async hasImage(fileName) {
38+
try {
39+
await this.imageBedInstance.head(`${this.config.prefixKey}/${fileName}`);
40+
return `https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${this.config.prefixKey}/${fileName}`;
41+
} catch (e) {
42+
return '';
43+
}
44+
}
45+
46+
/**
47+
* 上传图片到图床
48+
*
49+
* @param {Buffer} imgBuffer 文件buffer
50+
* @param {string} fileName 文件名
51+
* @return {Promise<string>} 图床的图片url
52+
*/
53+
async uploadImg(imgBuffer, fileName) {
54+
try {
55+
const res = await this.imageBedInstance.put(`${this.config.prefixKey}/${fileName}`, imgBuffer);
56+
return res.url;
57+
} catch (e) {
58+
out.error(`上传图片失败,请检查: ${e}`);
59+
process.exit(-1);
60+
}
61+
}
62+
}
63+
64+
module.exports = OssClient;

0 commit comments

Comments
 (0)