学会了这招,排查生产问题再也不头疼了
背景
上周遇到一个生产问题,有一个页面底部有个分享按钮,在生产环境不显示。而同样的代码,在测试环境却是正常的。 如下图所示,左边是生产的,右边是测试环境的。


这是为什么呢? 心中带着困惑,开启了破案模式。
初步确定嫌疑犯
先确定一下是不是定位样式引起的问题,之前发生过在IOS12版本手机上,fixed定位显示有问题,导致元素在页面不展示。对比查看了一下测试环境和生产环境渲染之后的页面dom元素,发现生产环境页面底部的分享按钮不显示,是因为dom元素未满足渲染。而非定位样式问题。嫌疑犯已经基本确定。


通过查看源代码,我们猜测一下嫌疑犯的作案过程。相关的代码片段如下:
<ActivityFooter
v-if="isShowFooter"
:staffId="staffId"
:activityFooterInfo="state.bannerImageInfo"
@share="shareShow"
/>
isShowFooter
控制底部按钮的显示隐藏,跟isShowFooter
相关的变量有三个,分别是state.isChat
,state.bannerDetail?.shareSidebar
, state.bannerImageInfo?.id
。这三个变量初始化之后,会发生改变的代码片段如下:
const state = reactive({
bannerDetail: {} as IBannerDetail,
bannerImageInfo: {} as IBannerImageInfo,
isChat: false, // 侧边栏进入
});
const isShowFooter = computed(() => {
return !(state.isChat && !state.bannerDetail?.shareSidebar) && state.bannerImageInfo?.id;
});
state.isChat的值改变逻辑是:
wx.invoke('getContext', {}, (res: TRes) => {
console.log('%c res:', 'color: #0e93e0;background: #aaefe5;', res);
if (res.err_msg === 'getContext:ok') {
const entries = ['single_chat_tools', 'group_chat_tools', 'chat_attachment'];
state.isChat = entries.includes(res?.entry);
} else {
// 错误处理
console.log('getContext error', res);
}
});
在控制台打印输出res?.entry
,可以看到取值是normal,那么state.isChat
的最终值应该是false
。
state.bannerDetail的值改变逻辑是:
const detailRes = await bannerApi.getBannerDetails({ longId: bannerId, staffId: staffId });
state.bannerDetail = detailRes.retdata?.detail;
抓包查看接口的响应数据是:
那么state.bannerDetail?.shareSidebar
的值应该是true
。
state.bannerImageInfo的值改变逻辑是:
const bannerImageInfoRes = await bannerApi.getBannerInfo({ bannerId: bannerId, staffId: staffId });
state.bannerImageInfo = { ...bannerImageInfoRes.retdata };
抓包查看接口的响应数据是:
所以state.bannerImageInfo?.id
的值是44
。
因为isShowFooter
的计算表达式为:
const isShowFooter = computed(() => {
return !(state.isChat && !state.bannerDetail?.shareSidebar) && state.bannerImageInfo?.id;
});
这么计算下来,isShowFooter = !(state.isChat && !state.bannerDetail?.shareSidebar) && state.bannerImageInfo?.id = !(false && false) && 44 = true
,应该显示底部分享按钮才对,可是为什么不显示呢。这是一个令人感到困惑的地方,一时间破案陷入僵局。没有了思路。
打破僵局
有一句说的好,只要思想不滑坡,方法总比问题多。在网上不断搜索调试方法,不断尝试,功夫不负有心人,终于找到了救星。这个救星就是Chrome浏览器自带的文件替换功能。下面我们来看看如何使用这个神器,破解僵局。
第一步 查找调试文件
如何找到要调试的在线业务文件呢? 从Chrome开发调试工具的网络面板中可以看到,这个页面大约有20多个脚本文件请求,而大部分根据名称就能排除掉。比如像sdkMsgXXX.js
,polyfillXXX.js
,vue.XXX.js
,vant.XXX,js
等。真正需要查看文件内容的,也就是其中3个以index.XXX.js
命名的文件。然后依次点击这三个index.XXX.js
文件, 按下CTRL+F
搜索快捷键,输入上下文关联变量isChat
,定位出要调试的业务文件是index.58a9f303.js
。
第二步 启用文件替换功能
在本地创建一个文件夹,名称随意。这里命名为assets。然后切换到Chrome开发调试工具的源代码面板,点击下方的替换==>选择放置替换项的文件夹==>选择刚才创建的assets文件夹
,接着浏览器会弹出一行文字,询问是否给予浏览器完整访问本地文件夹的权限,选择允许。
接着点击启用本地文件
切换到浏览器开发调试工具的网络面板,找到刚才定位到的文件,鼠标右键,点击保存并覆盖。之后刷新页面,浏览器每次就会读取本地的文件而不是线上的文件,改本地文件调试代码是令程序员感到很舒服的事情。这个功能极大了方便了调试在线问题。
第三步 修改代码调试
上一步保存到本地的在线文件,是个混淆压缩文件, 可读性比较差。我们可以在VSCode中打开这个文件,对这个代码进行美化,然后添加调试打印语句
刷新页面,可以看到,空值台输出了isShowFooter相关的三个响应式变量的取值。
令人诧异的是,这条日志应该打印两次,而实际上只执行了一次。从打印出来的值来看,就是页面初始化的时候执行了一次,后面接口数据返回后,被没有再次执行,这是为什么呢?
第四步 破案
我思考了良久,综合查看的页面的dom结构,接口数据,打印日志,认为最有可能出错的地方,就是isShowFooter
计算表达式那里。
const isShowFooter = computed(() => {
return !(state.isChat && !state.bannerDetail?.shareSidebar) && state.bannerImageInfo?.id;
});
查看了一下压缩文件,结合源代码推算出混淆压缩文件中的st
代表vue的computed
关键字, 分别查看一下逻辑正确的测试环境和有问题的生产环境,这个st
是怎么来的。
测试环境,st
是从./vue.183497e1.js
中导出来的。
import {
u as Y,
w as U,
_ as $,
C as M,
b as H,
o as kt,
H as At,
y as $t
} from "./index.be88cf51.js";
import {
j as T,
S as lt,
J as Tt,
K as s,
P as m,
Q as o,
u as t,
_ as D,
L as b,
M as C,
t as B,
a as O,
b as dt,
k as f,
a0 as W,
a1 as X,
H as G,
O as g,
$ as z,
r as N,
Y as xt,
F as St,
U as Lt,
V as st,
} from "./vue.183497e1.js";
生产环境,st
是从index.c3315d0c.js
导出的。
import {
u as X,
w as U,
_ as $,
C as M,
b as H,
o as kt,
aR as st,
H as At,
y as $t
} from "./index.c3315d0c.js";
import {
j as T,
S as lt,
J as Tt,
K as s,
P as m,
Q as o,
u as t,
Z as D,
L as b,
M as C,
t as B,
a as O,
b as dt,
k as f,
$ as W,
a0 as Y,
H as G,
O as g,
_ as z,
r as N,
X as xt,
F as St,
U as Lt,
} from "./vue.85dd2d76.js";
是不是这个st
导出文件不同,引起的生产问题?验证一下。将生产环境中的st
导出方式,修改为和测试环境一样,从./vue.85dd2d76.js
导出st
。
import {
// ...
V as st
} from "./vue.85dd2d76.js";
将从./index.c3315d0c.js
导出的st
变量删除
import {
// ...
// 删除这句
aR as st,
} from "./index.c3315d0c.js";
保存文件,Ctrl+R
刷新页面,可以看到,isShowFooter
的运行逻辑正常了,分享按钮也显示出来了。
至此,本案告破。
最后
Chrome开发工具还有这么好用的功能,以前也看到过,泛泛而学。等真的遇到问题需要使用的时候,根本想不起来。所以我感觉,学习知识要带着应用场景去学,你怎么用,就怎么记忆,学习要学到可以应用的程度。否则可能会产生虚假的收获感和充实感,以为自己把时间都用在了该用的地方,当时也感觉学到了不少知识。然而实践是检验真理的唯一标准,等需要用的时候,如果联想不起来,学过就等于白学。实践不会陪你演戏。
转载自:https://juejin.cn/post/7255939146375299133