From 1fcd6a129dd23373215a715b6fd167c5d840fcac Mon Sep 17 00:00:00 2001 From: wyn <1074145239@qq.com> Date: Tue, 26 May 2026 19:02:37 +0800 Subject: [PATCH] Changes --- application/api/controller/References.php | 13 +- application/api/job/ReferenceCheck.php | 4 +- application/api/job/ReferenceCheckTwo.php | 10 +- application/common/ReferenceCheckService.php | 120 ++++++++++--------- 4 files changed, 81 insertions(+), 66 deletions(-) diff --git a/application/api/controller/References.php b/application/api/controller/References.php index ac1a2b77..659c12b6 100644 --- a/application/api/controller/References.php +++ b/application/api/controller/References.php @@ -1426,11 +1426,16 @@ class References extends Base * * POST/GET: p_article_id(必填) * - * 返回 list 中每项含:reference_no、p_refer_id、status(数值)、 + * 返回 list 中每项含:reference_no、p_refer_id、progress_status(数值)、 * total、pending、done、failed、pass、is_pass、last_updated_at、records * - * status 数值含义: + * 分组状态字段 progress_status 数值含义(生命周期顺序): * 0 = 待校验 1 = 校对中 2 = 校对完成 3 = 校对失败 + * + * records[i].status 与分组同一套数值含义(但 record 不会出现 1=校对中): + * 0 = 待校验 2 = 校对完成 3 = 校对失败 + * + * summary 用字符串键:pending / checking / completed / failed */ public function referenceCheckProgressAI() { @@ -1467,8 +1472,8 @@ class References extends Base * 返回字段:p_article_id、status、total、pending、done、failed、progress_percent * total —— 参考文献条数 * pending —— 该条参考文献仍有未跑完明细的数量(含"部分跑完") - * done —— 该条参考文献所有明细都 status=1 的数量 - * failed —— 该条参考文献全部跑完且至少 1 条 status=2 的数量 + * done —— 该条参考文献所有明细都 status=2(完成) 的数量 + * failed —— 该条参考文献全部跑完且至少 1 条 status=3(失败) 的数量 * pending + done + failed = total;progress_percent = (done+failed)/total * * 分组明细请走 referenceCheckProgressAI。 diff --git a/application/api/job/ReferenceCheck.php b/application/api/job/ReferenceCheck.php index 89c5c67d..3977e39e 100644 --- a/application/api/job/ReferenceCheck.php +++ b/application/api/job/ReferenceCheck.php @@ -49,7 +49,7 @@ class ReferenceCheck return; } - if (intval($row['status']) === 1) { + if (intval($row['status']) === ReferenceCheckService::RECORD_COMPLETED) { $job->delete(); return; } @@ -100,7 +100,7 @@ class ReferenceCheck $row = Db::name('article_reference_check_result')->where('id', $checkId)->find(); try { (new ReferenceCheckService())->updateCheckResult($checkId, [ - 'status' => 2, + 'status' => ReferenceCheckService::RECORD_FAILED, 'error_msg' => $msg, ]); } catch (\Exception $e) { diff --git a/application/api/job/ReferenceCheckTwo.php b/application/api/job/ReferenceCheckTwo.php index 564af204..53cb0939 100644 --- a/application/api/job/ReferenceCheckTwo.php +++ b/application/api/job/ReferenceCheckTwo.php @@ -58,7 +58,7 @@ class ReferenceCheckTwo return; } -// if (intval($row['status']) === 1) { +// if (intval($row['status']) === ReferenceCheckService::RECORD_COMPLETED) { // $job->delete(); // return; // } @@ -95,12 +95,12 @@ class ReferenceCheckTwo : '[Crossref复核-无摘要]'; $reason = $tag . ' ' . (isset($llmResult['reason']) ? $llmResult['reason'] : ''); - // LLM 通讯失败:写 status=2 并抛异常触发队列重试 + // LLM 通讯失败:写 status=RECORD_FAILED(3) 并抛异常触发队列重试 if ($requestFailed) { $svc->updateCheckResult($checkId, [ 'confidence' => floatval($llmResult['confidence']), 'reason' => $reason, - 'status' => 2, + 'status' => ReferenceCheckService::RECORD_FAILED, 'error_msg' => isset($llmResult['reason']) ? $llmResult['reason'] : 'LLM request failed', ]); throw new \RuntimeException(isset($llmResult['reason']) ? $llmResult['reason'] : 'LLM request failed'); @@ -111,7 +111,7 @@ class ReferenceCheckTwo 'is_match' => $canSupport ? 1 : 0, 'confidence' => floatval($llmResult['confidence']), 'reason' => $reason, - 'status' => 1, + 'status' => ReferenceCheckService::RECORD_COMPLETED, 'error_msg' => '', ]); $this->oQueueJob->log("Crossref复核写入 id={$checkId} affected={$affected} can_support=" . ($canSupport ? 1 : 0) . " confidence=" . floatval($llmResult['confidence'])); @@ -148,7 +148,7 @@ class ReferenceCheckTwo $row = Db::name('article_reference_check_result')->where('id', $checkId)->find(); try { (new ReferenceCheckService())->updateCheckResult($checkId, [ - 'status' => 2, + 'status' => ReferenceCheckService::RECORD_FAILED, 'error_msg' => $msg, ]); } catch (\Exception $e) { diff --git a/application/common/ReferenceCheckService.php b/application/common/ReferenceCheckService.php index 77b44e9d..b1d3223f 100644 --- a/application/common/ReferenceCheckService.php +++ b/application/common/ReferenceCheckService.php @@ -21,28 +21,32 @@ class ReferenceCheckService const AM_STATUS_FAIL = 2; const AM_STATUS_RUNNING = 3; - /** 引用校对进度(按 reference_no 分组聚合后的对外状态) */ - const PROGRESS_PENDING = 0; // 待校验:分组内全部明细 status=0 - const PROGRESS_CHECKING = 1; // 校对中:分组内部分明细已结束、部分仍为 0 - const PROGRESS_COMPLETED = 2; // 校对完成:分组内全部明细 status=1 - const PROGRESS_FAILED = 3; // 校对失败:分组内全部明细已结束,且至少 1 条 status=2 + /** + * 引用校对状态(生命周期顺序:0→1→2→3 = 待→进行→完成→失败) + * + * 这套常量在两个维度共用: + * - 单条明细(article_reference_check_result.status)只会取 {0, 2, 3} —— 明细不会出现"校对中" + * - 分组(按 reference_no 聚合后的 progress_status)四个值都会用 —— 1=部分跑完、部分仍为 0 + */ + const PROGRESS_PENDING = 0; // 待校验:明细 status=0;分组内全部明细 status=0 + const PROGRESS_CHECKING = 1; // 校对中:仅分组层 —— 部分明细已结束、部分仍为 0 + const PROGRESS_COMPLETED = 2; // 校对完成:明细 status=2;分组内全部明细 status=2 + const PROGRESS_FAILED = 3; // 校对失败:明细 status=3;分组内全部跑完、≥1 条 status=3 /** 整篇文章的引用校对状态(对外整体状态,用于"开始/重置"按钮分流) */ const ARTICLE_PROGRESS_NONE = 0; // 还没有任何校对记录 - const ARTICLE_PROGRESS_RUNNING = 1; // 至少 1 条 status=0(队列里还有未跑完的) + const ARTICLE_PROGRESS_RUNNING = 1; // 至少 1 条明细 status=0(队列里还有未跑完的) const ARTICLE_PROGRESS_COMPLETED = 2; // 所有明细 status != 0(全部已完成或失败) /** - * 单条校对明细的对外状态(getProgressByPArticleId 返回的 records[i].status) + * 单条校对明细的状态(DB 字段 article_reference_check_result.status) * - * DB 里 article_reference_check_result.status 只有 0/1/2 三种值; - * RECORD_PROCESSING 是基于 Redis 队列锁 :status='processing' 的瞬时态, - * 并不持久化。worker 进入 LLM 调用期间 DB.status 仍是 0,需要靠队列锁识别。 + * 这里只列实际写入 DB 的三种值。"校对中"(值 1)是分组层专用,明细不会出现。 + * 数值与 PROGRESS_* 对齐(同一套语义),方便前端/后端混用。 */ const RECORD_PENDING = 0; // 待校对,已入队但还没被 worker 拾起 - const RECORD_COMPLETED = 1; // 校对完成 - const RECORD_FAILED = 2; // 校对失败 - const RECORD_PROCESSING = 3; // 处理中:worker 正在跑 LLM(Redis :status='processing') + const RECORD_COMPLETED = 2; // 校对完成 + const RECORD_FAILED = 3; // 校对失败 /** LLM 评分(confidence)通过阈值:>= 该值视为"通过" */ const PASS_CONFIDENCE_THRESHOLD = 0.65; @@ -187,7 +191,7 @@ class ReferenceCheckService $rows = Db::name('article_reference_check_result') ->where('article_id', $articleId) - ->where('status', 1) + ->where('status', self::RECORD_COMPLETED) ->where('confidence', '<=', 0.65) ->orderRaw('rand()') ->limit(2) @@ -399,14 +403,14 @@ class ReferenceCheckService foreach ($rows as $row) { $st = intval($row['status']); - if ($st === 0) { + if ($st === self::RECORD_PENDING) { $pending++; continue; } - if ($st === 2 || ($st === 1 && intval($row['is_match']) === 0)) { + if ($st === self::RECORD_FAILED || ($st === self::RECORD_COMPLETED && intval($row['is_match']) === 0)) { $hasFail = true; } - if ($st === 1) { + if ($st === self::RECORD_COMPLETED) { $done++; } } @@ -595,8 +599,8 @@ class ReferenceCheckService * * 每条参考文献按其明细 status 分布落桶(互斥): * pending —— 组内任一明细 status=0(含部分跑完的"校对中"也归此桶) - * done —— 组内全部明细 status=1 - * failed —— 组内全部明细已结束、至少 1 条 status=2 + * done —— 组内全部明细 status=2(完成) + * failed —— 组内全部明细已结束、至少 1 条 status=3(失败) * * pending + done + failed = total;progress_percent = (done + failed) / total。 * 分组明细请走 getProgressByPArticleId(控制器 referenceCheckProgressAI)。 @@ -614,8 +618,8 @@ class ReferenceCheckService // 50 条参考文献 → 返回 50 行,PHP 走一次循环分桶即可 $rows = Db::name('article_reference_check_result') ->field('reference_no' - . ', SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS pending_cnt' - . ', SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) AS failed_cnt') + . ', SUM(CASE WHEN status = ' . self::RECORD_PENDING . ' THEN 1 ELSE 0 END) AS pending_cnt' + . ', SUM(CASE WHEN status = ' . self::RECORD_FAILED . ' THEN 1 ELSE 0 END) AS failed_cnt') ->where('p_article_id', $pArticleId) ->group('reference_no') ->select(); @@ -668,19 +672,19 @@ class ReferenceCheckService /** * 按 p_article_id 查整篇引用校对进度,按 reference_no 分组聚合状态,并展开每条明细。 * - * 单条 article_reference_check_result.status: - * 0 = 待校验 1 = 校对完成 2 = 校对失败 + * 状态映射统一遵循"生命周期顺序"(PROGRESS_* / RECORD_* 取值一致): + * 0 = 待校验 1 = 校对中(仅分组层) 2 = 校对完成 3 = 校对失败 * - * 分组(reference_no)状态(返回字段 status,数值类型): - * 0 = PROGRESS_PENDING 待校验 :分组内全部明细 status=0 - * 1 = PROGRESS_CHECKING 校对中 :分组内部分明细已结束、部分仍为 0 - * 2 = PROGRESS_COMPLETED 校对完成:分组内全部明细 status=1 - * 3 = PROGRESS_FAILED 校对失败:分组内全部明细已结束,且至少 1 条 status=2 + * 分组(reference_no)状态返回字段 progress_status: + * - 0 = PROGRESS_PENDING 分组内全部明细 status=0 + * - 1 = PROGRESS_CHECKING 分组内部分明细已结束、部分仍为 0(明细不会出现此值) + * - 2 = PROGRESS_COMPLETED 分组内全部明细 status=2 + * - 3 = PROGRESS_FAILED 分组内全部明细已结束、且至少 1 条 status=3 * - * 每个分组还会展开 records 子数组,给出该 reference_no 下每条 check 明细的: - * - status(同上 0/1/2) - * - confidence 评分 - * - is_pass(confidence >= PASS_CONFIDENCE_THRESHOLD 视为通过) + * records[i] 字段: + * - status 0=待校验 2=完成 3=失败(与分组同一套数值含义,不会出现 1) + * - confidence LLM 评分 + * - is_pass confidence >= PASS_CONFIDENCE_THRESHOLD 视为通过 * * @return array{p_article_id:int, total_groups:int, summary:array, list:array} */ @@ -697,12 +701,12 @@ class ReferenceCheckService ->order('reference_no asc, id asc') ->select(); - // summary 用数值键,0/1/2/3 对应 PROGRESS_* 常量 + // summary 用字符串键,避免数值下标看不出含义;同时保留数值键和 PROGRESS_* 常量对照 $summary = [ - self::PROGRESS_PENDING => 0, - self::PROGRESS_CHECKING => 0, - self::PROGRESS_COMPLETED => 0, - self::PROGRESS_FAILED => 0, + 'pending' => 0, // PROGRESS_PENDING = 0 + 'checking' => 0, // PROGRESS_CHECKING = 1 + 'completed' => 0, // PROGRESS_COMPLETED = 2 + 'failed' => 0, // PROGRESS_FAILED = 3 ]; if (empty($rows)) { return [ @@ -737,11 +741,12 @@ class ReferenceCheckService $groups[$refNo]['total']++; $st = intval($this->arrGet($row, 'status', 0)); - if ($st === 0) { + // record 仅存 {0=待校验, 2=完成, 3=失败};不会出现 1(校对中) + if ($st === self::RECORD_PENDING) { $groups[$refNo]['pending']++; - } elseif ($st === 1) { + } elseif ($st === self::RECORD_COMPLETED) { $groups[$refNo]['done']++; - } elseif ($st === 2) { + } elseif ($st === self::RECORD_FAILED) { $groups[$refNo]['failed']++; } @@ -778,22 +783,27 @@ class ReferenceCheckService $pass = $g['pass']; if ($pending === $total) { - $status = self::PROGRESS_PENDING; + $progressStatus = self::PROGRESS_PENDING; } elseif ($pending === 0) { - $status = $failed > 0 ? self::PROGRESS_FAILED : self::PROGRESS_COMPLETED; + $progressStatus = $failed > 0 ? self::PROGRESS_FAILED : self::PROGRESS_COMPLETED; } else { - $status = self::PROGRESS_CHECKING; + $progressStatus = self::PROGRESS_CHECKING; } // 整体通过校验:分组已全部完成(无 pending、无 failed),且每条 confidence >= 0.65 $g['is_pass'] = ( - $status === self::PROGRESS_COMPLETED + $progressStatus === self::PROGRESS_COMPLETED && $total > 0 && $pass === $total ); - $summary[$status]++; - $g['status'] = $status; + switch ($progressStatus) { + case self::PROGRESS_PENDING: $summary['pending']++; break; + case self::PROGRESS_CHECKING: $summary['checking']++; break; + case self::PROGRESS_COMPLETED: $summary['completed']++; break; + case self::PROGRESS_FAILED: $summary['failed']++; break; + } + $g['progress_status'] = $progressStatus; $list[] = $g; } @@ -988,7 +998,7 @@ class ReferenceCheckService $q->where('status', $status); } if ($onlyMismatch) { - $q->where('status', 1)->where('is_match', 0); + $q->where('status', self::RECORD_COMPLETED)->where('is_match', 0); } return $q->order('am_id asc, cite_tag_start asc, reference_no asc')->select(); } @@ -1018,7 +1028,7 @@ class ReferenceCheckService foreach ($this->listByArticle($articleId, -1) as $r) { $stats['total']++; - if (intval($r['status']) === 0) { + if (intval($r['status']) === self::RECORD_PENDING) { $stats['pending']++; } elseif (intval($r['is_match']) === 1) { $stats['match']++; @@ -1078,14 +1088,14 @@ class ReferenceCheckService } /** - * @param array $rows status=1 的检测结果 + * @param array $rows 已校对完成(status=RECORD_COMPLETED)但 is_match=0 的检测结果 * @return array am_id => indexed bad map */ private function indexBadResults($rows) { $byAm = []; foreach ($rows as $row) { - if (intval($row['status']) !== 1 || intval($row['is_match']) === 1) { + if (intval($row['status']) !== self::RECORD_COMPLETED || intval($row['is_match']) === 1) { continue; } $amId = intval($row['am_id']); @@ -1589,7 +1599,7 @@ class ReferenceCheckService if ($contentA === '' || $contentB === '') { $this->updateCheckResult($checkId, [ - 'status' => 2, + 'status' => self::RECORD_FAILED, 'error_msg' => 'Missing article_main.content or refer_text', ]); throw new \RuntimeException('Missing article_main.content or refer_text'); @@ -1601,13 +1611,13 @@ class ReferenceCheckService $confidence = floatval(isset($llmResult['confidence']) ? $llmResult['confidence'] : 0); $reason = isset($llmResult['reason']) ? $llmResult['reason'] : ''; - // LLM 通讯失败:写 status=2(校对失败) + error_msg,抛异常让队列 worker 走 release(30) 重试; - // 重试 3 次后 ReferenceCheck::markFailed 会保持 status=2 收尾 + // LLM 通讯失败:写 status=RECORD_FAILED(3) + error_msg,抛异常让队列 worker 走 release(30) 重试; + // 重试 3 次后 ReferenceCheck::markFailed 会保持 status=3 收尾 if ($requestFailed) { $this->updateCheckResult($checkId, [ 'confidence' => $confidence, 'reason' => $reason, - 'status' => 2, + 'status' => self::RECORD_FAILED, 'error_msg' => $reason, ]); $this->clearReferenceCheckQueueLock($checkId); @@ -1619,7 +1629,7 @@ class ReferenceCheckService 'is_match' => $canSupport ? 1 : 0, 'confidence' => $confidence, 'reason' => $reason, - 'status' => 1, + 'status' => self::RECORD_COMPLETED, 'error_msg' => '', ]);