博客部署调整记录
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: 博客图片迁移-后续
|
||||
date: 2019-04-23 19:13:17
|
||||
tags:
|
||||
- nodejs
|
||||
categories:
|
||||
- 前端杂烩
|
||||
---
|
||||
|
||||
本着不折腾不舒服的原则, 之前的图片迁移与构建时的自动同步虽然已经实现
|
||||
但是仍有很多可以改进的地方
|
||||
<!-- more -->
|
||||
|
||||
#### 引入二分法查找
|
||||
在执行本地文件列表与仓库内的文件列表比对的过程中
|
||||
直接逐个比对找差异的复杂度高达**O(n²)**
|
||||
何况还考虑之后要把本地没有, 但仓库里有的文件删除
|
||||
这个执行时间太过漫长
|
||||
|
||||
所以考虑对文件列表排序后采用二分法查找
|
||||
先写个二分法查找的js实现
|
||||
```javascript
|
||||
/**
|
||||
* 二分法查找
|
||||
* @param {Array} arr 执行查找的数组
|
||||
* @param {Object} target 要找到的目标元素
|
||||
* @param {String} key 数组元素上的键
|
||||
* @param {Number} start 查找的范围 起点
|
||||
* @param {Number} end 查找的范围 终点
|
||||
*/
|
||||
function _binarySearch(arr, target, key, start, end) {
|
||||
if(!Array.isArray(arr) || !arr.length) {
|
||||
return -1
|
||||
}
|
||||
if(start >= end) {
|
||||
return arr[start][key] === target ? start : -1
|
||||
}
|
||||
let index = Math.ceil((start + end)/2)
|
||||
if(arr[index][key] === target) {
|
||||
return index
|
||||
} else if(arr[index][key] > target) {
|
||||
return _binarySearch(arr, target, key, start, index-1)
|
||||
} else {
|
||||
return _binarySearch(arr, target, key, index+1, end)
|
||||
}
|
||||
}
|
||||
```
|
||||
> 二分法查找既可以使用递归方式实现, 也可以用循环实现
|
||||
|
||||
使用二分法查找需要保证数组是有序的
|
||||
虽然现在看起来接口返回的数据本身就是有序, 但是还是执行一下排序保证不会出错
|
||||
先使用`Array.prototype.sort`方法, 并指定排序规则进行排序
|
||||
之后考虑更换为原数组基本有序的情况下, 更为高效的`插入排序`
|
||||
|
||||
```javascript
|
||||
let storageItems = ret.items.filter((item) => {
|
||||
return /^images.+?\.(png|jpe?g|gif)$/.test(item.key)
|
||||
}).sort((item1, item2) => {
|
||||
if(item1.key > item2.key) {
|
||||
return 1
|
||||
} else if(item1.key < item2.key) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
// 待上传的文件列表
|
||||
let pendingUploadFiles = imagesList.filter(item => {
|
||||
let index = _binarySearch(storageItems, item.name, 'key', 0, storageItems.length-1)
|
||||
if(index === -1) {
|
||||
// 文件名不存在, 代表是新文件
|
||||
item.type = 'new'
|
||||
return true
|
||||
} else if(storageItems[index].eTag !== item.md5) {
|
||||
// 文件名存在, 但是hash值不同, 代表有变化
|
||||
item.type = 'change'
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
```
|
||||
整体来看效率提升不少
|
||||
|
||||
#### 删除仓库内的文件
|
||||
要找出本地不存在但是仓库内存在的文件, 调用删除文件的接口进行删除
|
||||
方法基本雷同, 甚至简单很多
|
||||
当然 imagesList 也需要是有序的
|
||||
```javascript
|
||||
// 待删除的文件列表( 仓库中存在, 本地不存在 )
|
||||
let pendingDeleteFiles = storageItems.filter(item => {
|
||||
return _binarySearch(imagesList, item.key, 'name', 0, imagesList.length-1) === -1
|
||||
})
|
||||
_deleteObjects(pendingDeleteFiles.map(item => item.key))
|
||||
/**
|
||||
* 批量删除文件
|
||||
* @param {Array} fileNamesList 文件名数组
|
||||
*/
|
||||
function _deleteObjects(fileNamesList) {
|
||||
if(!Array.isArray(fileNamesList) || !fileNamesList.length) return
|
||||
|
||||
client.deleteMultiObject({
|
||||
objectKeys: fileNamesList
|
||||
}).then(err => {
|
||||
console.log('===> 文件删除成功')
|
||||
fileNamesList.forEach(item => console.log(item))
|
||||
})
|
||||
}
|
||||
```
|
||||
为了节约接口的调用次数, 还是选择批量删除的接口了
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
---
|
||||
title: 博客图片迁移记
|
||||
date: 2019-4-18 10:53:34
|
||||
tags:
|
||||
- jenkins
|
||||
- nodejs
|
||||
categories:
|
||||
- 前端杂烩
|
||||
---
|
||||
|
||||
之前博客的图片都是直接访问自己的服务器的, 无奈我的服务器带宽太小
|
||||
大图加载缓慢
|
||||
考虑使用一些第三方的对象存储服务来保存图片
|
||||
比较主流的像是七牛云 又拍云, 都必须要绑定备案的域名才能访问
|
||||
所以选择了网易云的对象存储服务( 现在只能通过企业认证后才能创建资源了, 好在创建的比较早 )
|
||||
<!-- more -->
|
||||
网易云的对象存储体验总体来讲还不错, 提供若干API以及针对不同语言的SDK包
|
||||
简单整理了一下思路, 觉得同步图片的脚本还是用比较上手的js来写, 在服务器上用nodejs运行
|
||||
|
||||
### 1.对象存储的开通以及准备
|
||||
创建一个桶对象, 之后会获得Endpoint以及访问域名
|
||||
先记下来, 之后会用到
|
||||
> 要绑定自定义的域名同样需要该域名是备案的
|
||||

