| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- <?php
- namespace App\Http\Controllers\admin;
- use App\Constants\HttpStatus;
- use App\Http\Controllers\Controller;
- use App\Models\Admin;
- use App\Services\AdminRealtime\AdminRealtimeAccess;
- use App\Services\AdminRealtime\AdminRealtimeFeed;
- use App\Services\AdminRealtime\AdminRealtimeRows;
- use App\Services\JwtService;
- use Exception;
- use Illuminate\Http\JsonResponse;
- use Illuminate\Validation\ValidationException;
- /** 后台订单、用户列表实时推送(Reverb + Laravel Echo),接入说明见 docs/admin-websocket.md */
- class Realtime extends Controller
- {
- /** Echo 连接参数、可订阅频道、当前 seq 与登录过期时间 */
- public function config(JwtService $jwtService): JsonResponse
- {
- try {
- if (!config('admin_realtime.enabled')) {
- throw new Exception('实时推送未启用');
- }
- $session = $jwtService->validateToken($this->bearerToken());
- $client = config('admin_realtime.client');
- $result = [
- 'broadcaster' => 'reverb',
- 'key' => (string) $client['key'],
- 'ws_host' => (string) $client['host'],
- 'ws_port' => (int) $client['port'],
- 'force_tls' => $client['scheme'] === 'https',
- 'auth_endpoint' => '/admin/broadcasting/auth',
- 'channels' => AdminRealtimeAccess::allowedChannels((int) request()->user->id),
- 'last_seq' => AdminRealtimeFeed::lastSeq(),
- 'session_expires_at' => isset($session->exp) ? (int) $session->exp : null,
- ];
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->success($result);
- }
- /**
- * Echo 私有频道授权(Pusher 协议),直接返回 {auth, channel_data}。
- * 不走 admin.jwt:该中间件未登录时仍返回 HTTP 200,Echo 无法区分,这里改用 401 / 403。
- */
- public function auth(JwtService $jwtService): JsonResponse
- {
- if (!config('admin_realtime.enabled')) {
- return $this->denied(403, '实时推送未启用');
- }
- $session = $jwtService->validateToken($this->bearerToken());
- $admin = $session && isset($session->user_id) ? Admin::query()->find($session->user_id) : null;
- if (!$admin) {
- return $this->denied(401, __('messages.' . HttpStatus::AUTHORIZATION_HEADER_NOT_FOUND));
- }
- $channelName = (string) request()->input('channel_name');
- $channel = str_starts_with($channelName, 'private-') ? substr($channelName, 8) : '';
- if (!isset(AdminRealtimeRows::CHANNELS[$channel]) || !in_array($channel, AdminRealtimeAccess::allowedChannels((int) $admin->id), true)) {
- return $this->denied(403, '无权订阅该频道');
- }
- try {
- $auth = AdminRealtimeAccess::authorize((int) $admin->id, isset($session->exp) ? (int) $session->exp : null, $channelName, (string) request()->input('socket_id'));
- } catch (\Throwable $e) {
- return $this->denied(403, '参数错误');
- }
- return response()->json($auth);
- }
- /** 断线重连或首次订阅后补发 seq 之后的事件;返回 resync=true 时前端应重新拉取列表 */
- public function replay(): JsonResponse
- {
- try {
- request()->validate([
- 'after_seq' => ['required', 'integer', 'min:0'],
- 'channels' => ['required', 'string'],
- ]);
- if (!config('admin_realtime.enabled')) {
- throw new Exception('实时推送未启用');
- }
- $allowed = AdminRealtimeAccess::allowedChannels((int) request()->user->id);
- $channels = array_values(array_intersect(array_map('trim', explode(',', (string) request()->input('channels'))), $allowed));
- $result = AdminRealtimeFeed::replay((int) request()->input('after_seq'), $channels) + ['channels' => $channels];
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->success($result);
- }
- private function bearerToken(): string
- {
- return str_replace('Bearer ', '', (string) request()->header('Authorization'));
- }
- private function denied(int $status, string $message): JsonResponse
- {
- return response()->json(['code' => HttpStatus::CUSTOM_ERROR, 'timestamp' => time(), 'msg' => $message, 'data' => []], $status);
- }
- }
|