['admin.stats.platform_count', Online::class, 'platformCount'], 'payment_unprocessed' => ['admin.stats.unprocessed', PaymentOrder::class, 'unProcessed'], ]; public static function read(string $resource): array { [, $controller, $method] = self::DEFINITIONS[$resource]; // 复用 HTTP 端点本身,统计字段、筛选条件和时间窗口只有一个实现。 $response = app()->call([app()->make($controller), $method]); $body = $response instanceof JsonResponse ? $response->getData(true) : []; if (($body['code'] ?? null) !== HttpStatus::OK || !is_array($body['data'] ?? null)) { throw new RuntimeException($controller . '@' . $method . ' failed: ' . ($body['msg'] ?? 'invalid response')); } return $body['data']; } public static function scan(): void { if (!config('admin_realtime.enabled')) return; $interval = max(1, (int) config('admin_realtime.summary_scan_seconds', 5)); foreach (array_keys(self::DEFINITIONS) as $resource) { try { if (!Cache::add('admin_realtime:summary_scan:' . $resource, 1, $interval)) continue; $signature = hash('sha256', json_encode(self::read($resource), JSON_THROW_ON_ERROR)); $key = 'admin_realtime:summary_snapshot:' . $resource; if (Cache::get($key) === $signature) continue; // 必须先确认事件登记成功再记住快照;失败或崩溃最多重复,不会吞掉变化。 if (!DB::table('admin_realtime_events')->insert([ 'resource' => $resource, 'resource_id' => '1', 'action' => 'updated', 'source' => 'bot-28', 'created_at' => now('Asia/Shanghai')->format('Y-m-d H:i:s'), ])) throw new RuntimeException('Summary event registration failed'); Cache::forever($key, $signature); } catch (\Throwable $e) { Log::channel('realtime')->error('admin_realtime_summary_scan_failed', [ 'resource' => $resource, 'error' => $e->getMessage(), ]); } } } }