前言
在上一个博客中,已经通过 Egg 对 Gitlab Api 进行了基础的封装,本文将会围绕 DevOps 流程介绍项目设计(偏后台),需要读者具备一定的后端知识储备。
此系列即是持续交付项目的教程亦可作为 node 开发的教程来使用,从开发-测试-构建-部署的一整套 DevOps 项目
一共包含如下 2 个系列,分为前后端两个模块
后端模块
- DevOps - Gitlab Api使用(已完成,点击跳转)
- DevOps - 搭建 DevOps 基础平台(已完成 30%)
- DevOps - Gitlab CI 流水线构建
- DevOps - Jenkins 流水线构建
- DevOps - Docker 使用
- DevOps - 发布任务流程设计
- DevOps - 代码审查卡点
- DevOps - Node 服务质量监控
前端模块
- DevOps - H5 基础脚手架
- DevOps - React 项目开发
后期可能会根据 DevOps 项目的实际开发进度对上述系列进行调整
DevOps 设计
简单分析一下此项目研发流程的架构,接下来再做后续的步骤(剧本已写好,就看怎么演了)
项目需求分析(系统开发的目的跟结果)
- 从项目开发-测试-构建-部署一整套流程,简化交付成本
- 研发流程中加入能效概念(研发时间-测试时间-总体交付时间-bug 率及修复时间),作为项目提效的一个参考标准(影响因素太多,仅供参考)
- 合理的提测卡点,减少无效的提测,减轻测试负担,提高流程闭环质量
- 提供线上监控,分析每个版本使用率,报错率,提高项目研发质量
- 提供快速回滚指定版本功能,确保新版本崩溃情况下能够快速恢复服务
此项目是从零开发,在正式开发之前,需要先将需求理清,以免设计出现严重缺陷,造成后期开发或拓展困难(路可以走的慢,但不要走偏)。
流程设计
如上图所示,将上一篇的发布流程更进一步的细化可以分为下面 4 类:
- 单项目发布流程(一个需求只需要一个工程完成)
- 生产环境出问题,快速回滚功能
- 集成项目发布流程(一个需求可能会有多个工程参与开发、发布)
- Bug 修复发布流程(无需求,需要快速修复线上已知但不紧急 bug 的发布流程)
任务流的设计其实非常复杂,为了加快交付第一版,先将任务流固定为以上 4 类,减少开发量,后期会添加或者修改某个流程
数据库设计
sequelize 的使用
sequelize 提供了 sequelize-cli 工具来实现 Migrations,我们也可以在 egg 项目中引入 sequelize-cli(具体介绍参考 sequelize 操作)。
如果你参考上一篇博客已经将环境搭建完毕,可以使用 npm install --save-dev sequelize-cli
安装 sequelize-cli 工具,再通过下面配置生成需要的表。
use strict';
const path = require('path');
module.exports = {
config: path.join(__dirname, 'database/config.json'),
'migrations-path': path.join(__dirname, 'database/migrations'),
'seeders-path': path.join(__dirname, 'database/seeders'),
'models-path': path.join(__dirname, 'app/model'),
};
上述是 .sequelizerc 配置,请放在项目根目录下
npx sequelize init:config
npx sequelize init:migrations
执行完后会生成 database/config.json 文件和 database/migrations 目录,修改一下 database/config.json 中的内容,将其改成项目中使用的数据库配置:
{
"development": { // 本地数据库,其他环境数据库,照着例子自己改
"username": "root",
"password": "123456",
"database": "devops_dev",
"host": "127.0.0.1",
"dialect": "mysql"
},
}
再通过 npx sequelize migration:generate --name=init-users
来创建数据库表
module.exports = { // 为了减少工作量,权限我们直接使用 gitlab 的,所以我们只需要落库以下字段
up: async (queryInterface, Sequelize) => {
const { INTEGER, DATE, STRING } = Sequelize;
await queryInterface.createTable('users', {
id: { type: INTEGER, primaryKey: true, },
name: STRING(30),
username: STRING(30),
email: STRING(100),
avatar_url: STRING(200),
web_url: STRING(200),
created_at: DATE,
updated_at: DATE,
});
},
down: async queryInterface => {
await queryInterface.dropTable('users');
},
};
最后执行 migrate 进行数据库变更
# 升级数据库
npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all
设计基础表
[图片上传失败...(image-1e5ecf-1605149145994)]
将 gitlab project 与 branch 常用的数据落库到本地,再根据项目需求新增字段,大概的表结构如上图所示
结合上述项目流程设计,说明一下表结构关系
- 工程表 project 会管理多个分支 branch,可以查询当前工程下所有分支的状态(是否被提测,是否存在流程中)
- 创建一个流程(等同于需求)关联多个 branch 开发
- 流程创建完之后必走完所有步骤直至完结(开发-测试-预发-生产)
- 当 branch 被一个流程关联之后,既被所锁定,不会再次被加入到其他流程(需求锁定隔离,保证开发过程不会有干扰)
- 在流程的提测步骤中,可以针对不同 branch 进行多次提测(复杂需求通过分批提测,完成预期目标)
- 当流程中所有 branch 的状态都已测试通过之后,该流程状态才进入下一个阶段,否则一直停留在测试阶段
测试记录表没放上去,暂且先把上述功能开发完毕,再结合后面的分支管理进行修改
DevOps 开发
添加接口全局返回参数
import { Controller } from "egg";
export default class BaseController extends Controller {
get user() {
return this.ctx.user;
}
success(data) {
this.ctx.body = {
code: 0,
data,
};
}
error({ code, data, message }) {
// 根据业务返回不同的错误 code,提供给前端做业务判断处理
this.ctx.body = {
code,
data,
message,
};
}
}
定义全局返回参数基础类,业务 Controller 继承基础类,前端可以根据返回的 code 值进行业务判断
jwt 权限验证
上一篇介绍了从 Gitlab 获取 access_token 来操作 open api 的方法,但我们还是需要将用户信息从在本地落库,方便我们后期使用
项目的权限验证,采取简单的 jwt 来使用,将用户数据及 access_token 保存起来,后期完成第一阶段的目标之后再进行改进
具体的 egg-jwt 的使用可以参考(egg-jwt 使用),这里直接附上业务侧的代码供参考:
const excludeUrl = ["/user/getUserToken"]; // 请求白名单,过滤不需要校验的请求路径
export default () => {
const jwtAuth = async (ctx, next) => {
if (excludeUrl.includes(ctx.request.url)) {
return await next();
}
const token = ctx.request.header.authorization;
if (token) {
try {
// 解码token
const deCode = ctx.app.jwt.verify(
token.replace("Bearer ", ""), // jwt 中间件验证的时候,需要去掉 Bearer
ctx.app.config.jwt.secret
);
ctx.user = deCode;
await next();
} catch (error) {
ctx.status = 401;
ctx.body = {
code: 401,
message: error.message,
};
}
return;
}
ctx.status = 401;
ctx.body = {
code: 401,
message: "验证失败",
};
return;
};
return jwtAuth;
};
以上是全局拦截 jwt 权限中间件,验证权限之后,将用户数据存入 ctx 供后续业务侧调用。中间件的具体使用可以参考 egg 中间件
// Controller
import { Post, Prefix } from "egg-shell-decorators";
import BaseController from "./base";
@Prefix("user")
export default class UserController extends BaseController {
@Post("/getUserToken")
public async getUserToken({
request: {
body: { params },
},
}) {
const { ctx, app } = this;
const { username, password } = params;
// gitlab 获取 access_token
const userToken = await ctx.service.user.getUserToken({
username,
password,
});
// gitlab 获取用户信息
const userInfo = await ctx.service.user.getUserInfo({
accessToken: userToken.access_token,
});
// 用户数据本地落库
ctx.service.user.saveUser({
userInfo,
});
// 将用户信息及 token 使用 jwt 注册
const token = app.jwt.sign(
{
userToken,
userInfo,
},
app.config.jwt.secret
);
ctx.set({ authorization: token }); // 设置 headers
this.success(userInfo);
}
}
// Service
import { Service } from "egg";
export default class User extends Service {
// 使用 gitlab api 获取 access_token
public async getUserToken({ username, password }) {
const { data: token } = await this.ctx.helper.utils.http.post(
"/oauth/token",
{
grant_type: "password",
username,
password,
}
);
if (token && token.access_token) {
return token;
}
return false;
}
// 使用 gitlab api 获取 gitlab 用户信息
public async getUserInfo({ accessToken }) {
const userInfo = await this.ctx.helper.api.gitlab.user.getUserInfo({
accessToken,
});
return userInfo;
}
// 用户信息落库
public async saveUser({ userInfo }) {
const { ctx } = this;
const {
id,
name,
username,
email,
avatar_url: avatarUrl,
web_url: webUrl,
} = userInfo;
// 查询用户是否已经落库
const exist = await ctx.model.User.findOne({
where: {
id,
},
raw: true,
});
if (exist) return;
// 创建用户信息
ctx.model.User.create({
id,
name,
username,
email,
avatarUrl,
webUrl,
});
}
}
上述是服务端 jwt 的使用实例,在全局中间件拦截的时候可以解析出想要的信息来后续使用,客户端的实例,我们在 react 项目中单独说明。
以上是数据库建表以及用户、权限操作的实例与简介,此系列下一篇等基本的任务流开发完毕后再推出,预计 2 周左右
尾声
此项目是从零开发,后续此系列博客会根据实际开发进度推出,项目完成之后,会开放部分源码供各位同学参考。