fix(partner): 优化马豆汇率高精度自适应展示,防止微小汇率被截断四舍五入
这个提交包含在:
@@ -161,7 +161,7 @@ class PartnerClean extends Command
|
||||
$rateService = new \addons\shopro\service\partner\RateService();
|
||||
$resetRate = $rateService->currentRate();
|
||||
$resetN = $rateService->countN();
|
||||
$formattedRate = number_format($resetRate, 8, '.', '');
|
||||
$formattedRate = \addons\shopro\service\partner\IdentityService::formatRate($resetRate);
|
||||
|
||||
$output->writeln("\n========================================================");
|
||||
$output->writeln(" 重置后指标核对:");
|
||||
|
||||
@@ -274,7 +274,7 @@ class DailySettleService
|
||||
'order_count' => $orderCount,
|
||||
'order_count_n' => $orderCountN,
|
||||
'gmv' => number_format($gmv, 2, '.', ''),
|
||||
'rate' => number_format($rate, 8, '.', ''),
|
||||
'rate' => IdentityService::formatRate($rate),
|
||||
'madow' => [
|
||||
'pool' => IdentityService::formatMadow($madowPool),
|
||||
'user_count' => $tasterCount,
|
||||
|
||||
@@ -97,7 +97,14 @@ class IdentityService
|
||||
if ($value === null || $value === '') {
|
||||
$value = PartnerConfig::get('partner_madow_base_rate', '0.00000075');
|
||||
}
|
||||
return number_format((float) $value, 8, '.', '');
|
||||
$floatVal = (float) $value;
|
||||
if ($floatVal <= 0) {
|
||||
return '0';
|
||||
}
|
||||
// 高精度十进制展开,防止科学计数法 (如 7.15E-7) 并避免微小汇率被 8 位截断四舍五入
|
||||
$formatted = number_format($floatVal, 12, '.', '');
|
||||
$trimmed = rtrim(rtrim($formatted, '0'), '.');
|
||||
return $trimmed !== '' ? $trimmed : '0';
|
||||
}
|
||||
|
||||
public static function attachAgent($agent, $rate = null)
|
||||
|
||||
@@ -46,7 +46,7 @@ class RateService
|
||||
// 关键防坑:PHP 8.0+ 下小于 0.0001 的浮点数转为 string 时会自动变成科学计数法 (如 "7.5E-7")
|
||||
// 传给 bcmul() 会直接抛出 Fatal error: Argument #1 ($num1) is not well-formed
|
||||
// 必须使用 number_format 格式化为无科学计数法的高精度十进制字符串
|
||||
$baseStr = number_format($base, 10, '.', '');
|
||||
$baseStr = number_format($base, 12, '.', '');
|
||||
|
||||
$step = intval(PartnerConfig::get('partner_madow_step_orders', 1));
|
||||
$step = $step > 0 ? $step : 1;
|
||||
@@ -54,7 +54,7 @@ class RateService
|
||||
$multipleStr = number_format($multiple, 0, '.', '');
|
||||
|
||||
if (function_exists('bcmul')) {
|
||||
return (float) bcmul($baseStr, $multipleStr, 10);
|
||||
return (float) bcmul($baseStr, $multipleStr, 12);
|
||||
}
|
||||
return (float) ($base * $multiple);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(condition, testName, detail = '') {
|
||||
if (condition) {
|
||||
console.log(` [PASS] ${testName}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.error(` [FAIL] ${testName} - ${detail}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("========================================================");
|
||||
console.log(" 马豆汇率高精度(不四舍五入)全链路自适应规范自动化测试");
|
||||
console.log("========================================================\n");
|
||||
|
||||
// 1. 验证 IdentityService.php 的 formatRate 改造
|
||||
const identityPath = path.resolve(__dirname, '../addons/shopro/service/partner/IdentityService.php');
|
||||
assert(fs.existsSync(identityPath), 'IdentityService.php 存在');
|
||||
const identityContent = fs.readFileSync(identityPath, 'utf-8');
|
||||
|
||||
assert(
|
||||
!identityContent.includes("number_format((float) $value, 8, '.', '')"),
|
||||
'formatRate() 已废除死锁的 8 位截断四舍五入 number_format(..., 8)'
|
||||
);
|
||||
assert(
|
||||
identityContent.includes("number_format($floatVal, 12, '.', '')"),
|
||||
'formatRate() 升级为 12 位高精度展开,杜绝科学计数法并容纳 9~12 位微小汇率'
|
||||
);
|
||||
assert(
|
||||
identityContent.includes("rtrim(rtrim($formatted, '0'), '.')"),
|
||||
'formatRate() 具备末尾无效 0 动态裁切能力,实现原值精准输出'
|
||||
);
|
||||
|
||||
// 2. 验证 RateService.php 的 calcRate 计算精度
|
||||
const rateServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/RateService.php');
|
||||
assert(fs.existsSync(rateServicePath), 'RateService.php 存在');
|
||||
const rateServiceContent = fs.readFileSync(rateServicePath, 'utf-8');
|
||||
|
||||
assert(
|
||||
rateServiceContent.includes("number_format($base, 12, '.', '')"),
|
||||
'calcRate() 展开 baseStr 使用 12 位高精度,防止乘法前被截断'
|
||||
);
|
||||
assert(
|
||||
rateServiceContent.includes("bcmul($baseStr, $multipleStr, 12)"),
|
||||
'calcRate() 使用 bcmul 保留 12 位高精度安全计算'
|
||||
);
|
||||
|
||||
// 3. 验证 DailySettleService 与 PartnerClean 统一调用
|
||||
const dailySettlePath = path.resolve(__dirname, '../addons/shopro/service/partner/DailySettleService.php');
|
||||
const dailySettleContent = fs.readFileSync(dailySettlePath, 'utf-8');
|
||||
assert(
|
||||
dailySettleContent.includes("'rate' => IdentityService::formatRate($rate)"),
|
||||
'DailySettleService 试算预览统一使用 IdentityService::formatRate'
|
||||
);
|
||||
|
||||
const cleanCmdPath = path.resolve(__dirname, '../addons/shopro/console/PartnerClean.php');
|
||||
const cleanCmdContent = fs.readFileSync(cleanCmdPath, 'utf-8');
|
||||
assert(
|
||||
cleanCmdContent.includes("IdentityService::formatRate($resetRate)"),
|
||||
'PartnerClean 重置提示统一使用 IdentityService::formatRate'
|
||||
);
|
||||
|
||||
// 4. 精确模拟 PHP formatRate 算法逻辑测试
|
||||
function formatRateSim(value) {
|
||||
if (value === null || value === '' || value === undefined) {
|
||||
value = '0.00000075';
|
||||
}
|
||||
const floatVal = parseFloat(value);
|
||||
if (isNaN(floatVal) || floatVal <= 0) {
|
||||
return '0';
|
||||
}
|
||||
// 模拟 number_format($floatVal, 12, '.', '')
|
||||
const formatted = floatVal.toFixed(12);
|
||||
// 模拟 rtrim(rtrim($formatted, '0'), '.')
|
||||
const trimmed = formatted.replace(/\.?0+$/, '');
|
||||
return trimmed !== '' ? trimmed : '0';
|
||||
}
|
||||
|
||||
// 针对用户实际痛点:设置 0.000000715 时不可四舍五入为 0.00000072
|
||||
assert(
|
||||
formatRateSim('0.000000715') === '0.000000715',
|
||||
'汇率 0.000000715(9位小数)精准输出为 0.000000715,不发生四舍五入'
|
||||
);
|
||||
assert(
|
||||
formatRateSim('0.000000715') !== '0.00000072',
|
||||
'汇率 0.000000715 绝对不能被四舍五入成 0.00000072'
|
||||
);
|
||||
assert(
|
||||
formatRateSim('0.00000075') === '0.00000075',
|
||||
'默认汇率 0.00000075(8位小数)保持为 0.00000075'
|
||||
);
|
||||
assert(
|
||||
formatRateSim('0.0000007153') === '0.0000007153',
|
||||
'高精度汇率 0.0000007153(10位小数)精准输出为 0.0000007153'
|
||||
);
|
||||
assert(
|
||||
formatRateSim(0.000000715 * 2) === '0.00000143',
|
||||
'翻倍汇率 0.000000715 * 2 = 0.00000143 精确输出,不被截断'
|
||||
);
|
||||
assert(
|
||||
formatRateSim('0') === '0',
|
||||
'0 值正确输出为 0'
|
||||
);
|
||||
assert(
|
||||
formatRateSim(null) === '0.00000075',
|
||||
'null 兜底输出基准汇率 0.00000075'
|
||||
);
|
||||
|
||||
console.log("\n--------------------------------------------------------");
|
||||
console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`);
|
||||
console.log("========================================================\n");
|
||||
|
||||
if (failed > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户