# 资源模块分页与搜索修复（素材 limit 参数 + 活动/餐厅/服务按城市搜索）

- **日期**: 2026-04-18
- **服务**: hl-resource-service（端口 8082）
- **类型**: BUG 修复（非破坏性，向后兼容）
- **优先级**: P2
- **前端影响**: **无需改代码**，但会有"分页不再多算空页 / 搜索匹配范围扩大"的观感变化
- **关联 PR**: #798、#806
- **关联 Issue**: #797、#804

---

## 总览

本次合并两个修复到 dev，都在 `hl-resource-service`：

| PR | 影响接口 | 问题 | 修复 |
|---|---|---|---|
| #798 | `GET /admin/material/list` | 前端传 `?limit=12` 后端不识别，始终按默认 `pageSize=20` 返回 | `pageSize` 字段追加 `@JsonAlias("limit")`，并新增 `setLimit` setter |
| #806 | `GET /admin/activity/items`、`GET /admin/restaurant/items`、`GET /admin/service/items` | `keyword` 只匹配名称/描述/亮点，搜"呼伦贝尔"等城市名匹配不到 | `keyword` like 条件扩展到省/市/区（或地址）字段 |

> ⚠️ `GET /admin/scenic/spots`（景区列表）**未改动**，保持原行为（按用户要求）。

---

## 修复一：素材列表支持 `limit` 参数（PR #798）

### 影响接口

```
GET /admin/material/list
```

### 现象对比

**修复前**：

```
请求: GET /admin/material/list?page=1&limit=12&categoryCode=product&fileType=image
响应: { "data": { "records": [...共 20 条...], "total": 56, "page": 1, "pageSize": 20 } }
```

- 前端本来就在传 `?limit=12`，但后端字段名叫 `pageSize`，不识别 `limit`，直接走默认值 20
- 前端计算 `总页数 = ceil(total / limit) = ceil(56 / 12) = 5`，但实际后端按 pageSize=20 返回 → 真实页数只有 3
- 用户点到第 4、5 页 → `records` 为空数组，体验是"后面几页是空的"

**修复后**：

```
请求: GET /admin/material/list?page=1&limit=12&categoryCode=product&fileType=image
响应: { "data": { "records": [...共 12 条...], "total": 56, "page": 1, "pageSize": 12 } }
```

- `limit` 被后端识别，`pageSize` 回传为请求的 `limit` 值
- 前端分页控件算出的页数与后端实际页数一致，不再出现空页

**行为保持不变**：

- 前端如果传 `?pageSize=15` → 后端仍按 15 处理（原行为）
- 同时传 `?pageSize=20&limit=12` → 以 `limit=12` 为准（`@JsonAlias` 会覆盖同名字段，不建议前端同时传）
- 不传任何分页参数 → 默认 `pageSize=20`

### 前端适配

- **无需改代码**。前端本来就传 `limit`，之前是无效参数，现在生效
- **观感变化**：分页器的页数显示可能"变少了"，那是因为之前的页数是虚高的；真实数据没变
- 若前端之前为了兼容"后面是空页"写过判断（如跳到最后一页发现是空就回退），可以保留，不会有副作用

### 接口契约

**请求**：

```
GET /admin/material/list?page=1&limit=12&categoryCode=product&fileType=image
Authorization: Bearer {token}
tenant-id: {租户ID}
```

常用查询参数：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | Integer | 否 | 页码，默认 1 |
| pageSize / limit | Integer | 否 | 每页条数，默认 20；**两个名字后端都认**，推荐 `pageSize` |
| categoryCode | String | 否 | 素材分类码（例：`product`） |
| fileType | String | 否 | 文件类型（例：`image`） |

**响应**：

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [ /* 素材对象列表 */ ],
    "total": 56,
    "page": 1,
    "pageSize": 12
  },
  "success": true
}
```

---

## 修复二：活动/餐厅/服务列表 `keyword` 支持省市区模糊搜索（PR #806）

### 影响接口

| 接口 | 模块 | 说明 |
|------|------|------|
| `GET /admin/activity/items` | 游玩项目 | 活动列表（按 keyword / cityKeyword 检索） |
| `GET /admin/restaurant/items` | 餐厅 | 餐厅列表 |
| `GET /admin/service/items` | 服务 | 服务项列表 |

> `GET /admin/scenic/spots`（景区）**未改**，保持原行为。

### 现象对比

**修复前**（以 activity 为例）：

```
请求: GET /admin/activity/items?keyword=呼伦贝尔&page=1&pageSize=20
响应: { "data": { "records": [], "total": 0, ... } }   ← 0 条
```

- `keyword` 只 like 匹配 `name` / `description` / `highlights` 三个字段
- 如果一条活动名字叫"草原骑马"、描述里没写"呼伦贝尔"，就算它 city 是"呼伦贝尔"，也搜不到

**修复后**：

```
请求: GET /admin/activity/items?keyword=呼伦贝尔&page=1&pageSize=20
响应: { "data": { "records": [...呼伦贝尔下面的活动...], "total": 18, ... } }
```

- `keyword` like 匹配范围扩大到 **名称 + 描述 + 亮点 + 省 + 市 + 区**
- 搜"海拉尔"、"内蒙古"、"额尔古纳"等都会命中对应地名的记录

### 各接口的 keyword 匹配字段

| 接口 | 修复前 keyword 匹配字段 | 修复后 keyword 匹配字段 |
|------|-------------------------|-------------------------|
| `/admin/activity/items` | name, description, highlights | name, description, highlights, **province, city, district** |
| `/admin/restaurant/items` | name, description, highlights | name, description, highlights, **province, city, district** |
| `/admin/service/items` | name, description, highlights | name, description, highlights, **province, city, address** *(service_item 表无 district 字段,用 address 代替)* |

### 与 `cityKeyword` 的关系

- `cityKeyword` 是三个接口已有的**独立参数**，仍然只按 city 字段模糊匹配
- 前端若同时传 `keyword` 和 `cityKeyword`，两者是 **AND** 组合（都要满足），行为不变
- 建议前端保持现有用法不动；单独搜框用 `keyword` 即可覆盖绝大多数场景

### 前端适配

- **无需改代码**。`keyword` 参数名、类型、返回结构完全不变
- **观感变化**：同一个 keyword 在活动/餐厅/服务列表里可能比以前多返回一些结果（因为多匹配了省/市/区字段），这是预期内的
- 若前端文案里显式写了"按名称/描述搜索"，建议改成更宽松的"按名称、描述、地点搜索"（可选，非强制）

### 接口契约（以 activity 为例，另两个类似）

**请求**：

```
GET /admin/activity/items?keyword=呼伦贝尔&page=1&pageSize=20
Authorization: Bearer {token}
tenant-id: {租户ID}
```

常用查询参数（均为可选）：

| 参数 | 类型 | 说明 |
|------|------|------|
| keyword | String | 关键词（**本次扩展**：名称/描述/亮点/省/市/区） |
| cityKeyword | String | 仅按城市匹配（未变） |
| page | Integer | 页码，默认 1 |
| pageSize | Integer | 每页条数，默认 20 |

**响应结构**：保持原样，不赘述。

---

## 向后兼容性

- ✅ 接口路径、请求参数名、响应字段结构、HTTP code、业务 code **完全不变**
- ✅ 前端已有代码无需任何修改
- ✅ 景区接口未动
- ⚠️ 两个修复会带来"分页页数更准"和"搜索结果变多"的观感变化，属于预期行为

---

## 重启服务

> **仅需重启** `hl-resource-service`（端口 8082）

- **测试环境**：通过 Deploy Panel 重启（参考 `memory/deploy-panel.md`）
- **正式环境**：本次**先不上正式**
- **DDL 变更**：无
- **配置变更**：无

---

## 关联信息

| 项 | 值 |
|---|---|
| PR #798 | fix(resource): 素材列表分页兼容前端 limit 参数 → dev |
| PR #806 | fix(resource): 活动/餐厅/服务列表 keyword 支持省市区模糊搜索 → dev |
| Issue #797 | 素材列表 limit 参数不生效导致分页虚高 |
| Issue #804 | 活动/餐厅/服务按城市名搜索匹配不到 |
| 服务 | hl-resource-service（8082） |
