orderIds = array_values(array_map('intval', $orderIds)); $this->adminId = $adminId; $this->scope = $scope; $this->filterSummary = $filterSummary; $this->limit = $limit; $this->matchedTotal = $matchedTotal; $this->processed = $processed; // 说明:优先使用投递时生成的 run_id(便于控制台立即可见/可复制);若未提供则在 job 内兜底生成。 $this->runId = trim($runId) !== '' ? (string) $runId : ('BAS' . now()->format('YmdHis') . str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT)); } public function handle(SubscriptionActivationService $service): void { // 批次号:用于把一次队列批量执行关联起来,便于后续追溯/筛选/可观测。 $runId = (string) $this->runId; $success = 0; $failed = 0; $failedReasonCounts = []; foreach ($this->orderIds as $orderId) { /** @var PlatformOrder|null $order */ $order = PlatformOrder::query()->find($orderId); if (! $order) { continue; } try { // 双保险:续费单必须绑定订阅 if ((string) ($order->order_type ?? '') === 'renewal' && ! (int) ($order->site_subscription_id ?? 0)) { throw new \InvalidArgumentException('续费单未绑定订阅(site_subscription_id 为空),不允许批量同步订阅。'); } $subscription = $service->activateOrder($orderId, $this->adminId); // 注意:activateOrder 内会更新 meta;这里 refresh 避免覆盖 $order->refresh(); $meta = (array) ($order->meta ?? []); $audit = (array) (data_get($meta, 'audit', []) ?? []); $nowStr = now()->toDateTimeString(); $audit[] = [ 'action' => 'batch_activate_subscription', 'scope' => $this->scope, 'at' => $nowStr, 'admin_id' => $this->adminId, 'subscription_id' => $subscription->id, 'filters' => $this->filterSummary, 'run_id' => $runId, 'note' => '批量同步订阅(queue, run_id=' . $runId . ', limit=' . $this->limit . ', matched=' . $this->matchedTotal . ', processed=' . $this->processed . ')', ]; data_set($meta, 'audit', $audit); // 便于筛选/统计:记录最近一次批量同步信息(扁平字段) data_set($meta, 'batch_activation', [ 'at' => $nowStr, 'admin_id' => $this->adminId, 'scope' => $this->scope, 'mode' => 'queue', 'run_id' => $runId, ]); $order->meta = $meta; $order->save(); $success++; } catch (\Throwable $e) { $failed++; $reason = trim((string) $e->getMessage()); $reason = $reason !== '' ? $reason : '未知错误'; $failedReasonCounts[$reason] = ($failedReasonCounts[$reason] ?? 0) + 1; $meta = (array) ($order->meta ?? []); $nowStr = now()->toDateTimeString(); data_set($meta, 'subscription_activation_error', [ 'message' => $reason, 'at' => $nowStr, 'admin_id' => $this->adminId, ]); // 即使失败也写入 batch_activation(包含 run_id),确保本批次可追溯/可汇总。 data_set($meta, 'batch_activation', [ 'at' => $nowStr, 'admin_id' => $this->adminId, 'scope' => $this->scope, 'mode' => 'queue', 'run_id' => $runId, ]); $order->meta = $meta; $order->save(); } } // 最小结果汇总(写入到每个订单的 meta.batch_activation.last_result),便于运营在列表页直接看到“本次队列批量的执行结果”。 // 注意:为避免引入新表,当前阶段采取“冗余写入到每条订单”策略;后续可演进为独立批次表或日志表。 $topReasons = []; if ($failed > 0 && count($failedReasonCounts) > 0) { arsort($failedReasonCounts); $top = array_slice($failedReasonCounts, 0, 3, true); foreach ($top as $reason => $cnt) { $topReasons[] = [ 'reason' => mb_substr((string) $reason, 0, 80), 'count' => (int) $cnt, ]; } } $summary = [ 'run_id' => $runId, 'success' => $success, 'failed' => $failed, 'matched' => $this->matchedTotal, 'processed' => $this->processed, 'top_reasons' => $topReasons, 'at' => now()->toDateTimeString(), ]; foreach ($this->orderIds as $orderId) { $order = PlatformOrder::query()->find($orderId); if (! $order) { continue; } $meta = (array) ($order->meta ?? []); $ba = (array) (data_get($meta, 'batch_activation', []) ?? []); // 仅当 run_id 与当前 job 一致时才回写 last_result,避免并发覆盖。 if ((string) (data_get($ba, 'run_id') ?? '') !== $runId) { continue; } data_set($ba, 'last_result', $summary); data_set($meta, 'batch_activation', $ba); $order->meta = $meta; $order->save(); } } }