前言
搭建基础平台搭建上篇的时候的时候,已经介绍过了项目流程设计、数据库搭建、jwt 登录等模块。
此篇我们介绍分支管理设计及其他的基础模块。
后端模块
- DevOps - Gitlab Api使用(已完成,点击跳转)
- DevOps - 搭建 DevOps 基础平台(已完成 50%)基础平台搭建上,点击跳转
- DevOps - Gitlab CI 流水线构建
- DevOps - Jenkins 流水线构建
- DevOps - Docker 使用
- DevOps - 发布任务流程设计
- DevOps - 代码审查卡点
- DevOps - Node 服务质量监控
后期可能会根据 DevOps 项目的实际开发进度对上述系列进行调整
Git 分支管理流程
Git Flow 流程
Production 分支
就是常用的 Master 分支,这个分支包含最近发布到生产环境的代码,最近发布的 Release, 这个分支只能从其他分支合并,不能在这个分支直接修改
Develop 分支
这个分支是的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并于其他分支,比如 Feature 分支
Feature 分支
这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回 Develop 分支,并进入下一个 Release
Release 分支
当需要发布一个新 Release 的时候,基于 Develop 分支创建一个 Release 分支,完成 Release 后,合并到 Master 和 Develop 分支
Hotfix 分支
当在 Production 发现新的 Bu g时候,需要创建一个 Hotfix, 完成 Hotfix 后,合并回 Master 和 Develop 分支,所以 Hotfix 的改动会进入下一个 Release
整体的分支管理流程如下图所示
项目自建流程
上述的 Git Flow 流程使用可以规范约束开发质量跟流程,我们稍微修改一下部分流程,融入到项目中进行使用。
如图每个工程都共享一个 version 版本号,分支创建分为版本升级、特性更新、修订补丁三种模式,强制项目所有分支创建的命名规则都会升级,不会出现重复跟降级。
上述流程的优点:
- 工程使用固定的版本锁死,版本对应需求流程,上线质量得到保障
- 每个开发分支都只能部署到测试环境,必须合并到合并到对应的版本分支之后才能上生产
- 所有合并到 master 或者 relase 分支会被删除,防止一条分支处理过多业务,后期 review、回滚难度提升
- realse 版本分支上线之后,生成对应 tag
- hotfix 版本可以从对应的 tag 拉出,可以明确的知道 hotfix 具体修复的是哪个版本的问题
上述流程的缺点:
- 固化版本流程导致创建命名规则固定,且版本号不能升级只能降级
- 流程限制,降低开发灵活性
没有完美的解决方法,所有 devops 流程都要结合真实项目需求来设计,上述只是一种解决方案,有更通用的方案设计请加我微信 Cookieboty 探讨
DevOps 开发中篇
添加全局报错回调
没有绝对安全的程序,所有程序在运行中因各种情况会出现 error,全局错误回调是基础模块必要的。
export default class HttpExceptions extends Error { // 继承修改 error 类型 code: number; msg: string; httpCode: number; constructor({ msg = "服务器异常", code = 1, httpCode = 400 }) { super(); this.msg = msg; this.code = code; this.httpCode = httpCode; }}import HttpExceptions from "../exceptions/http_exceptions"; // 全局拦截错误处理export default () => { return async function errorHandler(ctx, next) { try { await next(); } catch (err) { // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志 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 { // 未知异常,系统异常,线上不显示堆栈信息 // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息 error.code = 500; error.errsInfo = status === 500 && ctx.app.config.env === "prod" ? "Internal Server Error" : err.message; } // 从 error 对象上读出各个属性,设置到响应中 ctx.body = error; if (status === 422) { ctx.body.detail = err.errors; } ctx.status = status; } };};
如上,我们拓展默认错误类,添加错误中间件拦截全局异常,如果出现自定义异常抛出的时候,则处理全局异常,否则统一抛出 500 错误,去除敏感信息。
webSocket 使用
为什么要使用 webSocket
项目管理中,会涉及到同一个项目多人协作操作,而 ajax 轮训既消耗性能,实时性也不能完全保证,也会推送大量无效信息。所以项目采用 websocket 来推送多人协作信息以及后期构建流程的状态推送。
egg-socket
框架提供了 egg-socket.io 插件,增加了以下开发规约:
- namespace: 通过配置的方式定义 namespace(命名空间)
- middleware: 对每一次 socket 连接的建立/断开、每一次消息/数据传递进行预处理
- controller: 响应 socket.io 的 event 事件
- router: 统一了 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 会报类型未定义,所以需要修改一下 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', { // 实际使用中可以在这里传递参数 query: { room: 'nsp', userId: `client_${Math.random()}`, }, transports: ['websocket'], }); socket.on('connect', () => { const id = socket.id; log('#connect,', id, socket); // 监听自身 id 以实现 p2p 通讯 socket.on(id, (msg: any) => { log('#receive,', msg); }); }); // 接收在线用户信息 socket.on('online', (msg: any) => { log('#online,', msg); }); // 系统事件 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。上述是基础链接部分,具体的实现要根据业务需求开发。
客服端实现
为了保障项目开发速度,客户端选择了 ANT DESIGN PRO。具体安装步骤请参考教程,这边展示一下部分业务端的代码。
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: `请求错误 ${status}: ${url}`, description: errorText, }); } else if (!response) { notification.error({ description: '您的网络发生异常,无法连接服务器', message: '网络异常', }); } return response;};/** * 配置request请求时的默认参数 */const request = extend({ prefix: '/api', errorHandler, // 默认错误处理 credentials: 'include', // 默认请求是否带上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 参数,将对应的 authorization token 存入本地使用 method: 'POST', data: { params }, });}
如上,拿到 response header 里面的 token,后续可以正常请求接口。
尾声
此项目是从零开发,后续此系列博客会根据实际开发进度推出(真 TMD 累),项目完成之后,会开放部分源码供各位同学参考。
如对文章内容有任何疑问、见解可添加微信 Cookieboty 沟通。
另外关注公众号 Cookieboty1024,欢迎加入前端小兵成长营