最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 自动化部署前端项目到服务器

    正文概述 掘金(前端点心)   2021-02-26   612

    1.明确需求

    进行开发前需要首先明确需求,根据常见的前端部署流程总结为以下流程: 自动化部署前端项目到服务器 根据部署流程明确自动化部署的需求: 自动化部署前端项目到服务器

    2.开发前准备

    2.1 导入依赖模块

    由于需要实现文件压缩、模拟form表单提交、loading效果、友好提示效果、文件读写、文件上传,因此至少需要以下模块:

    • compressing 模块(支持压缩文件夹,支持zip压缩)
    • child_process 模块 (可以用来创建一个子进程,在js里面调用shell命令)
    • fs 模块 (可用于与文件系统进行交互)
    • form-data 模块 (将form表单元素的name与value进行组合,实现表单数据的序列化,从而减少表单元素的拼接,提高工作效率)
    • Ora 模块 (命令行环境的loading效果及显示各种状态的图标)
    • Chalk 模块(修改控制台中字符串的样式,包括字体样式、字体颜色、背影颜色)
    • http 模块(创建http服务器、客户端)
    2.2 如何实现规范

    为实现需求中的解耦合理与逻辑清晰/灵活,需要关注整体程序逻辑,这里选择封装相关功能实现,并在主程序中自由调度(可灵活调用、关闭、修改相关功能),并对于当前所执行的功能给与提示,以保证功能实现的完整性和异常提示。

    到这里就完成了对程序功能构建的梳理工作,下面进入项目实现。

    3.功能实现

    3.1 打包构建

    使用child_process模块调用shell命令,进行本地打包构建,其中env代表不同的构建环境,可从不同的执行脚本中获取到。

    由于package.json scripts下面配置如下: "upload:server": "node build/deploy.js --sit",可以根据自己需要灵活配置。

    const env = process.argv[process.argv.length - 1].replace('--', ''); // 用于获取构建脚本环境参数
    childProcess.exec(`npm run build:${env}`, function(error, stdout, stderr) {
      if (error) {
        console.log('exec error: ' + error);
      }
      compress();
    });
    
    3.2 本地压缩文件、功能提示

    项目打包构建完成后,执行功能提示、压缩操作,此处使用compressing、ora模块,用于对产物文件夹进行zip压缩及功能性提示

    const spinner = ora('开始打包……').start(); 
    spinner.text = '打包完成,开始压缩';
      // 压缩命令
      // 此处第一个参数为要打包的目录, 第二个参数是打包后的文件名
      compressing.zip
        .compressDir('web/', 'web.tar.gz')
        .then(() => {
          setTimeout(() => {
            spinner.text = '压缩完成,开始上传';
            upload();
          }, 300);
        })
        .catch(handleError);
    
    3.3 上传压缩后的文件到文件服务器

    文件服务器搭建方式:

    如何构建简易版的文件服务器

    使用说明:

    • Form-data表单模拟文件上传
    • 发送post请求上传文件到服务器
    • 获取接口返回code,用于下载上传到文件服务器的文件
    <form action="/ishare/profile" method="post" enctype="multipart/form-data">
      <input type="file" name="avatar" />
      <button type="submit">submit</button>
    </form>
    
    • form表单中entype属性可以用来控制对表单数据发送前如何进行编码
    • multipart/form-data不对字符编码,用于发送二进制的文件,其他两种类型(text/plain、application/x-www-form-urlencoded)不能用于发送文件;
    • type=file 用于文件上传
    • name 用于获取文件名

    大致实现过程如下:

    var FormData = require('form-data');
    var formData = new FormData();
    formData.append(
        'avatar',
        fs.createReadStream(path.resolve(__dirname, '../web.tar.gz'))
      ); //'file'是服务器接受的key
      var headers = formData.getHeaders(); //这个不能少
      var request = http.request(
        {
          method: 'post',
          host: config.remotePath,
          path: '/ishare/profile',
          headers: headers
        },
        function(res) {
          var str = '';
          res.on('data', function(buffer) {
            str += buffer; //用字符串拼接
          });
          res.on('end', () => {
            if (str) {
              const result = JSON.parse(str);
              const uploaded = result.uploaded;
              const fileCode = uploaded.substring(uploaded.indexOf('/') + 1);
              spinner.text = '上传完成,开始从服务器获取文件';
              getFile(fileCode);
            }
          });
        }
      );
      formData.pipe(request);
    
    3.4 在远程服务器上获取上传的文件

    压缩文件上传到文件服务器后,通过接口返回的fileCode,调用远程服务器部署好的接口服务。

    接口服务主要做以下几件事:

    • 避免连接无端服务器操作,减少开墙、不稳定因素
    • 下载上传成功的文件到服务器指定位置
    • 配置化配置服务器和目标文件目录地址
    • 不重复备份原始文件
    • 删除静态资源文件目录
    • 解压新的压缩文件
    3.5 获取fileCode和sessionTick(避免用户恶意调用接口)并下载文件到服务器指定位置
    app.get('/getFile', function(req, res) {
      const fileCode = req.query.code;
      const sessionTick = req.query.sessionTick;
      getFileByCode({fileCode: fileCode,sessionTick: sessionTick}, res);
    });
    
     async function getFileByCode(obj, res) {
      if (obj.sessionTick && obj.sessionTick !== '522314cc-f038-4abf-bb36-adc912e506e2') {
        res.msg = 'sessionTick missed or error';
        res.code = '0';
        res.status = 200;
        res.json(res);
        return
      }
      console.log('sessionTick missed');
      await dirExists(`./${config.filePath}`);
      let httpStream = request({
        method: 'GET',
        url: `http://${config.remotePath}/ishare/${obj.fileCode}`
      });
      let writeStream = fs.createWriteStream(`./${config.filePath}/${config.fileName}`);
      // 联接Readable和Writable
      httpStream.pipe(writeStream);
    
      let totalLength = 0;
    
      // 当获取到第一个HTTP请求的响应获取
      httpStream.on('response', response => {
        console.log('response headers is: ', response.headers);
      });
    
      httpStream.on('data', chunk => {
        totalLength += chunk.length;
        console.log('recevied data size: ' + totalLength + 'KB');
      });
      // 下载完成
      writeStream.on('close', () => {
        console.log('download finished');
        executeShell(res);
      });
    }
    

    其中dirExists用于判断指定路径是否存在,不存在就创建

    /**
     * 路径是否存在,不存在则创建
     * @param {string} dir 路径
     */
    async function dirExists(dir) {
      let isExists = await getStat(dir)
      //如果该路径且不是文件,返回true
      if (isExists && isExists.isDirectory()) {
        return true
      } else if (isExists) {
        //如果该路径存在但是文件,返回false
        return false
      }
      //如果该路径不存在
      let tempDir = path.parse(dir).dir //拿到上级路径
      //递归判断,如果上级目录也不存在,则会代码会在此处继续循环执行,直到目录存在
      let status = await dirExists(tempDir)
      let mkdirStatus
      if (status) {
        mkdirStatus = await mkdir(dir)
      }
      return mkdirStatus
    }
    /**
     * 读取路径信息
     * @param {string} path 路径
     */
    function getStat(path) {
      return new Promise((resolve, reject) => {
        fs.stat(path, (err, stats) => {
          if (err) {
            resolve(false);
          } else {
            resolve(stats);
          }
        });
      });
    }
    
    /**
     * 创建路径
     * @param {string} dir 路径
     */
    function mkdir(dir) {
      return new Promise((resolve, reject) => {
        fs.mkdir(dir, err => {
          if (err) {
            resolve(false);
          } else {
            resolve(true);
          }
        });
      });
    }
    
    3.6 执行deploy.sh并备份服务器前端资源

    其中executeShell函数主要用于执行shell命令sh deploy.sh ,通过传递的目标文件目录备份原始文件

    async function executeShell(res) {
      await dirExists(`${config.projectPath}/${config.projectName}/${config.filePath}`);
      childprocess.exec(`sh deploy.sh ${config.projectName} ${config.filePath} ${config.fileName}`, function(error, stdout, stderr) {
        if (error) {
          result.msg = `执行脚本deploy.sh报错,错误信息为: ${error}`;
          result.code = '1';
          result.status = 500;
          res.json(result);
          console.log('exec error: ' + error);
        } else {
          console.log('执行脚本deploy.sh完成');
          copy(res);
        }
      });
    }
    

    deploy.sh代码实现如下:

    #!/bin/sh
    nowtime=$(date "+%Y%m%d")
    echo $nowtime
    
    cd /home/bankdplyop/${1}
    rm -rf ${3}
    tar -cf ${2}$nowtime.tar.gz ${2}
    
    

    其中1{1}、1、{2}、3指执行shell命令时传递的参数,此处执行的命令为:shdeploy.sh{3}指执行shell命令时传递的参数,此处执行的命令为: sh deploy.sh 3指执行shell命令时传递的参数,此处执行的命令为:shdeploy.sh{config.projectName} config.filePath{config.filePath} config.filePath{config.fileName}

    config文件用于自由化配置目标文件地址、项目源文件地址及一些远程服务器相关参数

    module.exports = {
      sit: {
        remotePath: '29.2.221.176', //上传的远程服务器的目录
        filePath: 'web',
        fileName: 'web.tar.gz',
        projectName: 'iris_front',
        projectPath: '/home/bankdplyop',
        host: '0.0.0.0', //远程主机
        port: 3000 //服务器端口号
      }
    };
    
    
    3.7 删除静态资源文件目录并解压下载的文件
    • 拷贝下载完成的文件到待部署的项目所在地
    • 执行replace.sh脚本
    • 提示用户发布完成,请测试
    async function copy(res) {
      const destFilePath = path.resolve(__dirname, `${config.projectPath}/${config.projectName}/${config.fileName}`);
      const filePath = path.resolve(__dirname, `./${config.filePath}/${config.fileName}`);
      await dirExists(`${config.projectPath}/${config.projectName}`);
      fs.writeFile(destFilePath, fs.readFileSync(filePath), function(err) {
        if (err) {
          result.msg = '文件拷贝失败';
          result.code = '1';
          result.status = 500;
          res.json(result);
        };
       executeReplaceShell(res);
      });
    }
    

    其中executeReplaceShell(res),当文件拷贝完成时,执行删除和解压操作

    async function executeReplaceShell(res) {
      console.log(
        'replace shell start: ' +
          `${config.projectName}/${config.filePath}/${config.fileName}`
      )
      try {
        childprocess.exec(
          `sh replace.sh ${config.projectName} ${config.filePath} ${config.fileName} `,
          function (error, stdout, stderr) {
            if (error) {
              result.msg = `替换脚本replace.sh报错,错误信息为: ${error}`
              result.code = '1'
              result.status = 500
              res.json(result)
            } else {
              result.msg = '发布完成,请测试'
              result.code = '0'
              result.status = 200
              res.json(result)
            }
          }
        )
      } catch (e) {
        console.log(e)
      }
    }
    

    replace.sh代码实现如下:

    cd /home/bankdplyop/${1}
    ls ${2}
    rm -rf ${2}
    unzip ${3}
    
    

    其中1{1}、1、{2}、3指执行shell命令时传递的参数,此处执行的命令为:shreplace.sh{3}指执行shell命令时传递的参数,此处执行的命令为: sh replace.sh 3指执行shell命令时传递的参数,此处执行的命令为:shreplace.sh{config.projectName} config.filePath{config.filePath} config.filePath{config.fileName}

    至此完成了整个自动化发布流程。

    使用

    项目根目录直接运行即可

    npm run upload:server
    
    总结

    前端自动化部署是一件非常有意义的功能,记得最开始代码上传服务器,都需要手动进行操作,真得是好不麻烦,希望感兴趣的同学,如果公司内部没有一套完整的自动化部署流程,可以自己玩玩看。里面还有很多可以完善的点,希望大家多多交流,提一些宝贵意见。


    起源地下载网 » 自动化部署前端项目到服务器

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元