研发效能 结合 Jcci!生成代码提交影响分析报告!

底层贫困人员 · 2024年07月09日 · 最后由 底层贫困人员 回复于 2024年07月12日 · 3467 次阅读

背景

在使用 jcci 项目后,发现了一些功能其实可以更加完美,通过传入项目 git 地址 分支 两次的 commit id,即可分析出两次 commit id 之间代码改动所带来的影响,并生成树图数据方便展示影响链路(README 原话)通过解析 cci 文件,得知具体受影响的 API,但无法得知具体是哪个微服务下的 API 受影响了,也不知道具体 API 名称,为此需要对这个进行完善,做到精准回归测试

方案设计

流程图

先看一下流程图,我们是以部署节点作为最新的 commit id,与本地的 commit id 作为比较分析,譬如刚开始初始化克隆项目后,去到项目根目录执行git rev-parse HEAD即可获取当前的 commit id,当开发在流水线平台操作部署时,通过 JenkinsFile 脚本,即可获取最新的 commit id,再调用执行分析 api,即可分析出此次需要回归的接口(图中红色框则需要完善的功能,jcci 分析直接调包即可调包侠

维护关系

上图,我们可以得知有以下关系,一个 Git 项目维护着好几个微服务应用,mh-oms-parent 项目下面有 3 个微服务应用(mh-oms-adminmh-oms-bizmh-oms-paymh-oms-user)需要把各个微服务的接口文档数据同步下来,这里需要把解析 swagger 文档数据,再将解析后的数据进行入库,支持手动页面同步 + 定时器补偿(这里就不分享源码,都是些 curd 没意义,倒是可以看看库表设计,有兴趣可以问问我相关的细节)

# api项目表(微服务维度)
CREATE TABLE `api_project` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `business` varchar(64) NOT NULL COMMENT '业务线: MH_BUSINESS_TYPE',
  `project_name` varchar(64) NOT NULL COMMENT '项目名',
  `git_project_name` varchar(64) NOT NULL COMMENT 'git项目名',
  `description` varchar(64) DEFAULT NULL COMMENT '项目描述',
  `owner_name` varchar(32) NOT NULL COMMENT '负责人',
  `owner_code` varchar(32) NOT NULL COMMENT '负责人编码',
  `app_name` varchar(32) NOT NULL COMMENT '服务名',
  `create_code` varchar(20) NOT NULL COMMENT '创建人编码',
  `create_name` varchar(20) NOT NULL COMMENT '创建人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_code` varchar(20) DEFAULT NULL COMMENT '更新人编码',
  `update_name` varchar(20) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` smallint(6) NOT NULL COMMENT '0: 未删除 1: 已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
# 数据源表
CREATE TABLE `api_project_source` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `project_id` int(11) NOT NULL COMMENT '关联的项目id',
  `source_name` varchar(64) NOT NULL COMMENT '数据源名称',
  `source_format` smallint(6) NOT NULL COMMENT '数据源格式: TOOLS_DOC_TYPE',
  `source_url` varchar(255) NOT NULL COMMENT '数据源URL',
  `path_prefix` varchar(32) DEFAULT NULL COMMENT '路径前缀',
  `enable` tinyint(1) NOT NULL COMMENT '是否启用',
  `import_rate` smallint(6) NOT NULL COMMENT '导入频率,业务字典:TOOLS_DOC_IMPORT_RATE',
  `state` smallint(6) DEFAULT NULL COMMENT '运行状态,业务字典:TOOLS_DOC_RUN_STATE',
  `last_import_time` datetime DEFAULT NULL COMMENT '上次导入时间',
  `create_code` varchar(20) NOT NULL COMMENT '创建人编码',
  `create_name` varchar(20) NOT NULL COMMENT '创建人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_code` varchar(20) DEFAULT NULL COMMENT '更新人编码',
  `update_name` varchar(20) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` smallint(6) NOT NULL COMMENT '0: 未删除 1: 已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
# 目录表
CREATE TABLE `api_directory` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `project_id` int(11) NOT NULL COMMENT '项目id',
  `parent_id` int(11) DEFAULT NULL COMMENT '父id,为空就是根目录',
  `name` varchar(128) DEFAULT NULL COMMENT '目录名',
  `type` smallint(6) NOT NULL COMMENT '目录类型: 1:api_object_data, 2:sql_object, 3:dubbo_object, 4:case_object, 5:suite_object',
  `index` int(11) NOT NULL COMMENT '目录排序',
  `create_code` varchar(20) NOT NULL COMMENT '创建人编码',
  `create_name` varchar(20) NOT NULL COMMENT '创建人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_code` varchar(20) DEFAULT NULL COMMENT '更新人编码',
  `update_name` varchar(20) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` smallint(6) NOT NULL COMMENT '0: 未删除 1: 已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4;
# api对象数据表
CREATE TABLE `api_object_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `project_id` int(11) NOT NULL COMMENT '项目id',
  `directory_id` int(11) DEFAULT NULL COMMENT '目录id',
  `name` varchar(128) NOT NULL COMMENT '接口名称',
  `description` varchar(256) DEFAULT NULL COMMENT '接口描述',
  `tag` varchar(256) DEFAULT NULL COMMENT '接口标签',
  `base_url` varchar(256) DEFAULT NULL COMMENT '接口域名',
  `base_path` varchar(128) NOT NULL COMMENT '接口路径',
  `method` varchar(32) NOT NULL COMMENT '接口请求方法',
  `header` text COMMENT '接口请求头',
  `query` text COMMENT '接口查询参数',
  `body` text COMMENT '接口请求参数',
  `body_type` smallint(6) NOT NULL COMMENT 'body类型: 0: none 1: json 2: form 3: x-form 4: binary 5: GraphQL',
  `response` text COMMENT '接口返回参数',
  `create_code` varchar(20) NOT NULL COMMENT '创建人编码',
  `create_name` varchar(20) NOT NULL COMMENT '创建人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_code` varchar(20) DEFAULT NULL COMMENT '更新人编码',
  `update_name` varchar(20) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` smallint(6) NOT NULL COMMENT '0: 未删除 1: 已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=171 DEFAULT CHARSET=utf8mb4;

维护 Jcci 项目(Git 项目)

这里需要维护 Git 项目的相关信息,主要为 git url、branch、git token 等

# Jcci项目表
CREATE TABLE `jcci_git_project` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `project_name` varchar(64) NOT NULL COMMENT '项目名称',
  `description` varchar(64) DEFAULT NULL COMMENT '项目描述',
  `git_project` varchar(64) NOT NULL COMMENT 'git项目名',
  `git_url` varchar(256) NOT NULL COMMENT 'git地址',
  `git_branch` varchar(32) NOT NULL COMMENT 'git分支名',
  `git_token` varchar(256) NOT NULL COMMENT 'git token',
  `local_commit_id` varchar(256) DEFAULT NULL COMMENT '本地提交id',
  `status` smallint(6) NOT NULL COMMENT 'jcci项目状态: jcci_project_status',
  `create_code` varchar(20) NOT NULL COMMENT '创建人编码',
  `create_name` varchar(20) NOT NULL COMMENT '创建人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_code` varchar(20) DEFAULT NULL COMMENT '更新人编码',
  `update_name` varchar(20) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` smallint(6) NOT NULL COMMENT '0: 未删除 1: 已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

执行步骤

  1. 执行更新 swagger 文档数据
  2. 触发 jcci 分析 获取影响 urls
  3. 通过 urls 拼接机器人报告

重点说一下第二步,如何改造 jcci 的代码

主要用的是 jcci 项目中的analyze_two_commit方法,里面_can_analyze方法存在sys.exit(0)需要将其抛出异常或者直接 return,具体微改造如下:

analyze_two_commit方法改造如下:

嗯,改动点不是很多,调包完事,分析完,只要解析分析结果数据就好了

踩坑点

踩坑一:apscheduler 多进程环境重复运行

使用 Gunicorn 部署时,Gunicorn 可以指定 worker 参数,指的是开启的进程数,项目启动时,每次开一个 worker,都会启动一个 scheduler,这就导致了这些定时任务是由不同的进程创建的。
解决方法:
使用 Redlock(redis 分布式锁)进行定时任务上锁

def lock(key: Union[str, Callable], ttl: int = 3000):
    """
    redis分布式锁,基于redlock
    :param key: Redis key
    :param ttl: 锁释放时间
    :return:
    """

    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                redis_key = key(*args, **kwargs) if callable(key) else key
                if not redis_key:
                    raise BusinessException('缺少redis key无法上锁操作!')
                # 锁释放时间为30s
                with RedLock(redis_key, connection_details=Config.REDIS_NODES, ttl=ttl):
                    return func(*args, **kwargs)
            except RedLockError:
                appContextRepo.logger.error(f"redis key 为 {redis_key}")
                appContextRepo.logger.error(f"进程: {os.getpid()}, 执行函数{func.__name__}失败, 不用担心, 还有其他哥们给你执行了")
                raise RedLockException("操作太频繁了, 请稍后再试!!!")
        return wrapper
    return decorator

踩坑二:TimeZone offset does not match system offset

这里意思是运行的时区和系统时区不匹配
解决方法:

  1. 初始化调度器,指定时区

  1. 修改 Dockerfile 文件,指定时区

线上环境是 docker 容器,进行 cat /etc/timezone,显示的是 Etc/UTC,解决的思路是修改 Dockerfile,配置正确的时区,在 Dockerfile 中加入此行配置即可

踩坑三:json 格式化问题

将发送版本变更接口影响报告集成到流水线上,发现获取的提交信息和提交作者出现了换行,导致解析 json 失败

解决办法:

通过replace('\n','')将换行符替换掉

踩坑四:发送企微报告脚本异常

原本脚本上设定的超时时间为 2s,如果影响接口比较多的情况下,接口处理会比较慢,导致超时

解决方法:

将接口设置成异步接口 (直接用BackgroundTasks)

JenkinsFile 脚本

新增一个节点(作为分支节点,不影响原来部署流程),编写 curl 脚本,触发影响分析报告(异步生成报告,不影响原有流水线的构建速度)

# 获取提交人和提交信息 
commit_author = sh( script:"""echo `git log -1 --pretty=tformat:"%cn" `""", returnStdout: true)
println commit_author
commit_text = sh( script:"""echo `git log  -1 --pretty=tformat:"%s" `""", returnStdout: true)
println commit_text
try{
      timeout(2){
      def wxstdout = sh script:"""
      curl --location --request POST    'http://192.168.240.110:30080/api/tool/application/send' \
      --header 'Content-Type: application/json' \
      --data-raw '{
      "project": "mh-oms-parent",
      "commit_id": "${env.GIT_COMMIT}",
      "commit_text": "${commit_text.replace('\n','')}",
      "commit_author": "${commit_author.replace('\n','')}",
      "branch": "${env.GIT_BRANCH}",
      "key": "xxxxxxxxxxxxx"
      }'
      """, returnStdout: true
      println wxstdout
      }

      } catch (Exception e){
      println e
      echo '触发异常2!'
      }
}

飞书卡片报告模板

🏄  **项目:**<font color="grey">{project}</font>
🏁  **Commit_Id:**<font color="grey">{commit_id}</font>
📖  **分支:**<font color="grey">{branch}</font>
🙎🏻  **代码提交人:**<font color="grey">{commit_author}</font>
🚀  **代码提交信息:**<font color="grey">{commit_text}</font>
⏰  **发送时间:**<font color="grey">{now_time}</font>


**影响情况:**

报告效果

感谢

感谢 jcci 作者,写出这么棒的项目!@ 白开水 pp

Todo

  1. jcci 项目分析结果记录,前端页面可查看脑图节点(具体 mapper => impl => service => controller)
  2. 盘点出影响 API,回归执行对应的接口自动化脚本和推荐功能测试用例生成
  3. 盘点 N 个 xx 微服务(消费者)因为 yy 微服务(提供者)改动代码所影响的 API【联合注册中心应该能实现,有依赖关系,PS:开发觉得单个项目分析意义不大,比较鸡肋,还不如 ide 点点点我也同意,开发比较关注的是微服务级别的(说人话就是,我改了一个 rpc 接口 [dubbo、feign 等] 的实现方式,但是不知道这个接口,被哪些微服务所依赖,人工盘点影响范围,容易缺漏)】

总结

此次只是实现方案分享,没有源码,没有源码,没有源码(一大堆 curd 的代码,分享了也没意义);大家且看且珍惜,毕竟现在技术 xxxx,有兴趣可以问问我相关的细节,欢迎大家交流👏🏻👏🏻👏🏻

共收到 6 条回复 时间 点赞

附上 jcci 项目介绍以及地址:
testerhome:https://testerhome.com/topics/39516
github:https://github.com/baikaishuipp

这是针对 java 的改动分析影响,有针对 golang 语言的码

难以怀瑾 回复

golang 的不是很了解,github 找找😂

大佬 也在等被裁么😂 😂 😂

Nightwish 回复

是呀,年初裁了一波, 没在名单内😂

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册