项目错误经验记录.md 10 KB

项目错误经验记录

本文档记录在开发过程中遇到的典型错误及其解决方案,供团队参考避免重复踩坑。


1. 支付金额计算顺序错误(2026-07-02)

问题描述

GamePayService::orderData() 方法中,$payData['pay_amount'] 的计算依赖于 Pay::calPayAmount() 方法。当使用专属币(ptb_amt)且存在折扣时,计算结果偏高。

错误代码

// Pay.php 第166-176行
$diff = bcsub($amount, $ptb_amount, 2);      // 先扣除专属币
$raw_amount = bcmul($diff, $discount, 4);    // 再应用折扣

正确逻辑

$discounted = bcmul($amount, $discount, 2);   // 先应用折扣
$result = bcsub($discounted, $ptb_amount, 2); // 再扣除专属币

影响范围

  • 触发条件:专属币 > 0 且折扣 < 1
  • 影响接口:所有使用 calPayAmount() 的支付入口
  • 业务影响:用户实际支付金额比正确金额偏高

根本原因

  1. 开发时未理清业务规则:折扣应作用于原价,专属币抵扣折扣后的金额
  2. 缺乏单元测试覆盖边界场景
  3. 新旧逻辑迁移时引入错误(旧逻辑只处理折扣,新逻辑增加专属币扣除)

解决方案

调整 calPayAmount() 中的计算顺序,确保先折扣后扣除。

预防措施

  • 涉及金额计算的修改必须编写单元测试
  • 明确业务规则文档化(折扣、抵扣的优先级)
  • 代码审查时重点关注计算顺序

2. API调用丢失折扣参数(2026-07-02)

问题描述

api/controller/v1/Pay.php 中调用 calPayAmount() 时未传递 $discount 参数,导致折扣配置失效。

错误代码

// api/controller/v1/Pay.php 第257、260行
$arrPayAmount = $payLogic->calPayAmount($amount, $ptb_amt);  // 缺少 $discount
$arrPayAmount = $payLogic->calPayAmount($amount);            // 缺少 $discount

影响范围

  • 触发条件:所有通过API发起的支付请求
  • 业务影响:游戏配置的折扣活动完全失效

根本原因

  1. 方法签名 $discount = 1 的默认值掩盖了参数缺失
  2. 调用方未遵循方法契约(应传递完整参数)

解决方案

补充 $discount 参数传递:

$discount = getDiscount($member_id, $game_id, $channel_id);
$arrPayAmount = $payLogic->calPayAmount($amount, $ptb_amt, $discount);

