From d77b1088248d20c5bd108c0504fd6f67fb54857d Mon Sep 17 00:00:00 2001 From: jw Date: Wed, 16 Sep 2026 19:15:28 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#7827=20=E5=9B=A2=E6=9C=9F?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=AF=BC=E6=B8=B8/=E6=91=84=E5=BD=B1?= =?UTF-8?q?=E5=80=99=E9=80=89=E5=88=86=E9=A1=B5=E4=B8=8E=E6=A8=A1=E7=B3=8A?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=EF=BC=88=E6=96=B0=E5=A2=9E=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...¯¼游摄影候选分页与模糊搜索-新增接口-管理后台.md | 328 ++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 changelogs-v2/2026-09/16_7827_团期配置导游摄影候选分页与模糊搜索-新增接口-管理后台.md diff --git a/changelogs-v2/2026-09/16_7827_团期配置导游摄影候选分页与模糊搜索-新增接口-管理后台.md b/changelogs-v2/2026-09/16_7827_团期配置导游摄影候选分页与模糊搜索-新增接口-管理后台.md new file mode 100644 index 00000000..ac24d065 --- /dev/null +++ b/changelogs-v2/2026-09/16_7827_团期配置导游摄影候选分页与模糊搜索-新增接口-管理后台.md @@ -0,0 +1,328 @@ +--- +schema: "hl-changelog/v2" +ticket: "7827" +title: "团期配置导游/摄影候选新增分页与模糊搜索端点(旧候选端点不变)" +consumer: "admin" +author: "jw(GIT)" +change_type: "新增接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "新增分页候选端点,已在测试服经真实网关验收(26 项全部通过,见第八节与工单 #7827 验收评论);旧候选端点不变。前端待办:① 配置导游 / 配置摄影弹窗切换到 GET .../staff/candidates/page,加搜索框与分页;② 每行显示 staffTypeName · staffName · staffPhone(电话保持脱敏);③ 已选名单以 GET .../staff 为准,保存时整份提交 PUT .../staff,不能只提交当前页勾选的人(见第四节)。前端切换后,后端另开工单下线旧候选端点。" +updated_at: "2026-09-16" +base: "dev-v3" +--- + +# order-v3: 团期配置导游/摄影候选新增分页与模糊搜索端点 + +> **存放目录**: `changelogs-v2/{YYYY-MM}/`(管理后台,二期 order-v3) +> +> **服务**: hl-order-service-v3 (端口 8086)、hl-resource-service (端口 8082,仅新增内部接口) +> **PR**: #7831 +> **Issue**: #7827 +> **日期**: 2026-09-16 +> **影响范围**: 团期详情页「配置导游」「配置摄影」弹窗的人员候选列表 + +--- + +## ⚠️ 关键变化 + +1. **新增分页端点** `GET /v3/admin/group-batch/{productBatchId}/staff/candidates/page`,支持 `keyword` 模糊搜索与 `page` / `pageSize` 分页,返回 `PageResult`。**旧端点 `GET .../staff/candidates` 请求与响应完全不变**,前端可按自己的节奏切换;切换完成后后端再另开工单下线旧端点。 +2. **分页后保存弹窗有一个必须处理的坑**:`PUT /v3/admin/group-batch/{productBatchId}/staff` 是**全量覆盖**——请求里没有的人会被软删,并同步到全团活跃订单。前端**不能只拿当前页的勾选去拼保存请求**,否则其他页已选的人会被静默删掉。已选名单必须以 `GET /v3/admin/group-batch/{productBatchId}/staff` 的返回为准,候选页只负责增删,见第四节。 +3. **排序与旧端点不同**:旧端点是「先导游、后领队」按类型拼接;分页端点把导游位的两类人放在一次查询里,统一按 `sortOrder` 升序、`staffId` 升序排列,所以导游和领队会穿插出现。 +4. **弹窗展示字段后端已齐,无需新增**(jw 2026-09-16 口径):每行显示「角色名称 · 人员名称 · 联系电话」,分别取 `staffTypeName`、`staffName`、`staffPhone`。电话**保持脱敏**(前 3 后 4,如 `138****1002`),后端不提供明文。 + +--- + +## 一、背景 + +「配置导游」「配置摄影」弹窗此前调用的旧候选端点会一次性拉取资源库全部在职人员:没有分页,也不能搜索;导游位还要分两次拉取(导游、领队)再合并,头像也按全量人员去取。人员库一变大,弹窗就不好找人,接口也越来越慢。 + +本次新增分页端点:资源服务一次按类型集合分页查询,order-v3 只为**当前页**的人标记已选、取头像。 + +jw 2026-09-16 同时确认:按日期分段配置导游/摄影(例如第 1–3 天是 A、第 4–5 天是 B)**本期不做**。现有三张人员配置表都没有日期字段,一个人配上去就代表负责整个行程。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 分页查询团期人员配置候选 | GET | `/v3/admin/group-batch/{productBatchId}/staff/candidates/page` | 新增 | 带关键词与分页;条目结构与旧候选端点相同 | + +> 资源服务同时新增了内部接口 `GET /internal/staff/page-available`,仅供 order-v3 通过 Feign 调用,网关不对外暴露,前端无需关注。 + +--- + +## 三、接口详情 + +### 1. 分页查询团期人员配置候选 `GET /v3/admin/group-batch/{productBatchId}/staff/candidates/page` + +**VO**: `Result>` + +#### 使用场景 + +团期详情页点「配置导游」或「配置摄影」,弹窗打开时请求第 1 页;输入搜索词、翻页时再次请求。导游位传 `role=GUIDE`,摄影位传 `role=PHOTOGRAPHER`。 + +弹窗里「已选 N 人」和保存请求用的已选名单,**必须来自** `GET /v3/admin/group-batch/{productBatchId}/staff`,不能由本接口各页的 `assigned` 拼出来。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| productBatchId | Path | Long | ✅ | 产品侧排期 ID | 与旧候选端点、`GET/PUT .../staff` 用同一个 ID(`group_tour_batch.batch_id`),**不是**运营团期主键 `groupBatchId` | +| role | Query | String | ✅ | `GUIDE` / `PHOTOGRAPHER` | 配置位。`GUIDE` 同时返回资源库里的导游(GUIDE)和领队(LEADER);`PHOTOGRAPHER` 只返回摄影(PHOTOGRAPHER);其他值返回 582113 | +| keyword | Query | String | 否 | ≤50 字符;去掉首尾空格后为空视为不传 | 按姓名模糊匹配(包含即命中);**纯数字**时另按手机号**尾号**匹配(如 `1002`);`%`、`_` 按普通字符处理 | +| page | Query | Integer | 否 | 缺省 1;<1 按 1 处理 | 页码,从 1 开始 | +| pageSize | Query | Integer | 否 | 缺省 20;<1 按 20 处理;>100 按 100 处理 | 每页条数 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| total | Integer | 符合条件的总人数(只统计在职且未删除的人) | +| page | Integer | 实际使用的页码(已按上面的规则处理) | +| pageSize | Integer | 实际使用的每页条数(已按上面的规则处理) | +| records | List | 当前页候选,字段如下;页码超出范围时为空数组 | +| records[].staffId | Long | 人员 ID,保存时传给 `PUT .../staff` 的 `staffList[].staffId` | +| records[].staffName | String | **人员名称**(弹窗显示) | +| records[].staffType | String | 资源库人员类型:`GUIDE` / `LEADER` / `PHOTOGRAPHER` | +| records[].staffTypeName | String | **角色名称**(弹窗显示):导游 / 领队 / 摄影 | +| records[].staffPhone | String | **联系电话**(弹窗显示),前 3 后 4 脱敏,如 `138****1002`;资源库没有手机号时为 null | +| records[].avatarUrl | String | 头像 URL,没有头像或头像服务失败时为 null | +| records[].assigned | Boolean | 是否已选在**本配置位**(true 显示为已勾选) | +| records[].assignedRole | String | 该人在本团名册里的角色;不在名册时为 null。可能与本配置位不同(例如一个领队被配到了摄影位),可据此提示「已在摄影位」 | +| records[].assignedRoleName | String | `assignedRole` 的中文名 | +| records[].reporterRank | String | 本团名册里的报账人等级:`PRIMARY` / `SECONDARY` / `NONE`;不在名册时为 null(不是 `NONE`) | +| records[].reporterRankName | String | 主报账人 / 次报账人 / 非报账人 | +| records[].productBatchId | String | 原样返回路径里的 `productBatchId` | +| records[].groupBatchId | String | 运营团期 ID;**团期尚未创建时为 null,属正常情况**,不要拿它去拼后续请求 | + +#### 请求示例 + +```http +GET /v3/admin/group-batch/2099751575655247875/staff/candidates/page?role=GUIDE&keyword=李&page=1&pageSize=20 +Authorization: Bearer +``` + +#### 响应示例 + +(测试服真实响应,2026-09-16,dev-v3 `6ef9458ed`) + +```json +{ + "code": 200, + "message": "成功", + "data": { + "records": [ + { + "productBatchId": "2099751575655247875", + "groupBatchId": "2099751580084420609", + "staffId": 1002, + "staffName": "李雪梅", + "staffPhone": "138****1002", + "staffType": "GUIDE", + "staffTypeName": "导游", + "avatarUrl": null, + "assigned": false, + "assignedRole": null, + "assignedRoleName": null, + "reporterRank": null, + "reporterRankName": null + } + ], + "total": 1, + "page": 1, + "pageSize": 20 + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +没有候选人、搜索无结果,或页码超出范围时,返回空的 `records`,`total` 为真实总数: + +```json +{ + "code": 200, + "message": "成功", + "data": { "records": [], "total": 7, "page": 99, "pageSize": 20 } +} +``` + +头像服务失败不会导致接口报错,只是对应的 `avatarUrl` 为 null。 + +#### 错误响应 + +```json +{ "code": 582113, "message": "人员配置位不合法,请检查配置位标识", "data": null } +``` + +| code | 触发条件 | +|------|----------| +| 582113 | `role` 不是 `GUIDE` / `PHOTOGRAPHER`(包括不传) | +| 582103 | 资源服务查询失败(超时、不可用或返回失败)。不会把失败当成空列表返回 | +| 589507 | 当前账号没有团期查看权限(与旧候选端点同码) | +| 非 200 参数校验码 | `keyword` 超过 50 个字符 | +| 401 | 未登录(网关拦截) | + +#### 业务边界 + +- 判权:需要团期查看权限(`group-batch:view`),与旧候选端点、`GET .../staff` 一致;没有权限时不查询任何数据。 +- 只读接口,不写库,可重复调用。 +- 只返回资源库中在职(上架)且未删除的人员。导游位的导游和领队在一次查询里完成,同一个人不会重复出现。 +- 关键词只在当前配置位的候选范围内搜索:在导游位搜摄影师不会有结果。 +- 手机号只按**尾号**匹配:`1002` 能搜到尾号为 1002 的人,`138` 这种号段前缀搜不到。 +- `assigned` 按**当前配置位**判断,`assignedRole` / `reporterRank` 按**整个团的名册**判断:被配到另一个位的人,在本位里 `assigned=false`,但 `assignedRole` / `reporterRank` 仍有值。这是预期行为,不是 bug。 +- 分页不影响保存语义:`PUT .../staff` 仍是全量覆盖,见第四节。 + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误 调用对照 + +| 场景 | 调用 | +|------|------| +| ✅ 打开导游弹窗 | `GET .../staff/candidates/page?role=GUIDE`(缺省第 1 页,每页 20 条) | +| ✅ 按姓名搜索 | `GET .../staff/candidates/page?role=GUIDE&keyword=李雪` | +| ✅ 按手机尾号搜索 | `GET .../staff/candidates/page?role=GUIDE&keyword=1002` | +| ✅ 翻页 | `GET .../staff/candidates/page?role=GUIDE&keyword=李&page=2&pageSize=20` | +| ❌ 不传 role | `GET .../staff/candidates/page` → 582113 | +| ❌ 用运营团期 ID 拼路径 | `GET .../group-batch/{groupBatchId}/staff/candidates/page` → 路径参数必须是产品侧 `productBatchId` | +| ❌ 用 records[].groupBatchId 拼后续请求 | 团期未创建时该字段为 null,会拼出 `/group-batch/null/...` | + +### 保存时已选名单的正确来源(必须处理) + +`PUT /v3/admin/group-batch/{productBatchId}/staff` 的 `staffList` 是**最终名单**,不在里面的人会被删除。分页后请按下面的方式处理: + +1. 弹窗打开时调用 `GET /v3/admin/group-batch/{productBatchId}/staff`,得到**全团**已配置名单,作为「已选」初始值;弹窗里的「已选 N 人」按本配置位过滤后计数(导游位统计 `staffRole` 为 GUIDE / LEADER 的人,摄影位统计 PHOTOGRAPHER)。 +2. 翻页、搜索时,用这份已选名单回显勾选状态;`records[].assigned` 只能作参考,不能作为唯一依据。 +3. 用户勾选或取消勾选时,只在这份已选名单上增删对应的人。 +4. 保存时把**完整的已选名单**(包括其他配置位的人,例如在导游弹窗里保存时,也要带上已配置的摄影)一起传给 `PUT .../staff`。 + +| 场景 | 结果 | +|------|------| +| ✅ 已选名单来自 `GET .../staff`,保存时整份提交 | 其他页、其他配置位的人都保留 | +| ❌ 只提交当前页勾选的人 | 其他页已选的人被软删,并同步到全团活跃订单 | + +### 弹窗展示建议字段 + +| 展示内容 | 字段 | 示例 | +|----------|------|------| +| 角色名称 | `staffTypeName` | 导游 / 领队 / 摄影 | +| 人员名称 | `staffName` | 李雪梅 | +| 联系电话 | `staffPhone` | 138****1002 | + +--- + +## 六、边界行为 + +- 未登录 → 网关返回 401。 +- 没有团期查看权限 → 589507,不查询任何数据。 +- `role` 非法或缺失 → 582113,不调用资源服务。 +- 资源服务不可用 → 582103,不返回空页。 +- 头像服务不可用 → 接口正常返回,`avatarUrl` 为 null。 +- 页码超出范围 → `records` 为空数组,`total` 为真实总数。 +- `page` / `pageSize` 不合法(≤0 或过大)→ 按「缺省 1 / 缺省 20 / 最大 100」处理,不报错;实际使用的值在响应的 `page` / `pageSize` 里返回。 +- 团期尚未创建(还没有第一个订单成团)→ 接口正常返回,`records[].groupBatchId` 为 null。 +- 资源库中已下架或已删除的人员不会出现在候选里。 + +--- + +## 六.5、枚举 / 数据字典 + +### role(配置位,请求参数) + +**所属字段**: `role`(Query) | **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `GUIDE` | 导游位 | 候选同时包含资源库的导游(GUIDE)和领队(LEADER) | +| `PHOTOGRAPHER` | 摄影位 | 候选只包含资源库的摄影(PHOTOGRAPHER) | + +### staffType(资源库人员类型) + +**所属字段**: `records[].staffType` | **类型**: `String` + +| 值 | 中文(`staffTypeName`) | 说明 | +|----|------|------| +| `GUIDE` | 导游 | 出现在导游位 | +| `LEADER` | 领队 | 出现在导游位 | +| `PHOTOGRAPHER` | 摄影 | 出现在摄影位 | + +### reporterRank(报账人等级) + +**所属字段**: `records[].reporterRank` | **类型**: `String` + +| 值 | 中文(`reporterRankName`) | 说明 | +|----|------|------| +| `PRIMARY` | 主报账人 | 本团名册中唯一 | +| `SECONDARY` | 次报账人 | 本团名册中唯一 | +| `NONE` | 非报账人 | 在名册中但不是报账人 | +| `null` | — | 该人不在本团名册中 | + +--- + +## 七、不影响范围 + +- **仅影响**: 新增的分页候选端点。 +- **零影响**: + - 旧候选端点 `GET /v3/admin/group-batch/{productBatchId}/staff/candidates`(请求和响应都不变) + - `GET /v3/admin/group-batch/{productBatchId}/staff`(已配置名单) + - `PUT /v3/admin/group-batch/{productBatchId}/staff`(保存,仍为全量覆盖,同步规则不变) + - `PUT .../staff/{staffId}/reporter-rank`(报账人设置) + - 资源服务既有的 `/internal/staff/list-available`(其他模块仍在使用) + - 数据库:无表结构变更,无数据迁移 + +--- + +## 八、测试环境已验证 + +被测版本:hl-order-service-v3、hl-resource-service = dev-v3 `6ef9458ed`(2026-09-16 18:40 部署)。此前已先用特性分支 `415f4d077` 做过同一套验收。验收脚本两轮均 **26/26** 通过。 + +``` +GET .../staff/candidates/page?role=GUIDE → 200,total=7(= 库中在职导游+领队人数),page=1,pageSize=20 ✓ +GET .../staff/candidates/page?role=PHOTOGRAPHER → 200,total=1,只含 PHOTOGRAPHER ✓ +GET .../staff/candidates/page?role=GUIDE&pageSize=3&page=1/2/3 → 3+3+1 条,拼起来等于全量且无重复 ✓ +GET .../staff/candidates/page?role=GUIDE&page=99 → 200,records=[],total=7 ✓ +GET .../staff/candidates/page?role=GUIDE&page=0&pageSize=0 → page=1,pageSize=20 ✓ +GET .../staff/candidates/page?role=GUIDE&pageSize=500 → pageSize=100 ✓ +keyword=李雪 → 李雪梅 ✓ keyword=1002 → 李雪梅(尾号)✓ keyword=10 → 只有尾号为 10 的乌日娜 ✓ +keyword=138 → 0 条 ✓ keyword=% → 0 条 ✓ keyword=_ → 0 条 ✓ keyword=(空格) → 7 条 ✓ +导游位 keyword=王强 → 0 条;摄影位 keyword=王强 → 王强 ✓ +keyword 51 个字符 → 400「关键词不能超过50个字符」✓ +role=DRIVER → 582113 ✓ +CUSTOMIZER 角色调用新端点 / 旧端点 → 589507 / 589507 ✓ +未登录 → 401 ✓ +pageSize=100 与旧端点 GET .../staff/candidates 同一批人 → 7 条逐字段一致 ✓ +名册中的刘大山 → assigned=true,assignedRole=LEADER,reporterRank=NONE ✓ +Feign 超时注入(1ms) → 582103(配置已还原)✓ +``` + +验证团期:`productBatchId=2099751575655247875`(名册中有刘大山)、`2099751542897733635`(名册中有王强)。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#7827](https://git.1814.love:8443/wx/HL/issues/7827) +- 关联 PR: [wx/HL#7831](https://git.1814.love:8443/wx/HL/pulls/7831) +- 后续计划: 前端切换到分页端点后,另开工单下线旧候选端点 `GET .../staff/candidates` + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7827](https://git.1814.love:8443/wx/HL/issues/7827) +- **PR**: [#7831](https://git.1814.love:8443/wx/HL/pulls/7831) +- **Merge commit**: [6ef9458ed](https://git.1814.love:8443/wx/HL/commit/6ef9458ed) + +### 联系人 + +- **后端负责人**: @jw