经常出现例如用户充值 1 元到账 0.994 元这类情况,导致用户余额无法购买商品
以下是充值部分业务逻辑代码:
$paidCredit = checkStringSign($invoice->credit);
if (isset($user->Credits) && $user->Credits != "") {
$currentCredit = checkStringSign($user->Credits);
}
$currentCredit = $currentCredit + $paidCredit;
$updatedCredit = signString($currentCredit);
$updatePricePlanId = "";
if ($paidCredit > 99.5 && $user->RiskyScore < 1) {
$updatePricePlanId = ",RiskyScore=0";
}
$query = "UPDATE users SET Credits = '$updatedCredit' $updatePricePlanId WHERE UserId = '$user->UserId'";
$objDBCD14->execute($query);
$comments = $out_trade_no;
$updateQuery = "update payments set Paid=1,TransactionId='$trade_no' WHERE PaymentId = '$out_trade_no' ";
$objDBCD14->execute($updateQuery);
$paymentId = $payments->PaymentId;
$objDBCD14->execute("INSERT INTO topUpRecords SET UserId ='$user->UserId', Credits = '$paidCredit', CreditsLeft = '$updatedCredit', Comments = '$comments'");
$invoice
中记录的金额是正确的
topUpRecords
中记录的金额就变成 0.994 了
但又不是 100%复现,排查了一晚上还没找出问题
checkStringSign 和 signString 接受的参数都是字符串,signString 内部逻辑是把字符串通过简单变换签名后将签名用 .{sign}
的格式附加在末尾,checkStringSign 内部逻辑是根据最后一个.分隔原文和签名,验签成功则返回原文,否则抛出错误,其中都不含显式的 cast 逻辑。
不熟悉 PHP ,之前写这个程序的人已经离职了,临时翻文档学的
1
hobbyliu 2022-12-04 07:45:33 +08:00
```
if (isset($user->Credits) && $user->Credits != "") { $currentCredit = checkStringSign($user->Credits); } ``` 怀疑是这段逻辑的问题,所以才会不稳定复现,打个日志看看呗。 |
2
edis0n0 OP @hobbyliu #1 之前考虑过是这里的问题,但$currentCredit 应该影响不到变量$paidCredit 呀,topUpRecords 里的 Credits 也变成 0.994 了
|
3
momocha 2022-12-04 07:53:59 +08:00 via iPhone
把货币*100 换成整数避免浮点数在存储和运算过程中丢失精度
|
5
fzlqr091314 2022-12-04 08:13:56 +08:00 via iPhone
用 bcmath
|
6
edis0n0 OP @fzlqr091314 #5 问题是我不确定这个小一点的问题是在哪一步产生的
|
7
ysc3839 2022-12-04 08:15:17 +08:00 via Android
直接字符串拼接,不怕 SQL 注入的吗?
|
9
eason1874 2022-12-04 08:23:22 +08:00
这段代码看不出来啥,这段代码唯一动了 $paidCredit 的是 checkStringSign ,而这个函数又没贴出来
我的排查方法是先看 SQL 日志,确定 PHP 提交的 SQL 里的值是 0.994 ,先排除掉是 MySQL 把 1 变成 0.994 的可能,然后看 $paidCredit ,再看 $invoice->credit |
10
edis0n0 OP @eason1874 #9 查看了 MySQL 日志,确定提交的是 0.994 ,invoice 表里存的是正确的签名后的整数字符串 1 ,弄了一个单独的 PHP 文件 var_dump $paidCredit ,刷新了几次都能正确输出 1
|
11
eason1874 2022-12-04 09:14:53 +08:00
@edis0n0 不好复现的话就先插个眼,等复现了再复盘吧。在 $paidCredit 后,提交 SQL 前,加个判断,发现对不上就阻止提交,提示用户重试,在日志记下所有变量用来复盘
|
12
rekulas 2022-12-04 09:37:10 +08:00
定位到这一句
$objDBCD14->execute("INSERT INTO topUpRecords SET UserId ='$user->UserId', Credits = '$paidCredit', CreditsLeft = '$updatedCredit', Comments = '$comments'"); 将 sql 打印出来,如果金额不对就是上面语句的问题 如果正确继续定位到 db 库提交 sql 到数据库之前,打印 sql 出来看是否正确,如果还正确只能怀疑数据库加了什么机制了 |
13
ydpro 2022-12-04 09:42:53 +08:00
这段代码中确实存在一个 bug 。首先,在检查 $invoice 对象的签名时,应该将金额转换为数字类型,而不是字符串类型。
其次,在计算新的积分值时,应该将新支付的积分转换为数字类型,然后再进行加法运算,而不是直接将字符串拼接在一起。 修改后的代码应该如下所示: |
14
ydpro 2022-12-04 09:43:48 +08:00 1
$paidCredit = checkStringSign($invoice->credit);
$paidCredit = (float)$paidCredit; if (isset($user->Credits) && $user->Credits != "") { $currentCredit = (float)$user->Credits; } $currentCredit = $currentCredit + $paidCredit; $updatedCredit = signString($currentCredit); $updatePricePlanId = ""; if ($paidCredit > 99.5 && $user->RiskyScore < 1) { $updatePricePlanId = ",RiskyScore=0"; } $query = "UPDATE users SET Credits = '$updatedCredit' $updatePricePlanId WHERE UserId = '$user->UserId'"; $objDBCD14->execute($query); $comments = $out_trade_no; $updateQuery = "update payments set Paid=1,TransactionId='$trade_no' WHERE PaymentId = '$out_trade_no' "; $objDBCD14->execute($updateQuery); $paymentId = $payments->PaymentId; $objDBCD14->execute("INSERT INTO topUpRecords SET UserId ='$user->UserId', Credits = '$paidCredit', CreditsLeft = '$updatedCredit', Comments = '$comments'"); 回答来自:From chatgpt |
16
T0m008 2022-12-04 11:06:00 +08:00
`$paidCredit = checkStringSign($invoice->credit);`
只能是这个 function, 这段里面唯一修改$paidCredit 的 |
17
vacker 2022-12-04 17:28:58 +08:00 via iPhone
先不说代码,你这金额好像扣了手续费后的金额
|
18
edis0n0 OP @vacker #17 卧槽真的是这个原因,这个函数还 include 了一个后扣手续费的 PHP 文件 sendEmailNotice.php ,直接改余额和已产生的充值记录,单看文件名完全想不到还做了这事,那里面查询写的有问题经常找不到充值记录所以没扣手续费,不知道多少年了一直有这个问题,都是财务手工加回去的
|
19
edis0n0 OP @edis0n0 #18 查询写的有问题:它只查询有绑定邮箱的用户,没绑定邮箱就找不到用户,没扣手续费,坑死了谁想得到能写成这样
|
20
Rache1 2022-12-04 18:50:58 +08:00
$objDBCD14 ,哈哈,这前面不会还有 $objDBCD1 到 $objDBCD13 吧 😂
|
21
proxychains 2022-12-05 09:14:43 +08:00
@msg7086 这是 AI?
|
22
msg7086 2022-12-05 09:49:44 +08:00
@proxychains 是啊,AI 帮你抓 Bug 。
|
23
lisxour 2022-12-05 10:37:39 +08:00
代码都不用看就知道不是浮点数精度问题,一个正常的语言,怎么可能 3 位小数就开始有精度问题。。。至少都是好几位以上才有这种问题
|
24
Twnysta 2022-12-05 11:00:57 +08:00
php 有专门的 bc 函数啊,算账都是用这个。直接对比不是自己跟自己不痛快吗?
|
26
proxychains 2022-12-06 13:53:59 +08:00
@msg7086 已经通过图灵测试了...语气像是客服,有些地方感觉不对劲, 但找不到哪地方不对劲...
|