订单号固定格式改递增序列(00000000→CYZJ00000001起);补单器改定向补单:查本地活跃完整订单号精确匹配平台,不再按前缀批量扫

This commit is contained in:
ypsdf1
2026-10-07 23:54:42 +08:00
parent 629c16ec84
commit ba5352121e
2 changed files with 36 additions and 23 deletions
+13 -12
View File
@@ -204,8 +204,10 @@ if (!function_exists('payComposeOrderNo')) {
if (!function_exists('payMakeFixedOrderNo')) {
/**
* 固定订单号格式分支:前缀码 + 订单号格式模板。
* 模板 [] 去掉;纯数字 → 每个 0 换成随机数字(如 00000000 → 48392017);
* 模板非纯数字或为空 → 回退保底 6 位递增序列 000001、000002…(持久化计数器防撞单)。
* 模板 [] 去掉;
* - 纯数字(如 00000000)→ 按【模板位数】递增序列:00000001、00000002…
* (计数器持久化在 db/pay_order_seq.txt,flock 防撞单;当前无订单则从 1 开始);
* - 非纯数字或为空 → 保底 6 位递增序列 000001、000002…。
* @param array $cfg pay.md 配置区
* @return string
*/
@@ -214,15 +216,11 @@ if (!function_exists('payMakeFixedOrderNo')) {
$tpl = isset($cfg['订单号格式']) ? (string)$cfg['订单号格式'] : '';
$tpl = str_replace(array('[', ']', "\u{FF3B}", "\u{FF3D}", ' ', "\t"), '', $tpl);
if ($tpl !== '' && ctype_digit($tpl)) {
$out = '';
$n = strlen($tpl);
for ($i = 0; $i < $n; $i++) {
$out .= ($tpl[$i] === '0') ? (string)mt_rand(0, 9) : $tpl[$i];
}
$no = $p . $out;
// ★ 纯数字模板:按模板位数递增序列(00000000 → CYZJ00000001, CYZJ00000002…)
$no = payMakeSequentialOrderNo($tpl);
} else {
// 非纯数字模板(或空)→ 保底 6 位递增序列
$no = payMakeSequentialOrderNo();
$no = payMakeSequentialOrderNo('000000');
}
$max = defined('PAY_ORDER_MAX_LEN') ? (int)PAY_ORDER_MAX_LEN : 64;
if ($max > 0 && strlen($no) > $max) $no = substr($no, 0, $max);
@@ -232,13 +230,16 @@ if (!function_exists('payMakeFixedOrderNo')) {
if (!function_exists('payMakeSequentialOrderNo')) {
/**
* 保底 6 位递增序列:前缀 + 000001、000002…(纯数字模板不可用时的固定格式回退)。
* 递增序列订单号:前缀 + 模板位数的序列(000001、000002…,默认 6 位)。
* 计数器持久化在 db/pay_order_seq.txt(flock 排他锁读写改,防并发撞单);
* 计数器不可写时退化为「前缀+日期时间+3位随机」,保底不与历史单撞号。
*
* @param string $tpl 位数模板(纯数字,如 '00000000'),决定序列补零位数
* @return string
*/
function payMakeSequentialOrderNo() {
function payMakeSequentialOrderNo($tpl = '000000') {
$p = payOrderPrefix();
$width = strlen(ctype_digit($tpl) ? $tpl : '000000');
$dir = dirname(__DIR__) . '/db';
if (!is_dir($dir)) @mkdir($dir, 0755, true);
$file = $dir . '/pay_order_seq.txt';
@@ -260,7 +261,7 @@ if (!function_exists('payMakeSequentialOrderNo')) {
if ($n <= 0) {
return $p . date('ymdHis') . str_pad((string)mt_rand(0, 999), 3, '0', STR_PAD_LEFT);
}
return $p . str_pad((string)$n, 6, '0', STR_PAD_LEFT);
return $p . str_pad((string)$n, $width, '0', STR_PAD_LEFT);
}
}
+23 -11
View File
@@ -215,25 +215,37 @@ function pollPaidOrders() {
// 仍不过滤 notify:平台可能已标记 notify=1 但本地 DB 未更新(HTTP 回调被
// CF WAF 拦截),是否补单以本地 pay_orders 状态为准。
$prefix = $PLATFORM_DB_PREFIX;
// ★ 2026-10-06:扫描前缀改为读配置(原来写死 'RE')
// ★ 2026-10-07:前缀第一来源改为站点根 pay.md「前缀码」;扫描改为「当前前缀+历史前缀」
// 双条件(payOrderPrefixLikeAny),手工改过前缀码后,改动前已生成的在途单仍能补回。
$stmt = $pdo->prepare("SELECT out_trade_no, trade_no, uid, money, status, notify, param, version, addtime FROM `{$prefix}order` WHERE (" . payOrderPrefixLikeAny() . ") AND status IN (1, 2) ORDER BY addtime ASC LIMIT 50");
// ★ 2026-10-07 改为【定向补单】:不再按前缀批量扫平台共享库,
// 而是先取本地 pay_orders 里所有未补单(非 paid)的【完整订单号】,
// 再让平台只按这些完整订单号精确匹配。这样就算生产/测试共用 CYZJ 前缀,
// 测试服脚本也只会查它自己本地活跃的订单号,抓不到生产服的单。
$activeStmt = $sqlite->prepare("SELECT out_trade_no FROM pay_orders WHERE status != 'paid' AND out_trade_no IS NOT NULL AND out_trade_no != ''");
$activeStmt->execute();
$activeNos = array();
while ($a = $activeStmt->fetchArray(SQLITE3_ASSOC)) {
$activeNos[] = (string)$a['out_trade_no'];
}
if (empty($activeNos)) {
echo json_encode(['result' => 'no_local_active_orders']);
return;
}
// 平台侧按完整订单号精确匹配(IN 子句,最多取前 200 个,足够覆盖并发场景)
$activeNos = array_slice($activeNos, 0, 200);
$inList = "'" . implode("','", array_map('addslashes', $activeNos)) . "'";
$stmt = $pdo->prepare("SELECT out_trade_no, trade_no, uid, money, status, notify, param, version, addtime FROM `{$prefix}order` WHERE out_trade_no IN (" . $inList . ") AND status IN (1, 2)");
$stmt->execute();
$allOrders = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($allOrders)) {
echo json_encode(['result' => 'no_paid_orders_on_platform']);
echo json_encode(['result' => 'no_paid_orders_on_platform', 'active_local' => count($activeNos)]);
return;
}
// 只保留本方订单(前缀兜底校验,与创单端同一份配置),再过滤掉本地已处理(paid)的
// 只保留「本地确实在跟踪、且还没补单」的订单(二次过滤,防本地已被补单/已支付)
$orders = [];
$foreign = 0;
foreach ($allOrders as $o) {
$outNo = (string)$o['out_trade_no'];
if (!payOrderIsOurs($outNo)) {
$foreign++; // 非本方订单:不建单、不写流水、不改它的 notify
if (!in_array($outNo, $activeNos, true)) {
continue;
}
$check = $sqlite->prepare("SELECT status FROM pay_orders WHERE out_trade_no = :no");
@@ -245,7 +257,7 @@ function pollPaidOrders() {
}
if (empty($orders)) {
echo json_encode(['result' => 'no_pending_orders', 'platform_paid_count' => count($allOrders), 'foreign_skipped' => $foreign]);
echo json_encode(['result' => 'no_pending_orders', 'active_local' => count($activeNos)]);
return;
}
@@ -337,7 +349,7 @@ function pollPaidOrders() {
'result' => 'ok',
'processed' => $processed,
'skipped' => $skipped,
'foreign_skipped' => $foreign,
'foreign_skipped' => 0,
'total' => count($orders),
'elapsed_ms' => $elapsed,
]);