如何自動(dòng)化部署項(xiàng)目?折騰服務(wù)器之旅~
本篇文章講的不是如何把一個(gè)項(xiàng)目部署上線,而是如何自動(dòng)化上線。
開(kāi)發(fā)了一個(gè)需求管理和發(fā)布系統(tǒng)。
通過(guò)這個(gè)系統(tǒng),可以創(chuàng)建需求、創(chuàng)建發(fā)布計(jì)劃、創(chuàng)建分支、部署到測(cè)試環(huán)境、部署到生產(chǎn)環(huán)境、正式上線、合并代碼等。
一、功能設(shè)計(jì)
9.9元的阿里云服務(wù)器真的很慢,但還是足夠折騰完這個(gè)項(xiàng)目。
用3個(gè)目錄來(lái)模擬不同的環(huán)境。
| 目錄 | 存放 |
|---|---|
| project | 存放所有的項(xiàng)目,比如本系統(tǒng)的前后端代碼。 |
| pre-dir | 預(yù)發(fā)環(huán)境,當(dāng)然是用來(lái)測(cè)試的。 |
| pro-dir | 生產(chǎn)環(huán)境,測(cè)試沒(méi)問(wèn)題,部署上線。 |
一圖勝千言。

二、系統(tǒng)頁(yè)面
我的任務(wù)
接到一個(gè)新的需求,可以新建一個(gè)需求,并創(chuàng)建開(kāi)發(fā)分支。

發(fā)布隊(duì)列
開(kāi)發(fā)結(jié)束之后,便可以到發(fā)布隊(duì)列中,部署到預(yù)發(fā)環(huán)境進(jìn)行測(cè)試。 測(cè)試通過(guò)指定Cookie 就可以訪問(wèn)到測(cè)試的代碼。最終再進(jìn)行線上部署。

項(xiàng)目信息

二、技術(shù)棧
前端技術(shù)棧
Vue + elementUI,具體代碼在Github,感興趣的可以看下并點(diǎn)個(gè)star哈~✨
服務(wù)端技術(shù)棧
非常常見(jiàn)的Node.js(Koa2) + Mysql + Redis + Pm2。
具體代碼在Github,感興趣的可以看下并點(diǎn)個(gè)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看起來(lái)更優(yōu)雅,也是通過(guò)中間件
// 1、middleware配置文件
const routers = require('../routers');
module.exports = (app) => {
app.use(routers());
}
// 2、index.js入口文件
const middleware = require('./middleware');
middleware(app);
// 3、routers 注冊(cè)文件
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配置了,因?yàn)椴皇呛苁煜?,一直在試錯(cuò)、踩坑。不過(guò)還好終于成功了!
前后端項(xiàng)目通過(guò)Nignx提供服務(wù),Node服務(wù)通過(guò)Nginx轉(zhuǎn)發(fā),主要是為了驗(yàn)證各種環(huán)境。
如果不設(shè)置Cookie,默認(rèn)訪問(wèn)的就是線上環(huán)境,設(shè)置Cookie 就會(huì)走到預(yù)發(fā)布測(cè)試環(huán)境,用于測(cè)試。
# cookie 取TEST 賦值給$proxy_node
map $cookie_TEST $proxy_node {
default "";
"1" "1";
"2" "2";
"3" "3";
}
# 發(fā)布管理系統(tǒng)前端設(shè)置
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;
}
}
# 發(fā)布管理系統(tǒng)后端設(shè)置
# 反向代理到node服務(wù)
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項(xiàng)目前端設(shè)置
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設(shè)置
解決跨域,OPTIONS請(qǐng)求,攜帶Cookie憑證等問(wèn)題。
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');
// 這個(gè)響應(yīng)頭的意義在于,設(shè)置一個(gè)相對(duì)時(shí)間,在該非簡(jiǎn)單請(qǐng)求在服務(wù)器端通過(guò)檢驗(yàn)的那一刻起,
// 當(dāng)流逝的時(shí)間的毫秒數(shù)不足Access-Control-Max-Age時(shí),就不需要再進(jìn)行預(yù)檢,可以直接發(fā)送一次請(qǐng)求。
ctx.set('Access-Control-Max-Age', 3600 * 24);
if (ctx.method == 'OPTIONS') {
ctx.body = 200;
} else {
await next();
}
}
}
登錄
這個(gè)系統(tǒng)屬于強(qiáng)制登錄的,登錄統(tǒng)一進(jìn)行了處理。
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: '沒(méi)有攜帶SESSIONID~',
data: '',
err_code: 1,
success: false,
};
}
const redisData = await redis.get(SESSIONID);
if (!redisData) {
return ctx.body = {
mes: 'SESSIONID已經(jīng)過(guò)期~',
data: '',
err_code: 1,
success: false,
};
}
if (redisData && redisData.uid) {
console.log(`登錄了,用戶uid為${redisData.uid}`);
await next();
}
}
}
七、操作shell腳本
舉個(gè)例子,創(chuàng)建項(xiàng)目分支
let path = ''; // 項(xiàng)目路徑
// 創(chuàng)建分支
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
八、連接數(shù)據(jù)庫(kù)
config.js配置文件
let dbConf = null;
const DEV = {
database: 'dandelion', //數(shù)據(jù)庫(kù)
user: 'root', //用戶
password: '123456', //密碼
port: '3306', //端口
host: '127.0.0.1' //服務(wù)ip地址
}
const PRO = {
database: 'dandelion', //數(shù)據(jù)庫(kù)
user: 'root', //用戶
password: '123456', //密碼
port: '3306', //端口
host: 'xx.xx.xx.xx' //服務(wù)ip地址
}
dbConf = PRO; //這個(gè)可以通過(guò)判斷區(qū)分開(kāi)發(fā)環(huán)境
module.exports = dbConf;
數(shù)據(jù)庫(kù)連接文件
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層調(diào)用了~
const {query} = require('../common/mysql');
class UserModel {
constructor() {}
/**
* @description: 根據(jù)pid和did創(chuàng)建一個(gè)分支
* @param {pid} 項(xiàng)目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;
}
}
九、域名
沒(méi)有買域名,通過(guò)本地修改hosts(可以直接用工具)
47.107.188.xx為服務(wù)器IP
47.107.188.xx test.xue.com
47.107.188.xx api.xue.com
47.107.188.xx m.xue.com
總結(jié)
算是第一次自己搭建一個(gè)完整的項(xiàng)目,從前端到后端。
尤其是后端,作為一個(gè)前端小白,從學(xué)習(xí)如何使用服務(wù)器,到Linux/Vim/Shell/Nignx/Pm2/Redis/Session/Mysql/Koa2。沒(méi)有像以前一樣,直接拿別的項(xiàng)目看,而是一步一個(gè)腳印的學(xué)習(xí),雖然也都是皮毛,但是感覺(jué)自己的知識(shí)體系豐富了很多。也去了解了很多持續(xù)集成的知識(shí),當(dāng)然我做的小項(xiàng)目還是比較簡(jiǎn)單的啦~ 喜歡就點(diǎn)個(gè)贊鼓勵(lì)一下吧,(^__^) 嘻嘻……
詳細(xì)的使用都在前端項(xiàng)目、后端項(xiàng)目,感興趣的可以看下并點(diǎn)個(gè)star哈~✨
以上所述是小編給大家介紹的自動(dòng)化部署項(xiàng)目詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
在Linux上用forever實(shí)現(xiàn)Node.js項(xiàng)目自啟動(dòng)
在一臺(tái)計(jì)算機(jī)上手動(dòng)跑Node項(xiàng)目簡(jiǎn)單,node xx.js就搞定了,想讓Node項(xiàng)目后臺(tái)運(yùn)行,雖然不能直接用node命令搞定,但是在安裝了forever這個(gè)包以后,還是很輕松的。不過(guò)要是在遠(yuǎn)程服務(wù)器上構(gòu)建Node項(xiàng)目,如果沒(méi)法自啟動(dòng),一旦服務(wù)器重啟,那就麻煩了。2014-07-07
Node.JS在命令行中檢查Chrome瀏覽器是否安裝并打開(kāi)指定網(wǎng)址
這篇文章主要介紹了Node.JS在命令行中檢查Chrome瀏覽器是否安裝,并打開(kāi)指定網(wǎng)址,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
node.js事件輪詢機(jī)制原理知識(shí)點(diǎn)
在本篇文章里小編給大家分享的是一篇關(guān)于node.js事件輪詢機(jī)制的相關(guān)知識(shí)點(diǎn)文章,有興趣的朋友們可以參考下。2019-12-12
在Node.js應(yīng)用中讀寫Redis數(shù)據(jù)庫(kù)的簡(jiǎn)單方法
這篇文章主要介紹了在Node.js應(yīng)用中讀寫Redis數(shù)據(jù)庫(kù)的簡(jiǎn)單方法,Redis是一個(gè)內(nèi)存式高速數(shù)據(jù)庫(kù),需要的朋友可以參考下2015-06-06
Mongoose經(jīng)常返回e11000 error的原因分析
這篇文章主要給大家分析了Mongoose經(jīng)常返回e11000 error的原因,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考價(jià)值,需要的朋友可以們下面來(lái)一起看看吧。2017-03-03

