如何自动化部署项目?折腾服务器之旅~


Posted in Javascript onApril 16, 2019

本篇文章讲的不是如何把一个项目部署上线,而是如何自动化上线。

开发了一个需求管理和发布系统。

通过这个系统,可以创建需求、创建发布计划、创建分支、部署到测试环境、部署到生产环境、正式上线、合并代码等。

一、功能设计

9.9元的阿里云服务器真的很慢,但还是足够折腾完这个项目。
用3个目录来模拟不同的环境。

目录 存放
project 存放所有的项目,比如本系统的前后端代码。
pre-dir 预发环境,当然是用来测试的。
pro-dir 生产环境,测试没问题,部署上线。

一图胜千言。

如何自动化部署项目?折腾服务器之旅~

二、系统页面

我的任务

接到一个新的需求,可以新建一个需求,并创建开发分支。

如何自动化部署项目?折腾服务器之旅~

发布队列

开发结束之后,便可以到发布队列中,部署到预发环境进行测试。 测试通过指定Cookie 就可以访问到测试的代码。最终再进行线上部署。

 如何自动化部署项目?折腾服务器之旅~

项目信息

如何自动化部署项目?折腾服务器之旅~ 

二、技术栈

前端技术栈
Vue + elementUI,具体代码在Github,感兴趣的可以看下并点个star哈~✨
服务端技术栈
非常常见的Node.js(Koa2) + Mysql + Redis + Pm2。
具体代码在Github,感兴趣的可以看下并点个star哈~✨

三、Redis和Session配置

 

// utils/Store.js
const Redis = require("ioredis");
const { Store } = require("koa-session2");
 
class RedisStore extends Store {
  constructor() {
    super();
    this.redis = new Redis();
  }
 
  async get(sid, ctx) {
    let data = await this.redis.get(`SESSION:${sid}`);
    return JSON.parse(data);
  }
 
  async set(session, { sid = this.getID(24), maxAge = 1000 * 60 * 60 } = {}, ctx) {
    try {
      console.log(`SESSION:${sid}`);
      // Use redis set EX to automatically drop expired sessions
      await this.redis.set(`SESSION:${sid}`, JSON.stringify(session), 'EX', maxAge / 1000);
    } catch (e) {}
    return sid;
  }
 
  async destroy(sid, ctx) {
    return await this.redis.del(`SESSION:${sid}`);
  }
}
 
module.exports = RedisStore;
// 入口文件
const session = require("koa-session2");
const Store = require("./utils/Store.js");
// session配置
app.use(session({
  store: new Store(),
  key: "SESSIONID",
}));

 四、Router配置

为了Router看起来更优雅,也是通过中间件

// 1、middleware配置文件
const routers = require('../routers');

module.exports = (app) => {
  app.use(routers());
}

// 2、index.js入口文件
const middleware = require('./middleware');
middleware(app);

// 3、routers 注册文件
const Router = require('koa-router');
const router = new Router();
const koaCompose = require('koa-compose');

// 接口入口
const {insertDemand} = require('../controllers/demand/insertDemand');
const {deleteDemand} = require('../controllers/demand/deleteDemandByDid');
const {updateDemand} = require('../controllers/demand/updateDemandByDid');

// 加前缀
router.prefix('/api');

module.exports = () => {
  // 新增需求
  router.get('/insertDemand', insertDemand);
  // 删除需求
  router.get('/deleteDemand', deleteDemand);
  return koaCompose([router.routes(), router.allowedMethods()]);
}

五、nginx配置

最头痛的就是nginx配置了,因为不是很熟悉,一直在试错、踩坑。不过还好终于成功了!
前后端项目通过Nignx提供服务,Node服务通过Nginx转发,主要是为了验证各种环境。
如果不设置Cookie,默认访问的就是线上环境,设置Cookie 就会走到预发布测试环境,用于测试。

# cookie 取TEST 赋值给$proxy_node
map $cookie_TEST $proxy_node {
  default "";
  "1"   "1";
  "2"   "2";
  "3"   "3";
}

# 发布管理系统前端设置
server {
  listen    80;
  server_name test.xue.com;
  if ($proxy_node = ''){
    set $dollar "/data/pro-dir/dandelion/dist/";
  }
  if ($proxy_node = "1") {
    set $dollar "/data/pre-dir/dandelion/dist/";
  }
  location / {
    root $dollar;
    index index.html;
    try_files $uri $uri/ /index.html;
  }
}

# 发布管理系统后端设置
# 反向代理到node服务
server {
  listen    80;
  server_name m.xue.com;
  if ($proxy_node = ''){
    set $dollar "/data/pro-dir/study-demo/";
  }
  if ($proxy_node = "2") {
    set $dollar "/data/pre-dir/study-demo/";
  }
  location / {
    root $dollar;
    index index.html;
  }
}

