阅读 2800

从 0 到 1 node 项目管理系统: Gitlab Api

前言

DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

它是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。

此系列即是持续交付项目的教程亦可作为 node 开发的教程来使用。

阅读准备

本系列需要读者具备一定的 node 以及部分运维、项目管理流程的基础,然后配合食用效果更佳。

环境准备

开发 使用技术 Or 工具
后台开发框架 Egg
前端管理界面开发语言 React + Ant Design
数据库 Mysql
构建工具 Jenkins && GitLab CI
项目管理仓库 GitLab
应用容器 Docker

项目介绍

本系列会围绕通用项目管理流程(上图简单概括了此项目流程),从开发-测试-构建-部署的一整套 DevOps 项目

一共包含如下 2 个系列,分为前后端两个模块

后端模块

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

前端模块

  1. DevOps - H5 基础脚手架
  2. DevOps - React 项目开发

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

基础开发环境搭建准备

Gitlab 安装

本项目的开发依赖 Gitlab,所以在进行开发之前,请参考 Gitlab 安装步骤

搭建 Egg 项目

我们选择基础的 ts egg 版本进行开发,前置工作我们就不详细解释,有需求可以参考 Egg 教程

这里介绍两个插件可以方便我们项目开发使用(egg-helper,egg-shell-decorators)

一个是方便我们写 helper 基础的 utility 函数,一个是可以使用装饰器,方便我们管理路由。

数据库选择

数据库我们使用常规的 Mysql,具体安装详情可见 Mysql 安装教程,本文就不详细描述。

Mysql 插件,我们使用 Sequelize,下一篇会有详细说明,本篇暂时不需要用到。

Hello World

在 Egg 项目正常安装完依赖之后,传统开发的第一步,跟世界打个招呼!

Gitlab

简介

GitLab 是一个用于仓库管理系统的开源项目,使用 Git 作为代码管理工具,并在此基础上搭建起来的 web 服务。所以大部分公司都会选择使用 Gitlab 作为私有仓库管理。

认证授权

我们要使用 Gitlab Api 首先要拿到 Gitlab 的认证,才可以获得 Gitlab Api 的操作权限

GitLab Api 授权有如下几种方式:

  1. OAuth2 tokens
  2. Personal access tokens
  3. Project access tokens
  4. Session cookie
  5. GitLab CI/CD job token (Specific endpoints only)

由于我们是第三方使用Gitlab Api 所以我们选择 OAuth2 授权方式

OAuth2 授权有以下三种方式:

  1. Resource owner password credentials flow(客户端用户密码验证授权)
  2. Web application flow(Web应用程序授权)
  3. Implicit grant flow(隐式授权流)

本项目采取简单但安全性具有一定风险的第一种客户端验证授权(最好使用 Web application flow 授权方式)

封装基础 Gitlab Api 工具类

const qs = require("qs");

const baseUrl = "https://gitlab.xxxxxx.com"; // 此处替换为你自己的 gitlab 地址

export default (app) => {
  return {
    async post({url, params = {}, query = {}}) {
      const sendUrl = `${baseUrl}${url}?${qs.stringify(query)}`;
      try {
        const { data, code } = await app.curl(sendUrl, {
          dataType: "json",
          method: "POST",
          data: params,
        });
        return { data, code };
      } catch (error) {
        return error;
      }
    },
    async methodV({ url, method, params = {}, query = {} }) {
      const sendUrl = `${baseUrl}/api/v4${url}?${qs.stringify(query)}`;
      try {
        const { data, code } = await app.curl(sendUrl, {
          dataType: "json",
          method,
          data: params,
        });
        return { data, code };
      } catch (error) {
        return error;
      }
    },
  };
};
复制代码

上面封装的请求方法有两种,稍微注意一下,Gitlab Api 获取 access_token 的 url 前缀是不带 '/api/v4',而其他的请求是需要带上 '/api/v4', 所以我们多封装了一个 methodV 方法来请求其他的 Api。

// Service User
import { Service } from "egg";

