# GET /admin/material/list 分类查询语义变更：只查自身，不再含子分类/后代

> **仓库**: HL (后端 hl-resource-service)
> **关联 PR/Issue**: PR #2682, Closes #2677
> **日期**: 2026-05-20
> **影响范围**: admin 「素材库」左侧分类树点击 → 右侧列表查询
> **接收方**: mmg (前端)
> **前端**: 无需改动（请求参数 `categoryCode` / `subCategoryId` 字段没变）

---

## ⚠️ 关键变化

`GET /admin/material/list` 的 `categoryCode` / `subCategoryId` 语义改为「严格只查选中分类自身」，不再向下递归后代。**接口请求/响应字段完全不变**，但**同一个请求返回的素材数会变少**（原本含子的现在只看本节点）。

前端不需要改代码。需要注意的是：UI 上点击同一个分类，列表数字可能**变小**，这是符合本次需求的预期行为，不是 bug。

---

## 一、行为对比

| 请求 | 改前 | 改后 |
|------|------|------|
| `?categoryCode=hotel` | 返回 hotel 一级 + 所有子分类（共 114 条） | 只返回直挂 hotel 一级（`sub_category_id IS NULL`，99 条） |
| `?categoryCode=hotel&subCategoryId=2037033579896369153` | 返回该子分类 + **所有后代**（15 条，因为该叶子无后代故无差异） | 只返回该子分类自身（15 条） |
| `?categoryCode=scenic` | 返回 scenic 一级 + 所有子分类（221 条） | 只返回直挂 scenic（100 条） |
| 无任何分类参数（"全部素材"） | 全部 909 条 | **不变**，仍 909 条 |

测试服 round-trip 实测以上 4 个 case 全部匹配 DB 严格只查自身的预期。

---

## 二、UI 侧需要注意

### 1）左侧分类树左侧的统计数字 vs 右侧列表 total 可能不一致

`GET /admin/material/categories` 接口**未改动**，左侧树各节点显示的统计数仍然是「该节点 + 所有子节点累计」（如 hotel 一级仍显示 114，scenic 一级仍显示 221）。

但点进列表后右侧 `total` 是「只查自身」的数（如 hotel 点开后右侧 99，scenic 99 → 100）。

这是有意为之，不一致是已知现象。用户原话：

> 这个素材分类现在是选中把子分类的数据也查出来的，现在改为只能查看自己分类的数据。

如果产品/运营反馈"左侧数字和列表不一致"，可以解释：左侧数字是「分类总量参考」、右侧 total 是「严格本节点」。

### 2）点击有子的根分类（如 hotel / scenic），右侧可能显示得很少甚至 0

如果一个一级分类下所有素材都被运营整理到了二级子分类（没有直挂一级的），那点击一级根节点列表会接近空。用户需要手动展开二级子分类去看具体素材。这也是符合本次需求"只查自身"的字面语义。

---

## 三、单元测试覆盖

`MaterialListCategoryFilterTest`（新增）用 ArgumentCaptor 覆盖 3 个 case：

1. 选根节点 → mapper 入参 `selectedSubIds=null, requireSubCategoryNull=true`
2. 选叶子子分类 → mapper 入参 `selectedSubIds=[X], requireSubCategoryNull=false`
3. 选有后代的中间子分类 → mapper 入参 `selectedSubIds=[X]`（不含后代），且 `verifyNoInteractions(categoryService)` 证明递归调用链已彻底断开

180 个 material 包测试全绿。

---

## 四、不需要前端配合

- ✅ 无接口字段变更（VO / DTO 不动）
- ✅ 无新增接口
- ✅ 无网关路由变更
- ✅ 无 Flyway V*.sql 数据库迁移

mmg 不需要发版。仅在产品/运营反馈"分类点开少了/为空"时，按本文档解释即可。