# demo项目前端设置
server {
  listen    80;
  server_name api.xue.com;

  location / {
    if ($proxy_node = "") {
      set $from 3001;
      proxy_pass http://47.107.188.55:3001;
    }
    if ($proxy_node = "3") {
      set $from 3002;
      proxy_pass http://47.107.188.55:3002;
    }
  }
}

六、一些中间件

常用的HTTP设置

解决跨域,OPTIONS请求,携带Cookie凭证等问题。

module.exports = () => {
  return async (ctx, next) => {
    ctx.set('Access-Control-Allow-Origin', 'http://test.xue.com');
    ctx.set('Access-Control-Allow-Credentials', true);
    ctx.set('Access-Control-Allow-Headers', 'content-type');
    ctx.set('Access-Control-Allow-Methods', 'OPTIONS, GET, HEAD, PUT, POST, DELETE, PATCH');

    // 这个响应头的意义在于,设置一个相对时间,在该非简单请求在服务器端通过检验的那一刻起,
    // 当流逝的时间的毫秒数不足Access-Control-Max-Age时,就不需要再进行预检,可以直接发送一次请求。
    ctx.set('Access-Control-Max-Age', 3600 * 24);
    if (ctx.method == 'OPTIONS') {
      ctx.body = 200; 
    } else {
      await next();
    }
  }
}

登录

这个系统属于强制登录的,登录统一进行了处理。

const Store = require("../../utils/Store");
const redis = new Store();
module.exports = () => {
  return async (ctx, next) => {
    // 白名单
    if (ctx.request.url === '/api/login') {
      return await next();
    } 
    const SESSIONID = ctx.cookies.get('SESSIONID');

    if (!SESSIONID) {
      return ctx.body = {
        mes: '没有携带SESSIONID~',
        data: '',
        err_code: 1,
        success: false,
      };
    }

    const redisData = await redis.get(SESSIONID);
    if (!redisData) {
      return ctx.body = {
        mes: 'SESSIONID已经过期~',
        data: '',
        err_code: 1,
        success: false,
      };
    }

    if (redisData && redisData.uid) {
      console.log(`登录了,用户uid为${redisData.uid}`);
      await next();
    }
  }
}

七、操作shell脚本

举个例子,创建项目分支

let path = ''; // 项目路径
// 创建分支
const branch_name = `branch_${new Date().getTime()}`;
cp.execSync(`/data/dandelion-server/shell/createBranch.sh ${path} ${branch_name}`);
#!/bin/bash

cd $1
git pull origin master
git checkout -b $2
git push --set-upstream origin $2

八、连接数据库

config.js配置文件

let dbConf = null;
const DEV = {
  database: 'dandelion',  //数据库
  user: 'root',  //用户
  password: '123456',   //密码
  port: '3306',    //端口
  host: '127.0.0.1'   //服务ip地址
}

const PRO = {
  database: 'dandelion',  //数据库
  user: 'root',  //用户
  password: '123456',   //密码
  port: '3306',    //端口
  host: 'xx.xx.xx.xx'   //服务ip地址
}
dbConf = PRO; //这个可以通过判断区分开发环境
module.exports = dbConf;

数据库连接文件

const mysql = require('mysql');
const dbConf = require('./../config/dbConf');
const pool = mysql.createPool({
 host: dbConf.host,
 user: dbConf.user,
 password: dbConf.password,
 database: dbConf.database,
})

let query = function( sql, values ) {
  return new Promise(( resolve, reject ) => {
    pool.getConnection(function(err, connection) {
      if (err) {
        reject( err )
      } else {
        connection.query(sql, values, ( err, rows) => {
          if ( err ) {
            reject( err )
          } else {
            resolve( rows )
          }
          connection.release()
        })
      }
    })
  })
}
module.exports = {
  query,
}

就可以在model层调用了~

const {query} = require('../common/mysql');

class UserModel {
  constructor() {}

  /**
   * @description: 根据pid和did创建一个分支
   * @param {pid} 项目id
   * @param {did} 需求id
   * @param {branch_name} 分支名
   * @return: 分支信息
   */
  async insertBranchInfo(sqlParams) {
    const sql = 'insert branch_info (pid, bid, branch_name, pub_time) values(?,?,?,?)';
    console.log(sql)
    let data = await query(sql, sqlParams, (err, result) => {
      return result;
    });
    return data; 
  }
}

九、域名

没有买域名,通过本地修改hosts(可以直接用工具)

47.107.188.xx为服务器IP

47.107.188.xx test.xue.com
47.107.188.xx api.xue.com
47.107.188.xx m.xue.com

 总结

算是第一次自己搭建一个完整的项目,从前端到后端。

