如何自動化部署項目?折騰服務器之旅~

本篇文章講的不是如何把一個項目部署上線,而是如何自動化上線。javascript

開發了一個需求管理和發佈系統。html

經過這個系統,能夠建立需求、建立發佈計劃、建立分支、部署到測試環境、部署到生產環境、正式上線、合併代碼等。前端

1、功能設計

9.9元的阿里雲服務器真的很慢,但仍是足夠折騰完這個項目。 用3個目錄來模擬不一樣的環境。java

目錄 存放
project 存放全部的項目,好比本系統的先後端代碼。
pre-dir 預發環境,固然是用來測試的。
pro-dir 生產環境,測試沒問題,部署上線。

一圖勝千言。 node

功能設計

2、系統頁面

個人任務

接到一個新的需求,能夠新建一個需求,並建立開發分支。mysql

個人任務

發佈隊列

開發結束以後,即可以到發佈隊列中,部署到預發環境進行測試。 測試經過指定Cookie 就能夠訪問到測試的代碼。最終再進行線上部署。nginx

發佈隊列

項目信息

項目列表

2、技術棧

前端技術棧

Vue + elementUI,具體代碼在Github,感興趣的能夠看下並點個star哈~✨git

服務端技術棧

很是常見的Node.js(Koa2) + Mysql + Redis + Pm2。 具體代碼在Github,感興趣的能夠看下並點個star哈~✨github

3、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",
}));
複製代碼

4、Router配置

爲了Router看起來更優雅,也是經過中間件redis

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

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

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

// 三、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()]);
}

複製代碼

5、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;
        }
    }
}
複製代碼

6、一些中間件

經常使用的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();
        }
    }
}
複製代碼

7、操做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
複製代碼

8、鏈接數據庫

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; 
    }
}
複製代碼

9、域名

沒有買域名,經過本地修改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哈~✨

相關文章
相關標籤/搜索