14

门神——转转前端代码校验系统

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzU0OTExNzYwNg%3D%3D&%3Bmid=2247489188&%3Bidx=1&%3Bsn=b2240a4104d77ff5440a9b1a6cab345a
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

UNF3qaF.jpg!mobile

每逢新春佳节,家家户户都会按照习俗,在门上贴上凶神恶煞的门神,希望能驱邪避灾,保护家人平安。

而转转 FE 团队,也在农历新年前,正式上线了门神系统——一个在 编译 & 部署 环节可以通过静态扫描,发现并拦截含有不安全代码的项目上线的系统,保护线上项目稳定运行。

QNfeyij.png!mobile

背景

我们先从几个简单的场景入手,来发现业务开发中的一些常见痛点。

首先,即使转转 FE 团队有着非常完善的脚手架支持和 git commit 规范,并搭配 githook 以确保每次提交的代码都是经由 eslint 校验通过的,但这里面也有两个显而易见的漏洞:

  1. 本地 .eslintrc 文件中的规则可以随意修改
  2. 可以通过 git commit -n 指令轻易绕过 githook 设置的钩子

而我们既不能保证开发人员在匆忙上线时还能保持高度自律,也无力在繁忙的的业务代码开发中推行全面的 code review,所以我们需要借助自动化工具,对其中比较危险的 eslint 错误进行拦截,比如使用了未声明的变量( no-undef ),确保线上代码不会出现低级错误,这是其一。

其次,据前段时间运维反馈,前端项目访问了不少返回状态是 404 的链接。经排查,这些链接大都指向了错误或者失效的图片地址,比如旧的 58CDN 地址的图片,如果未经处理,无疑非常影响用户体验。所以我们要求所有图片链接都需要经过基础工具 setPicSize (转转内部对 CDN 图片进行解析、压缩等操作的工具)的处理,统一修正错误地址的同时,也能在访问出错时提供兜底方案。显然,这份枯燥而又不可省略的检查和修复工作也需要自动化工具的帮助。

最后,我们需要确保项目使用了必要的基础库,充分发挥转转 FE 团队的基建能力,比如:

  1. 确保项目接入了 lego 性能上报系统,上传用户页面性能数据,为性能平台做统计提供真实的原始数据
  2. 确保项目接入了 sentry 线上监控系统,及时发现并上报线上报错
  3. 确保移动端项目使用了最新版本的 zz-ui 组件库,使用了统一的、最新的 UI 样式

上述三个方面,都是需要能够收拢管理,以保证项目质量的地方,但是在传统的开发流程中却很难实现。可能在线 IDE 方案可以解决这些问题,但是对现阶段的转转 FE 团队规模而言探索在线 IDE 方案无疑是不现实的。所以转转 FE 团队提出了门神系统的概念,作为项目上线前的最后一道防线。这其实也和业界很多优秀的前端团队的方案所见略同,比如阿里的飞冰团队开源的 iceworks,从 5 个维度给代码打分;京东 FE 团队也在几天前分享了他们基于 EOS-JS 引擎扫描代码的方案...

预期

这个功能的业务需求很直白,就是扫描并拦截有问题的代码上线。但如果我们希望设计一个高可用度、高拓展度、功能完备的代码校验系统,那要做的还有很多。

门神系统项目第一期的目标大概分为以下几点:

  1. 无缝接入现有的代码部署系统,侵入性小,业务无感知

  2. 建设数据平台,供项目上线被拦截时快速查阅

  3. 支持本地调试

  4. 项目可以配置拦截规则、可以扩展拦截能力

  5. 既能严格拦截有严重安全隐患的代码,也能温柔提示可以优化的地方

接下来我们将从这几点需求出发,逐一介绍转转 FE 的代码校验系统技术方案。

方案设计

无缝接入现有的代码部署系统

说到这点,得好好介绍一下转转的代码部署系统:Beetle。

在转转的开发体系中,需求管理平台 TAPD、代码托管平台 GitLab、代码编译部署系统 Beetle 三位一体,相互打通。开发一个需求时,在 Beetle 上创建分支、关联需求;开发完成后,依然只需在 Beetle 上完成构建代码、部署测试/沙箱、正式上线等操作,Beetle 会自动修改需求状态,合并分支。这便是一个需求完整的开发周期。

我们需要关心的,是这其中创建分支、构建代码、部署测试/沙箱、正式上线四步操作。

I7RfUjQ.png!mobile

如图所示,只需要在 Beetle 系统触发上述四个钩子:

  • 在创建分支时,通知门神系统存储分支基础信息,为后续的代码校验做准备

  • 在构建代码时,通知门神系统当前分支代码已更新,可以开始校验

  • 在部署测试/沙箱时,通知门神系统,可以开始页面测试

  • 最终正式上线时,从门神系统读取校验结果,根据结果判断是否拦截上线操作

如此,得益于强大 Beetle 系统,借助现有的钩子即可完成系统对接。对业务侧开发的同学而言,除了在被系统拦截时会一脸懵逼外,整个过程毫无感知。

如下图所示,高亮区域的“静态扫描”卡片,还显示最后一次构建的结果,若 error > 0 ,则会触发系统的拦截,此时可以点击“扫描结果”,前往门神系统的后台界面,查看问题出在哪。

Zneiu2b.png!mobile

建设可视化平台

现在我们的项目被拦截了,我们跳转到门神系统的后台界面。这个页面会展示详细的校验结果,让你“死”得明明白白。

详细的校验结果是从门神系统的服务端读取的,门神系统的服务端记录着每个项目、每个分支、每次构建的静态扫描记录,每次记录都会根据项目的配置,返回一个“是否通过”的最终结果。这个结果既供 Beetle 系统读取,也供后台界面可视化访问。

这一节就会介绍可视化的后台系统。后台系统主要分为三大板块:

  • 以项目维度粗略预览

  • 以单次构建维度查看详细报错信息

  • 项目配置

我们主要介绍以单次构建维度查看详细报错信息。

我们从 Beetle 的拦截页面跳转到门神系统的后台界面,首先会看到以下信息:

YjUrQrm.png!mobile

这个页面详细显示了在这次扫描中,哪个文件的哪一行代码,触发了哪一个规则。如果这个规则在项目中配置为 error 级别,则会统计到“错误”类型中,触发上线前的拦截。比如这个截图中的错误,就是触发了 zz-check-plugin-404 插件的“未使用 setPicSize 处理图片链接,可能出现 404 问题”的规则。这里先提一句,系统采用插件机制,通过遍历插件完成扫描,不同插件实现不同的拦截需求,这会在后面的章节介绍。

其次,这个界面还可以查看项目的代码质量评分。这里要非常感谢阿里飞冰团队的开源方案 icoworks ,让我们可以站在巨人的肩膀上,更好地打造门神系统。通过整合这部分能力,并做了一定的本地化改造,我们从代码规范、代码可维护度和代码重复度三个方面对代码进行打分,并给出修改建议,但不做强制要求,也不会进行拦截。这可以有效帮助精益求精的同学提高代码质量。

下面的图片展示了一个转转 eslint 规范建议和一个代码重复度提示的例子。

Y3uu6zI.png!mobileNBrYJn6.png!mobile

最后需要说明的是,这里我们做了一个优化——删减了部分日志。因为在实际的项目中,比较大、比较旧的项目或者第一次接入的项目、描结果的日志数量可能会达到成千上万条,而检测代码重复度中也会粘贴大量的代码拷贝。这些数据其实没有太大的保存意义,买硬盘还是很贵的。所以如果要查看完整的日志,还请在本地手动执行扫描并修改~

支持本地调试

为了节省硬盘空间和更好地支持调试,本地手动执行扫描能力亦不可或缺。这部分能力通过将指令集成到转转现有的脚手架工具 zz-cli ,在项目根目录下通过 zz check 指令一键扫描,扫描完成后会起一个本地服务,获得和线上页面一样的浏览体验。不一样的地方在于:

  • 这次看到的是完整日志

  • 完全保留 eslint 自动修复能力
  • 可以通过点击文件链接直接跳转到文件进行修改

