从零构建一个服务器环境前端项目自动更新部署服务(上)

1,887 阅读6分钟

今天我们来从零构建一个简易的前端自动部署项目,为了方便大家理解,除了基础依赖,尽量不使用其他第三方插件(我连ui库都没装),尽量减少学习成本,项目中用到了pikaz-shell,一个操作shell插件,对此有疑惑的话可以前往我之前的pikaz-shell文章查看。

项目目标

搭建一个服务器前端项目自动部署api服务,搭建一个部署平台,通过界面操作完成项目部署。

文件结构

首先创建一个结构如下的文件夹,并切换到api文件夹,执行npm init -y初始化

├─api
│  ├─app.js
│  └─base.json
└─admin

api编写

切换到api文件夹

在package.json文件中加入项目所需依赖

"dependencies": {
    "koa": "^2.13.0",
    "koa-bodyparser": "^4.3.0",
    "koa-router": "^9.0.1",
    "koa2-cors": "^2.0.6",
    "pikaz-shell": "^0.1.3"
  }

执行npm install安装所有依赖

app.js

打开app.js文件,编写所需接口。

写入koa一个基础架构,引入koa和koa路由,处理跨域(因为我的测试环境都是部署在内网服务器上,所以跨域设置全部通过,如果需要个性化设置,则可以自己去koa2-cors仓库了解),解析post请求中的body(偷懒,所有请求都使用post方式~~~),以及其他所需功能模块,端口为3000,可自行修改app.listen(3000)为其他端口。

// Koa
const Koa = require('koa');
const app = new Koa();

// 跨域设置
const cors = require('koa2-cors');
app.use(cors());

// 路由
const Router = require('koa-router');
const router = new Router();

// 解析body
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());

const fs = require('fs');

const shell = require('pikaz-shell');

// 启动路由
app.use(router.routes()).use(router.allowedMethods())

/*
*  api编写
*/

console.log("启动成功")

app.listen(3000);

数据库

因为我们这个项目是非常小的,使用太大的数据库未免有点太浪费了,而且迁移部署很麻烦,所以我们使用之前创建的base.json文件当做一个微型数据库,写入

{
  "project": [],
  "nginx": {}
}

其中project为项目信息,nginx为nginx配置

接口

查询列表接口
router.post('/list', async ctx => {
  const { project, nginx } = JSON.parse(fs.readFileSync('./base.json', 'utf-8'));
  ctx.body = { code: 200, data: { project, nginx }, message: '查询成功' };
})

读取base.json文件,取出其中的project和nginx值返回

添加项目接口
/**
 * @name: 添加项目
 * @param {String} name/项目名称
 * @param {String} git/项目git地址
 * @param {String} path/项目所在文件夹位置
 * @param {String} build/打包命令
 * @return: 
 */
router.post('/add', async ctx => {
  const { name, git, path, build } = ctx.request.body
  const data = JSON.parse(fs.readFileSync('./base.json', 'utf-8'));
  const gitProject = data.project.find(item => item.name === name)
  if (gitProject) {
    ctx.body = { code: 500, data: null, message: '已存在相同名称的项目' };
    return
  }
  // 工程文件名
  const n = git.split('/')
  const projectName = n[n.length - 1].replace('.git', '')
  // clone项目并安装依赖打包
  const result = await shell([{ cmd: [`git clone ${git}`], path }])
  if (result === true) {
    // 添加入配置中
    data.project.push({ name, git, path, projectName, build })
    fs.writeFileSync('./base.json', JSON.stringify(data))
    ctx.body = { code: 200, data: null, message: '添加成功' };
  } else {
    ctx.body = { code: 500, data: null, message: '添加失败' };
  }
})

接收四个参数,分别为项目名称,项目git地址,项目所在文件夹位置,打包命令,我们使用项目名称作为主键,不可重复,如果重复则返回重复错误信息,之后使用pikaz-shell插件执行git clone项目,从git地址中获取clone下来的文件夹名称,并添加进配置中,之后将项目信息写入base.json数据库中。

编辑项目接口
/**
 * @name: 编辑
 * @param {String} name/项目名称
 * @param {String} git/项目git地址
 * @param {String} path/项目所在文件夹位置
 * @param {String} build/打包命令
 * @return: 
 */
router.post('/edit', async ctx => {
  const { name, git, path, build } = ctx.request.body
  const data = JSON.parse(fs.readFileSync('./base.json', 'utf-8'));
  const index = data.project.findIndex(item => item.name === name)
  if (index === -1) {
    ctx.body = { code: 500, data: null, message: `不存在${name}项目` };
    return
  } else {
    // 配置修改项目
    data.project[index] = { ...data.project[index], git, path, build }
    fs.writeFileSync('./base.json', JSON.stringify(data))
    ctx.body = { code: 200, data: null, message: '修改成功' };
  }
})

和添加接口类似,接收同样的四个参数,读取base.json数据库,查看是否存在相同项目名称name,不存在则返回错误信息,存在则将对应的项目信息修改(name是主键,不可修改,所以在赋值时并没有将name赋值,而是保留原有的name),之后将处理好的数据写入base.json数据库中。

