Skip to content

Commit

Permalink
Merge pull request #107 from LetTTGACO/img2cos-master
Browse files Browse the repository at this point in the history
feat(图床): 图片替换支持腾讯云图床、阿里云图床、七牛云图床
  • Loading branch information
LetTTGACO authored Feb 17, 2022
2 parents 0f13e1e + 92bc038 commit 2d14487
Showing 10 changed files with 351 additions and 92 deletions.
53 changes: 39 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -38,11 +38,15 @@ A downloader for articles from yuque(语雀知识库同步工具)
- mac / linux: `YUQUE_TOKEN=xxx yuque-hexo sync`
- windows: `set YUQUE_TOKEN=xxx && yuque-hexo sync`

### 配置 腾讯云对象存储TOKEN(可选)
### 配置 图床TOKEN(可选)
语雀的url存在防盗链的问题,直接部署可能导致图片无法加载。
如果需要语雀URL上传到腾讯云的COS中并替换原链接,就需要配置上传密钥。
如果需要语雀URL上传到图床中并替换原链接,就需要配置上传密钥。

访问图床的密钥管理获取密钥,然后传入密钥到yuque-hexo
- 腾讯云[API密钥管理](https://console.cloud.tencent.com/cam/capi)
- 阿里云[API密钥管理](https://ram.console.aliyun.com/manage/ak)
- 七牛云[API密钥管理](https://portal.qiniu.com/user/key)

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

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

| 参数名 | 含义 | 默认值 |
| ------------- | ------------------------------------ | -------------------- |
| enabled | 是否开启 | false |
| bucket | 腾讯COS的bucket名称 | - |
| region | 腾讯COS的region(地域名称) | - |
| imageBed | 选择将图片上传的图床,目前支持腾讯云(cos)、阿里云(oss)和七牛云(qiniu),默认使用七牛云 | 'qiniu' |
| host | 使用七牛云图床时,需要指定CDN域名前缀
| bucket | 图床的bucket名称 | - |
| region | 图床的的region | - |
| prefixKey | 文件前缀 | - |
> prefixKey 说明

> host 说明
>
> 由于七牛云默认使用CND进行图片外链访问(默认提供30天的临时域名或者添加自定义CDN域名),所以需要指定访问的域名前缀
> 例如:'host': `http://image.1874.cool`,域名后面不需要加斜杠
> bucket和region说明
>
> [获取腾讯云的bucket和region](https://console.cloud.tencent.com/cos/bucket),示例:{ bucket: "blog", region: "ap-guangzhou" }
>
> [获取阿里云的bucket和region](https://oss.console.aliyun.com/bucket),示例:{ bucket: "blog", region: "oss-cn-shenzhen" }
>
> 如果需要将图片上传到COS的根目录,那么prefixKey不用配置。
> [获取七牛云的bucket(空间)和region(机房)](https://portal.qiniu.com/kodo/overview),示例:{ bucket: "blog", region: "Zone_z2" }
>
> 七牛云机房取值: 华东(Zone_z0)华北(Zone_z0)华南(Zone_z0)北美(Zone_z0)
> prefixKey 说明
>
> 如果需要将图片上传到bucket的根目录,那么prefixKey不用配置。
>
> 如果想上传到指定目录blog/image下,则需要配置prefixKey为"prefixKey": "blog/image"。
>
> 目录名前后都不需要加斜杠
@@ -149,7 +173,7 @@ DEBUG=yuque-hexo.* yuque-hexo sync
```

## Best practice

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

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

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

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

# Example

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

# Changelog

### v1.9.0
- 🔥 支持腾讯云/阿里云/七牛云图床链接替换语雀链接

### v1.8.0
- 🔥 支持自定义的适配器 adapter,具体查看 [配置示例](https://github.com/x-cold/yuque-hexo/tree/master/test/custom-adapter-project),如果需要实现类似图床上传的功能,可以参考[文章](https://juejin.cn/post/6875192087705288718)
- 🔥 支持自定义的适配器 adapter,具体查看 [配置示例](https://github.com/x-cold/yuque-hexo/tree/master/test/custom-adapter-project)

### v1.7.0

4 changes: 2 additions & 2 deletions adapter/hexo.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ const ejs = require('ejs');
const Entities = require('html-entities').AllHtmlEntities;
const FrontMatter = require('hexo-front-matter');
const { formatDate, formatRaw } = require('../util');
const img2Cos = require('../util/img2cdn');
const img2Cdn = require('../util/img2cdn');
const config = require('../config');


@@ -66,7 +66,7 @@ function parseMatter(body) {
module.exports = async function(post) {
// 语雀img转成自己的cdn图片
if (config.imgCdn.enabled) {
post = await img2Cos(post);
post = await img2Cdn(post);
}
// matter 解析
const parseRet = parseMatter(post.body);
4 changes: 2 additions & 2 deletions adapter/markdown.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const { formatRaw } = require('../util');
const img2Cos = require('../util/img2cdn');
const img2Cdn = require('../util/img2cdn');
const config = require('../config');

/**
@@ -13,7 +13,7 @@ const config = require('../config');
module.exports = async function(post) {
// 语雀img转成自己的cdn图片
if (config.imgCdn.enabled) {
post = await img2Cos(post);
post = await img2Cdn(post);
}
const { body } = post;
const raw = formatRaw(body);
2 changes: 2 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@ const defaultConfig = {
onlyPublic: false,
imgCdn: {
enabled: false,
imageBed: 'qiniu',
host: '',
bucket: '',
region: '',
prefixKey: '',
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "yuque-hexo",
"version": "1.8.0",
"version": "1.9.0",
"description": "A downloader for articles from yuque",
"main": "index.js",
"scripts": {
@@ -26,6 +26,7 @@
"homepage": "https://github.com/x-cold/yuque-hexo#readme",
"dependencies": {
"@yuque/sdk": "^1.1.1",
"ali-oss": "6.8.0",
"chalk": "^2.4.1",
"common-bin": "^2.7.3",
"cos-nodejs-sdk-v5": "^2.11.6",
@@ -39,6 +40,7 @@
"mkdirp": "^1.0.0",
"moment": "^2.22.2",
"prettier": "^2.0.4",
"qiniu": "^7.4.0",
"queue": "^4.5.0",
"rimraf": "^2.6.2",
"superagent": "^7.0.2",
70 changes: 70 additions & 0 deletions util/imageBeds/cos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

// 腾讯云图床
const COS = require('cos-nodejs-sdk-v5');
const out = require('../../lib/out');

const secretId = process.env.SECRET_ID;
const secretKey = process.env.SECRET_KEY;


class CosClient {
constructor(config) {
this.config = config;
this.imageBedInstance = new COS({
SecretId: secretId, // 身份识别ID
SecretKey: secretKey, // 身份秘钥
});
}

static getInstance(config) {
if (!this.instance) {
this.instance = new CosClient(config);
}
return this.instance;
}

/**
* 检查图床是否已经存在图片,存在则返回url,不存在返回空
*
* @param {string} fileName 文件名
* @return {Promise<string>} 图片url
*/
async hasImage(fileName) {
try {
await this.imageBedInstance.headObject({
Bucket: this.config.bucket, // 存储桶名字(必须)
Region: this.config.region, // 存储桶所在地域,必须字段
Key: `${this.config.prefixKey}/${fileName}`, // 文件名 必须
});
return `https://${this.config.bucket}.cos.${this.config.region}.myqcloud.com/${this.config.prefixKey}/${fileName}`;
} catch (e) {
return '';
}
}

/**
* 上传图片到图床
*
* @param {Buffer} imgBuffer 文件buffer
* @param {string} fileName 文件名
* @return {Promise<string>} 图床的图片url
*/
async uploadImg(imgBuffer, fileName) {
try {
const res = await this.imageBedInstance.putObject({
Bucket: this.config.bucket, // 存储桶名字(必须)
Region: this.config.region, // 存储桶所在地域,必须字段
Key: `${this.config.prefixKey}/${fileName}`, // 文件名 必须
StorageClass: 'STANDARD', // 上传模式(标准模式)
Body: imgBuffer, // 上传文件对象
});
return `https://${res.Location}`;
} catch (e) {
out.error(`上传图片失败,请检查: ${e}`);
process.exit(-1);
}
}
}

module.exports = CosClient;
71 changes: 71 additions & 0 deletions util/imageBeds/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';

const CosClient = require('./cos');
const OssClient = require('./oss');
const QiniuClient = require('./qiniu');
const out = require('../../lib/out');

// 目前已适配图床列表
const imageBedList = [ 'qiniu', 'cos', 'oss' ];

class ImageBeds {
constructor(config) {
this.config = config;
this.imageBedInstance = this.getImageBedInstance(config.imageBed);
}

static getInstance(config) {
if (!this.instance) {
this.instance = new ImageBeds(config);
}
return this.instance;
}

/**
* 获取图床对象的实例
*
* @param {string} imageBed 图床类型: cos | oss
* @return {any} 图床实例
*/
getImageBedInstance(imageBed) {
if (!imageBedList.includes(imageBed)) {
out.error(`imageBed配置错误,目前只支持${imageBedList.toString()}`);
process.exit(-1);
}
switch (imageBed) {
case 'cos':
return CosClient.getInstance(this.config);
case 'oss':
return OssClient.getInstance(this.config);
case 'qiniu':
return QiniuClient.getInstance(this.config);
default:
return QiniuClient.getInstance(this.config);
}
}

/**
* 检查图床是否已经存在图片,存在则返回url
*
* @param {string} fileName 文件名
* @return {Promise<string>} 图片url
*/
async hasImage(fileName) {
return await this.imageBedInstance.hasImage(fileName);
}

/**
* 上传图片到图床
*
* @param {Buffer} imgBuffer 文件buffer
* @param {string} fileName 文件名
* @return {Promise<string>} 图床的图片url
*/
async uploadImg(imgBuffer, fileName) {
return await this.imageBedInstance.uploadImg(imgBuffer, fileName);
}

}

module.exports = ImageBeds;

64 changes: 64 additions & 0 deletions util/imageBeds/oss.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';

// 阿里云图床
const OSS = require('ali-oss');
const out = require('../../lib/out');

const secretId = process.env.SECRET_ID;
const secretKey = process.env.SECRET_KEY;


class OssClient {
constructor(config) {
this.config = config;
this.imageBedInstance = new OSS({
bucket: config.bucket,
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: config.region,
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
accessKeyId: secretId,
accessKeySecret: secretKey,
});
}

static getInstance(config) {
if (!this.instance) {
this.instance = new OssClient(config);
}
return this.instance;
}

/**
* 检查图床是否已经存在图片,存在则返回url,不存在返回空
*
* @param {string} fileName 文件名
* @return {Promise<string>} 图片url
*/
async hasImage(fileName) {
try {
await this.imageBedInstance.head(`${this.config.prefixKey}/${fileName}`);
return `https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${this.config.prefixKey}/${fileName}`;
} catch (e) {
return '';
}
}

/**
* 上传图片到图床
*
* @param {Buffer} imgBuffer 文件buffer
* @param {string} fileName 文件名
* @return {Promise<string>} 图床的图片url
*/
async uploadImg(imgBuffer, fileName) {
try {
const res = await this.imageBedInstance.put(`${this.config.prefixKey}/${fileName}`, imgBuffer);
return res.url;
} catch (e) {
out.error(`上传图片失败,请检查: ${e}`);
process.exit(-1);
}
}
}

module.exports = OssClient;
Loading
Oops, something went wrong.

0 comments on commit 2d14487

Please sign in to comment.