预防措施

  • 关键业务参数不应有默认值(或默认值为异常值如 null
  • 静态分析工具检查函数调用参数完整性
  • 统一支付入口,避免多处重复逻辑

3. 调试代码残留(2026-07-02)

问题描述

生产代码中残留 dump() 调试语句,影响性能和输出。

错误代码

// common.php 第639行
dump($ancestors);

// Pay.php 第550行
dump($channel);

影响

  • 输出额外内容,可能破坏JSON响应
  • 性能损耗(尤其循环中)
  • 暴露内部数据结构

预防措施

  • 代码提交前搜索 dump(var_dump(print_r(
  • IDE配置保存时自动移除调试语句
  • Code Review 时重点关注

4. 登录时未添加新设备到常用设备列表(2026-07-02)

问题描述

用户登录时,新的设备号不会被添加到常用设备列表中,数据库中 nw_subaccount.recent_devices 字段始终只保存一个设备。

错误代码

// api/controller/v1/Login.php 第594行
$memberDevice->addDevice($userinfo['id'], $gameid, $imeil, $device, false, true);
//                                                                              ↑
//                                                              $onlyUpdateExisting = true

正确逻辑

$memberDevice->addDevice($userinfo['id'], $gameid, $imeil, $device, false, false);
//                                                                              ↑
//                                                              $onlyUpdateExisting = false

影响范围

  • 触发条件:用户使用新设备登录
  • 影响接口api/controller/v1/Login.php 登录接口
  • 业务影响:常用设备列表无法正常维护,设备切换验证功能异常

根本原因

  1. MemberDevice::addDevice()$onlyUpdateExisting 参数含义混淆:
    • true = 仅更新已存在设备,不添加新设备
    • false = 不存在时添加新设备
  2. 登录接口错误地传入 true,与注册接口逻辑不一致
  3. 注册接口正确传入 false(或使用默认值)

解决方案

将 Login.php 中 $onlyUpdateExisting 参数改为 false,使登录与注册保持一致,都允许添加新设备。

预防措施

  • 关键业务参数应使用有意义的常量或枚举,避免裸露的 true/false
  • 相同功能的代码应保持一致的调用方式
  • 代码审查时对比相似场景的实现

5. GamePayService 中 real_amount 与 pay_amount 概念混淆(2026-07-02)

问题描述

GamePayService::gamePay() 方法中,$payData['real_amount']$payData['pay_amount'] 两个字段容易混淆,需要明确区分其业务含义和计算逻辑。

字段定义对比

字段 含义 考虑代金券 考虑币抵扣 考虑折扣
pay_amount 折扣后应付金额
real_amount 实际需第三方支付金额

计算公式

pay_amount  = amount * discount
real_amount = (amount - coupon_amount - ptb_amount/coin_amount) * discount

数据流向

$data['amount'] (原始充值金额)
       ↓
   代金券抵扣 → $amount = amount - coupon_amount
       ↓
   calPayAmount() / calCoinPayAmount() → $arrPayAmount
       ↓
   orderData() → $payData['real_amount'] + $payData['pay_amount']

关键代码位置

  1. 代金券抵扣GamePayService.php 第215-235行
  2. 币抵扣计算Pay::calPayAmount() / Pay::calCoinPayAmount()
  3. orderData赋值GamePayService.php 第583-619行

易错点

  1. pay_amount 不考虑代金券和币抵扣:仅反映折扣因素
  2. real_amount 是最终支付金额:传给第三方支付渠道的金额
  3. real_amount = 0 时直接标记支付成功:无需调用第三方支付

具体示例

假设:充值金额 100元,折扣 0.9,代金券 10元,专属币 20元

原始金额:     100元
代金券抵扣后: 100 - 10 = 90元
折扣后应付:   100 * 0.9 = 90元 → pay_amount
实际支付:     (100 - 10 - 20) * 0.9 = 63元 → real_amount

预防措施

  • 修改金额计算逻辑前,先明确字段的业务定义
  • 建议在代码注释中标注字段含义和计算公式
  • 涉及金额的接口必须编写单元测试覆盖边界场景

最后更新:2026-07-02

6. 定时任务循环中使用致命错误导致后续任务中断(2026-07-03)

问题描述

在 PolyChannelSmsWarn.php 定时任务脚本中,当处理多个渠道的短信预警时,第一个渠道发送失败后,后续渠道不会被执行。

错误代码

问题1:致命错误导致脚本终止

`php // fetchContent 方法 if(\ === false) {

// ❌ E_USER_ERROR 是致命错误,会立即终止整个 PHP 脚本执行
trigger_error("[CURL_" . curl_errno(\) . "]: " . curl_error(\), E_USER_ERROR);

} `

问题2:number_format 重复调用

`php // execute 方法中的短信发送逻辑 \ = number_format(['total_advance'] - \, 2); // 返回值: "-42,299.56"(带逗号的字符串)

\ = ['name' => ['channel_name'], 'money' => number_format(\, 2)]; // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ // 对带逗号的字符串再次 number_format 会报错 // 报错信息:A non well formed numeric value encountered `

正确逻辑

修复1:改为抛出异常

`php // fetchContent 方法 if(\ === false) {

\ = "[CURL_" . curl_errno(\) . "]: " . curl_error(\);
curl_close(\);
// ✅ 抛出异常,可被 try-catch 捕获
throw new \RuntimeException(\);

} `

修复2:添加 try-catch 捕获异常

`php foreach ( \ as \ ) {

try {
    // ... 原有的发送逻辑 ...

} catch (\Exception \) {
    // ✅ 记录错误日志,继续执行下一个渠道
    \->writeln(date('H:i:s')." [\['channel_id']] 处理异常: ".\->getMessage());
    continue;
}

} `

修复3:避免 number_format 重复调用

`php \ = number_format(['total_advance'] - \, 2);

// ❌ 错误写法:对已经格式化的字符串再次格式化 // \ = ['name' => ['channel_name'], 'money' => number_format(\, 2)];

// ✅ 正确写法:直接使用已格式化的值 \ = ['name' => ['channel_name'], 'money' => ]; `

影响范围

  • 触发条件:定时任务处理多个渠道,且至少有一个渠道发送失败
  • 影响脚本:pplication/crontab/PolyChannelSmsWarn.php
  • 业务影响:短信预警通知不完整,可能导致部分渠道余额不足未被及时发现

根本原因

  1. 致命错误处理不当:在循环中使用 E_USER_ERROR 会导致整个脚本终止,无法容错
  2. number_format 理解偏差: umber_format() 返回的是带千分位逗号的字符串,不能直接再次格式化

预防措施

代码层面

  • 循环中禁止使用致命错误:E_USER_ERROR、xit()、die() 等会导致脚本终止的语句
  • 使用 try-catch 包裹可能失败的操作:HTTP请求、数据库操作、文件操作等
  • number_format 返回值是字符串:如需再次参与计算,应使用原始数值而非格式化后的字符串

测试层面

  • 多场景测试:测试部分成功、部分失败的混合场景
  • 边界值测试:测试负数、零、超大数等特殊情况

架构层面

  • 批量任务设计:单个任务失败不应影响其他任务
  • 日志记录:记录每个子任务的执行结果,便于排查问题

相关文件

  • www/new_sdk/application/crontab/PolyChannelSmsWarn.php

关键教训

在循环处理多个任务时,永远不要使用会导致脚本终止的错误处理方式(如 E_USER_ERROR),而应该使用 ry-catch 捕获异常,确保单个失败不会影响其他任务的执行。

** umber_format() 返回的是带千分位的字符串,不是数值类型,不能直接再次格式化或参与需要数值的运算。**


最后更新:2026-07-03