Files
saasshop/app/Jobs/BatchActivateSubscriptionsJob.php

185 lines
6.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Jobs;
use App\Models\PlatformOrder;
use App\Support\SubscriptionActivationService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class BatchActivateSubscriptionsJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var int[] */
public array $orderIds;
public int $adminId;
public string $scope;
public string $filterSummary;
public int $limit;
public int $matchedTotal;
public int $processed;
/**
* @param int[] $orderIds
*/
public function __construct(
array $orderIds,
int $adminId,
string $scope,
string $filterSummary,
int $limit,
int $matchedTotal,
int $processed,
) {
$this->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;
}
public function handle(SubscriptionActivationService $service): void
{
// 批次号:用于把一次队列批量执行关联起来,便于后续追溯/筛选/可观测。
$runId = 'BAS' . now()->format('YmdHis') . str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT);
$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();
}
}
}