|
||||
|
||||
|
||||
然后创建Access Key
|
||||

|
||||
|
||||
> **注意** : 网易云的对象存储对于权限控制做的比较简单
|
||||
这个key具备所有的API访问权限, 一定不要上传到公共仓库以免泄露
|
||||
|
||||
### 2.nodejs脚本编写以及调试
|
||||
官方虽然有提供nodejs使用的sdk包, 但是年久失修, 官方已经基本不维护, 问题很多
|
||||
调用起来非常麻烦
|
||||
感谢 XGHeaven 大佬自己编写的sdk包 [@xgheaven/nos-node-sdk](https://www.npmjs.com/package/@xgheaven/nos-node-sdk)
|
||||
|
||||
使用`npm init`初始化一个nodejs项目
|
||||
然后安装需要的依赖包`npm install @xgheaven/nos-node-sdk optimist --save`
|
||||
|
||||
optimist这个包可以方便处理运行时的命令行参数
|
||||
为之后在持续集成当中的调用提供方便
|
||||
|
||||
#### list_images.js
|
||||
我的博客图片都保存在 **source/images** 当中, 要与对象存储库中同步, 首先需要读取到本地有的图片文件列表
|
||||
把这部分功能作为一个module封装一下
|
||||
|
||||
```javascript
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const crypto = require('crypto')
|
||||
|
||||
/**
|
||||
* 递归遍历目录中的所有文件
|
||||
* @param {String} imageFolderPath 文件夹路径
|
||||
* @param {Array} images 图片列表
|
||||
* @param {String} rootPath 根路径
|
||||
*/
|
||||
function readDirSync(imageFolderPath, images, rootPath){
|
||||
var files = fs.readdirSync(imageFolderPath)
|
||||
files.forEach((item,index) => {
|
||||
var fileInfo = fs.statSync(`${imageFolderPath}/${item}`)
|
||||
if(fileInfo.isDirectory()){
|
||||
// 该文件是一个目录, 则遍历该目录内容
|
||||
readDirSync(`${imageFolderPath}/${item}`, images, rootPath)
|
||||
}else{
|
||||
//读取一个Buffer
|
||||
let buffer = fs.readFileSync(`${imageFolderPath}/${item}`)
|
||||
let fsHash = crypto.createHash('md5')
|
||||
fsHash.update(buffer)
|
||||
images.push({
|
||||
name: `${imageFolderPath}/${item}`.replace(rootPath, ''),
|
||||
md5: fsHash.digest('hex')
|
||||
})
|
||||
}
|
||||
})
|
||||
return images
|
||||
}
|
||||
|
||||
module.exports = function (rootPath, imageFloder) {
|
||||
return readDirSync(path.resolve(rootPath, imageFloder), [], rootPath)
|
||||
}
|
||||
```
|
||||
1. 由于目录当中可能包含子目录, 所以需要进行递归遍历
|
||||
2. 使用nodejs提供的`crypto`模块来计算文件的md5哈希值
|
||||
( 由于接口返回的数据也是md5的哈希值, 可以用它来比对文件差异 )
|
||||
|
||||
> 调用该模块暴露出的函数传入根目录和图片目录
|
||||
比如imageFloder传入的是'images/'
|
||||
获得的是形如 _\[{name:'images/a.png',md5:'xxx'},...\]_ 的一个数组
|
||||
|
||||
#### auth_info.json
|
||||
把访问对象仓库API需要的认证信息放在一个json文件当中
|
||||
```json
|
||||
{
|
||||
"defaultBucket":"桶名称",
|
||||
"endpoint":"http://nos-eastchina1.126.net",
|
||||
"accessKey":"XXXXXXXXX",
|
||||
"accessSecret":"XXXXXXXXX"
|
||||
}
|
||||
```
|
||||
其中的字段根据第1步当中获得的写入
|
||||
|
||||
#### index.js
|
||||
在这个模块里面大概需要做以下几件事
|
||||
1. 调用sdk当中提供的listObject接口获取到对象仓库里面已有的文件列表
|
||||
2. 将对象仓库的文件列表与本地文件列表进行比对, 包括文件名和hash值的比对
|
||||
找出本地有但是对象仓库里面没有的, 或者都有但是hash值不同, 就是文件有差别的
|
||||
3. 调用sdk当中提供的putObject接口上传差异文件
|
||||
|
||||
```javascript
|
||||
// 程序执行的传参
|
||||
var argv = require('optimist')
|
||||
.usage('$0 --rootPath [str]')
|
||||
.demand(['rootPath'])
|
||||
.argv
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const listImages = require('./list_images')
|
||||
// 当前本地存在的所有图片
|
||||
const rootPath = argv.rootPath, prefix = 'images/'
|
||||
const imagesList = listImages(rootPath, prefix)
|
||||
|
||||
// 网易云对象存储调用接口client
|
||||
const NosClient = require('@xgheaven/nos-node-sdk').NosClient
|
||||
const client = new NosClient(require('./auth_info.json'))
|
||||
|
||||
queryObjects.call(client, {limit: imagesList.length+1, prefix})
|
||||
/**
|
||||
* 查询所有对象存储库已存在的文件
|
||||
* @param {Object} params
|
||||
*/
|
||||
function queryObjects(params) {
|
||||
// 列出所有已存储的对象
|
||||
this.listObject(params).then(ret => {
|
||||
// ret 包括 items(元素),limit(请求的数量),nextMarker(下一个标记)
|
||||
let storageItems = ret.items.filter((item) => {
|
||||
return /^images.+?\.(png|jpe?g|gif)$/.test(item.key)
|
||||
})
|
||||
let notExistFiles = imagesList.filter((item) => {
|
||||
let index = storageItems.findIndex(storageItem => {
|
||||
return storageItem.key === item.name
|
||||
})
|
||||
if(index === -1) {
|
||||
// 文件名不存在, 代表是新文件
|
||||
item.type = 'new'
|
||||
return true
|
||||
} else if(storageItems[index].eTag !== item.md5) {
|
||||
// 文件名存在, 但是hash值不同, 代表有变化
|
||||
item.type = 'change'
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
uploadObject.call(this, notExistFiles, 0)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 上传文件对象
|
||||
* @param {Array} filesList 待上传的文件列表
|
||||
* @param {Number} index 索引值
|
||||
*/
|
||||
function uploadObject(filesList, index) {
|
||||
if(index >= filesList.length) return
|
||||
|
||||
this.putObject({
|
||||
objectKey: filesList[index].name,
|
||||
body: fs.createReadStream(path.resolve(rootPath, filesList[index].name)), // 支持 Buffer/Readable/string
|
||||
}).then(result => {
|
||||
// eTag是上传后远端校验的md5值, 用于和本地进行比对
|
||||
let eTag = result.eTag.replace(/"/g,'')
|
||||
if(filesList[index].md5 === eTag) {
|
||||
console.log(`${filesList[index].name} 上传成功, md5:${eTag} 类型: ${filesList[index].type}`)
|
||||
} else {
|
||||
console.warn(`${filesList[index].name} 上传出错, md5值不一致`)
|
||||
console.warn(`===> 本地文件: ${filesList[index].md5}, 接口返回: ${eTag}`)
|
||||
}
|
||||
uploadObject.call(this, filesList, ++index)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
有以下几点注意:
|
||||
1. listObject 接口返回的数据不只有文件, 还有目录, 所以需要筛选到是文件的item, 因为这里我只是存图片, 就简单用正则来处理了
|
||||
2. listObject 接口调用时传入的prefix用于过滤item的前缀, 可以用于查询某个目录当中的所有对象
|
||||
3. limit参数用于指定获取最大的条数, 配合响应返回的nextMarker以及marker参数可以简单实现分页
|
||||
但是实际使用发现官方的接口实现似乎存在bug, 导致获取到的内容不全
|
||||
所以只好使用`imagesList.length + 1`一次性获取所有了, 该值存在上限, 此处算是一个隐患
|
||||
4. 由于在服务器上使用jenkins构建博客, rootPath会不同 ( 取决于jenkins的工作空间 )
|
||||
所以把该路径作为一个参数, 用于执行该脚本时从命令行传入
|
||||
`optimist`库可以对传参进行校验, 如果指定参数未传入会抛出一个Error
|
||||
5. putObject 接口传入的 objectKey 是保存在仓库中的位置, 比如可以是`images/sub/a.png`
|
||||
这里正好可以与listImages当中获取到的文件路径对应, 实现本地与对象仓库的目录结构完全一致
|
||||
6. prefix取决于图片目录在项目中所处的位置, 这个也可以考虑提取为参数传入, 让脚本更具备通用性
|
||||

|
||||
|
||||
#### package.json
|
||||
主要是加上执行index.js的script
|
||||
```json
|
||||
{
|
||||
"name": "nos-test",
|
||||
"version": "1.0.0",
|
||||
"description": "网易云对象存储",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"author": "sookie",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@xgheaven/nos-node-sdk": "^0.2.5",
|
||||
"optimist": "^0.6.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.持续集成
|
||||
相关的js脚本在服务器上准备好之后, 就可以在持续集成当中写shell添加该脚本的调用了
|
||||
```bash
|
||||
# 切换到nodejs脚本所在位置
|
||||
cd /root/upload_picture
|
||||
npm run start -- --rootPath ${WORKSPACE}/source/
|
||||
```
|
||||
|
||||
> `WORKSPACE`是jenkins提供的一个环境变量, 代表工作空间分配给构建的目录的绝对路径
|
||||
|
||||
### 4.hexo静态化钩子函数编写
|
||||
由于以往的文章当中的图片路径都是写的本地路径
|
||||
形如`/images/linux/nos/AccessKey.png`, 批量修改又不利于将来的迁移
|
||||
所以参考了hexo的官方文档之后, 考虑在静态化过程当中添加一个过滤器
|
||||
|
||||
#### 添加配置
|
||||
在根目录的`_config.yml`文件添加自定义的配置项
|
||||
```yaml
|
||||
# 图片存储仓库地址
|
||||
picture_cdn: https://blog-cdn.nos-eastchina1.126.net
|
||||
```
|
||||
|
||||
#### filter.js
|
||||
创建**scripts**目录 ( 可以是根目录或者主题目录下, 均有效 )
|
||||
创建**filter.js**文件
|
||||
```javascript
|
||||
hexo.extend.filter.register('before_post_render', function(data){
|
||||
// data.raw 是原始的文件内容
|
||||
// data.content 是处理过代码块语法高亮的内容
|
||||
if(hexo.config.picture_cdn) {
|
||||
data.content = data.content.replace(/\]\s*\((?=(?!http).*?\))/gi,
|
||||
']'+`(${hexo.config.picture_cdn}`)
|
||||
}
|
||||
return data;
|
||||
});
|
||||
```
|
||||
`before_post_render`这个钩子在文章开始渲染前执行
|
||||
如果此时修改文章中的图片路径, 静态化之后的文件就是指向对象仓库的URL地址了
|
||||
日常编写正则处理一下( 注意使用前瞻断言, 正则尽可能精确, 避免影响到其他内容 )
|
||||
|
||||
### 5.验证
|
||||
以上所有做好之后, 在jenkins里面执行构建验证文件同步是否成功
|
||||

|
||||
|
||||
之前的构建步骤没什么变化, 最后调用nodejs脚本只要成功把新图片上传上去就可以了
|
||||
然后访问博客
|
||||
查看文章页面中的图片路径是否正确
|
||||

|
||||
@@ -0,0 +1,300 @@
|
||||
---
|
||||
title: 博客部署调整记录
|
||||
date: 2019-05-11 19:01:11
|
||||
tags:
|
||||
- gulp
|
||||
- nodejs
|
||||
categories:
|
||||
- 前端杂烩
|
||||
---
|
||||
|
||||
让你永远都有事情可忙正是前端的魅力所在
|
||||
<!-- more -->
|
||||
按照目前的策略, 博客构建部署大概是以下几个步骤
|
||||
1. 主题内容的webpack打包
|
||||
2. hexo clean && hexo generate
|
||||
3. 图片与对象存储仓库同步
|
||||
4. 生成的html文件的压缩
|
||||
5. public目录中所有内容的拷贝发布
|
||||
|
||||
因为不想把主题和内容过多整合, 这样会导致将来更换主题困难
|
||||
所以计划是把2 3 4 5步骤在构建过程中一键完成
|
||||
( 虽然有jenkins可以写多步的执行脚本, 但是仍然感觉不够优雅 )
|
||||
最好是能有个js脚本, 用nodejs一次执行完成这些步骤
|
||||
|
||||
第2步之前用的是hexo的命令行工具, 现在可以换成hexo的api, 在js当中调用之
|
||||
|
||||
第3步之前已经完成了, 就是用js写的脚本, 简单封装一下拿过来就可以用
|
||||
|
||||
第4步目前还没有做到, 准备实现一下, 因为webpack完成了对css和js的压缩输出, 所以静态化后就无需再次处理了
|
||||
但是html文件是由hexo生成, 所以对主题内容的webpack打包, 是无法完成这件事的
|
||||
查了一些资料发现gulp可以很简单做到, 于是就计划用它了
|
||||
|
||||
第5步, 就在js里面写递归删除与递归拷贝吧
|
||||
|
||||
### hexo的api
|
||||
根据官网的介绍, 可以按照下面的方式使用hexo的api
|
||||
```javascript
|
||||
const Hexo = require('hexo')
|
||||
const hexo = new Hexo(process.cwd(), {})
|
||||
hexo.init().then(()=>{ // 链式的Promise调用, 每一步都要返回Promise对象
|
||||
return hexo.call('clean')
|
||||
}).then(()=>{
|
||||
return hexo.call('generate', {watch: false})
|
||||
}).then(()=>{
|
||||
return hexo.exit()
|
||||
}).catch(err => {
|
||||
return hexo.exit(err)
|
||||
})
|
||||
/* 当然也可以采用更加简洁的await
|
||||
try {
|
||||
await hexo.init()
|
||||
await hexo.call('clean')
|
||||
await hexo.call('generate', { watch: false })
|
||||
return hexo.exit()
|
||||
} catch (err) {
|
||||
return hexo.exit(err)
|
||||
}
|
||||
*/
|
||||
```
|
||||
上面的写法就如同是在命令行执行`hexo clean && hexo generate`
|
||||
正好gulp配置任务的时候也需要返回Promise对象( 下面会提到 ), 正好能结合, 不需要二次封装了, 不错
|
||||
|
||||
### gulp
|
||||
之前没用过这个前端构建工具, 因为跟webpack的作用有不少的重合, 前端实在学不完
|
||||
这里使用一下它的任务自动管理的功能, 整体的体验挺好
|
||||
|
||||
#### 安装
|
||||
除了gulp本身, 还有其他几个用的到的插件 ( 当然也可以用npm安装 )
|
||||
```bash
|
||||
# 这几个是gulp相关的
|
||||
yarn add gulp gulp-htmlclean gulp-htmlmin gulp-plumber -D
|
||||
# 这是图片同步到对象仓库相关的
|
||||
yarn add @xgheaven/nos-node-sdk optimist -D
|
||||
```
|
||||
gulp在**4.0**这个大版本做了很大的改变, 这里我使用的是4.0.2
|
||||
|
||||
#### gulpfile.js
|
||||
在根目录下创建`gulpfile.js`文件, 这个文件是gulp的配置文件
|
||||
先简单规划一下结构
|
||||
```javascript
|
||||
const gulp = require('gulp')
|
||||
|
||||
// 创建静态页面 (等同 hexo generate)
|
||||
gulp.task('generate', () => {
|
||||
// TODO 执行hexo的api
|
||||
})
|
||||
|
||||
// 压缩public目录下的html文件
|
||||
gulp.task('compressHtml', () => {
|
||||
// TODO 使用gulp-htmlmin插件压缩html
|
||||
})
|
||||
|
||||
// 同步图片到对象存储仓库
|
||||
gulp.task('syncImages', () => {
|
||||
//TODO 直接调用之前写的同步文件的代码
|
||||
})
|
||||
|
||||
gulp.task('deploy', () => {
|
||||
//TODO 递归拷贝public目录中的所有文件到站点根目录
|
||||
})
|
||||
|
||||
// 默认任务
|
||||
gulp.task('default',
|
||||
gulp.series('generate', 'compressHtml', 'syncImages', 'deploy') // 串行执行任务
|
||||
)
|
||||
```
|
||||
1. **gulp.task** 用于定义一个任务, 第一个参数是任务的名称, 第二个参数是任务要执行的函数
|
||||
从4.0版本开始, 这个函数必须返回Promise对象, 让gulp来监测该任务是否执行完毕
|
||||
应该是有利于更优化串行任务的执行过程
|
||||
2. `default`就是在直接执行`gulp`的时候会执行的任务
|
||||
当然也可以执行`gulp 任务名称`用来指定执行某个任务
|
||||
3. **gulp.series**用来定义串行的任务, 也就是把参数里面这几个任务作为子任务, 并按照串行的方式执行
|
||||
如果不使用它, 而是直接写
|
||||
```javascript
|
||||
gulp.task('default', ['generate', 'compressHtml', 'syncImages', 'deploy'])
|
||||
```
|
||||
任务这几个子任务就会以并行的方式执行, 但是在这里因为有执行的顺序要求
|
||||
比如必须在generate完成之后再执行html的压缩, 所以选择串行方式
|
||||
这个api也是gulp4.0新增的
|
||||
以往只能定义任务的完成依赖, 比如在定义B任务必须在A任务完成后执行
|
||||
```javascript
|
||||
gulp.task('B', ['A'], ()=>{/* do something */})
|
||||
```
|
||||
|
||||
#### 添加一些细节
|
||||
```javascript
|
||||
// gulpfile.js
|
||||
|
||||
const gulp = require('gulp'),
|
||||
htmlmin = require('gulp-htmlmin'), //html压缩组件
|
||||
htmlclean = require('gulp-htmlclean'), //html清理组件
|
||||
plumber = require('gulp-plumber'), //容错组件(发生错误不跳出任务,并报出错误内容)
|
||||
Hexo = require('hexo')
|
||||
|
||||
// 程序执行的传参
|
||||
const argv = require('optimist')
|
||||
.demand(['accessKey', 'accessSecret', 'deployPath'])
|
||||
.describe('accessKey', '网易云对象存储key')
|
||||
.describe('accessSecret', '网易云对象存储secret')
|
||||
.describe('deployPath', '静态化后发布的目录')
|
||||
.argv
|
||||
|
||||
const hexo = new Hexo(process.cwd(), {})
|
||||
|
||||
// 创建静态页面 (等同 hexo generate)
|
||||
gulp.task('generate', async function() {
|
||||
try {
|
||||
await hexo.init()
|
||||
await hexo.call('clean')
|
||||
await hexo.call('generate', { watch: false })
|
||||
return hexo.exit()
|
||||
} catch (err) {
|
||||
return hexo.exit(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 压缩public目录下的html文件
|
||||
gulp.task('compressHtml', () => {
|
||||
const cleanOptions = {
|
||||
protect: /<\!--%fooTemplate\b.*?%-->/g, //忽略处理
|
||||
unprotect: /<script [^>]*\btype="text\/x-handlebars-template"[\s\S]+?<\/script>/ig //特殊处理
|
||||
}
|
||||
const minOption = {
|
||||
collapseWhitespace: true, //压缩HTML
|
||||
collapseBooleanAttributes: true, //省略布尔属性的值 <input checked="true"/> ==> <input />
|
||||
removeEmptyAttributes: true, //删除所有空属性 <input id="" /> ==> <input />
|
||||
removeScriptTypeAttributes: true, //删除<script>的type="text/javascript"
|
||||
removeStyleLinkTypeAttributes: true,//删除<style>和<link>的type="text/css"
|
||||
removeComments: true, //清除HTML注释
|
||||
minifyJS: true, //压缩页面JS
|
||||
minifyCSS: true, //压缩页面CSS
|
||||
minifyURLs: true //替换页面URL
|
||||
}
|
||||
return gulp.src('./public/**/*.html')
|
||||
.pipe(plumber())
|
||||
.pipe(htmlclean(cleanOptions))
|
||||
.pipe(htmlmin(minOption))
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
// 同步图片到对象存储仓库
|
||||
gulp.task('syncImages', () => {
|
||||
const listImages = require('./deploy_utils/list_images')
|
||||
// 当前本地存在的所有图片
|
||||
const imagesList = listImages(`${process.cwd()}/source/`, 'images/')
|
||||
const ImageSynchronizer = require('./deploy_utils/image_synchronize')
|
||||
const nosSetting = {
|
||||
defaultBucket: 'blog-cdn',
|
||||
endpoint: 'http://nos-eastchina1.126.net',
|
||||
accessKey: argv.accessKey,
|
||||
accessSecret: argv.accessSecret
|
||||
}
|
||||
const imageSynchronizer = new ImageSynchronizer(nosSetting, imagesList, `${process.cwd()}/source/`)
|
||||
return imageSynchronizer.synchronize('images/')
|
||||
})
|
||||
|
||||
gulp.task('deploy', () => {
|
||||
const deploy = require('./deploy_utils/deploy')
|
||||
return deploy.exec('./public', argv.deployPath, false)
|
||||
})
|
||||
|
||||
// 默认任务
|
||||
gulp.task('default',
|
||||
gulp.series('generate', 'compressHtml', 'syncImages', 'deploy') // 串行执行任务
|
||||
)
|
||||
```
|
||||
1. `syncImages`里面相关的东西可以看这篇 [博客图片迁移记](https://www.colorfulsweet.site/linux/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87%E8%BF%81%E7%A7%BB%E8%AE%B0/)
|
||||
大体上都一致, 之后又简单封装了一下, 这都不重要
|
||||
2. `accessKey, accessSecret, deployPath`这三个参数分别在运行时传入
|
||||
前两个是网易云对象存储的访问token, deployPath用来指定页面发布的目录, 在`deploy`当中拷贝到该位置
|
||||
3. `deploy`里面写了递归删除目录与递归拷贝目录的函数
|
||||
```javascript
|
||||
// deploy.js
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* 发布静态化的站点
|
||||
* @param {String} source 源位置
|
||||
* @param {String} target 目标位置
|
||||
* @param {Boolean} copyRoot 是否拷贝根目录
|
||||
*/
|
||||
async exec(source, target, copyRoot) {
|
||||
await new Promise((resolve, reject) => {
|
||||
console.log(`删除${target}目录中的文件`)
|
||||
this._deleteFolderRecursive(target, true)
|
||||
resolve()
|
||||
})
|
||||
console.log(`拷贝${source}所有文件 -> ${target}`)
|
||||
this._copyFolderRecursive(source, target, copyRoot)
|
||||
},
|
||||
|
||||
/**
|
||||
* 递归删除目录以及子目录中的所有文件
|
||||
* @param {String} curPath 要递归删除的目录
|
||||
* @param {Boolean} retainRoot 是否保留根目录不删除
|
||||
*/
|
||||
_deleteFolderRecursive(curPath, retainRoot) {
|
||||
fs.readdirSync(curPath).forEach(file => {
|
||||
var nextPath = path.resolve(curPath, file)
|
||||
if(fs.statSync(nextPath).isDirectory()) { // recurse
|
||||
this._deleteFolderRecursive(nextPath)
|
||||
} else {
|
||||
fs.unlinkSync(nextPath)
|
||||
}
|
||||
})
|
||||
if(!retainRoot) { // 根目录保留
|
||||
fs.rmdirSync(curPath)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 递归拷贝目录
|
||||
* @param {String} source 源位置
|
||||
* @param {String} target 目标位置
|
||||
*/
|
||||
_copyFolderRecursive(source, target) {
|
||||
let files = fs.readdirSync(source); //同步读取当前目录
|
||||
files.forEach(file => {
|
||||
var _src = path.resolve(source, file)
|
||||
var _target = path.resolve(target, file)
|
||||
fs.stat(_src,(err,stats) => { //stats 该对象 包含文件属性
|
||||
if (err) throw err
|
||||
if (stats.isFile()) { //如果是个文件则拷贝
|
||||
let readable = fs.createReadStream(_src) //创建读取流
|
||||
let writable = fs.createWriteStream(_target) //创建写入流
|
||||
readable.pipe(writable);
|
||||
} else if (stats.isDirectory()) { //是目录则 递归
|
||||
this._checkDirectory(_src, _target, this._copyFolderRecursive)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验目标目录是否存在
|
||||
* @param {String} src 源目录
|
||||
* @param {String} target 目标目录
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
_checkDirectory (src,target,callback) {
|
||||
fs.access(target, fs.constants.F_OK, err => {
|
||||
if (err) {
|
||||
fs.mkdirSync(target)
|
||||
}
|
||||
callback.call(this, src, target)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 持续集成
|
||||
在**package.json**的scripts里面添加 `"build": "gulp"` 之后, 就可以配置持续集成的脚本了
|
||||
现在只需要一行了
|
||||
```bash
|
||||
npm run build -- --accessKey xxx --accessSecret xxx --deployPath /path/to/deploy
|
||||
```
|
||||
jenkins输出日志
|
||||

|
||||
Reference in New Issue
Block a user