2], null, '*', null, true); if (!empty($list)) { foreach ($list as $clear) { $payTime = $clear->payTime ?? ''; $saleIds = $clear->saleIds ?? ''; $ids = explode(',', trim($saleIds)); print_r($ids); if (!empty($ids)) { OrderClass::updateByIds($ids, ['clearTime' => $payTime]); } $purchaseIds = $clear->purchaseIds ?? ''; $purchaseIds = explode(',', $purchaseIds); print_r($purchaseIds); if (!empty($purchaseIds)) { PurchaseClass::updateByIds($purchaseIds, ['clearTime' => $payTime]); } echo "-----------------\n"; } } } /** * 历史 xhClear 回填 xhOrderCgClear * * 大数据量友好:按主键 id 分批倒序扫描,无长事务;批间休眠;关系表 clearId 批量去重。 * * 用法: * php yii clear/backfill-order-cg-clear * php yii clear/backfill-order-cg-clear --batchSize=50 --sleepMs=200 * php yii clear/backfill-order-cg-clear --dryRun=1 --limit=1000 * php yii clear/backfill-order-cg-clear --clearId=123 * php yii clear/backfill-order-cg-clear --reset=1 */ public function actionBackfillOrderCgClear() { ini_set('memory_limit', '512M'); set_time_limit(0); if ($this->batchSize < 1) { $this->stderr("batchSize 必须大于 0\n"); return ExitCode::UNSPECIFIED_ERROR; } if ($this->sleepMs < 0) { $this->stderr("sleepMs 不能小于 0\n"); return ExitCode::UNSPECIFIED_ERROR; } $hd2Gys = intval(dict::getDict('clearStyle', 'hd2Gys')); $gys2Hd = intval(dict::getDict('clearStyle', 'gys2Hd')); if ($this->reset && $this->clearId <= 0) { $this->clearBackfillProgress(); $this->stdout("已重置回填进度\n"); } $cursorId = $this->clearId > 0 ? null : $this->loadBackfillProgress(); $total = 0; $inserted = 0; $skipped = 0; $batchNo = 0; $this->stdout(sprintf( "开始回填 xhOrderCgClear | id DESC 分批 | dryRun=%d batchSize=%d sleepMs=%d limit=%d | 续跑 id<%s | 进度=%s\n", (int)$this->dryRun, $this->batchSize, $this->sleepMs, (int)$this->limit, $cursorId === null ? 'max' : (string)$cursorId, $this->getBackfillProgressFile() )); while (true) { if ($this->limit > 0 && $total >= $this->limit) { break; } $fetchLimit = $this->batchSize; if ($this->limit > 0) { $fetchLimit = min($fetchLimit, $this->limit - $total); if ($fetchLimit < 1) { break; } } $list = $this->fetchClearBatch($hd2Gys, $gys2Hd, $cursorId, $fetchLimit); if (empty($list)) { break; } $batchNo++; $batchMinId = null; $clearIds = array_map('intval', array_column($list, 'id')); $existingClearIds = OrderCgClearClass::getExistingClearIds($clearIds); $emptyAmountClearIds = OrderCgClearClass::getClearIdsWithEmptyAmount($clearIds); $existingMap = array_fill_keys($existingClearIds, true); $emptyAmountMap = array_fill_keys($emptyAmountClearIds, true); foreach ($list as $clear) { $total++; $currentClearId = intval($clear['id'] ?? 0); $batchMinId = $currentClearId; $cursorId = $currentClearId; $saleIds = trim($clear['saleIds'] ?? ''); $purchaseIds = trim($clear['purchaseIds'] ?? ''); if ($saleIds === '' && $purchaseIds === '') { $skipped++; continue; } if (isset($existingMap[$currentClearId]) && !isset($emptyAmountMap[$currentClearId])) { $skipped++; continue; } if ($this->dryRun) { if ($this->logEvery > 0 && $total % $this->logEvery === 0) { $this->stdout("dryRun 已扫描 {$total} 条,当前 clearId={$currentClearId}\n"); } continue; } $count = OrderCgClearClass::backfillFromClear($clear, true); if ($count > 0) { $inserted += $count; } else { $skipped++; } if ($this->logEvery > 0 && $total % $this->logEvery === 0) { $this->stdout("进度:扫描 {$total},写入关系 {$inserted} 行,跳过 {$skipped},当前 clearId={$currentClearId}\n"); } } if (!$this->dryRun && $this->clearId <= 0 && $batchMinId !== null) { $this->saveBackfillProgress($batchMinId); } $this->stdout(sprintf( "第 %d 批完成:本批 %d 条,累计扫描 %d,写入关系 %d 行,跳过 %d,checkpoint id=%d\n", $batchNo, count($list), $total, $inserted, $skipped, (int)$batchMinId )); if ($this->clearId > 0 || count($list) < $fetchLimit) { break; } $this->sleepBetweenBatch(); } if (!$this->dryRun && $this->clearId <= 0 && $cursorId !== null) { $this->stdout("进度已保存,下次从 id < {$cursorId} 继续\n"); } $this->stdout("全部完成:扫描 {$total} 条 xhClear,写入 {$inserted} 行关系,跳过 {$skipped} 条\n"); return ExitCode::OK; } /** * 按主键倒序轻量拉取一批 xhClear(只查回填所需字段,LIMIT 走主键索引) */ private function fetchClearBatch($hd2Gys, $gys2Hd, $cursorId, $limit) { if ($this->clearId > 0) { $row = Yii::$app->db->createCommand( 'SELECT id, orderSn, saleIds, purchaseIds, status, clearStyle FROM ' . Clear::tableName() . ' WHERE id = :id AND clearStyle IN (:s1, :s2) LIMIT 1' )->bindValues([ ':id' => intval($this->clearId), ':s1' => $hd2Gys, ':s2' => $gys2Hd, ])->queryOne(); return $row ? [$row] : []; } $sql = 'SELECT id, orderSn, saleIds, purchaseIds, status, clearStyle FROM ' . Clear::tableName() . ' WHERE clearStyle IN (:s1, :s2)'; $params = [ ':s1' => $hd2Gys, ':s2' => $gys2Hd, ]; if ($cursorId !== null) { $sql .= ' AND id < :cursorId'; $params[':cursorId'] = intval($cursorId); } $sql .= ' ORDER BY id DESC LIMIT ' . intval($limit); return Yii::$app->db->createCommand($sql)->bindValues($params)->queryAll(); } private function sleepBetweenBatch() { if ($this->sleepMs > 0) { usleep($this->sleepMs * 1000); } } private function getBackfillProgressFile() { return Yii::getAlias('@console/runtime/order_cg_clear_backfill.last_id'); } private function loadBackfillProgress() { $file = $this->getBackfillProgressFile(); if (!is_file($file)) { return null; } $id = intval(trim((string)file_get_contents($file))); return $id > 0 ? $id : null; } private function saveBackfillProgress($clearId) { if ($this->dryRun || $this->clearId > 0) { return; } $clearId = intval($clearId); if ($clearId <= 0) { return; } $file = $this->getBackfillProgressFile(); $dir = dirname($file); if (!is_dir($dir)) { mkdir($dir, 0777, true); } file_put_contents($file, (string)$clearId); } private function clearBackfillProgress() { $file = $this->getBackfillProgressFile(); if (is_file($file)) { unlink($file); } } }