完善构建过程

This commit is contained in:
结发受长生
2019-05-11 17:13:36 +08:00
parent ed816bb30a
commit 1618668b14
4 changed files with 128 additions and 44 deletions
+76
View File
@@ -0,0 +1,76 @@
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)
})
}
}
+136
View File
@@ -0,0 +1,136 @@
const fs = require('fs'),
path = require('path'),
nos = require('@xgheaven/nos-node-sdk')
class ImageSynchronizer {
/**
* 构造方法
* @param {Object} setting NosClient的设置项
* @param {Array} imagesList 本地图片的列表
* @param {String} rootPath 本地文件根路径
*/
constructor(setting, imagesList, rootPath) {
// 网易云对象存储调用接口client
this.client = new nos.NosClient(setting)
this.imagesList = imagesList
this.rootPath = rootPath
}
/**
* 执行文件同步
* @param {String} prefix 图片目录前缀
*/
synchronize(prefix) {
return this._queryObjects({limit: this.imagesList.length+1, prefix}, function(pendingUploadFiles){
this._uploadObject(pendingUploadFiles)
}, function(pendingDeleteFiles){
this._deleteObjects(pendingDeleteFiles)
})
}
/**
* 查询所有对象存储库已存在的文件
* @param {Object} params 查询的参数
* @param {Function} uploadCallback 处理待上传文件的回调函数
* @param {Function} deleteCallback 处理待删除文件的回调函数
*/
async _queryObjects(params, uploadCallback, deleteCallback) {
// 列出所有已存储的对象
const ret = await this.client.listObject(params)
// ret 包括 items(元素)limit(请求的数量)nextMarker(下一个标记)
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 = this.imagesList.filter(item => {
let index = this._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
});
// 处理待上传的文件
uploadCallback.call(this, pendingUploadFiles);
// 待删除的文件列表( 仓库中存在, 本地不存在 )
let pendingDeleteFiles = storageItems.filter(item => {
return this._binarySearch(this.imagesList, item.key, 'name', 0, this.imagesList.length - 1) === -1;
})
// 处理待删除的文件
deleteCallback.call(this, pendingDeleteFiles.map(item => item.key))
}
/**
* 上传文件对象
* @param {Array} filesList 待上传的文件列表
* @param {Number} index 索引值
*/
_uploadObject(filesList, index=0) {
if(index >= filesList.length) return
this.client.putObject({
objectKey: filesList[index].name,
body: fs.createReadStream(path.resolve(this.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}`)
}
this._uploadObject(filesList, ++index)
})
}
/**
* 批量删除文件
* @param {Array} fileNamesList 文件名数组
*/
_deleteObjects(fileNamesList) {
if(!Array.isArray(fileNamesList) || !fileNamesList.length) return
this.client.deleteMultiObject({
objectKeys: fileNamesList
}).then(err => {
console.log('===> 文件删除成功')
fileNamesList.forEach(item => console.log(item))
})
}
/**
* 二分法查找
* @param {Array} arr 执行查找的数组
* @param {Object} target 要找到的目标元素
* @param {String} key 数组元素上的键
* @param {Number} start 查找的范围 起点
* @param {Number} end 查找的范围 终点
*/
_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 this._binarySearch(arr, target, key, start, index-1)
} else {
return this._binarySearch(arr, target, key, index+1, end)
}
}
}
module.exports = ImageSynchronizer
+41
View File
@@ -0,0 +1,41 @@
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 => {
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).sort(function(item1, item2){
if(item1.name > item2.name) {
return 1
} else if(item1.name < item2.name) {
return -1
}
return 0
})
}