Boloop 阅读示意图(AI 生成):合法长小数不应被任意精度限制拒绝。

2026 年 10 月 21 日,Boloop 的 GA4 概览可以显示,详细报表却被标成不可用。最直观的猜测是 Google 请求失败,但只读检查给出了另一组事实:同一批七个报表查询,全都返回 HTTP 200。

问题发生在响应回来之后。本站的小数解析器给小数部分设了最多九位的限制,刚好拒绝了 Google 返回的合法指标值。对外展示的“服务商不可用”,实际包住了一个本站解析错误。

不可用是结果分类还不是根因

后台需要在统计读取失败时保持可用,所以外部来源查询有降级处理:无法得到可信数据时,显示 unavailable,而不是伪造一组零。

这个行为本身有必要。麻烦在于,网络失败、响应结构异常和本地解析失败,最终都可能落入同一类显示状态。如果排查只停留在界面文案,就会把“这张卡片拿不到可用数据”误读为“Google 没有正常响应”。

当时先在正式后台复现明细不可用,再单独检查对应查询。概览与明细也不是同一次结果,概览正常不能替明细证明成功,明细失败也不应把已有概览一并改成零。

两个真实值找到了校验边界

返回的参与率和平均会话时长分别是:

engagementRate: 0.2857142857142857
averageSessionDuration: 39.193496142857143

它们的小数部分都超过九位。旧校验在数值转换前就拒绝了字符串,抛出 Unexpected GA4 engagement rate,随后外层将整份明细降级为不可用。

Google Data API 用字符串承载指标的 value,具体类型由指标头说明。收到字符串后做校验是合理的,但小数位数限制需要有业务或接口依据,不能把界面准备显示几位小数直接当作输入格式约束。Google MetricValue 文档

参与率在界面显示为 28.6%,也不意味着上游只能返回一位、两位或九位小数。显示精度应放在展示环节处理。

只移除错误限制保留真正的边界

修复没有改成“任何值都先转数字”。例如 JavaScript 的 Number('') 和 Number(' ') 都会得到零,直接转换会把缺失或脏数据变成看似有效的指标。

这次只去掉小数部分最多九位的限制。输入仍必须符合本站接受的非负十进制形式,转换后必须是有限数,并遵守数值上限;整数指标继续用自己的整数校验。响应大小、超时、权限和汇总一致性检查也保留。

共享解析器当时有两个调用方,正好对应参与率与平均会话时长。后台和日报都通过共享报表方法读取明细,因此应修在这一层,而不是分别给两处显示补一个特殊处理。

把失败值放进真实调用链的测试

原来的正常数据没有碰到这个边界。回归时,将上面两个原始失败值放进已有的完整明细测试数据,让自定义日期查询经过实际报表解析过程。

这个测试在旧解析器下失败,修复后通过。它不仅断言正则能匹配,还断言解析后的参与率与平均时长等于预期数值,避免只证明了一个脱离调用方的小函数。

还补了相反方向的检查:空串、纯空格、负数、NaN、Infinity、带尾随文字的数值,以及超过本站上限的值,都仍应让来源返回不可用。修复一个过严条件,不意味着其余校验都需要放松。

当次统计回归共 114 项通过,覆盖报表、日报、权限、日期范围及汇总不一致等路径。

上线后分别确认界面和下一封邮件

部署后,正式后台刷新出了参与率、平均时长、趋势和各类明细。一次成功查询的每日会话和浏览量之和,与汇总的 7 次会话、15 次浏览一致,说明结果还通过了已有的一致性检查。

检查期间也出现过另一次不可用。因此结论只应落在已证实的解析缺陷:这个合法长小数不再被本站错误拒绝,不能据此承诺所有上游请求永远成功。这次没有增加盲目重试,也没有通过延长超时掩盖解析问题。

邮件与后台共用明细逻辑,但网页恢复并不等于已经收到新邮件。部署当天保留下一次正常投递作为邮件验收点,没有为验证文案或数值重复发送日报。10 月 22 日的正常日报中,明细与新时间文案才取得实际收信证据。

以后再遇到“请求成功,页面却不可用”,我会按三个边界追查:HTTP 是否成功,响应是否符合接口结构,应用是否正确接受了合法值。先找出第一处拒绝,才知道该重试网络、修正数据,还是删除一条没有依据的限制。