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 业务隐患
-
金额运算后分位偏差,对账数据不平
-
退款、结算、统计汇总数值异常
-
存取数值失真,前后端展示不一致
-
支付回调、财务校验判定失败
三、统一存储方案:整数分单位存储
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" }
}
}
]);
六、前后端交互适配规则
-
后端统一以分整数入库存储,不存储浮点小数
-
接口出参金额统一转为保留两位小数字符串,前端直接渲染
-
接口入参接收元格式金额,后端校验后转换为分再入库
-
前后端禁止直接使用浮点数值做判断、计算、比对逻辑
七、项目避坑总结
-
财务金额严禁浮点小数存储,统一整数分单位存储
-
JavaScript 安全整数范围满足业务所有金额场景,无溢出风险
-
全部运算依托整数计算,无需额外高精度第三方库
-
存取严格执行元分转换规则,统一项目编码标准
-
数据库查询、聚合直接使用整型字段,效率更高不易出错
(注:文档部分内容可能由 AI 生成)
本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。
萧兮的博客:https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/019e4ece-a8ff-7cc0-97cb-e58a509fa9b1