前言
Hello 大家好,这里是Anyin。
在上一篇服务器和数据库统一管理平台-mayfly-go初体验 我们了解了mayfly-go是一款web版linux、数据库、redis、mongo统一管理操作平台,它可以解决在平时公司资产管理、前后端开发人员日常操作的80%的问题。
本篇我们通过后端代码来看看它是如何通过标签树实现资产隔离、数据库执行记录和回滚、终端和终端回放等核心功能。
代码结构
├── pkg # 基础公共包,包含所有模块共用工具、实体等
├── initialize # 初始化系统所有模块路由
├── internal # 功能模块
│ ├── common # 公共功能
│ ├── machine # linux机器相关功能接口等
│ ├───── api # 对前端提供的http api接口
│ ├───── application # 应用层,供api层调用
│ ├───── domain # 领域层,主要供application层调用
│ ├──────── entity # 实体对象
│ ├──────── repository # 实体仓库操作接口
│ ├───── infrastructure # 基础设施层。主要实现domain->repository操作相关接口等
│ ├───── router # 接口路由配置等
│ ├── sys # 系统管理相关api、application、domain、router等
│ config.yml # 项目整体配置。如后端运行端口、mysql数据库信息等
│ main.go # 主入口
└── go.mod # 项目包依赖信息等
项目的分层架构实现参考文章:mp.weixin.qq.com/s/ODY-RUyhU…
标签树实现资产隔离
资产包括4种类型:机器(t_machine)、数据库(t_db)、Redis(t_redis)、Mongo(t_mongo)。
这4张表都有tag_id和tag_path字段,即资产和标签是一对多的关系。
一个团队可以绑定多个标签,在t_tag_tree_team表可以看到,主要记录有3个字段:tag_id、tag_path、team_id。即团队和标签是多对多关系,一个团队可以绑定多个标签,一个标签也可以被多个团队绑定。
一个团队有多个成员,团队下每个成员可以通过团队找到标签,再通过标签找到有权限操作的资产。关系图如下:
这里以机器列表为例。
数据请求接口在internal/machine/api/machine.go#Machines
方法。
首先根据当前用户ID查询可以操作的标签列表,然后再根据标签列表查询相应的资产。
核心就是根据当前用户ID查询可以操作的标签列表,具体SQL在internal/tag/infrastructure/persistence/tag_tree_team#SelectTagPathsByAccountId
方法,
SQL语句如下:
SELECT DISTINCT(t1.tag_path)
FROM t_tag_tree_team t1
JOIN t_team_member t2 ON t1.team_id = t2.team_id WHERE t2.account_id = ?
ORDER BY t1.tag_path
但是这里只查询出直接和团队关联的标签,对于继承类型的标签无法查询处理。举例:
配置团队1对标签A有操作权限,通过该SQL可以查询出标签A,但是无法查询出标签A的子标签及其子子标签。
所以对于标签表(t_tag_tree)设计了2个字段:code和code_path,code用于标识当前标签唯一,code_path则为其父级code_path和自己code组成的一个字符串,同时在新增和编辑的时候不允许出现兄弟标签相同的开头。 举例:
- 标签A,code为:A,code_path为:A
- 子标签B,code为:B,code_path为:A/B
- 子子标签C,code为:C,code_path为:A/B/C
- 不允许出现子标签BC的情况
基于以上的设计,在第一次查询出和团队直接关联的标签集合,再用这些集合进行一次like操作,即可查询出所有可以操作的标签及其所有子标签。
具体代码逻辑在internal/tag/application/tag_tree.go#ListTagIdByAccountId
方法。
具体的SQL如下:
通过for循环拼接一个类似and ( code_path like 'A%' or code_path like 'B%' )
的SQL语句。
数据库执行记录和回滚
对于数据库执行记录和回滚,我们以MySQL为例(因为其他数据库我也不熟悉)。
在web版的控制台,我们执行一句SQL,调用的接口是/dbs/:id/exec-sql
,这里的ID是一条数据库记录的ID,它包含对应的数据库实例信息和数据库信息。
最终它会调用方法internal/db/application/db_sql_exec.go#Exec
,对于回滚操作,它只支持Update和Delete类型的操作进行回滚。
数据库的执行记录源码中首先是用sqlparser类库对前端传递的SQL进行解析,拿到更新的字段信息(删除操作无)、更新的表信息、更新的where条件信息,以及主键字段,接着根据这些信息把旧值查询出来(删除操作查询所有字段),最后把查询出来的值放到执行记录的oldValue字段中。
总结:
- 更新操作,oldValue字段记录更新字段和主键
- 删除操作,oldValue字段记录所有字段包含主键
另外,这里可能有个坑,因为记录旧值的查询语句包含了Limit 200,所以会导致回滚的时候一次操作最多只能回滚200条记录。
这个时候,数据库中已经有了执行SQL语句之前的数据信息,那又是如何回滚的呢?
通过界面操作,我们发现回滚的实现是通过生成回滚的SQL脚本,由用户手动执行。
我们在界面操作的时候,发现生成回滚SQL的时候,只请求了一个接口/dbs/:id/c-metadata
,它是用来返回执行记录SQL对应表的列信息。返回结果如下:
所以,对于回滚SQL是由前端根据oldValue字段信息和当前表的列信息来生成的。
根据当前表的列信息找到主键字段加上主键的值在oldValue里面已提前记录,所以最终拼接一个类似where id=?
的字符串作为回滚的where条件。
对于Update操作生成update语句,对于Delete操作生成insert语句,语句的主体都由oldValue字段提供。代码如下
终端和终端回放
终端的实现是基于websocket和ssh包实现的。在打开终端的时候,有一个Get请求,后端把这个Get请求升级为websocket,这个Get请求会把token携带上,方便后端进行鉴权。
接着,通过配置的服务器信息,实例化一个ssh client实例。
如果配置的服务器信息还勾选了终端回放,还会实例化一个回放recorder的实例。
最后websocket、ssh client 、recorder组装并且实例化一个terminal session实例,并启动。
在web版本的终端输入命令实现和服务器交互的流程如下:
接收websocket命令信息
接着,从终端中读出数据
最后,再通过websocket写入前端
在通过websocket写入前端的时候,还会判断是否存在回放记录,如果存在,则同样把当前操作写入回放记录中(其实就是写入到.cast文件当中)。
最后,在前端获取到.cast文件的base64内容,再通过AsciinemaPlayer进行回放。
总结
至此,终于了解了mayfly-go的基本分层架构、标签实现的资产权限隔离、SQL脚本的执行记录和回滚、ssh终端web版本和终端回放。
标签实现的资产权限隔离可以应用到角色继承或者组织架构的数据权限中;SQL脚本的执行记录和回滚,感觉可以应用到操作记录日志场景,明确当前操作的新旧值。