hexo-deployer-upyun-pro
Version:
Hexo deployer plugin for UPYUN, with auto remote cleaning feature.
150 lines (145 loc) • 5.24 kB
JavaScript
const glob = require('glob');
const path = require('path');
const fs = require('fs');
const md5 = require('md5');
const ora = require('ora'); // 执行动画
const upyun = require('upyun');
class filesUpload {
constructor(config, ctx) {
const { serviceName, operatorName, operatorPassword, path, autoclean } = config;
if (!(serviceName && operatorName && operatorPassword)) {
throw(new Error('serviceName、operatorName、operatorPassword为必填项'));
}
this.storageInfo = {
serviceName,
operatorName,
operatorPassword,
path: path ? path.substring(0, 1) === '/' ? path : `/${path}` : '/',
autoclean: typeof autoclean === 'boolean' ? autoclean : false // 默认关闭自动清理
};
this.ctx = ctx;
this.errorFile = []; // 上传失败的文件
this.init();
}
init() {
const { serviceName, operatorName, operatorPassword } = this.storageInfo;
const service = new upyun.Service(serviceName, operatorName, operatorPassword);
this.client = new upyun.Client(service);
}
async startUpload() {
const {
config: {
public_dir
}
} = this.ctx;
const filePath = path.join(public_dir, '/**/*');
const allFiles = glob.sync(filePath, {
nodir: true // 只匹配文件,不匹配文件夹。防止文件夹名称中有dot符号被匹配出来
});
// 如果开启自动清理,先删除云端多余的文件
if (this.storageInfo.autoclean) {
await this.cleanRemoteFiles(allFiles);
}
await this.upload(allFiles);
}
readFilePromise(filepath) {
return new Promise((resolve, reject) => {
fs.readFile(filepath, (err, data) => {
if (err) reject(err);
resolve(data);
});
});
}
async upload(fileList) {
for (let index = 0; index < fileList.length; index++) {
const localFilePath = fileList[index];
const remotePath = (this.storageInfo.path.endsWith('/') ? this.storageInfo.path.slice(0, -1) : this.storageInfo.path) + '/' + fileList[index].split('/').slice(1).join('/'); // 文件远程保存路径
const spinner = ora().start();
spinner.text = `开始上传文件:${localFilePath}`;
try {
const localFile = await this.readFilePromise(localFilePath); // 本地文件md5值
const localFileMd5 = md5(localFile);
const remoteFileMd5 = (await this.client.headFile(remotePath))['Content-Md5']; // 获取云存储上文件的md5值
if (localFileMd5 === remoteFileMd5) {
spinner.succeed(localFilePath + ' 本地文件与云端文件md5值相同,不上传');
} else {
// 上传
try {
const result = await this.client.putFile(remotePath, localFile);
if (result) {
spinner.succeed(`${localFilePath}上传成功`);
} else {
throw new Error(`${localFilePath}上传失败`);
}
} catch (e) {
spinner.fail(e);
this.errorFile.push({
filePath: localFilePath,
error: e
});
}
}
} catch (e) {
this.ctx.log.error(e, '本地文件读取失败');
}
}
this.errorFile.forEach(ele => {
this.ctx.log.info('文件:', ele.filePath, ' 上传失败,原因:', ele.error);
});
}
async getRemoteFiles() {
const remoteFiles = [];
const listFiles = async (prefix = '') => {
try {
const { files } = await this.client.listDir(prefix);
for (const file of files) {
if (file.type === 'N') { // 假设 'N' 代表文件
const filePath = prefix + file.name;
remoteFiles.push(filePath);
} else if (file.type === 'F') { // 假设 'F' 代表文件夹
await listFiles(prefix + file.name + '/');
}
}
} catch (e) {
console.error('获取文件列表时出错:', e);
}
};
await listFiles(this.storageInfo.path);
return remoteFiles;
}
getFilesToDelete(localFiles, remoteFiles) {
const localFilePaths = localFiles.map(file => {
return (this.storageInfo.path.endsWith('/') ? this.storageInfo.path.slice(0, -1) : this.storageInfo.path) + '/' + file.split('/').slice(1).join('/');
});
return remoteFiles.filter(remoteFile => !localFilePaths.includes(remoteFile));
}
async cleanRemoteFiles(localFiles) {
const remoteFiles = await this.getRemoteFiles();
const filesToDelete = this.getFilesToDelete(localFiles, remoteFiles);
for (const file of filesToDelete) {
const spinner = ora().start();
spinner.text = `开始删除文件:${file}`;
try {
await this.client.deleteFile(file);
spinner.succeed(`${file} 删除成功`);
} catch (e) {
spinner.fail(`删除 ${file} 失败: ${e}`);
this.errorFile.push({
filePath: file,
error: e
});
}
}
}
}
module.exports = async function (args, cb) {
try {
// 连接upyun
const upyunInstance = new filesUpload(args, this); // ftp上传实例
await upyunInstance.startUpload();
return cb();
} catch (error) {
this.log.error('发生错误:', error);
return;
}
}