respond('GET', static fn (): array => (new TangDetectiveProgress())->catalog()); } public function progress() { return $this->respond('GET', fn (): array => (new TangDetectiveLogic())->read((int) $this->userId)); } public function saveProgress() { return $this->respond('POST', function (): array { if (strtolower($this->request->contentType()) !== 'application/json') { throw new TangDetectiveProgressException('UNSUPPORTED_MEDIA_TYPE', '请使用 JSON 提交存档'); } $policy = new TangDetectiveProgress(); $request = $policy->decodeRequest((string) $this->request->getContent()); return (new TangDetectiveLogic($policy))->save((int) $this->userId, $request); }); } private function respond(string $method, callable $action) { try { // Guard methods too: ThinkPHP's conventional controller routes remain enabled. if (($method === 'GET' && !$this->request->isGet()) || ($method === 'POST' && !$this->request->isPost())) { throw new TangDetectiveProgressException('METHOD_NOT_ALLOWED', '请求方式不正确'); } if ($this->userId <= 0) { throw new TangDetectiveProgressException('AUTH_REQUIRED', '请先登录'); } return $this->data($action())->header(['Cache-Control' => 'no-store']); } catch (TangDetectiveProgressException $exception) { return $this->fail($exception->getMessage(), ['error_code' => $exception->errorCode()], 0, 0) ->header(['Cache-Control' => 'no-store']); } catch (\Throwable $exception) { try { Log::warning('tang_detective_request_failure', [ 'user_id' => $this->userId, 'error_code' => 'STORAGE_UNAVAILABLE', 'exception_class' => get_class($exception), ]); } catch (\Throwable $loggingException) { // No raw exception, request body, token or SQL is exposed on failure. } return $this->fail('云端存档暂时不可用,本机进度仍可保留', ['error_code' => 'STORAGE_UNAVAILABLE'], 0, 0) ->header(['Cache-Control' => 'no-store']); } } }