fix: 第28轮搜索审计修复 — 否定范围pub_year/异常降级/YYYY-MM组内/NULL安全等8项
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

This commit is contained in:
34047007@qq.com
2026-07-29 05:33:21 +08:00
parent d064dcd0ca
commit 2b085d3445
3 changed files with 160 additions and 10 deletions
+60
View File
@@ -2095,3 +2095,63 @@ if _PARTIAL_DATE_RE.match(end_val):
- 解析器 36 测试全部通过
- 5 个新增 R27 正确性检查通过
- 全量套件中仅外部服务连接失败(httpx.ConnectError),与改动无关
## R28 (2026-07-29): 第三轮并行审计修复
### R28-1 (MEDIUM): 组作用域标记污染 `negated_date_ranges`
**文件**[pubmed_query_parser.py:483-486](backend/app/services/pubmed_query_parser.py#L483)
**根因**`__RANGE_*` 标记的 `negated_date_ranges` 聚合未过滤组内标记(`group_id >= 0`),导致组内 NOT 日期范围被错误地应用到顶层日期字段的 `not_(cond)`,产生 `NOT (pub_year >= X AND pub_year <= Y)` 而非正确的组内处理。
**修复**:过滤 `t.group_id < 0`(仅顶层标记),组内标记由 `_build_marker_condition` + group processing 路径独立处理。
### R28-2 (LOW): 标记 field 被组 field 覆盖
**文件**[pubmed_query_parser.py:883](backend/app/services/pubmed_query_parser.py#L883)
**根因**:组 field 标签赋值循环未跳过 `__RANGE_*` 标记,导致标记的 `field` 属性从 `__RANGE_DP__` 被覆盖为 `dp`,破坏后续标记识别。
**修复**:跳过 `getattr(t, '_is_range_end', False)` 的标记项。
### R28-3 (CRITICAL): DP 否定范围使用 `pub_date` 而非 `pub_year`
**文件**[search_engine.py:1407-1436](backend/app/services/search_engine.py#L1407)
**根因**`_neg_single_dates` DP 分支对所有否定范围使用 `pub_date` 比较,但当范围是完整年份(如 `2024:2024`)且用户在 `pub_year` 列上有不同数据质量时,`pub_date` 可能产生不准确的结果。
**修复**:检测 `_neg_from` 以 `-01-01` 结尾且 `_neg_to` 以 `-12-31` 结尾时,切换到 `pub_year` 比较,同时保持 NULL 安全(`or_(not_(cond), pub_year.is_(None))`)。
### R28-4 (CRITICAL): `_pubmed_conditions` 异常导致未清理查询
**文件**[search_engine.py:254,300,306,331](backend/app/services/search_engine.py#L254)
**根因**`_pubmed_conditions()` 内未捕获的异常传播到 `search()` 外导致 500 错误,且解析失败后的文本降级检查 `not any([...])` 未包含此路径。
**修复**:添加 `_pubmed_failed` 标志,`except` 块中设为 `True` 并强制文本降级和查询清理。
### R28-5 (HIGH): YYYY-MM 在组内路径中未处理
**文件**[search_engine.py:1645-1665](backend/app/services/search_engine.py#L1645)
**根因**`_single_term_condition()` 处理日期字段时,仅在 `len(_term_text) == 4`(年份)和完整 ISO 日期(YYYY-MM-DD)之间处理。`YYYY-MM` 格式(如 `2024-06`)既不匹配 4 位数字也不匹配完整日期,静默降级。R27 修复仅在顶层路径生效,组内 `_single_term_condition` 路径遗漏。
**修复**:插入 `_PARTIAL_DATE_RE` + `_expand_partial_date` 处理,将 `YYYY-MM` 展开为 `YYYY-MM-01`~`YYYY-MM-30/31` 的日期范围条件。
### R28-6 (MEDIUM): NULL 安全 NOT 缺失
**文件**[search_engine.py:1267-1273,1343-1365,1444-1446,1502-1505](backend/app/services/search_engine.py)
**根因**:多处 `not_(date_condition)` 未包装 NULL 安全,导致 `pub_date`/`pub_year` 等日期列为 NULL 的行被错误排除:
- 顶层 DP `not_(cond)` 路径(line 1446
- 非 DP 字段 `negated_date_ranges` 路径(line 1505
- 组标记 NOT 路径(非否定组中的 `is_not` 标记,line 1271
- 否定组包装 `not_(combined)`line 1347,通过 `_neg_group_date_fields` 跟踪)
**修复**:对所有路径使用 `or_(not_(cond), col.is_(None))`。组标记路径按 `_tag` 查找对应列(DP 同时加上 `pub_year.is_(None)` + `pub_date.is_(None)`)。
## 验证
-**1007 tests passed, 0 failed**
- 全部搜索测试通过(parser 36 + search engine 集成测试)
- 无回归