编辑项目接口
/**
 * @name: 删除
 * @param {String} name/删除项目的名称
 * @return: 
 */
router.post('/del', async ctx => {
  const { name } = ctx.request.body
  const data = JSON.parse(fs.readFileSync('./base.json', 'utf-8'));
  const index = data.project.findIndex(item => item.name === name)
  if (index === -1) {
    ctx.body = { code: 500, data: null, message: `不存在${name}项目` };
  } else {
    // 删除项目文件
    const project = data.project[index]
    const result = await shell([{ cmd: [`rd/s/q ${project.projectName}`], path: project.path }])
    if (result) {
      // 配置删除项目
      data.project.splice(index, 1)
      fs.writeFileSync('./base.json', JSON.stringify(data))
      ctx.body = { code: 200, data: null, message: '删除成功' };
      return
    }
    ctx.body = { code: 500, data: null, message: '删除失败' };
  }
})

基本操作和添加修改没太大区别,读取base.json数据库,查找传过来的name,删除该条项目信息,再写入base.json数据库。

构建项目接口
/**
 * @name: 构建
 * @param {String} name/项目名称 
 * @return: 
 */
router.post('/build', async ctx => {
  const { name } = ctx.request.body
  // 查找项目
  const data = JSON.parse(fs.readFileSync('./base.json', 'utf-8'));
  const index = data.project.findIndex(item => item.name === name)
  if (index === -1) {
    ctx.body = { code: 500, data: null, message: `${name}项目文件不存在` };
  } else {
    const project = data.project[index]
    const path = `${project.path}/${project.projectName}`
    if (fs.existsSync(`${path}/node_modules`)) {
      // 存在node_modules则删除
      await shell([{ cmd: ["rd/s/q node_modules"], path }])
    }
    // 安装依赖打包
    const result = await shell([{ cmd: ["git pull", "npm install", project.build], path }])
    if (result === true) {
      ctx.body = { code: 200, data: null, message: "build成功" };
    } else {
      ctx.body = { code: 500, data: null, message: "build失败" };
    }
  }
})

构建接口接收一个项目名称name参数,查找base.json中porject中的相同name的项目信息,使用项目信息中的路径path和文件夹名称projectName组成项目的绝对定位地址,在项目中查找是否有node_modules依赖文件夹,如果有,则删除;之后进行git pull拉取项目,npm install安装依赖,以及项目信息中的build打包命令。

至此,整个项目构建api服务已完成(nginx部署部分放到下篇)

api调用

如项目的添加api调用

// api服务地址
const api="http://127.0.0.1:3000";
// 添加的项目信息
const addProject={ name: "pikaz-excel-js", git: "https://github.com/pikaz-18/pikaz-excel-js.git", path: "D:\", build: "npm run build" };
// 添加
fetch(`${api}/add`, {
        method: 'POST',
        body: JSON.stringify(addProject),
        headers: {
          'Content-Type': 'application/json'
        }
      }).then(res => {
        res.json().then(data => {
          if (data.code === 200) {
            // 构建
            fetch(`${api}/build`, {
              method: 'POST',
              body: JSON.stringify({ name:addProject.name }),
              headers: {
                'Content-Type': 'application/json'
              }
            }).then(res => {
              res.json().then(data => {
            })
            }).catch(err => {
              console.log(err)
            })
          }
        })
      }).catch(err => {
        console.log(err)
      })

调用添加/add接口,添加成功后调用/build构建接口,即可完成项目的添加与构建。

可以以此为基础构建一个项目部署界面,也可以在项目根目录中加入一个build.js文件,里面写好请求/build接口的构建请求,在package.json中的打包命令

"scripts": {
    "test": "node build.js && vue-cli-service build"
  },

加入node build.js,即可在执行npm run test,在项目构建的时候可同时更新测试环境,完成自动更新啦。

admin项目部署界面就不细说了,可直接查看本项目的admin文件夹,因为只是很简单的调取api,相信这一定难不倒聪明的你,。

项目启动

我使用pm2的方式启动,只需切换到api文件夹执行

npm run dev

即可启动,如果不想使用pm2的话,也可以直接启动

node app.js

注意

在使用本项目前,请确保服务器环境安装了git,node,pm2这些必要环境与插件,并且开放了api服务所使用的端口(也可以使用nginx代理),本项目使用的是win系统命令行,如需在linux系统使用,则将rd/s/q删除命令替换为rm -rf,如需使用如yarn或者cnpm,将npm替换为对应的命令即可,如果项目需要切换分支,可手动切换,或者自己再写个切换分支的接口,因为篇幅的原因在此就不多写了,只完成最基础的接口服务。

项目地址

github.com/pikaz-18/pi…

最后

nginx配置修改放在下篇讲,如果全部放在本篇太臃肿了(其实我也想再水一篇)。

本项目只是一个极简实现(只为了让大家对思路看的更清楚),实现了一个测试环境服务器端的前端项目自动添加、修改、删除、查询、构建,因为我是使用在内网服务器环境中的,所以我也没做账号验证,如果需要的话可自行添加如jwt验证等验证方式,另外错误处理也基本没做太多,如果想要更完善的错误处理,也可自行添加。

如果觉得对你有帮助的话,请点个赞吧。