PHP 返回雪花 ID,前端点编辑却 404:JSON 大整数精度丢失全记录
某后台管理系统列表页一切正常,点某行「编辑」按钮,后端返回 404「记录不存在」。刷新列表,那条记录又好端端地显示在那里。开 Network 面板一对比,发送编辑请求时携带的 ID 和列表接口返回的 ID……看起来一模一样,却差了几个数字。
这就是 IEEE 754 双精度浮点数在 JSON 里埋下的无声地雷。
现象
- 列表接口正常返回,每行都有 ID
- 点击编辑,弹窗调用详情接口,后端返回 404
- Network 对比:列表响应
"id": 2030718534015598593,详情请求 URL 里是...598600——末尾悄悄多了 7 - Console 没有任何报错,
JSON.parse()没有抛出异常,一切看起来正常
根因
Java 后端使用雪花算法生成 ID,典型值为 19 位整数,远超 JavaScript 安全整数上限:
Number.MAX_SAFE_INTEGER = 9007199254740991 // 16 位
雪花 ID = 2030718534015598593 // 19 位,超出 2^53-1
PHP json_encode() 会如实输出完整数字字面量,JSON 规范本身也没有精度限制。问题出在浏览器:JSON.parse() 把所有 JSON 数字都用 IEEE 754 double 表示,超出 2^53-1 的整数会被静默舍入。
完整链路:
DB bigint PHP json_encode() JS JSON.parse()
2030718534015598593 → {"id":2030718534015598593} → 2030718534015598592
↑ 差 1,且无任何错误提示
看到文档里有 JSON_BIGINT_AS_STRING 常量,以为加上就能解决:
json_encode($data, JSON_BIGINT_AS_STRING);实际上这个 flag 只影响 json_decode(),对 json_encode() 毫无作用。PHP 的 json_encode() 本来就会输出完整整数——精度丢失发生在浏览器端,不在 PHP 端。加了这个常量等于什么都没做。
正解
方案 A:PHP 后端统一转字符串(推荐)
在响应序列化的递归转换函数里,检测超出 JS 安全范围的整数并强转字符串:
// 递归转换 key/value 时,在值处理分支加:
if (is_int($value) && ($value > 9007199254740991 || $value < -9007199254740991)) {
$value = (string) $value;
}
$result[$newKey] = $value;ThinkPHP 项目通常放在 app/common/controller/Api.php 的 convertKeysToCamelRecursive() 方法里。改完后 JSON 输出变为:
{"id": "2030718534015598593"}字符串类型,JavaScript 原样保留,无精度丢失。一处修改,全部接口生效。
方案 B:前端用 json-bigint 库
import JSONBig from 'json-bigint'
const instance = axios.create({
transformResponse: [data => {
try { return JSONBig.parse(data) } catch (e) { return data }
}]
})优点:保留数值类型,可做算术。缺点:每个 axios 实例都要配;BigInt 值回传时还需手动 .toString()。
字符串 ID 在前端的常见用途全部安全:=== 比较、POST body 参数、URL 拼接、v-for :key、SQL 绑定参数。唯一不兼容的是直接做算术——如果有此需求,用 BigInt() 或 localeCompare 排序。
快速自查
在浏览器 Console 粘贴列表接口返回的一个 ID:
const id = 2030718534015598593
console.log(Number.isSafeInteger(id)) // false → 中招了
console.log(id === 2030718534015598593) // true → JS 自己都骗了自己如果 isSafeInteger 返回 false,且列表 ID 和详情请求 ID 在 Network 里肉眼看上去相同但数值不一致,基本可以确诊。
一句话外卖
只要后端有 Java 雪花 ID 流经 JSON → 浏览器的链路,就必须在序列化层把大整数转字符串,
JSON.parse()的静默截断不会给你任何报错。