middleware('auth:api'); $this->middleware('permission:platform.accounts.view,api')->only(['index', 'show']); $this->middleware('permission:platform.accounts.manage,api')->only(['store', 'update', 'destroy', 'refreshToken', 'refreshTokenStatus']); } #[Apidoc\Title('账号列表'), Apidoc\Method('GET'), Apidoc\Url('/accounts')] public function index(): JsonResponse { return response()->json(['code' => 0, 'message' => 'ok', 'data' => BastionAccount::query()->latest()->paginate(20)]); } #[Apidoc\Title('创建账号'), Apidoc\Method('POST'), Apidoc\Url('/accounts')] public function store(StoreBastionAccountRequest $request): JsonResponse { $account = BastionAccount::query()->create($request->validated()); $this->auditLog($request, 'account_create', ['bastion_account_id' => $account->id]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $account], 201); } #[Apidoc\Title('账号详情'), Apidoc\Method('GET'), Apidoc\Url('/accounts/{id}')] public function show(int $id): JsonResponse { return response()->json(['code' => 0, 'message' => 'ok', 'data' => BastionAccount::query()->findOrFail($id)]); } #[Apidoc\Title('更新账号'), Apidoc\Method('PUT'), Apidoc\Url('/accounts/{id}')] public function update(UpdateBastionAccountRequest $request, int $id): JsonResponse { $account = BastionAccount::query()->findOrFail($id); $account->update($request->validated()); $this->auditLog($request, 'account_update', ['bastion_account_id' => $account->id]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $account]); } #[Apidoc\Title('删除账号'), Apidoc\Method('DELETE'), Apidoc\Url('/accounts/{id}')] public function destroy(Request $request, int $id): JsonResponse { $account = BastionAccount::query()->findOrFail($id); $this->auditLog($request, 'account_delete', ['bastion_account_id' => $account->id]); $account->delete(); return response()->json(['code' => 0, 'message' => 'ok', 'data' => null]); } #[Apidoc\Title('刷新账号token'), Apidoc\Method('POST'), Apidoc\Url('/accounts/{id}/refresh-token')] public function refreshToken(Request $request, int $id): JsonResponse { $account = BastionAccount::query()->findOrFail($id); $baseUrl = (string) config('services.bastion_token.base_url'); if ($baseUrl === '') { return response()->json([ 'code' => 500, 'message' => '未配置堡垒机 Token 服务地址,请先在 .env 中配置 BASTION_TOKEN_API_BASE_URL', 'data' => null, ], 500); } $submitEndpoint = (string) config('services.bastion_token.submit_endpoint', '/bastion_token'); $timeout = (int) config('services.bastion_token.timeout', 30); $taskTtlSeconds = (int) config('services.bastion_token.task_ttl_seconds', 1800); $service = (string) config('services.bastion_token.service', 'https://myapp.cdu.edu.cn/index.html'); $verifySsl = (bool) config('services.bastion_token.verify_ssl', false); try { $submitResponse = Http::baseUrl($baseUrl) ->acceptJson() ->timeout($timeout) ->retry(2, 300, throw: false) ->post($submitEndpoint, [ 'username' => $account->username, 'password' => $account->password, 'service' => $service, 'verify_ssl' => $verifySsl, ]); if (! $submitResponse->successful()) { return response()->json([ 'code' => 502, 'message' => '提交 Token 刷新任务失败', 'data' => ['response' => $submitResponse->json()], ], 502); } $taskId = (string) data_get($submitResponse->json(), 'task_id', ''); if ($taskId === '') { return response()->json([ 'code' => 502, 'message' => 'Token 服务返回任务ID为空', 'data' => ['response' => $submitResponse->json()], ], 502); } } catch (ConnectionException|RequestException $exception) { return response()->json([ 'code' => 502, 'message' => '调用 Token 服务失败:'.$exception->getMessage(), 'data' => null, ], 502); } $cacheKey = $this->tokenTaskCacheKey($taskId); Cache::put($cacheKey, [ 'task_id' => $taskId, 'account_id' => $account->id, 'finished' => false, ], now()->addSeconds(max(60, $taskTtlSeconds))); $this->auditLog($request, 'account_refresh_token_submit', ['bastion_account_id' => $account->id, 'task_id' => $taskId]); return response()->json([ 'code' => 0, 'message' => 'Token 刷新任务已提交', 'data' => [ 'task_id' => $taskId, 'status' => 'pending', ], ]); } #[Apidoc\Title('查询刷新账号token状态'), Apidoc\Method('GET'), Apidoc\Url('/accounts/{id}/refresh-token/{taskId}')] public function refreshTokenStatus(Request $request, int $id, string $taskId): JsonResponse { $account = BastionAccount::query()->findOrFail($id); $baseUrl = (string) config('services.bastion_token.base_url'); if ($baseUrl === '') { return response()->json([ 'code' => 500, 'message' => '未配置堡垒机 Token 服务地址,请先在 .env 中配置 BASTION_TOKEN_API_BASE_URL', 'data' => null, ], 500); } $cacheKey = $this->tokenTaskCacheKey($taskId); $taskMeta = Cache::get($cacheKey); if (! is_array($taskMeta) || (int) ($taskMeta['account_id'] ?? 0) !== $account->id) { return response()->json([ 'code' => 404, 'message' => '任务不存在或已过期', 'data' => ['task_id' => $taskId], ], 404); } $statusEndpoint = (string) config('services.bastion_token.status_endpoint', '/bastion_token/{task_id}'); $timeout = (int) config('services.bastion_token.timeout', 30); $statusUrl = str_replace('{task_id}', $taskId, $statusEndpoint); try { $statusResponse = Http::baseUrl($baseUrl) ->acceptJson() ->timeout($timeout) ->retry(2, 300, throw: false) ->get($statusUrl); } catch (ConnectionException|RequestException $exception) { return response()->json([ 'code' => 502, 'message' => '查询 Token 任务状态失败:'.$exception->getMessage(), 'data' => ['task_id' => $taskId], ], 502); } if (! $statusResponse->successful()) { return response()->json([ 'code' => 502, 'message' => '查询 Token 任务状态失败', 'data' => ['task_id' => $taskId, 'response' => $statusResponse->json()], ], 502); } $taskResult = $statusResponse->json(); $status = Str::lower((string) data_get($taskResult, 'status', 'pending')); if ($status !== 'success') { if ($status === 'error') { return response()->json([ 'code' => 502, 'message' => 'Token 刷新失败:'.((string) data_get($taskResult, 'message', '未知错误')), 'data' => ['task_id' => $taskId, 'status' => $status, 'result' => $taskResult], ], 502); } return response()->json([ 'code' => 0, 'message' => 'Token 刷新任务执行中', 'data' => ['task_id' => $taskId, 'status' => 'pending'], ]); } if (! (bool) ($taskMeta['finished'] ?? false)) { $usmAuthentication = (string) data_get($taskResult, 'data.bastion.token_cookies.USM-AUTHENTICATION', ''); $usm = (string) data_get($taskResult, 'data.bastion.token_cookies.USM', ''); // 向后兼容旧版任务服务返回结构 if ($usmAuthentication === '' || $usm === '') { $usmAuthentication = (string) data_get($taskResult, 'data.USM-AUTHENTICATION', $usmAuthentication); $usm = (string) data_get($taskResult, 'data.USM', $usm); } if ($usmAuthentication === '' || $usm === '') { return response()->json([ 'code' => 502, 'message' => 'Token 服务返回数据缺失', 'data' => ['task_id' => $taskId, 'result' => $taskResult], ], 502); } $account->update([ 'usm_authentication' => $usmAuthentication, 'usm' => $usm, 'last_token_refreshed_at' => now(), ]); $taskMeta['finished'] = true; Cache::put($cacheKey, $taskMeta, now()->addMinutes(10)); $this->auditLog($request, 'account_refresh_token', ['bastion_account_id' => $account->id, 'task_id' => $taskId]); } return response()->json([ 'code' => 0, 'message' => 'Token 刷新成功', 'data' => ['task_id' => $taskId, 'status' => 'success', 'account' => $account->fresh()], ]); } private function tokenTaskCacheKey(string $taskId): string { return 'bastion_token_task:'.$taskId; } }