# 前端性能优化实战 - 将本博客部署到七牛云

目前博客开发到可以上线的地步,部署到了云服务器上,由于带宽只有可怜的 1MB , 首次打开的情况要 10 秒左右的时间

由于 Hexo 博客打包后都是静态文件,可以直接将静态文件全量上传至七牛云中,并使用 https 访问,相信性能也会有质的提升

首先,我们需要递归获取 public 下的文件夹与文件

# 递归获取 public 下的文件夹与文件

通过下面函数可以递归获取文件夹与文件全路径

t
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

h
$ npm install qiniu -S

这里我采用了 es6 class 语法构建七牛上传 SDK, 并对上面函数做了一定修改

accessKeysecretKey 需要在七牛云账号中创建秘钥,并且要保护好该秘钥,由于是本地的关系,这里就直接放文件里了

以下代码中需要修改 accessKey , secretKey , bucket , zone 为自己的七牛云账号参数

t
//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 中加入我们的脚本

n
"deploy:qiniu": "node ./deployQiniu.js",

执行 npm run deploy:qiniu 即可将 public 目录下的文件全部上传至七牛云了

# 结束语

目前,本博客已在七牛云部署成功,首页在初次加载的时候只有 400 毫秒左右,足足快了十几倍,以后再也不担心博客网站打开慢了

同理,使用 Vue 开发的项目或其它前后分离项目均可以采用该方式部署,速度飞快

# 遇到的问题

前一天使用七牛云部署成功之后,又修改了博客一点东西,重新使用脚本部署成功,但是线上网站迟迟没有改变,怀疑是 CDN 有缓存

查阅了相关资料,七牛云如果是更新同一个文件,是需要进行 CDN 刷新的,文件和文件夹都需要刷新

故今天修改了脚本,在上传之后把文件和文件夹都进行刷新,注意的是文件一次请求最多刷新 100 个,文件夹则是 10 个

t
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();

请我喝杯[咖啡]~( ̄▽ ̄)~*

一个放羊娃 微信支付

微信支付

一个放羊娃 支付宝

支付宝