极简自动化部署

415 阅读2分钟

最近因为修改自己的文件上传系统有点频繁,想想能不能做一个自动化部署系统:只要我push github然后它就自动打包部署了

基于我这样的需求,说干就干!!!

webhooks

探究了一翻,发现一个好东西啊!

它是干嘛的?

下面三种类型你选择一种

  • 第一种:git提交push
  • 第二种:git干的一切
  • 第三种:自定义git哪些操作

如果你干了你选择类型的操作,他就会自动请求你定义的请求地址(Payload URL)

那不就是我所需要的吗!!!nice~

功能定义

  1. 一个node服务,接受请求
  2. 打包进程
  3. 钉钉通知

代码其实很简单,因为仅仅只是为了 懒 而已!

接受请求

http.createServer((req, res) => {
  const params = querystring.parse(req.url.split('?')[1])
  if (params.projectName && params.projectName === 'ossUpload') {
    autobuild()
  }
  res.end('开始打包了')
})

自动打包部署

autobuild:创建一个进程来跑 shell命令执行打包部署

const notice = require('./notice') // 钉钉通知
const PM2_NAME = 'upload'
const PROJECTNAME = 'ossUpload'

function write (cp) {
  notice('### 准备打包')
  cp.stdout.on('data', (chunk) => {
    console.log('' + chunk)
  })

  cp.on('exit', (code) => {
    notice(`### 部署成功!`)
  });
}

// autobuild
module.exports = () => {
  const { exec } = require('child_process');
  function cpExec (shell, update = true) {
    // 是否已经是pm2部署过的
    let shellConnect = update ? `pm2 delete ${PM2_NAME} && ` + shell : shell
    
    return exec(shellConnect, { maxBuffer: 200 * 1024 * 1024 }, (error, stdout, stderr) => {
      if (error) {
        let cp = cpExec(shell, false)
        write(cp)
      }
    });
  }

  let cp = cpExec(`git pull && cnpm i && cd .. && cd ${PROJECTNAME} && npm run start`)
  write(cp)
}

整个核心其实是下面这句:

cpExec(`git pull && cnpm i && cd .. && cd ${PROJECTNAME} && npm run start`)

然后一步步执行脚本语句

  1. 拉取git
  2. 装载npm
  3. 返回上一级
  4. 选择文件上传系统文件夹
  5. 跑文件内package.json script start命令

start命令如下:

'start': 'nuxt build && cross-env NODE_ENV=production && pm2 start --name upload server/index.js',

也是一步步执行脚本语句

  1. 执行打包操作
  2. 定义环境变量为生产环境
  3. pm2 启动上线及进程守护

至此就完成部署上线的过程

消息通知

这里我用钉钉通知我,打包开始以及部署结束

notice

参考钉钉的以下两个文档

API调试 API文档

定义请求函数(可忽略)

注:用https模块

const https = require('https');
const querystring = require('querystring')

// 请求函数
const request = (path, params, method = 'GET') => {
  let postData = ''
  if (method === 'GET') {
    postData = `?${querystring.stringify(params)}`
    path += postData
  } else {
    postData = JSON.stringify(params)
  }
  const options = {
    hostname: 'oapi.dingtalk.com',
    port: 443,
    path: path,
    method: method,
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  };

  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let dataJSON = ''
      res.setEncoding('utf8');
      res.on('data', (chunk) => {
        dataJSON += chunk
      });
      res.on('end', () => {
          const data = JSON.parse(dataJSON)
          if (!data.errcode) {
            resolve(data)
          } else {
            console.error(data)
            process.exit(1)
          }
          
      });
    }).on('error', (e) => {
      console.error(`出现错误: ${e.message}`);
      reject()
    });
    // 将数据写入请求主体。
    req.write(postData);
    req.end();
  })
}

调用钉钉api逻辑

// 获取token
let token = ''
const getToken = () => {
  return request('/gettoken', {
    appkey: '申请的钉钉应用的key',
    appsecret: '申请的钉钉的密码'
  }).then(res => {
    token = res.access_token
  })
}

const sendMessage = (markdown) => {
  return request(`/topapi/message/corpconversation/asyncsend_v2?access_token=${token}`, {
    agent_id : '799270047', // 应用id
    msg: {
      msgtype: 'markdown',
      markdown :{
        title: `${new Date().valueOf()}`,
        text: markdown
      }
    },
    to_all_user: 'false', // 设置为true 则下面的部门不用填
    dept_id_list: '364849020' // 部门id
  }, 'POST')
}

module.exports = async (markdown) => {
  !token && await getToken()
  await sendMessage(markdown)
}

简单逻辑:

用一个变量存储token,因为就为了我一个文件上传使用系统,如果已经存在token那么直接发送消息,否则先文件上传获取token

至此简单自动化部署结束!再次推荐一下我的这个文件上传系统(开源在完善)

结语

其实还有很多问题,比如下次打包时他会杀掉当前的进程然后部署,那么就会导致当前线上就无法访问了等等一些问题,目前在写的时候我已经有想法了,嗯~等我优化完之后,下一篇补上。