前端构建 DevOps :搭建 DevOps 基础平台(中)

前言

搭建基础平台搭建上篇的时候的时候,已经介绍过了项目流程设计、数据库搭建、jwt 登录等模块。

此篇我们介绍分支管理设计及其他的基础模块。

后端模块

  1. DevOps - Gitlab Api使用(已完成,点击跳转)
  2. DevOps - 搭建 DevOps 基础平台(已完成 50%)基础平台搭建上,点击跳转
  3. DevOps - Gitlab CI 流水线构建
  4. DevOps - Jenkins 流水线构建
  5. DevOps - Docker 使用
  6. DevOps - 发布任务流程设计
  7. DevOps - 代码审查卡点
  8. DevOps - Node 服务质量监控

后期可能会根据 DevOps 项目的实际开发进度对上述系列进行调整

Git 分支管理流程

Git Flow 流程

image

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

整体的分支管理流程如下图所示

[图片上传失败...(image-7159d7-1605429069259)]

项目自建流程

上述的 Git Flow 流程使用可以规范约束开发质量跟流程,我们稍微修改一下部分流程,融入到项目中进行使用。

[图片上传失败...(image-44b69a-1605429069259)]

image

如图每个工程都共享一个 version 版本号,分支创建分为版本升级、特性更新、修订补丁三种模式,强制项目所有分支创建的命名规则都会升级,不会出现重复跟降级。

image

上述流程的优点

  1. 工程使用固定的版本锁死,版本对应需求流程,上线质量得到保障
  2. 每个开发分支都只能部署到测试环境,必须合并到合并到对应的版本分支之后才能上生产
  3. 所有合并到 master 或者 relase 分支会被删除,防止一条分支处理过多业务,后期 review、回滚难度提升
  4. realse 版本分支上线之后,生成对应 tag
  5. hotfix 版本可以从对应的 tag 拉出,可以明确的知道 hotfix 具体修复的是哪个版本的问题

上述流程的缺点

  1. 固化版本流程导致创建命名规则固定,且版本号不能升级只能降级
  2. 流程限制,降低开发灵活性

没有完美的解决方法,所有 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。具体安装步骤请参考教程,这边展示一下部分业务端的代码。

image

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 累),项目完成之后,会开放部分源码供各位同学参考。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容