docs: 第11轮搜索审计修复记录(16项P0-P2修复)
This commit is contained in:
+92
-3
@@ -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 项已知限制:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user