request->param('article_id', 0)); $fileUrl = trim($this->request->param('file_url', '')); $editorId = intval($this->request->param('editor_id', 0)); if ($articleId <= 0) { return jsonError('article_id required'); } try { $svc = new PlagiarismService(); $localPath = $fileUrl !== '' ? $svc->resolveFileUrlToLocal($fileUrl) : $svc->locateArticleManuscript($articleId); $checkId = $svc->submit($articleId, $localPath, $editorId, 'manual'); return jsonSuccess(['check_id' => $checkId]); } catch (\Throwable $e) { return jsonError($e->getMessage()); } } /** * 重试 = 提交一次新查重(保留历史) */ public function retry() { return $this->submit(); } /** * 取单条查重状态 */ public function getStatus() { $checkId = intval($this->request->param('check_id', 0)); if ($checkId <= 0) { return jsonError('check_id required'); } $row = Db::name('plagiarism_check')->where('check_id', $checkId)->find(); if (!$row) { return jsonError('not found'); } return jsonSuccess($this->formatRow($row)); } /** * 列出某 article 的全部查重记录(按时间倒序) */ public function getList() { $articleId = intval($this->request->param('article_id', 0)); if ($articleId <= 0) { return jsonError('article_id required'); } $rows = Db::name('plagiarism_check') ->where('article_id', $articleId) ->order('check_id desc') ->select(); $out = []; foreach ($rows as $r) { $out[] = $this->formatRow($r); } return jsonSuccess(['list' => $out]); } /** * 取在线查看 URL;过期则自动刷新 */ public function getReportUrl() { $checkId = intval($this->request->param('check_id', 0)); if ($checkId <= 0) { return jsonError('check_id required'); } try { $row = Db::name('plagiarism_check')->where('check_id', $checkId)->find(); if (!$row) { return jsonError('not found'); } if ($row['state'] != 3) { return jsonError('check not completed yet, state=' . $row['state']); } $needRefresh = empty($row['view_only_url']) || intval($row['view_only_url_expire']) < time() + 60; if ($needRefresh) { $svc = new PlagiarismService(); $info = $svc->refreshViewerUrlFor($checkId); return jsonSuccess([ 'view_only_url' => $info['url'], 'expire' => $info['expire'], ]); } return jsonSuccess([ 'view_only_url' => $row['view_only_url'], 'expire' => intval($row['view_only_url_expire']), ]); } catch (\Throwable $e) { return jsonError($e->getMessage()); } } /** * 直接吐 PDF 二进制流给浏览器下载 */ public function downloadReport() { $checkId = intval($this->request->param('check_id', 0)); if ($checkId <= 0) { return jsonError('check_id required'); } $row = Db::name('plagiarism_check')->where('check_id', $checkId)->find(); if (!$row || empty($row['pdf_local_path'])) { return jsonError('report not available'); } $rootDir = ROOT_PATH ?: dirname(dirname(dirname(__DIR__))); $abs = rtrim($rootDir, '/\\') . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $row['pdf_local_path']); if (!is_file($abs)) { return jsonError('pdf file missing on disk: ' . $row['pdf_local_path']); } $filename = sprintf('plagiarism_check_%d_article_%d.pdf', $row['check_id'], $row['article_id']); return Response::create(file_get_contents($abs), 'html', 200, [ 'Content-Type' => 'application/pdf', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', 'Content-Length' => (string)filesize($abs), ]); } /** * Turnitin 探活(开发调试用) */ public function features() { try { $tii = new \app\common\TurnitinService(); return jsonSuccess($tii->featuresEnabled()); } catch (\Throwable $e) { return jsonError($e->getMessage()); } } // ---------- 内部 ---------- private function formatRow($r) { return [ 'check_id' => intval($r['check_id']), 'article_id' => intval($r['article_id']), 'journal_id' => intval($r['journal_id']), 'state' => intval($r['state']), 'state_label' => $this->stateLabel($r['state']), 'similarity_score' => floatval($r['similarity_score']), 'tii_report_status' => (string)$r['tii_report_status'], 'has_pdf' => !empty($r['pdf_local_path']), 'has_viewer_url' => !empty($r['view_only_url']) && intval($r['view_only_url_expire']) > time(), 'attempts' => intval($r['attempts']), 'error_msg' => (string)$r['error_msg'], 'source_file_name' => (string)$r['source_file_name'], 'trigger_source' => (string)$r['trigger_source'], 'triggered_by' => intval($r['triggered_by']), 'ctime' => intval($r['ctime']), 'utime' => intval($r['utime']), ]; } private function stateLabel($state) { $map = [ 0 => '待上传', 1 => '上传中', 2 => '比对中', 3 => '完成', 4 => '失败', ]; return isset($map[$state]) ? $map[$state] : 'unknown'; } }