Node\.js \+ MongoDB 金额存储与运算规范

Node.js + MongoDB 金额存储与运算规范

一、前言

JavaScript 原生 Number 为双精度浮点数,存在二进制小数精度丢失问题,直接浮点运算易出现数值偏差。 项目商品价格、订单金额、退款、手续费、财务对账等金额字段,禁止以小数浮点形式存储运算。 统一采用分为最小整数单位存储,规避精度问题,适配 Node.js+MongoDB+Mongoose 技术栈。

二、错误存储方式及隐患

2.1 错误示例

interface Order {
  price: number;       // 直接存储小数元,存在精度风险
  refundPrice: number;
  cost: number;
}

2.2 精度问题演示

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(9.9 * 100); // 989.9999999999999
console.log(1 - 0.9);   // 0.09999999999999998

2.3 业务隐患

  1. 金额运算后分位偏差,对账数据不平

  2. 退款、结算、统计汇总数值异常

  3. 存取数值失真,前后端展示不一致

  4. 支付回调、财务校验判定失败

三、统一存储方案:整数分单位存储

3.1 方案说明

摒弃小数存储,全部以作为最小存储单位,元转分乘以 100,分转元除以 100。 整数运算无精度损耗,查询、排序、聚合性能优异,兼容性最强,为电商财务通用标准方案。

3.2 Mongoose 模型定义

const mongoose = require('mongoose');

const orderSchema = new mongoose.Schema({
  price: Number,        // 订单实付价,单位:分
  refundPrice: Number,  // 退款金额,单位:分
  originalPrice: Number,// 商品原价,单位:分
  cost: Number,         // 成本价,单位:分
  serviceFee: Number    // 手续费,单位:分
}, { timestamps: true });

module.exports = mongoose.model('Order', orderSchema);

四、金额转换与运算规范

4.1 通用转换工具函数

/**
 * 元转分,入库使用
 * @param {number|string} yuan 元金额
 * @returns {number} 分整数
 */
const yuanToFen = (yuan) => Math.round(Number(yuan) * 100);

/**
 * 分转元,页面展示使用
 * @param {number} fen 分整数
 * @returns {string} 保留两位小数金额
 */
const fenToYuan = (fen) => (fen / 100).toFixed(2);

4.2 业务存取示例

// 新增订单,129.90元转为分入库
const createOrder = async () => {
  const priceFen = yuanToFen("129.90");
  const feeFen = yuanToFen("2.50");
  return await Order.create({
    price: priceFen,
    serviceFee: feeFen,
    refundPrice: 0
  });
};

// 查询订单并格式化展示
const getOrder = async () => {
  const order = await Order.findOne();
  const showPrice = fenToYuan(order.price);
  return { ...order._doc, showPrice };
};

4.3 金额运算规则

所有加减乘除直接使用整数运算,天然无精度误差

// 原价、手续费、退款整数运算
const origin = 12990;
const fee = 250;
const refund = 3000;
const finalAmount = origin - fee - refund;

五、数据库查询、排序、聚合规范

5.1 价格区间查询

// 查询50元~200元订单,换算分为单位5000、20000
const orderList = await Order.find({
  price: { $gte: 5000, $lte: 20000 }
});

5.2 价格排序

// 金额降序排列
const sortList = await Order.find().sort({ price: -1 });

5.3 金额求和聚合

// 统计全部订单总金额
const totalData = await Order.aggregate([
  {
    $group: {
      _id: null,
      totalPrice: { $sum: "$price" }
    }
  }
]);

六、前后端交互适配规则

  1. 后端统一以分整数入库存储,不存储浮点小数

  2. 接口出参金额统一转为保留两位小数字符串,前端直接渲染

  3. 接口入参接收元格式金额,后端校验后转换为分再入库

  4. 前后端禁止直接使用浮点数值做判断、计算、比对逻辑

七、项目避坑总结

  1. 财务金额严禁浮点小数存储,统一整数分单位存储

  2. JavaScript 安全整数范围满足业务所有金额场景,无溢出风险

  3. 全部运算依托整数计算,无需额外高精度第三方库

  4. 存取严格执行元分转换规则,统一项目编码标准

  5. 数据库查询、聚合直接使用整型字段,效率更高不易出错

(注:文档部分内容可能由 AI 生成)


本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。

萧兮的博客https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/019e4ece-a8ff-7cc0-97cb-e58a509fa9b1