Files
tougao/application/api/controller/Plagiarism.php
2026-05-07 11:45:55 +08:00

218 lines
7.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\api\controller;
use think\Db;
use think\Response;
use app\common\PlagiarismService;
/**
* 论文查重Turnitin / Crossref Similarity Check控制器。
*
* 触发方式:纯手工(编辑后台点"查重"按钮)。
* 报告策略:在线 viewer URL 临时签名 + PDF 永久落盘 runtime/plagiarism/。
*
* 主要接口:
* POST submit 触发查重
* GET getStatus 轮询单条查重状态(前端 ajax
* GET getList 列出某 article 的全部查重记录
* GET getReportUrl 获取/刷新在线查看 URL
* GET downloadReport 下载本地 PDF
* POST retry 重新触发(创建新行)
* GET features 探活(开发调试用)
*/
class Plagiarism extends Base
{
public function __construct(\think\Request $request = null)
{
parent::__construct($request);
}
/**
* 触发查重
*
* 入参:
* article_id 必填
* file_url 选填;不传则按 article_id 在 t_article_file 找 manuscirpt
* editor_id 选填;触发人 user_id前端拿不到也可以传 0
*/
public function submit()
{
$articleId = intval($this->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';
}
}