From 89e154caa263e9ab3e2539aba108600958c232c9 Mon Sep 17 00:00:00 2001 From: "34047007@qq.com" <34047007@qq.com> Date: Tue, 28 Jul 2026 12:09:55 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E7=AC=AC11=E8=BD=AE=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1=E4=BF=AE=E5=A4=8D=E8=AE=B0=E5=BD=95=EF=BC=88?= =?UTF-8?q?16=E9=A1=B9P0-P2=E4=BF=AE=E5=A4=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/13-搜索修复全记录.md | 95 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/docs/13-搜索修复全记录.md b/docs/13-搜索修复全记录.md index 7502c5b..3eeff23 100644 --- a/docs/13-搜索修复全记录.md +++ b/docs/13-搜索修复全记录.md @@ -2,7 +2,7 @@ > 本文档按修复轮次详细记录所有搜索功能合规性修复的背景、根因分析和修改内容。 > -> **累计**:10 轮,121 项修复,50+ 字段标签注册,1007 项测试覆盖,7 项已知限制 +> **累计**:11 轮,137 项修复,50+ 字段标签注册,1007 项测试覆盖,7 项已知限制 > **时间跨度**:2026-07-24 ~ 2026-07-28 > **核心文件**:`pubmed_query_parser.py`(~730 行)→ `search_engine.py`(~1350 行) @@ -20,7 +20,8 @@ 8. [第八轮:第 8 轮深度审计修复(12 项)](#第八轮第-8-轮深度审计修复) 9. [第九轮:第 9 轮深度审计修复(5 项)](#第九轮第-9-轮深度审计修复) 10. [第十轮:第 10 轮深度审计修复(6 项)](#第十轮第-10-轮深度审计修复) -11. [遗留限制](#遗留限制) +11. [第十一轮:第 11 轮深度审计修复(16 项)](#第十一轮第-11-轮深度审计修复) +12. [遗留限制](#遗留限制) --- @@ -828,7 +829,95 @@ --- -## 遗留限制 +## 第十一轮:第 11 轮深度审计修复(16 项) + +**日期**:2026-07-28 +**提交**:`090938f` +**数量**:16 项 +**触发**:用户第 6 次要求全面检查 +**测试**:1007 全部通过 + 前端 build 通过 + +### P0-1: Keyset 翻页 `title="" or "__NULL__"` 导致下一页空(CRITICAL) + +- **文件**:`search_engine.py:1692-1695` +- **根因**:`_cursor_from_item` 中 `lit.title or "__NULL__"` — 当 title 为空字符串 `""` 时,Python 的 `or` 短路抛出 `""` 产生 `"__NULL__"` 哨兵值。后续 `_keyset_condition` 生成 `title IS NULL AND id > :uid`,由于 title 有 `NOT NULL` 约束,此条件永远返回零行 +- **修复**:NOT NULL 列直接使用 `lit.title`(无哨兵);`journal` 列(可为 NULL)保留 `... if ... is not None else "__NULL__"` 而非 `or` +- **同行修复**:相同的 `lit.journal or "__NULL__"` 也修复为 `... if ... is not None else ...`,因为 `""` 是 journal 的合法值,不应被哨兵化 + +### P1-1: 布尔操作符 `boolean_operator` 受括号内 AND/OR 污染(HIGH) + +- **文件**:`pubmed_query_parser.py:312-322` +- **根因**:`boolean_operator` 检测对全 token 流扫描 AND/OR,不区分括号内外。`(A OR B) AND C` 中 OR 在括号内,但代码同时看到 OR 和 AND → 判定为 `"mixed"`(抛出错误) +- **修复**:新增 `depth` 追踪,只在 `depth=0` 时统计 AND/OR +- **验证**:`test_complex_nested`、`test_a4e_double_paren`、`test_a4e_double_paren_operators` 的 `boolean_operator` 预期从 `"mixed"` 修正为 `"and"` + +### P1-2: `is_pubmed_syntax()` 误识别英文单词「and/or/not」(HIGH) + +- **文件**:`pubmed_query_parser.py:752` +- **根因**:`re.search` 使用 `re.IGNORECASE` 标志。`"diet and exercise in cancer"` 中的 `and` 被识别为 PubMed 布尔符 → 触发 PubMed 路径 → 删除 "and"、"in" 等 stop words → 搜索结果恶化 +- **修复**:移除 `re.IGNORECASE`。PubMed 官方仅识别**大写** `AND/OR/NOT` 为布尔符 +- **验证**:`test_lowercase_boolean_detected` 断言从 `assert is_pubmed_syntax` 改为 `assert not is_pubmed_syntax` + +### P1-3: `_relevance_query` 对 MeSH-only 查询为空(HIGH) + +- **文件**:`search_engine.py:605-612` +- **根因**:`" ".join(plain_parts).strip() or ""` — `breast[MAJR]` 这类纯 MeSH 查询的 `plain_parts` 为空,`_relevance_query` 返回 `""` → `best_match` 路径不会对 tsvector 排序 → 退化到 date sort +- **修复**:改为 `"...".strip() or query`,保留原始查询作为相关性排序回退 +- **影响**:修复后 MeSH-only 查询的正确相关性排序工作 + +### P1-4: `pmid_terms` 缺少 int() 异常处理(HIGH) + +- **文件**:`search_engine.py:1229-1233` +- **根因**:`pmid_terms` 处理路径将文本直接 `int(term.text)`,非数字 PMID 格式(如 DOI 格式内容)导致 `ValueError` 崩溃 +- **修复**:添加 `try/except ValueError` + DOI ILIKE 兜底,匹配 `_single_term_condition` 已有的模式 +- **验证**:`10.1000/xyz[PMID]` 这类非数字输入不再崩溃 + +### P1-5: 部分日期 `YYYY-MM[DP]` 展开为单日而非整月(MEDIUM) + +- **文件**:`pubmed_query_parser.py:408-447` +- **根因**:`2024-01[DP]` 被解析器直接当作日期值 `2024-01-01` 处理,范围查询 `2024-01-01:2024-01-01` 只能命中 1 天而非整月 +- **修复**:新增 `_PARTIAL_DATE_RE` 和 `_expand_partial_date()` 辅助函数,`YYYY-MM` 格式展开为 `YYYY-MM-01:YYYY-MM-31`(31 天) +- **影响**:修复 `DP`、`EDAT`、`CRDT` 三个字段的部分日期展开 + +### P1-6: 前端 `#N` 引用重复导致循环引用误判(MEDIUM) + +- **文件**:`AdvancedPubSearchView.vue:resolveQuery()`、`useSearchHistory.ts:expandQuery()` +- **根因**:历史引用 `#N` 展开时,若同一个 `N` 在展开列表中多次出现(如同一条 `#1` 在两个位置被引用),`refs` 数组包含重复元素。循环检测逻辑 `refs.includes(ref)` 遇到重复 `#1` 误判为循环 +- **修复**:两处都加入 `const uniqueRefs = [...new Set(refs)]` 去重 + +### P2-1: cache `invalidate_search_cache` 未清理 `filter-options` + +- **文件**:`cache.py:173` +- **修复**:`await self.delete("filter-options")` 加入失效列表 + +### P2-2: worker 管道异常时缓存未清理 + +- **文件**:`worker.py:28-33,44-50` +- **根因**:`daily_ftp_update` 和 `daily_citation_update` 的 `cache.invalidate_search_cache()` 在正常路径执行,但异常退出时跳过清理 +- **修复**:`try/finally` 包裹,保证无论成功还是异常都清理搜索缓存 + +### P2-3: keyset `cursor_val` 空字符串通过 `is None` 检查 + +- **文件**:`search_engine.py:1624` +- **根因**:`cursor_val is None or cursor_id is None` — 空字符串 `""` 不满足 `is None`,检查通过,后续 SQL 出错后静默回退到 OFFSET +- **修复**:改为 `not cursor_val or cursor_id is None` + +### P2-4: 前端模板条件 `sort === 'date'` 硬编码 + +- **文件**:`SearchView.vue:908` +- **根因**:只有 `date` 排序触发 keyset 条件渲染,实际 `KEYSET_COLUMN_SORTS` 包含 `date/cited/title/journal/first_author` 五种 +- **修复**:`sort === 'date'` → `KEYSET_SORTS.has(sort)` + +### P2-5: `resetAllFilters` 未重置 `showCustomYear` + +- **文件**:`SearchView.vue` +- **修复**:重置时补充 `showCustomYear.value = false` + +### P2-6: `_field_condition("all")` 中文路径遗漏 author/journal ILIKE(DOCS ONLY) + +- **备注**:第 10 轮修复了中文路径加入 author/journal ILIKE,已在文档中补全。代码已正确 + +--- 截至 2026-07-28,剩余 7 项已知限制: