docs: 第11轮搜索审计修复记录(16项P0-P2修复)
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

This commit is contained in:
34047007@qq.com
2026-07-28 12:09:55 +08:00
parent 090938fa2b
commit 89e154caa2
+92 -3
View File
@@ -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 ILIKEDOCS ONLY
- **备注**:第 10 轮修复了中文路径加入 author/journal ILIKE,已在文档中补全。代码已正确
---
截至 2026-07-28,剩余 7 项已知限制: