没有源代码怎么修复紧急bug
本周遇到一个线上紧急问题,在改不了源代码的情况下,修复了问题。
背景
最近几年碰到了不少这样的情况:
- 许多软件项目在规模、复杂性和人员流动等方面面临挑战,导致项目失控。
- 原先开发团队难以响应。
这次遇到的情况是后者。
当接到紧急电话,需要尽快修复,尽管获取到了代码,但不能确定是不是最新的,不过前后端项目中引用了内部封装的关键包,依赖包是缺失的。 整改方案得从长计议。 所以在这么紧急的时刻是动不了源码的,必须考虑其他解决方案以快速修复线上问题。
查找问题
- 业务反馈登录失败,显示验证码与手机号不匹配的错误。
- 通过堡垒机登录服务器,检查了代码配置和数据库,猜想验证码可能是缓存的,使用redis-cli仿照key,value格式生成验证码直接模拟登录。
- 使用Chrome的开发工具发现,登录接口返回正常,但后续的一个查询接口出现错误,猜测前端代码存在登录状态逻辑错误。
- 检查前端的React代码,查看错误提示部分,审查报错接口的数据库表以及后端Go语言代码的业务逻辑。了解到这个bug在上线后并未被暴露出来,因为查询接口的后端逻辑不完整,而前端链接也指向一个不存在的地址。
应对方案
- 尽管无法修改前端代码,但可以编辑已编译的静态文件。
- go语言编译生成的是机器码,这种情况下反编译也没啥用。而服务器上也没有Go环境,可执行的ELF文件是编译好再传上来的,获取不到依赖包。后端可以先饶过了,在nginx中把报错的链接地址指向一个新的服务,当然这个服务是需要我们把业务逻辑补充好的。
- 针对涉及的5张数据库表,使用DDL生成实体,配置链式引用以保持请求、响应和查询参数的一致性,并完善业务逻辑,登录验证这些就先不管了。然后安装JDK环境,在Nginx配置导航路由,使用curl验证接口。运气还不错,一遍过。
- 对于前端编译后的静态文件,使用正则过滤找到相应的JS文件,首先创建备份,然后使用Vim正则匹配到相应行并直接修改逻辑。
在确认登录服务已恢复后,通知业务方。
后续处理
前端
尽管框架使用React,但在某些关键方面,如Router、Storage、Request和DVA,都使用了自定义封装,依赖包给不到。
- 如果要改成标准方法,将涉及大规模语法和代码结构更改,成本相对较高。
- 如果把封装的方法都实现一遍,成本高且没有实际价值。
然而,在Chrome 开发工具下,可以查看webpack模块,其中包含了某些经过webpack处理后的JS文件,但需要猜测这些JS文件的require引用。然后,补充package.json文件以猜测依赖包的版本号,重复这些步骤并集成到项目中,直到项目运行正常。此时,前端部分可以修改代码。
后端
再看后端,使用Go语言的Gin框架和GORM。与前端类似,关键部分都进行了自定义封装,无法获得依赖包。
总体代码结构看起来不算复杂,自定义封装主要涉及两个方面:
- 封装一些类似Ruby on Rails框架的特性。
- 类似OpenAPI的规约,生成前后端的类型约束和胶水代码。
虽然业务逻辑不复杂,涉及40多张表和200多个业务接口,但直接改造也有以下几种方案:
- 把通用方法的封装实现一遍。
- 剥离封装那一层,调整controller和service的代码结构。
- 使用其他框架重构业务逻辑,路由逐步替代,分批上线。
前两种方案都没有实际意义,未来也不会在其他项目中使用这些改造来编写业务代码。因此,最终选择了第三种方案,可以分批替代,有过渡期,相对较平滑。
大框架优势
后端小众轻量级框架,去实现一些良好的架构和特性,其实是比较吃力的,要看大量的源码实现,一层层重构和二开。以前做过不少这样的事,深有体会。
但是像Spring这样的大框架向下兼容更加轻松。Gin Web框架、自定义封装以及一些个性化写法可以迅速适应,而Spring中实现的方法非常丰富,速度也很快。 剩下的业务接口可以参照实现即可。
结尾
有些事情本来是简单的,多方面因素导致复杂度增加。