前言
搭建基礎(chǔ)平臺搭建上篇的時(shí)候的時(shí)候孕豹,已經(jīng)介紹過了項(xiàng)目流程設(shè)計(jì)涩盾、數(shù)據(jù)庫搭建、jwt 登錄等模塊巩步。
此篇我們介紹分支管理設(shè)計(jì)及其他的基礎(chǔ)模塊旁赊。
后端模塊
- DevOps - Gitlab Api使用(已完成,點(diǎn)擊跳轉(zhuǎn))
- DevOps - 搭建 DevOps 基礎(chǔ)平臺(已完成 50%)基礎(chǔ)平臺搭建上椅野,點(diǎn)擊跳轉(zhuǎn)
- DevOps - Gitlab CI 流水線構(gòu)建
- DevOps - Jenkins 流水線構(gòu)建
- DevOps - Docker 使用
- DevOps - 發(fā)布任務(wù)流程設(shè)計(jì)
- DevOps - 代碼審查卡點(diǎn)
- DevOps - Node 服務(wù)質(zhì)量監(jiān)控
后期可能會根據(jù) DevOps 項(xiàng)目的實(shí)際開發(fā)進(jìn)度對上述系列進(jìn)行調(diào)整
Git 分支管理流程
Git Flow 流程
Production 分支
就是常用的 Master 分支终畅,這個(gè)分支包含最近發(fā)布到生產(chǎn)環(huán)境的代碼,最近發(fā)布的 Release竟闪, 這個(gè)分支只能從其他分支合并离福,不能在這個(gè)分支直接修改
Develop 分支
這個(gè)分支是的主開發(fā)分支,包含所有要發(fā)布到下一個(gè)Release的代碼炼蛤,這個(gè)主要合并于其他分支妖爷,比如 Feature 分支
Feature 分支
這個(gè)分支主要是用來開發(fā)一個(gè)新的功能,一旦開發(fā)完成理朋,我們合并回 Develop 分支絮识,并進(jìn)入下一個(gè) Release
Release 分支
當(dāng)需要發(fā)布一個(gè)新 Release 的時(shí)候,基于 Develop 分支創(chuàng)建一個(gè) Release 分支嗽上,完成 Release 后次舌,合并到 Master 和 Develop 分支
Hotfix 分支
當(dāng)在 Production 發(fā)現(xiàn)新的 Bu g時(shí)候,需要?jiǎng)?chuàng)建一個(gè) Hotfix, 完成 Hotfix 后兽愤,合并回 Master 和 Develop 分支彼念,所以 Hotfix 的改動會進(jìn)入下一個(gè) Release
整體的分支管理流程如下圖所示
[圖片上傳失敗...(image-7159d7-1605429069259)]
項(xiàng)目自建流程
上述的 Git Flow 流程使用可以規(guī)范約束開發(fā)質(zhì)量跟流程,我們稍微修改一下部分流程浅萧,融入到項(xiàng)目中進(jìn)行使用逐沙。
[圖片上傳失敗...(image-44b69a-1605429069259)]
如圖每個(gè)工程都共享一個(gè) version 版本號,分支創(chuàng)建分為版本升級洼畅、特性更新吩案、修訂補(bǔ)丁三種模式,強(qiáng)制項(xiàng)目所有分支創(chuàng)建的命名規(guī)則都會升級帝簇,不會出現(xiàn)重復(fù)跟降級徘郭。
上述流程的優(yōu)點(diǎn):
- 工程使用固定的版本鎖死,版本對應(yīng)需求流程己儒,上線質(zhì)量得到保障
- 每個(gè)開發(fā)分支都只能部署到測試環(huán)境崎岂,必須合并到合并到對應(yīng)的版本分支之后才能上生產(chǎn)
- 所有合并到 master 或者 relase 分支會被刪除,防止一條分支處理過多業(yè)務(wù)闪湾,后期 review冲甘、回滾難度提升
- realse 版本分支上線之后,生成對應(yīng) tag
- hotfix 版本可以從對應(yīng)的 tag 拉出,可以明確的知道 hotfix 具體修復(fù)的是哪個(gè)版本的問題
上述流程的缺點(diǎn):
- 固化版本流程導(dǎo)致創(chuàng)建命名規(guī)則固定江醇,且版本號不能升級只能降級
- 流程限制濒憋,降低開發(fā)靈活性
沒有完美的解決方法,所有 devops 流程都要結(jié)合真實(shí)項(xiàng)目需求來設(shè)計(jì)陶夜,上述只是一種解決方案凛驮,有更通用的方案設(shè)計(jì)請加我微信 Cookieboty 探討
DevOps 開發(fā)中篇
添加全局報(bào)錯(cuò)回調(diào)
沒有絕對安全的程序,所有程序在運(yùn)行中因各種情況會出現(xiàn) error条辟,全局錯(cuò)誤回調(diào)是基礎(chǔ)模塊必要的黔夭。
export default class HttpExceptions extends Error { // 繼承修改 error 類型
code: number;
msg: string;
httpCode: number;
constructor({ msg = "服務(wù)器異常", code = 1, httpCode = 400 }) {
super();
this.msg = msg;
this.code = code;
this.httpCode = httpCode;
}
}
import HttpExceptions from "../exceptions/http_exceptions"; // 全局?jǐn)r截錯(cuò)誤處理
export default () => {
return async function errorHandler(ctx, next) {
try {
await next();
} catch (err) {
// 所有的異常都在 app 上觸發(fā)一個(gè) error 事件,框架會記錄一條錯(cuò)誤日志
ctx.app.emit("error", err, ctx);
let status = err.status || 500;
let error: any = {};
if (err instanceof HttpExceptions) {
status = err.httpCode;
error.requestUrl = `${ctx.method} : ${ctx.path}`;
error.msg = err.msg;
error.code = err.code;
error.httpCode = err.httpCode;
} else {
// 未知異常羽嫡,系統(tǒng)異常本姥,線上不顯示堆棧信息
// 生產(chǎn)環(huán)境時(shí) 500 錯(cuò)誤的詳細(xì)錯(cuò)誤內(nèi)容不返回給客戶端,因?yàn)榭赡馨舾行畔? error.code = 500;
error.errsInfo =
status === 500 && ctx.app.config.env === "prod"
? "Internal Server Error"
: err.message;
}
// 從 error 對象上讀出各個(gè)屬性杭棵,設(shè)置到響應(yīng)中
ctx.body = error;
if (status === 422) {
ctx.body.detail = err.errors;
}
ctx.status = status;
}
};
};
如上婚惫,我們拓展默認(rèn)錯(cuò)誤類,添加錯(cuò)誤中間件攔截全局異常魂爪,如果出現(xiàn)自定義異常拋出的時(shí)候先舷,則處理全局異常,否則統(tǒng)一拋出 500 錯(cuò)誤滓侍,去除敏感信息蒋川。
webSocket 使用
為什么要使用 webSocket
項(xiàng)目管理中,會涉及到同一個(gè)項(xiàng)目多人協(xié)作操作粗井,而 ajax 輪訓(xùn)既消耗性能尔破,實(shí)時(shí)性也不能完全保證街图,也會推送大量無效信息浇衬。所以項(xiàng)目采用 websocket 來推送多人協(xié)作信息以及后期構(gòu)建流程的狀態(tài)推送。
egg-socket
框架提供了 egg-socket.io 插件餐济,增加了以下開發(fā)規(guī)約:
- namespace: 通過配置的方式定義 namespace(命名空間)
- middleware: 對每一次 socket 連接的建立/斷開耘擂、每一次消息/數(shù)據(jù)傳遞進(jìn)行預(yù)處理
- controller: 響應(yīng) socket.io 的 event 事件
- router: 統(tǒng)一了 socket.io 的 event 與 框架路由的處理配置方式。
具體的使用方式請參考:egg-socket.io 使用絮姆,下面簡單說下 ts 的配置
import { Application } from "egg"; // io路由使用方式
import { EggShell } from "egg-shell-decorators";
export default (app: Application) => {
const { router, controller, io } = app;
EggShell(app);
// socket.io
io.of('/').route('server', io.controller.nsp.ping);
};
ts 使用中 io.controller.nsp 會報(bào)類型未定義醉冤,所以需要修改一下 typings/index.d.ts 文件。
import "egg";
declare module "egg" {
interface Application { }
interface CustomController {
nsp: any;
}
interface EggSocketNameSpace {
emit: any
}
}
socket.io-client
window.onload = function () {
// init
const socket = io('http://127.0.0.1:7001', {
// 實(shí)際使用中可以在這里傳遞參數(shù)
query: {
room: 'nsp',
userId: `client_${Math.random()}`,
},
transports: ['websocket'],
});
socket.on('connect', () => {
const id = socket.id;
log('#connect,', id, socket);
// 監(jiān)聽自身 id 以實(shí)現(xiàn) p2p 通訊
socket.on(id, (msg: any) => {
log('#receive,', msg);
});
});
// 接收在線用戶信息
socket.on('online', (msg: any) => {
log('#online,', msg);
});
// 系統(tǒng)事件
socket.on('disconnect', (msg: any) => {
log('#disconnect', msg);
});
socket.on('disconnecting', () => {
log('#disconnecting');
});
socket.on('error', () => {
log('#error');
});
window.socket = socket;
};
客服端采用 socket.io-client 去鏈接 websocket篙悯。上述是基礎(chǔ)鏈接部分蚁阳,具體的實(shí)現(xiàn)要根據(jù)業(yè)務(wù)需求開發(fā)。
客服端實(shí)現(xiàn)
為了保障項(xiàng)目開發(fā)速度鸽照,客戶端選擇了 ANT DESIGN PRO螺捐。具體安裝步驟請參考教程,這邊展示一下部分業(yè)務(wù)端的代碼。
JWT 前端使用
/**
* 異常處理程序
*/
const errorHandler = (error: { response: Response }): Response => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
if (response.status === 401) {
window.location.href = '/user/login';
}
notification.error({
message: `請求錯(cuò)誤 ${status}: ${url}`,
description: errorText,
});
} else if (!response) {
notification.error({
description: '您的網(wǎng)絡(luò)發(fā)生異常定血,無法連接服務(wù)器',
message: '網(wǎng)絡(luò)異常',
});
}
return response;
};
/**
* 配置request請求時(shí)的默認(rèn)參數(shù)
*/
const request = extend({
prefix: '/api',
errorHandler, // 默認(rèn)錯(cuò)誤處理
credentials: 'include', // 默認(rèn)請求是否帶上cookie
headers: {
authorization: localStorage.getItem('authorization'), // 讀取本地保存的 authorization token
},
});
export default request;
改造 request 模塊
import request from '@/utils/request';
export interface LoginParamsType {
username: string;
password: string;
mobile: string;
captcha: string;
}
export async function fakeAccountLogin(params: LoginParamsType) {
return request('/user/getUserToken', {
getResponse: true, // 開啟可以拿到返回 header 參數(shù)赔癌,將對應(yīng)的 authorization token 存入本地使用
method: 'POST',
data: { params },
});
}
如上,拿到 response header 里面的 token澜沟,后續(xù)可以正常請求接口灾票。
尾聲
此項(xiàng)目是從零開發(fā),后續(xù)此系列博客會根據(jù)實(shí)際開發(fā)進(jìn)度推出(真 TMD 累)茫虽,項(xiàng)目完成之后刊苍,會開放部分源碼供各位同學(xué)參考。