# 前端性能优化实战 - 将本博客部署到七牛云
目前博客开发到可以上线的地步,部署到了云服务器上,由于带宽只有可怜的 1MB
, 首次打开的情况要 10 秒左右的时间
由于 Hexo 博客打包后都是静态文件,可以直接将静态文件全量上传至七牛云中,并使用 https 访问,相信性能也会有质的提升
首先,我们需要递归获取 public
下的文件夹与文件
# 递归获取 public
下的文件夹与文件
通过下面函数可以递归获取文件夹与文件全路径
const Path = require("path"); | |
const Fs = require("fs"); | |
const public_path = Path.resolve(process.cwd(),"public"); | |
const readFolder = (folder)=>{ | |
const dirInfo = Fs.readdirSync(folder); | |
dirInfo.forEach(item=>{ | |
const location = Path.join(folder,item); | |
const info = Fs.statSync(location); | |
if(info.isDirectory()){ | |
console.log(`dir:${location}`); | |
readFolder(location); | |
}else{ | |
console.log(`file:${location}`); | |
} | |
}); | |
} | |
readFolder(public_path); |
# 编写脚本将文件上传至七牛云
首先安装七牛云 nodejs 的上传 SDK
$ npm install qiniu -S |
这里我采用了 es6 class
语法构建七牛上传 SDK, 并对上面函数做了一定修改
accessKey
与 secretKey
需要在七牛云账号中创建秘钥,并且要保护好该秘钥,由于是本地的关系,这里就直接放文件里了
以下代码中需要修改 accessKey
, secretKey
, bucket
, zone
为自己的七牛云账号参数
//deployQiniu.js | |
const Path = require("path"); | |
const Fs = require("fs"); | |
const public_path = Path.resolve(process.cwd(),"public"); | |
const qiniu = require("qiniu"); | |
// 七牛上传类 | |
class Uploader { | |
accessKey = "";// 七牛云秘钥 | |
secretKey = "";// 七牛云秘钥 | |
bucket = "";// 存储桶名称 | |
zone = "";// 地域 (根据自己的存储桶所在地域设置) 华东:Zone_z0 华北:Zone_z1 华南:Zone_z2 北美:Zone_na0 | |
mac = null; | |
// 使用单例模式 | |
instance = null; | |
constructor() { | |
if (!Uploader.instance) Uploader.instance = this.init(); | |
return Uploader.instance; | |
} | |
// 初始化七牛上传 SDK | |
init(){ | |
this.mac = new qiniu.auth.digest.Mac(this.accessKey, this.secretKey); | |
this.config = new qiniu.conf.Config(); | |
this.config.zone = qiniu.zone[this.zone]; | |
console.log("七牛SDK初始化完毕!"); | |
return this; | |
} | |
// 上传 | |
upload(filePath){ | |
return new Promise((resolve, reject)=>{ | |
const fileInfo = Fs.statSync(filePath); | |
if(fileInfo.isFile()){ | |
// 生成文件名 | |
let fileName = filePath.substring(public_path.length+1).replaceAll("\\","/"); | |
// 采用覆盖上传 | |
const option = { | |
scope: this.bucket+":"+fileName, | |
expires: 60 | |
}; | |
const putPolicy = new qiniu.rs.PutPolicy(option); | |
let uploadToken= putPolicy.uploadToken(this.mac); | |
const formUploader = new qiniu.form_up.FormUploader(this.config); | |
const putExtra = new qiniu.form_up.PutExtra(); | |
// 文件上传 | |
formUploader.putFile(uploadToken, fileName, filePath, putExtra, function(respErr, respBody, respInfo) { | |
if (respErr) { | |
reject(`上传错误:${respErr.toString()}`); | |
} | |
if (respInfo.statusCode === 200) { | |
// 上传成功 | |
resolve(respBody); | |
} else { | |
reject(`上传错误:${respInfo.statusCode}:${respBody}`); | |
} | |
}); | |
}else{ | |
reject(`找不到该文件:${filePath}.`); | |
} | |
}); | |
} | |
} | |
if(Fs.statSync(public_path).isDirectory()){ | |
const readFolder = (folder)=>{ | |
const dirInfo = Fs.readdirSync(folder); | |
dirInfo.forEach(item=>{ | |
const location = Path.join(folder,item); | |
const info = Fs.statSync(location); | |
if(info.isDirectory()){ | |
readFolder(location); | |
}else{ | |
// 使用七牛云上传 | |
const uploader = new Uploader(); | |
uploader.upload(location).then(res=>{ | |
console.log(`${location}上传成功!`); | |
}).catch(err=>{ | |
console.error(`${location}上传失败!`,err); | |
}); | |
} | |
}); | |
} | |
readFolder(public_path); | |
}else{ | |
console.error("根目录下没有public目录"); | |
} |
# 添加 scripts
最后一步,在 package.json
中加入我们的脚本
"deploy:qiniu": "node ./deployQiniu.js", |
执行 npm run deploy:qiniu
即可将 public
目录下的文件全部上传至七牛云了
# 结束语
目前,本博客已在七牛云部署成功,首页在初次加载的时候只有 400 毫秒左右,足足快了十几倍,以后再也不担心博客网站打开慢了
同理,使用 Vue 开发的项目或其它前后分离项目均可以采用该方式部署,速度飞快
# 遇到的问题
前一天使用七牛云部署成功之后,又修改了博客一点东西,重新使用脚本部署成功,但是线上网站迟迟没有改变,怀疑是 CDN 有缓存
查阅了相关资料,七牛云如果是更新同一个文件,是需要进行 CDN 刷新的,文件和文件夹都需要刷新
故今天修改了脚本,在上传之后把文件和文件夹都进行刷新,注意的是文件一次请求最多刷新 100 个,文件夹则是 10 个
const Path = require("path"); | |
const Fs = require("fs"); | |
const public_path = Path.resolve(process.cwd(),"public"); | |
const qiniu = require("qiniu"); | |
// 七牛上传类 | |
class Uploader { | |
accessKey = "";// 七牛云秘钥 | |
secretKey = "";// 七牛云秘钥 | |
bucket = "";// 存储桶名称 | |
zone = "";// 地域 (根据自己的存储桶所在地域设置) 华东:Zone_z0 华北:Zone_z1 华南:Zone_z2 北美:Zone_na0 | |
domain = "https://blog.aplus.pub/";// 需要刷新的域名 | |
urlsToRefresh = []; | |
dirToRefresh = []; | |
uploadFileList = []; | |
mac = null; | |
cdnManager = null; | |
// 使用单例模式 | |
instance = null; | |
constructor() { | |
if (!Uploader.instance) Uploader.instance = this.init(); | |
return Uploader.instance; | |
} | |
// 初始化七牛上传 SDK | |
init(){ | |
this.mac = new qiniu.auth.digest.Mac(this.accessKey, this.secretKey); | |
this.config = new qiniu.conf.Config(); | |
this.config.zone = qiniu.zone[this.zone]; | |
this.cdnManager = new qiniu.cdn.CdnManager(this.mac); | |
console.log("七牛SDK初始化完毕!"); | |
return this; | |
} | |
// 上传 | |
upload(filePath){ | |
return new Promise((resolve, reject)=>{ | |
const fileInfo = Fs.statSync(filePath); | |
if(fileInfo.isFile()){ | |
// 生成文件名 | |
let fileName = filePath.substring(public_path.length+1).replaceAll("\\","/"); | |
// 采用覆盖上传 | |
const option = { | |
scope: this.bucket+":"+fileName, | |
expires: 60 | |
}; | |
const putPolicy = new qiniu.rs.PutPolicy(option); | |
let uploadToken= putPolicy.uploadToken(this.mac); | |
const formUploader = new qiniu.form_up.FormUploader(this.config); | |
const putExtra = new qiniu.form_up.PutExtra(); | |
// 文件上传 | |
formUploader.putFile(uploadToken, fileName, filePath, putExtra, function(respErr, respBody, respInfo) { | |
if (respErr) { | |
reject(`上传错误:${respErr.toString()}`); | |
} | |
if (respInfo.statusCode === 200) { | |
// 上传成功 | |
resolve(fileName); | |
} else { | |
reject(`上传错误:${respInfo.statusCode}:${respBody}`); | |
} | |
}); | |
}else{ | |
reject(`找不到该文件:${filePath}.`); | |
} | |
}); | |
} | |
pushFileToRefresh(fileName){ | |
if(!this.urlsToRefresh.length){ | |
this.urlsToRefresh.push([]); | |
} | |
if(this.urlsToRefresh[this.urlsToRefresh.length - 1].length > 100){ | |
this.urlsToRefresh.push([]); | |
} | |
this.urlsToRefresh[this.urlsToRefresh.length - 1].push(this.domain+fileName); | |
} | |
pushDirToRefresh(dirName){ | |
if(!this.dirToRefresh.length){ | |
this.dirToRefresh.push([]); | |
} | |
if(this.dirToRefresh[this.dirToRefresh.length - 1].length > 10){ | |
this.dirToRefresh.push([]); | |
} | |
if(!dirName){ | |
this.dirToRefresh[this.dirToRefresh.length - 1].push(this.domain); | |
}else{ | |
this.dirToRefresh[this.dirToRefresh.length - 1].push(this.domain+dirName+"/"); | |
} | |
} | |
refresh(){ | |
this.urlsToRefresh.forEach(item=>{ | |
this.cdnManager.refreshUrls(item,(err,respBody, respInfo)=>{ | |
if (err) { | |
throw err; | |
} | |
console.log("刷新文件成功"); | |
}); | |
}); | |
this.dirToRefresh.forEach(item=>{ | |
this.cdnManager.refreshDirs(item,(err)=>{ | |
if (err) { | |
console.log(item); | |
throw err; | |
} | |
console.log("刷新目录成功"); | |
}); | |
}); | |
} | |
async exec(){ | |
for(let file of this.uploadFileList){ | |
await this.upload(file); | |
console.log(`${file} 上传成功!`); | |
} | |
this.refresh(); | |
} | |
} | |
// 加入操作队列 | |
if(Fs.statSync(public_path).isDirectory()){ | |
const readFolder = (folder)=>{ | |
const dirInfo = Fs.readdirSync(folder); | |
dirInfo.forEach(item=>{ | |
const location = Path.join(folder,item); | |
const info = Fs.statSync(location); | |
if(info.isDirectory()){ | |
// 放入缓存更新队列,以更新缓存 | |
const uploader = new Uploader(); | |
let dirName = location.substring(public_path.length+1).replaceAll("\\","/"); | |
uploader.pushDirToRefresh(dirName); | |
readFolder(location); | |
}else{ | |
// 放入缓存更新队列 以更新缓存 | |
const uploader = new Uploader(); | |
let fileName = location.substring(public_path.length+1).replaceAll("\\","/"); | |
uploader.pushFileToRefresh(fileName); | |
uploader.uploadFileList.push(location); | |
} | |
}); | |
} | |
readFolder(public_path); | |
}else{ | |
console.error("根目录下没有public目录"); | |
} | |
// 开始执行任务 | |
const uploader = new Uploader(); | |
uploader.pushDirToRefresh(""); | |
uploader.exec(); |