前端構(gòu)建 DevOps :搭建 DevOps 基礎(chǔ)平臺(下)

前言

基礎(chǔ)平臺搭建上篇 介紹項目流程設(shè)計、數(shù)據(jù)庫搭建、jwt 登錄等模塊

基礎(chǔ)平臺搭建中篇 介紹分支管理設(shè)計、webSocket 基礎(chǔ)模塊

本篇下將介紹流程管理與提測相關(guān)基礎(chǔ)模塊

后端模塊

  1. DevOps - Gitlab Api使用(已完成,點擊跳轉(zhuǎn))
  2. DevOps - 搭建 DevOps 基礎(chǔ)平臺(已完成 70%)
  3. DevOps - Gitlab CI 流水線構(gòu)建
  4. DevOps - Jenkins 流水線構(gòu)建
  5. DevOps - Docker 使用
  6. DevOps - 發(fā)布任務(wù)流程設(shè)計
  7. DevOps - 代碼審查卡點
  8. DevOps - Node 服務(wù)質(zhì)量監(jiān)控

前端模塊

  1. DevOps - H5 基礎(chǔ)腳手架
  2. DevOps - React 項目開發(fā)

后期可能會根據(jù) DevOps 項目的實際開發(fā)進度對上述系列進行調(diào)整

流程與提測管理

流程管理

基礎(chǔ)平臺搭建上篇已經(jīng)介紹過流程的設(shè)計,這里再簡單解釋下

  1. 開發(fā)同學(xué)創(chuàng)建對應(yīng)的工程以及分支,進行功能開發(fā)
  2. 項目負責(zé)人創(chuàng)建流程時,關(guān)聯(lián)多個開發(fā)分支,附加需求(需求模塊簡化成 desc 字段描述,沒有單獨抽出去)
  3. 流程的狀態(tài)由關(guān)聯(lián)的分支狀態(tài)組合,當(dāng)所關(guān)聯(lián)所有的開發(fā)分支狀態(tài)全部轉(zhuǎn)變?yōu)橐淹瓿傻臅r候,才會進入下一個狀態(tài)

整個項目管理,應(yīng)該拆解成項目->需求->工程,預(yù)留字段,將需求跟流程直接合并在一起,先完成主要功能,后期再進一步的拓展

提測管理

  1. 開發(fā)人員在開發(fā)完對應(yīng)功能進行項目提測
  2. 未關(guān)聯(lián)流程的分支不能進行提測
  3. 提測之后,測試同學(xué)介入測試,根據(jù) desc (需求)進行測試
  4. 開發(fā)內(nèi)容再提測之后,才能發(fā)布到預(yù)發(fā)或生產(chǎn),否則只能在測試環(huán)境發(fā)布(禁止未測試的需求直接上線)

不要嫌麻煩,現(xiàn)實中,產(chǎn)品隨便提個需求就上,出現(xiàn)問題到處甩鍋的情況還少嗎?嚴(yán)格卡關(guān)也是減輕工作量的一個小助力

DevOps 開發(fā)下篇

創(chuàng)建流程模塊

image
import { Post, Prefix, Get } from "egg-shell-decorators";
import BaseController from "./base";

@Prefix("process")
export default class ProcessController extends BaseController {
  /**
   * @author: Cookie
   * @description: 創(chuàng)建 devOps 任務(wù)流
   */
  @Post("/create")
  public async createProcess({
    request: {
      body: { params },
    },
  }) {
    const { ctx } = this;
    const { username } = this.userInfo;
    const { name, branchIds, workflowTplId, desc } = params;
    const branchStatus = await ctx.service.branch.checkProcess({ branchIds });
    if (!branchStatus)
      this.error({
        msg: "已有分支在流程中",
      });
    const status = await ctx.service.process.createProcess({
      desc,
      name,
      branchIds,
      workflowTplId,
      createdUser: username,
      updateUser: username,
    });
    await ctx.service.branch.updateBranch({
      branchIds,
      opt: {
        processId: status.id,
      },
    });
    this.success(status);
  }

  /**
   * @author: Cookie
   * @description: 查詢 devOps 任務(wù)流
   */
  @Get("/getList")
  public async getProcessList({ request: { query } }) {
    const { ctx } = this;
    const { pageSize = 10, pageNum = 1 } = query;
    const processList = await ctx.service.process.getProcessList({
      pageNum: parseInt(pageNum),
      pageSize: parseInt(pageSize),
    });
    // 聯(lián)表查詢分支信息
    for (let process of processList.rows) {
      const { branchIds } = process;
      process.branches = await ctx.service.branch.getSelfBranchList({
        branchIds,
      });
    }
    this.success(processList);
  }
}

提測模塊

image
import { Post, Prefix, Get } from "egg-shell-decorators";
import BaseController from "./base";

@Prefix("testRecord")
export default class TestRecord extends BaseController {
  /**
   * @author: Cookie
   * @description: 創(chuàng)建提測記錄
   */
  @Post("/create")
  public async createTestRecord({
    request: {
      body: { params },
    },
  }) {
    const { ctx } = this;
    const { id: submitUserId } = this.userInfo;
    const { desc, name, branchIds, testUserId } = params;

    const branchStatus = await ctx.service.branch.checkProcess({
      branchIds,
      status: "every",
    });

    if (branchStatus)
      this.error({
        msg: "存在未關(guān)聯(lián)流程的分支",
      });

    const status = await ctx.service.testRecord.createTestRecord({
      desc,
      name,
      branchIds,
      submitUserId,
      testUserId,
      testStatus: 0,
    });
    this.success(status);
  }
}

提測消息推送采用郵件(正式)與機器人(即時),提測內(nèi)容、次數(shù)、質(zhì)量等寫入數(shù)據(jù)庫,系統(tǒng)本身也能追蹤,作為后期效能評估的輔助