尤其是后端,作为一个前端小白,从学习如何使用服务器,到Linux/Vim/Shell/Nignx/Pm2/Redis/Session/Mysql/Koa2。没有像以前一样,直接拿别的项目看,而是一步一个脚印的学习,虽然也都是皮毛,但是感觉自己的知识体系丰富了很多。也去了解了很多持续集成的知识,当然我做的小项目还是比较简单的啦~ 喜欢就点个赞鼓励一下吧,(^__^) 嘻嘻……
详细的使用都在前端项目、后端项目,感兴趣的可以看下并点个star哈~✨

 以上所述是小编给大家介绍的自动化部署项目详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JS window对象的top、parent、opener含义介绍
Dec 03 Javascript
jq实现酷炫的鼠标经过图片翻滚效果
Mar 12 Javascript
extjs_02_grid显示本地数据、显示跨域数据
Jun 23 Javascript
js读取cookie方法总结
Oct 31 Javascript
Javascript使用post方法提交数据实例
Aug 03 Javascript
Javascript中的return作用及javascript return关键字用法详解
Nov 05 Javascript
AngularJS中的表单简单入门
Jul 28 Javascript
AngularJs IE Compatibility 兼容老版本IE
Sep 01 Javascript
JavaScript数据结构学习之数组、栈与队列
May 02 Javascript
详解webpack多页面配置记录
Jan 22 Javascript
JavaScript实现多个物体同时运动
Mar 12 Javascript
vue实现购物车案例
May 30 Javascript
每周一练 之 数据结构与算法(Stack)
Apr 16 #Javascript
在vue中获取微信支付code及code被占用问题的解决方法
Apr 16 #Javascript
JavaScript实现选项卡效果的分析及步骤
Apr 16 #Javascript
Vuex持久化插件(vuex-persistedstate)解决刷新数据消失的问题
Apr 16 #Javascript
详解滑动穿透(锁body)终极探索
Apr 16 #Javascript
一些手写JavaScript常用的函数汇总
Apr 16 #Javascript
浏览器事件循环与vue nextTicket的实现
Apr 16 #Javascript
You might like
超人钢铁侠联手合作?美漫作家呼吁DC漫威合作联动以抵抗疫情
2020/04/09 欧美动漫
别人整理的服务器变量:$_SERVER
2006/10/20 PHP
PHP教程 预定义变量
2009/10/23 PHP
PHP资源管理框架Assetic简介
2014/06/12 PHP
php检查页面是否被百度收录
2015/10/28 PHP
ThinkPHP下表单令牌错误与解决方法分析
2017/05/20 PHP
用JavaScript脚本实现Web页面信息交互
2006/12/21 Javascript
Dojo 学习要点
2010/09/03 Javascript
JQUERY dialog的用法详细解析
2013/12/19 Javascript
js实现带关闭按钮始终显示在网页最底部工具条的方法
2015/03/02 Javascript
node.js读取文件到字符串的方法
2015/06/29 Javascript
深入浅析react native es6语法
2015/12/09 Javascript
深入理解Ajax的get和post请求
2016/06/02 Javascript
json格式的javascript对象用法分析
2016/07/04 Javascript
AngularJS基础 ng-show 指令简单示例
2016/08/03 Javascript
Jquery Easyui菜单组件Menu使用详解(15)
2016/12/18 Javascript
获取JavaScript异步函数的返回值
2016/12/21 Javascript
JS实现页面打印功能
2017/03/16 Javascript
JavaScript实现职责链模式概述
2018/01/25 Javascript
JS获取input[file]的值并显示在页面的实现方法
2018/03/09 Javascript
JS的Ajax与后端交互数据的实例
2018/08/08 Javascript
Vue-axios-post数据后端接不到问题解决
2020/01/09 Javascript
[02:10]2018DOTA2亚洲邀请赛赛前采访-Liquid
2018/04/03 DOTA
Python推导式简单示例【列表推导式、字典推导式与集合推导式】
2018/12/04 Python
Python实现的线性回归算法示例【附csv文件下载】
2018/12/29 Python
扩展Django admin的list_filter()可使用范围方法
2019/08/21 Python
使用Matplotlib绘制不同颜色的带箭头的线实例
2020/04/17 Python
用opencv给图片换背景色的示例代码
2020/07/08 Python
Pytorch 扩展Tensor维度、压缩Tensor维度的方法
2020/09/09 Python
HTML5打开手机扫码功能及优缺点
2017/11/27 HTML / CSS
Canvas多边形绘制的实现方法
2019/08/05 HTML / CSS
Banana Republic欧盟:美国都市简约风格的代表品牌
2018/05/09 全球购物
婚宴致辞
2015/07/28 职场文书
2015年教师节感言
2015/08/03 职场文书
外出培训学习心得体会
2016/01/18 职场文书
2019终止劳动合同协议书最新范本!
2019/07/09 职场文书