export default class User extends Service {
  public async getUserToken({ username, password }) {
    const { data: token } = await this.ctx.helper.utils.http.post({
      url: "/oauth/token",
      params: {
        grant_type: "password",
        username,
        password,
      },
    });
    if (token && token.access_token) {
      return token;
    }
    return false;
  }
}

// Controller User
import { Controller } from "egg";
import { Post, Prefix } from "egg-shell-decorators";

@Prefix("user")
export default class UserController extends Controller {
  @Post("/getUserToken")
  public async getUserToken() {
    const { ctx } = this;
    const { params } = ctx.body;
    const { username, password } = params;
    const userToken = await ctx.service.user.getUserToken({
      username,
      password,
    });
    ctx.body = userToken;
  }
}
复制代码

如上,我们直接使用 postman 请求 http://127.0.0.1:7001/user/getUserToken 可以获取到 OAuth2 access_token,然后通过 access_token 调用对应的 open api 即可拿到我们想要的信息。

封装 Gitlab Api 请求

首先展示一下项目封装 api 的目录结构

这里之所以会根据 gitlab api 的分类做成工具类是因为在后面的操作过程中,我们会频繁的调用它。

而一般来说 service 层是做数据处理,Controller 层是做业务处理,在实际使用中都会遇到调用 api 的可能。所以我们直接把第三方的调用类都放在 helper 里面,方便我们开发使用

同样,后期的 Jenkins、Gitlab CI 等第三方调用也会封装于此

根据 project api 开始封装第一个经常用使用的项目请求类

import AJAX from "../../utils/http";

module.exports = (app) => {
  const getProjects = async ({ pageSize, pageNum, accessToken }) => {
    const { data: projectList } = await AJAX(app).methodV({
      url: "/projects",
      method: 'GET',
      query: {
        per_page: pageSize,
        page: pageNum,
        access_token: accessToken,
      },
    });
    return { projectList };
  };

  const createProjects = async ({ gitParams }) => {
    const status = await AJAX(app).methodV({
      url: "/projects",
      method: 'POST',
      params: {
        ...gitParams,
      },
    });
    return status;
  };

  const deleteProtectedBranches = async (projectId: number) => {
    const url = `/projects/${projectId}/protected_branches/master`;
    const status = await AJAX(app).methodV({
      url,
      method: 'DELETE',
    });
    return status;
  };

  const protectedBranches = async (projectId: number) => {
    const url = `/projects/${projectId}/protected_branches`;
    const status = await AJAX(app).methodV({
      url,
      method: 'POST',
      params: {
        name: "master",
        push_access_level: 0,
        merge_access_level: 40,
      },
    });
    return status;
  };

  return {
    getProjects,
    createProjects,
    deleteProtectedBranches,
    protectedBranches,
  };
};
复制代码

业务侧直接调用

// Service
import { Service } from "egg";

export default class Project extends Service {
  public async getProjectList({ pageSize = 100, pageNum = 1, accessToken }) {
    const {
      projectList,
    } = await this.ctx.helper.api.gitlab.project.getProjects({
      pageSize,
      pageNum,
      accessToken,
    });
    return projectList;
  }
}

// Controller
import { Controller } from "egg";
import { Post, Prefix } from "egg-shell-decorators";

@Prefix("project")
export default class ProjectController extends Controller {
  @Post("/getProjectList")
  public async getProjectList() {
    const { ctx } = this;
    const { params } = ctx.body;
    const { pageSize, pageNum, accessToken } = params;
    const projectList = await ctx.service.project.getProjectList({
      pageSize,
      pageNum,
      accessToken,
    });
    ctx.body = projectList;
  }
}
复制代码

如图,根据 accessToken 已经可以正常的拿到对应用户的项目信息,同理我们可以封装项目未来会使用到的常用 api,如 branch、merge 等。

尾声

此项目是从零开发,后续此系列博客会根据实际开发进度推出,项目完成之后,会开放部分源码供各位同学参考。

手动狗头镇楼