郵件推送

提測模塊的具體實現(xiàn)代碼,我們分為 3 塊

  1. 發(fā)送郵件使用 nodemailer
  2. 郵件模板使用 nunjucks 模板引擎,配置郵件模板
  3. 郵件前端自定義內(nèi)容使用 marked 插件解析 markdown 語法
import { MAIL_CONFIG } from "../../config/default.config";

const marked = require("marked"); // marked 轉(zhuǎn)換
const nodemailer = require("nodemailer"); // 發(fā)送郵件
const nunjucks = require("nunjucks"); // 模板引擎
const path = require("path");

// 郵箱配置初始化
const transporter = nodemailer.createTransport({
  host: MAIL_CONFIG.service,
  secureConnection: true, // 使用 SSL 方式(安全方式,防止被竊取信息)
  port: MAIL_CONFIG.port,
  auth: {
    user: MAIL_CONFIG.user_email, // 賬號
    pass: MAIL_CONFIG.auth_code, // 授權(quán)碼
  },
});

const htmlModel = ({ storyMail, exitInfo, summitUser, iterationMail }) => {
  const html = nunjucks.render(path.join(__dirname, "./emailTpl/email.njk"), {
    storyMail,
    exitInfo,
    summitUser,
    iterationMail,
  });
  return html;
};

/*
 * toEmail: String 接收者,可以同時發(fā)送多個,以逗號隔開
 * subject: String 標(biāo)題
 * cc: String 抄送
 * text: String 文本
 * html: Object titleList表頭 conterFontList內(nèi)容
 * attachments: any 附件
 * [
 *  {
     filename: 'img1.png',            // 改成你的附件名
     path: 'public/images/img1.png',  // 改成你的附件路徑
     cid : '00000001'                 // cid可被郵件使用
    }
 * ]
 */

interface mailInterface {
  toEmail: string;
  subject: string;
  cc?: string;
  text?: string;
  html?: any;
  attachments?: any;
  storyMail?: any;
  exitInfo?: any;
  summitUser?: String;
  iterationMail?: any;
}

const sendMail = async (mailOptions: mailInterface) => {
  const {
    toEmail,
    subject,
    cc,
    text,
    attachments,
    storyMail,
    exitInfo,
    summitUser,
    iterationMail,
  } = mailOptions;
  Object.keys(exitInfo).forEach((key) => {
    exitInfo[key] = marked(exitInfo[key]);
  });
  const html = htmlModel({ storyMail, exitInfo, summitUser, iterationMail });
  const mailOpts = {
    from: MAIL_CONFIG.user_email, // 發(fā)送者,與上面的 user 一致
    to: toEmail,
    subject,
    cc,
    text,
    html,
    attachments,
  };
  try {
    transporter.sendMail(mailOpts);
    return true;
  } catch (err) {
    console.log(err);
    return false;
  }
};

export default { sendMail };

釘釘群機器人

具體參考釘釘機器人文檔下面附帶具體的實現(xiàn)代碼(為了安全且簡單,采用加簽的安全驗證)

const crypto = require("crypto");
const secret ="";
const sendUrl =""; // 替換成自己的

export default (app) => {
  return {
    async send(content) {
      const timestamp = Date.now();
      const str = crypto
        .createHmac("sha256", secret)
        .update(timestamp + "\n" + secret)
        .digest()
        .toString("base64", "UTF-8");

      try {
        const { res, data } = await app.curl(
          `${sendUrl}&timestamp=${timestamp}&sign=${encodeURIComponent(str)}`,
          {
            headers: {
              "Content-Type": "application/json; charset=utf-8",
            },
            method: "POST",
            data: JSON.stringify(content),
          }
        );
        return res;
      } catch (error) {
        return error;
      }
    },
    text({ content = {}, at }) {
      console.log("content===>", content);
      at = at || {};
      this.send({
        msgtype: "text",
        text: {
          content,
        },
        at,
      });
    },
  };
};

// 測試機器人 Controller
import { Post, Prefix, Get } from "egg-shell-decorators";
import BaseController from "./base";

@Prefix("robot")
export default class ProjectController extends BaseController {
  @Post("/ding")
  public async getProjectList({
    request: {
      body: { params },
    },
  }) {
    const { ctx } = this;
    const { content } = params;
    await ctx.helper.robot.ding.text({ content });
    this.success({});
  }
}
image

上述只附帶了 text 文本消息推送,markdown、link、FeedCard 等其他消息類型,照著例子直接上手改就行了

建議

從第一篇看到目前這篇博客的同學(xué),如果團隊缺少合適的項目管理或者想練習(xí) node 的情況下,可以上手試試看,一般關(guān)鍵的代碼,我有直接貼在博客上(大部分復(fù)制就能用?。?。

后面的內(nèi)容就是貼合業(yè)務(wù)直接 curd 代碼,基礎(chǔ)篇到此結(jié)束。

下一篇就會出構(gòu)建篇,團隊可以結(jié)合自己項目實際情況增減功能,完善團隊基礎(chǔ)管理流程。

不明白的地方可以留言

尾聲

此項目是從零開發(fā),后續(xù)此系列博客會根據(jù)實際開發(fā)進度推出(真 TMD 累),項目完成之后,會開放部分源碼供各位同學(xué)參考。

為什么是開放部分源碼,因為有些業(yè)務(wù)是需要貼合實際項目針對性開發(fā)的,開放出去的公共模塊我寫的認真點

為了寫個系列博客,結(jié)果要寫完一整個系統(tǒng)(不是一般的累),覺得不錯的同學(xué)麻煩順手三連(點贊,關(guān)注,轉(zhuǎn)發(fā))。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容