validate([ 'site_url' => ['required', 'url', 'max:512'], 'site_name' => ['nullable', 'string', 'max:255'], 'typecho_version' => ['nullable', 'string', 'max:32'], 'php_version' => ['nullable', 'string', 'max:32'], 'plugin_version' => ['nullable', 'string', 'max:32'], 'user_count' => ['nullable', 'integer', 'min:0'], ]); $siteUrl = $this->normalizeSiteUrl((string) $validated['site_url']); $now = now(); $client = StoreClient::query()->firstOrNew([ 'site_url' => $siteUrl, ]); if (!$client->exists) { $client->registered_at = $now; } $client->site_name = $this->truncate((string) ($validated['site_name'] ?? ''), 255); $client->access_token = $this->issueAccessToken(); $client->status = 'online'; $client->user_count = max((int) ($validated['user_count'] ?? 0), 0); $client->typecho_version = $this->truncate((string) ($validated['typecho_version'] ?? ''), 32); $client->php_version = $this->truncate((string) ($validated['php_version'] ?? ''), 32); $client->plugin_version = $this->truncate((string) ($validated['plugin_version'] ?? ''), 32); $client->last_ip = $this->truncate((string) ($request->ip() ?? ''), 45); $client->last_user_agent = $this->truncate((string) ($request->userAgent() ?? ''), 512); $client->last_seen_at = $now; $client->save(); return response()->json([ 'code' => 0, 'message' => 'ok', 'data' => $this->clientPayload($client, true), ]); } public function heartbeat(Request $request): JsonResponse { $client = $this->resolveClient($request); if (!$client) { return $this->unauthorizedResponse(); } $validated = $request->validate([ 'site_name' => ['nullable', 'string', 'max:255'], 'typecho_version' => ['nullable', 'string', 'max:32'], 'php_version' => ['nullable', 'string', 'max:32'], 'plugin_version' => ['nullable', 'string', 'max:32'], 'user_count' => ['nullable', 'integer', 'min:0'], ]); if (array_key_exists('site_name', $validated)) { $client->site_name = $this->truncate((string) $validated['site_name'], 255); } if (array_key_exists('typecho_version', $validated)) { $client->typecho_version = $this->truncate((string) $validated['typecho_version'], 32); } if (array_key_exists('php_version', $validated)) { $client->php_version = $this->truncate((string) $validated['php_version'], 32); } if (array_key_exists('plugin_version', $validated)) { $client->plugin_version = $this->truncate((string) $validated['plugin_version'], 32); } if (array_key_exists('user_count', $validated)) { $client->user_count = max((int) $validated['user_count'], 0); } $client->status = 'online'; $client->last_ip = $this->truncate((string) ($request->ip() ?? ''), 45); $client->last_user_agent = $this->truncate((string) ($request->userAgent() ?? ''), 512); $client->last_seen_at = now(); $client->save(); return response()->json([ 'code' => 0, 'message' => 'ok', 'data' => $this->clientPayload($client, false), ]); } public function status(Request $request): JsonResponse { $client = $this->resolveClient($request); if (!$client) { return $this->unauthorizedResponse(); } $client->status = 'online'; $client->last_ip = $this->truncate((string) ($request->ip() ?? ''), 45); $client->last_user_agent = $this->truncate((string) ($request->userAgent() ?? ''), 512); $client->last_seen_at = now(); $client->save(); return response()->json([ 'code' => 0, 'message' => 'ok', 'data' => $this->clientPayload($client, false), ]); } private function resolveClient(Request $request): ?StoreClient { $token = $this->extractToken($request); if ($token === '') { return null; } return StoreClient::query()->where('access_token', $token)->first(); } private function extractToken(Request $request): string { return trim((string) ( $request->bearerToken() ?: $request->header('X-Store-Plugin-Token') ?: $request->query('access_token', '') )); } private function unauthorizedResponse(): JsonResponse { return response()->json([ 'code' => 401, 'message' => 'client unauthorized', 'data' => null, ], 401); } private function issueAccessToken(): string { do { $token = Str::random(96); } while (StoreClient::query()->where('access_token', $token)->exists()); return $token; } private function normalizeSiteUrl(string $url): string { $url = trim($url); if ($url === '') { return ''; } return rtrim($url, '/'); } private function truncate(string $value, int $length): string { return mb_substr(trim($value), 0, $length); } private function clientPayload(StoreClient $client, bool $includeToken): array { $payload = [ 'client_id' => $client->id, 'site_url' => $client->site_url, 'site_name' => $client->site_name, 'status' => $client->status, 'user_count' => (int) $client->user_count, 'typecho_version' => $client->typecho_version, 'php_version' => $client->php_version, 'plugin_version' => $client->plugin_version, 'registered_at' => optional($client->registered_at)->toAtomString(), 'last_seen_at' => optional($client->last_seen_at)->toAtomString(), 'server_time' => now()->toAtomString(), 'stats' => [ 'registered_sites' => StoreClient::query()->count(), 'online_sites' => StoreClient::query()->online()->count(), 'tracked_users' => (int) StoreClient::query()->sum('user_count'), ], ]; if ($includeToken) { $payload['access_token'] = $client->access_token; } return $payload; } }