至于详细的扫描流程,已经抽离成单独的基础包,和服务端保持一致,这部分内容会在“核心代码”章节介绍。

平台化+插件机制

一个长期维护的项目,项目最初的开发者往往无法预测后期多变的业务场景,和因此带来的需求变化。其次,项目的维护者也不应该无节制的地投入心力,为不同的业务需求制定定制化的规则。

作为前端行业从业者,我们每个人都使用过很多伟大的产品,比如 Chrome,VS Code;很多主流的工具,比如 eslint, babel...这些产品都有一个共同的机制,那就是平台化+插件机制。他们凭借着插件机制,解放生产力,爆发出了蓬勃的生命力,满足了海量用户千差万别的需求。

所以我们认为,平台化+插件机制,也许也是门神系统的最佳打开方式。实现这一步,我们需要完成以下工作:

  • 搭建插件管理平台

  • 支持注册自定义插件

  • 为用户开发插件提供文档和技术支持

  • 支持对不同的项目配置不同的插件组合

这样,系统通过遍历执行插件完成扫描,每个插件各司其职,达到千人千面的使用效果。如下图,系统默认提供了几个基础插件。

mMVrUn7.png!mobile

错误等级划分

好了,我们已经把系统接入 Beetle 系统,开发了后台界面,支持了本地调试,还提供了插件机制,这些都是基础能力建设。现在我们要静下心来思考下一个问题:如何制定规则。我们既不希望门神系统过于严格,成为业务上线的绊脚石,屡屡阻拦业务正常上线,影响开发效率,这个违背了提质增效的本意;也不希望过于放松,起不到“门神”的作用,徒增摆设,最后无人问津。

基于这个考虑,我们划分了错误等级。系统的扫描结果分为两大类:一个是日志,一个是评分。划分错误等级指的是对日志中的错误进行分级。

日志是系统通过遍历插件扫描项目的返回值,用于判断是否需要拦截项目上线。目标在于发现项目中不被察觉的低级错误和潜在的安全问题。插件根据自己的规则,返回统一的返回值,分为三个级别: errorwarninginfoerror 级别的日志默认会触发拦截,而 warninginfo 级别则只是告知,不做强制要求。

第二类是评分,评分的职责在于给项目的代码质量打分。这部分统一标准,而非采用插件机制。分为三个维度:

  • 代码规范

  • 代码可维护度

  • 代码重复度

虽然目前项目分数仅供参考,但随着打分机制的完善和稳定,这个分数既可以作为评价代码质量的考核标准,也可以作为代码重构的辅助工具。

自定义配置

即使有了插件机制和错误级别划分,要想有千人千面的使用效果,还要支持灵活的配置。

第一,支持插件配置,不同的项目除了强制使用基础插件外,还可以使用自定义配置的插件。很多业务线往往有着一些定制化的需求,这时完全可以开发一个插件,注册到平台,并在项目中配置使用,这既方便了自己,也造福了未来其他有着同样需求的用户。

第二,支持拦截配置,插件的错误等级划分是由插件开发者决定的,但不同的项目管理员配置插件时,可以决定是否拦截该插件的报错,实现优雅降级。

当然,这一部分的能力必须考虑权限控制,只有业务线负责人才有权限修改项目配置,否则门神系统的威严也无法保障,就和被随意修改的 .eslintrc 文件一样...

下图展示了项目中的插件配置界面。

AN36faf.png!mobile

总结

门神系统技术实现的设计思路大概就是这些内容,我们再重新梳理一下。

我们提供了一个扫描服务,实时扫描代码并保存结果,然后将结果在后台界面可视化展示。同时将它无缝接入到现有的代码编译部署环节。为了拓展性我们的扫描系统基于插件机制开发,并支持自定义配置。最后还支持本地调试。

核心代码

最后说一下技术实现上的细节,主要是扫描代码功能这部分的实现,这是整个系统的核心,无论是服务器端还是本地扫描,都会调用这部分能力,所以我们将它单独抽离成一个包,叫做 zzCheckService

zzCheckService 将扫描工作分为 5 个阶段:

quQzAjy.png!mobile

除了第一步的初始化和最后一步的输出结果外,核心工作是中间的 3 步。

其中比较复杂的是准备工作阶段。我们需要在这一步准备好项目源码、项目配置和插件的 npm 包才能进行后续的步骤。在服务端,我们需要

  • 根据 Beetle 系统在创建分支时给我们的分支信息,从 GitLab 上拉取当前分支的最新代码,并解压缩到特定的临时目录(方便扫描之后删除,硬盘空间真的很贵)

  • 从数据库读取当前项目的插件配置

  • npm 源检查每一个插件的最新版本,与本地插件版本对比
  • 下载新插件、更新旧插件

IZB3qer.png!mobile

在本地调试时,我们需要

  • 与服务端不同,我们本来就身处项目中,所以不需要拉取源码

  • 从服务端请求当前项目的插件配置

  • npm 源检查每一个插件的最新版本,与本地插件版本对比
  • 下载新插件、更新旧插件

vEFbI3I.png!mobile

最后一步安装插件时,有一个细节值得注意——这里的 npm 包管理应该是独立管理的,不能和当前运行环境的 node_modules 文件夹混合,我们可以将插件安装到指定目录,通过 --prefix 修饰符可以实现。

npm install pluginName --prefix targetDir

引入插件时,再次通过 require.resolve 覆盖 require 方法默认的解析顺序

const path = require.resolve(pluginName, { paths: [targetDir] })
const plugin = require(path)

做好了准备工作,接下来的代码校验和代码打分则是水到渠成了。之前我们说到扫描结果分为两大类,日志和评分,这分别对应代码校验和代码打分这两步。

  • 在代码校验环节,我们从拿到的插件配置中,遍历当前项目所用到的插件,得到一个包含每个项目输出日志的数组,存到日志中备用。

  • 在代码打分环节,我们和之前一样,从三个维度扫描代码,得到一个包含每个维度打分详情的数组,并存到日志中备用。

最后把扫描结果返回给调用方——在服务端则存储到服务器,在本地则生成本地日志文件。

需要注意的是,整个扫描过程根据项目大小不同,短则几十秒,长则几分钟。在服务端运行时,这么长的执行时间即使是异步操作,也会造成服务器的 I/O 不可接受的延迟,所以必须使用多进程能力,在子进程中执行扫描,只在扫描结束时把返回值发送给主进程,存储到数据库中。

结果

回望一下项目的背景和预期,最终实现的门神系统其实基本上满足了我们对他的期待。目前大部分的前端项目,包括 vuereactnodesdk 项目,都已经在搭载门神系统的 Beetle 系统中构建、部署,实现平稳过渡。

通过静态扫描,我们可以拦截一些危险的 eslint 错误,解决图片 404 的问题,确保项目使用了最新的基础包,为线上项目的稳定运行保驾护航。后续,我们也可以在插件机制的帮助下,释放更大的能量,提供更多的可能性。

展望

结束了门神系统第一期的开发工作,但是转转 FE 团队对前端开发提质增效的追求却从未停止,门神系统也有可以更进一步的地方。

比如本地调试功能还可以集成为 VS Code 插件,随时随地自查;

比如开发基础库的同学非常想知道公司的基础服务在项目中的覆盖率如何,那也可以通过开发插件,统计相关数据;

最后,打分系统还可以进一步定制化,提高准确度,提供更好的参考意义。

本月文章预告

预告下,接下来我们会陆续发布转转在多端 SDK、移动端等基础架构和中台技术相关的实践与思考,欢迎大家关注公众号 “大转转 FE”,期望与大家多多交流

YFNr6bv.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK