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


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 相关文章推荐
讲两件事:1.this指针的用法小探. 2.ie的attachEvent和firefox的addEventListener在事件处理上的区别
Apr 12 Javascript
Javascript常用运算符(Operators)-javascript基础教程
Dec 14 Javascript
Jquery+CSS3实现一款简洁大气带滑动效果的弹出层
May 15 Javascript
向左滚动文字 js代码效果
Aug 17 Javascript
JavaScript实现动态添加,删除行的方法实例详解
Jul 02 Javascript
jquery中checkbox使用方法简单实例演示
Nov 24 Javascript
JS获取数组中出现次数最多及第二多元素的方法
Oct 27 Javascript
详解vue静态资源打包中的坑与解决方案
Feb 05 Javascript
vue获取元素宽、高、距离左边距离,右,上距离等还有XY坐标轴的方法
Sep 05 Javascript
vue App.vue中的公共组件改变值触发其他组件或.vue页面监听
May 31 Javascript
微信小程序激励式视频广告组件使用详解
Dec 06 Javascript
基于vue hash模式微信分享#号的解决
Sep 07 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
初学者入门:细述PHP4的核心Zend
2006/09/05 PHP
回答PHPCHINA上的几个问题:URL映射
2007/02/14 PHP
JavaScript中常用的运算符小结
2012/01/18 Javascript
JavaScript高级程序设计(第3版)学习笔记11 内建js对象
2012/10/11 Javascript
js/jquery解析json和数组格式的方法详解
2014/01/09 Javascript
基于jquery步骤进度条源码分享
2015/11/12 Javascript
15位和18位身份证JS校验的简单实例
2016/07/18 Javascript
值得分享的Bootstrap Table使用教程
2016/11/23 Javascript
原生js实现addclass,removeclass,toggleclasss实例
2016/11/24 Javascript
原生node.js案例--前后台交互
2017/02/20 Javascript
详解es6超好用的语法糖Decorator
2018/08/01 Javascript
对Vue.js之事件的绑定(v-on: 或者 @ )详解
2018/09/15 Javascript
详解关于html,css,js三者的加载顺序问题
2019/04/10 Javascript
深入解析koa之异步回调处理
2019/06/17 Javascript
基于 vue-skeleton-webpack-plugin 的骨架屏实战
2019/08/05 Javascript
JS实现数据动态渲染的竖向步骤条
2020/06/24 Javascript
element-ui和vue表单(对话框)验证提示语(残留)清除操作
2020/09/11 Javascript
在Linux系统上安装Python的Scrapy框架的教程
2015/06/11 Python
python 拼接文件路径的方法
2018/10/23 Python
学习python分支结构
2019/05/17 Python
python print出共轭复数的方法详解
2019/06/25 Python
安装好Pycharm后如何配置Python解释器简易教程
2019/06/28 Python
Django 拼接两个queryset 或是两个不可以相加的对象实例
2020/03/28 Python
python 生成器需注意的小问题
2020/09/29 Python
联想香港官方网站及网店:Lenovo香港
2018/04/13 全球购物
中专三年学习的个人自我评价
2013/12/12 职场文书
大学考试作弊检讨书
2014/01/30 职场文书
八一慰问活动方案
2014/02/07 职场文书
家电业务员岗位职责
2014/03/10 职场文书
工程力学专业自荐信范文
2014/03/17 职场文书
低碳环保倡议书
2014/04/14 职场文书
3分钟演讲稿
2014/04/30 职场文书
慰问信格式规范
2015/03/23 职场文书
2016国庆促销广告语
2016/01/28 职场文书
创业计划书之DIY自助厨房
2019/09/06 职场文书
关于@OnetoMany关系映射的排序问题,使用注解@OrderBy
2021/12/06 Java/Android