一个 ID 溢出引发的线上资损

你给某支付平台做「交易流水导出」功能。

需求很直接:把数据库里 bigint(20) 的订单 ID 渲染到表格里。

你顺手写了这么一行:

js 复制代码
// ❌ 线上事故代码
const row = `<tr><td>${order.id}</td><td>${order.amount}</td></tr>`;

结果上线第二天,财务发现:

"有笔 1.8e+17 的订单,点进去详情金额对不上!"

排查发现:

  • 数据库 ID 是 18012345678901234567
  • 但 JS 里 Number(order.id) 变成了 18012345678901234000 ------ 尾部 567 直接丢了
  • 因为它超过了 Number.MAX_SAFE_INTEGER(9007199254740991),JS 的 64 位浮点数精度崩了。

这可不是显示问题,而是 ID 错位导致查串了订单,差点引发资损。


解决方案:三层防御,把大数关进"安全笼"

1. 表面用法:用 BigInt 代替 Number

js 复制代码
// ✅ 正确处理大数
const bigId = BigInt("18012345678901234567"); // 🔍 字符串转 BigInt
console.log(bigId.toString()); // "18012345678901234567"

// 用于计算
const nextId = bigId + 1n; // 🔍 必须加后缀 n

关键点:

  • 必须用字符串初始化BigInt(18012345678901234567) 会先被转成 Number 再转 BigInt,已经丢精度了;
  • 运算时操作数必须都是 BigInt,不能和 Number 混算;
  • 比较可以用 ===,但 == 会自动转换,有坑。

2. 底层机制:为什么 JS 数字会"失精"?

类型 存储方式 范围 精度
Number IEEE 754 双精度浮点 ±1.79e+308 53 位有效数字
BigInt 任意长度整数 无上限 完全精确

原理图(文字版):

flowchart LR A["Number: [1位符号][11位指数][52位尾数]"] --> B["实际精度 2^53 - 1 = 9007199254740991"] B --> C["超过这个值,尾数不够用,低位被舍入"]

所以 9007199254740992 === 9007199254740993 在 JS 里居然是 true

3. 设计哲学:从"传输"到"渲染"全链路防溢出

(1)接口层:后端传字符串,前端不碰大数

json 复制代码
{
  "order_id": "18012345678901234567",  // 🔍 ID 用字符串
  "amount": 123456789,                 // 数值小,可用 Number
  "user_id": "18012345678901234568"
}

(2)状态层:用 BigInt 做计算,但不存进 Redux

js 复制代码
// calc.js
export function addId(idStr, offset) {
  const id = BigInt(idStr);
  return (id + BigInt(offset)).toString(); // 🔍 计算完转回字符串
}

(3)渲染层:永远用字符串插值

js 复制代码
// ✅ 安全渲染
const row = `<tr data-id="${order.id}">  // 🔍 直接用字符串,不转 Number
  <td>${order.id}</td>
</tr>`;

应用扩展:可复用的配置片段

1. Axios 自动转换大数字段

js 复制代码
// axios.interceptor.js
axios.defaults.transformResponse = [
  (data, headers) => {
    if (headers['content-type']?.includes('json')) {
      return JSON.parse(data, (key, value) => {
        // 🔍 指定字段转 BigInt
        if (['order_id', 'user_id'].includes(key) && /^\d{16,}$/.test(value)) {
          return value; // 🔍 保持字符串,由业务层决定是否转 BigInt
        }
        return value;
      });
    }
    return data;
  }
];

2. 环境适配说明

场景 注意点
IE 浏览器 BigInt 不支持,需降级用 string + bignumber.js
TypeScript 类型定义用 bigintstring,别用 number
JSON 序列化 BigInt 不能直接 JSON.stringify(),需自定义 toJSON

举一反三:3 个变体场景

  1. 金融计算(高精度小数)
    BigInt 模拟定点数:123.45 存为 12345n(单位:分),运算后再除 100
  2. 数据库主键生成(Snowflake ID)
    前端生成 ID 时用 BigInt 拼接时间戳、机器码、序列号,避免重复;
  3. 区块链地址校验
    以太坊地址是 256 位整数,用 BigInt 做范围校验和签名计算。

小结

别让 Number 碰超过 16 位的数字。

传用字符串,算用 BigInt,渲染不转 Number,三招封死精度陷阱。

相关推荐
崔庆才丨静觅20 分钟前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment23 分钟前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅37 分钟前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊40 分钟前
jwt介绍
前端
爱敲代码的小鱼1 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte1 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
NEXT061 小时前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法
剪刀石头布啊2 小时前
生成随机数,Math.random的使用
前端
剪刀石头布啊2 小时前
css外边距重叠问题
前端
剪刀石头布啊2 小时前
chrome单页签内存分配上限问题,怎么解决
前端