PHP 写的老屎山代码经常出现字符串转浮点数时小一点点的情况,排查了一晚上还没解决,请 v 友帮忙看看 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
edis0n0
V2EX    PHP

PHP 写的老屎山代码经常出现字符串转浮点数时小一点点的情况,排查了一晚上还没解决,请 v 友帮忙看看

  •  
  •   edis0n0 2022-12-04 06:49:20 +08:00 3452 次点击
    这是一个创建于 1042 天前的主题,其中的信息可能已经有所发展或是发生改变。

    经常出现例如用户充值 1 元到账 0.994 元这类情况,导致用户余额无法购买商品

    以下是充值部分业务逻辑代码:

    $paidCredit = checkStringSign($invoic->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 ,之前写这个程序的人已经离职了,临时翻文档学的

    26 条回复    2022-12-06 13:53:59 +08:00
    hobbyliu
        1
    hobbyliu  
       2022-12-04 07:45:33 +08:00
    ```
    if (isset($user->Credits) && $user->Credits != "") {
    $currentCredit = checkStringSign($user->Credits);
    }

    ```
    怀疑是这段逻辑的问题,所以才会不稳定复现,打个日志看看呗。
    edis0n0
        2
    edis0n0  
    OP
       2022-12-04 07:47:48 +08:00
    @hobbyliu #1 之前考虑过是这里的问题,但$currentCredit 应该影响不到变量$paidCredit 呀,topUpRecords 里的 Credits 也变成 0.994 了
    momocha
        3
    momocha  
       2022-12-04 07:53:59 +08:00 via iPhone
    把货币*100 换成整数避免浮点数在存储和运算过程中丢失精度
    edis0n0
        4
    edis0n0  
    OP
       2022-12-04 07:55:20 +08:00
    @momocha #3 这套屎山代码修修补补已经运行 13 年,谁敢搞这么大改动
    fzlqr091314
        5
    fzlqr091314  
       2022-12-04 08:13:56 +08:00 via iPhone
    用 bcmath
    edis0n0
        6
    edis0n0  
    OP
       2022-12-04 08:14:52 +08:00
    @fzlqr091314 #5 问题是我不确定这个小一点的问题是在哪一步产生的
    ysc3839
        7
    ysc3839  
       2022-12-04 08:15:17 +08:00 via Android
    直接字符串拼接,不怕 SQL 注入的吗?
    edis0n0
        8
    edis0n0  
    OP
       2022-12-04 08:19:07 +08:00
    @ysc3839 #7 反正不是我写的,没提我肯定不敢改
    eason1874
        9
    eason1874  
       2022-12-04 08:23:22 +08:00
    这段代码看不出来啥,这段代码唯一动了 $paidCredit 的是 checkStringSign ,而这个函数又没贴出来

    我的排查方法是先看 SQL 日志,确定 PHP 提交的 SQL 里的值是 0.994 ,先排除掉是 MySQL 把 1 变成 0.994 的可能,然后看 $paidCredit ,再看 $invoice->credit
    edis0n0
        10
    edis0n0  
    OP
       2022-12-04 08:47:19 +08:00
    @eason1874 #9 查看了 MySQL 日志,确定提交的是 0.994 ,invoice 表里存的是正确的签名后的整数字符串 1 ,弄了一个单独的 PHP 文件 var_dump $paidCredit ,刷新了几次都能正确输出 1
    eason1874
        11
    eason1874  
       2022-12-04 09:14:53 +08:00
    @edis0n0 不好复现的话就先插个眼,等复现了再复盘吧。在 $paidCredit 后,提交 SQL 前,加个判断,发现对不上就阻止提交,提示用户重试,在日志记下所有变量用来复盘
    rekulas
        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 出来看是否正确,如果还正确只能怀疑数据库加了什么机制了
    ydpro
        13
    ydpro  
       2022-12-04 09:42:53 +08:00
    这段代码中确实存在一个 bug 。首先,在检查 $invoice 对象的签名时,应该将金额转换为数字类型,而不是字符串类型。

    其次,在计算新的积分值时,应该将新支付的积分转换为数字类型,然后再进行加法运算,而不是直接将字符串拼接在一起。

    修改后的代码应该如下所示:
    ydpro
        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
    msg7086
        15
    msg7086  
       2022-12-04 09:52:33 +08:00
    @ydpro 笑死,好好的一个聊天 AI 被你们抓来修代码……
    T0m008
        16
    T0m008  
       2022-12-04 11:06:00 +08:00
    `$paidCredit = checkStringSign($invoice->credit);`

    只能是这个 function, 这段里面唯一修改$paidCredit 的
    vovov
        17
    vovov  
       2022-12-04 17:28:58 +08:00 via iPhone
    先不说代码,你这金额好像扣了手续费后的金额
    edis0n0
        18
    edis0n0  
    OP
       2022-12-04 17:47:43 +08:00
    @vacker #17 卧槽真的是这个原因,这个函数还 include 了一个后扣手续费的 PHP 文件 sendEmailNotice.php ,直接改余额和已产生的充值记录,单看文件名完全想不到还做了这事,那里面查询写的有问题经常找不到充值记录所以没扣手续费,不知道多少年了一直有这个问题,都是财务手工加回去的
    edis0n0
        19
    edis0n0  
    OP
       2022-12-04 17:49:53 +08:00
    @edis0n0 #18 查询写的有问题:它只查询有绑定邮箱的用户,没绑定邮箱就找不到用户,没扣手续费,坑死了谁想得到能写成这样
    Rache1
        20
    Rache1  
       2022-12-04 18:50:58 +08:00
    $objDBCD14 ,哈哈,这前面不会还有 $objDBCD1 到 $objDBCD13 吧
    proxychains
        21
    proxychains  
       2022-12-05 09:14:43 +08:00
    @msg7086 这是 AI?
    msg7086
        22
    msg7086  
       2022-12-05 09:49:44 +08:00
    @proxychains 是啊,AI 帮你抓 Bug 。
    lisxour
        23
    lisxour  
       2022-12-05 10:37:39 +08:00
    代码都不用看就知道不是浮点数精度问题,一个正常的语言,怎么可能 3 位小数就开始有精度问题。。。至少都是好几位以上才有这种问题
    Twnysta
        24
    Twnysta  
       2022-12-05 11:00:57 +08:00
    php 有专门的 bc 函数啊,算账都是用这个。直接对比不是自己跟自己不痛快吗?
    vovov
        25
    vovov  
       2022-12-05 11:05:44 +08:00   1
    @edis0n0 微信支付代码写多了,看到 0.994 直觉告诉我这是扣了手续费后的金额,哈哈
    proxychains
        26
    proxychains  
       2022-12-06 13:53:59 +08:00
    @msg7086 已经通过图灵测试了...语气像是客服,有些地方感觉不对劲, 但找不到哪地方不对劲...
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2665 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 13:51 PVG 21:51 LAX 06:51 JFK 09:51
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86