--- schema: "hl-changelog/v2" ticket: "7770" title: "团期列表补出团日期区间筛选与排序参数" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "not_required" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "a9148fc9d13dedbccd2ad4f2a17b452172838efd" target_release: "" verified_at: "2026-09-16" status_note: "GB-ADM-001 入参新增 departFrom / departTo / sortBy / sortOrder,均为可选;不传时行集与行序与改前逐条一致。出参不变。PR #7777 已合入 dev-v3(合并提交 791506985),已部署 TEST 并逐条取证。;前端 hl-admin a9148fc9 已实现:筛选条出团日期 daterange+排序 select 组合值拆分透传(空不落参),四参仅 001 透传 009 不带,显式排序月组跟随 records 首见序,checkpoint 全绿。" updated_at: "2026-09-16" base: "dev-v3" --- # 团期列表补出团日期区间筛选与排序参数(修改接口) > **服务**: hl-order-service-v3 > **PR**: #7777 > **Issue**: #7770 > **日期**: 2026-09-16 > **影响范围**: 一个既有 GET 读接口的入参新增;出参不变、无新端点、无路由变化、无 DDL、无新增错误码 --- ## ⚠️ 关键变化 🔵 **纯新增可选入参,不传时行为与改前逐条一致。** 团期列表此前只能按整月(`month`)筛出团日,搜索框里输日期搜不到;排序也写死。本次补上日期区间与排序两组参数。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|---|---|---|---|---| | 1 | 团期分页列表 | GET | `/v3/admin/order/group-batch` | 修改接口 | 入参新增 `departFrom`、`departTo`、`sortBy`、`sortOrder` | --- ## 三、接口详情 ### 1. 团期分页列表 `GET /v3/admin/order/group-batch` **VO**: `GroupBatchListReqVO` #### 使用场景 团期订单列表页的「期号 / 日期」搜索与列表排序。日期区间用于按出团日精确筛选,排序用于按出发日或报名截止日重排。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |---|---|---|---|---|---| | departFrom | query | string | 否 | yyyy-MM-dd | **新增**。出团日区间起,含当日;非法串忽略该条件并记 warn,不报错 | | departTo | query | string | 否 | yyyy-MM-dd | **新增**。出团日区间止,含当日;口径同上 | | sortBy | query | string | 否 | departDate / enrollDeadline / createTime | **新增**。排序字段白名单,大小写不敏感;非白名单值回落默认排序并记 warn | | sortOrder | query | string | 否 | asc / desc | **新增**。排序方向,大小写不敏感;缺省或非法值回落 asc | #### 出参 | 字段 | 类型 | 说明 | |---|---|---| | records | array | 出参结构本次**无变化**,逐字段沿用既有契约 | | total | integer | 命中总数,语义不变 | #### 请求示例 ```http GET /v3/admin/order/group-batch?pageNo=1&pageSize=10&productId=2056947670512971778&departFrom=2026-12-01&departTo=2026-12-31&sortBy=departDate&sortOrder=asc Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "data": { "records": [ { "groupBatchId": "2096495107078328322", "batchNo": "Q202612202096494989117677570", "batchLabel": "12", "departDate": "2026-12-20", "endDate": "2026-12-21", "batchStatus": "RESOURCE_PREPARING", "opsStage": "FORMED" } ], "total": 1, "page": 1, "pageSize": 10 }, "success": true } ``` #### 空数据 / 降级响应 区间与其他条件的交集为空时返回空列表,不报错。 ```json { "code": 200, "message": "成功", "data": { "records": [], "total": 0, "page": 1, "pageSize": 10 }, "success": true } ``` #### 错误响应 ```json { "code": 401, "message": "Token无效或已过期", "data": null, "success": false } ``` 非法日期串与非白名单排序字段**不会**产生错误响应,均按降级处理返回 200。 #### 业务边界 - `departFrom` / `departTo` 与 `month` 是两个独立条件,同时传取**交集**,互不覆盖。 - 与 `scope` 同传是**叠加**(AND):`scope=ONGOING` 仍会把返团日已过的行剔掉,区间不会把它们捞回来。 - 无出发日的行落不进任何区间,与 `month` 同口径。 - 排序字段走枚举白名单,参数字符串不进 `ORDER BY`;排序结果按主键做次级排序,避免同值行跨页跳动。 - **期号 `batchLabel` 不在白名单**:它是字符串列,按字典序会把「第 10 期」排到「第 9 期」前;同产品内按 `departDate` 升序与期号升序等价。 --- ## 四、契约约束与正确调用方式 - 四个参数全部可选,**不传即完全保持改前行为**,前端可增量接入。 - 想按日期搜索请用 `departFrom` / `departTo`,不要指望 `keyword`——它只模糊匹配班期编号与名称。 - 要整月仍可继续用 `month`;与区间同传时请自行确认两者取交集后的预期。 - 排序只认白名单三个值,传别的不会报错但会被忽略,前端不要据此做「排序失败」提示。 --- ## 六、边界行为 - 非法日期串(如 `2026-13-45`、`abc`):忽略该条件、记 warn、返回 200,行集等同于不传。 - `sortOrder` 传非 asc/desc:回落 asc 并记 warn。 - `sortBy` 传注入串:不命中白名单,回落默认排序,字符串不进 SQL。 - 合并基底(`productId` 有值,内存分页)与 DB 基底(`productId` 缺省)两条路径的筛选与排序语义一致;未指定排序时分别保持既有的出发日升序与建团时间倒序。 --- ## 六.6、修改前后对比 | 能力 | 改前 | 改后 | |---|---|---| | 按出团日筛选 | 只有 `month`,整月粒度 | 增加 `departFrom` / `departTo` 任意区间,与 `month` 取交集 | | 排序 | 写死(合并基底出发日升序 / DB 基底建团时间倒序) | 可传 `sortBy` + `sortOrder`,白名单三字段,未传时与改前一致 | | 出参 | — | **无变化** | --- ## 六.7、影响评估 - **前端**:纯增量可选入参,不传无影响。 - **后端**:新增条件走既有 Wrapper 的 `geIfPresent` / `leIfPresent`,未引入新查询路径;排序由实体方法引用生成列名,无注入面。 - **数据**:无表变更、无 Flyway、无写链路。 - **兼容性**:无字段删除或改名,出参与错误码零变化。 --- ## 七、不影响范围 - 出参结构、分页语义、权限码、网关路由均未变化。 - 团期详情 GB-ADM-002、统计条 GB-ADM-009、子订单列表 GB-ADM-003 未改动。 - 既有入参 `deadlineFrom` / `deadlineTo` 的解析行为保持原样(非法值仍按既有方式处理),本次未一并调整。 - 小程序端接口零影响。 --- ## 八、测试环境已验证 **部署**:dev-v3 @ `791506985`(本 PR #7777 的合并提交),2026-09-16 11:47:47~11:49:18 滚动部署双实例成功,task `e68e54cd`,exit_code=0。 **验证环境**:`https://api.test.1814.love:9443`(真实网关,admin token)+ TEST MySQL 直连对照。 > 对照口径说明:`order_group_batch` 裸表 198 行,其中 `deleted_at IS NULL` 为 **132** 行,与接口 `scope=ALL` 的 `total=132` 一致,故下列 SQL 对照均带软删过滤。 ### AC-1 `departFrom` / `departTo` 单独传,行集与 SQL COUNT 一致 | 区间 | 接口 total | `SELECT COUNT(*) … WHERE depart_date BETWEEN ? AND ? AND deleted_at IS NULL` | 结果 | |---|---|---|---| | 2026-12-01 ~ 2026-12-31 | 18 | 18 | ✅ | | 2026-12-10 ~ 2026-12-20 | 7 | 7 | ✅ | | 2027-03-01 ~ 2027-03-31 | 0 | 0 | ✅ | 返回的 18 个 `groupBatchId` 全部存在于表中且未软删,`departDate` 全落在区间内(2026-12-01 ~ 2026-12-27)。 ### AC-2 与 `month` 取交集 | 组合 | 接口 total | SQL | 结果 | |---|---|---|---| | `month=2026-12` ∩ `[2026-12-10, 2026-12-20]` | 7 | 7 | ✅ 交集非空 | | `month=2026-12` ∩ `[2027-01-01, 2027-01-31]` | 0 | 0 | ✅ 交集为空 | (`month=2026-12` 单独为 18 行,区间单独为 7 行,交集 7 行——确为取交集而非互相覆盖。) ### AC-3 与 `scope` 叠加(AND) 区间 `[2026-09-01, 2027-01-31]` 跨越今天: | scope | total | |---|---| | ALL | 128 | | ONGOING | 122 | | FINISHED | 6 | `ONGOING(122) + FINISHED(6) = 128 = ALL(128)` ✅;且仅 `scope=ONGOING` 不带区间为 126 行,加区间后降为 122 行,证明区间是在 scope 之上**再过滤**而非覆盖 ✅ ### AC-4 非法日期串被忽略,返回 200 且行集等同于不传 | departFrom | code | total | 与不传(132)一致 | |---|---|---|---| | `2026-13-45` | 200 | 132 | ✅ | | `abc` | 200 | 132 | ✅ | | `2026/12/01` | 200 | 132 | ✅ | 均未抛 400,服务端记 warn 后忽略该条件 ✅ ### AC-5 `sortBy` 白名单升降序 | sortBy | asc | desc | |---|---|---| | `departDate` | ✅ 30/30 非空且升序(首 2026-09-05) | ✅ 降序(首 2027-04-15) | | `enrollDeadline` | ✅ 30/30 非空且升序(首 2026-09-04) | ✅ 降序(首 2027-04-14) | | `createTime` | ✅ 见下 | ✅ 见下 | `createTime` 不在响应字段中,故翻页取满全部 **132** 行 `groupBatchId` 后回表查 `create_time` 验证:asc/desc 两侧回表命中 132/132、各自严格单调,**行集相同且互为逆序**,首尾对称(`2026-08-18 18:56:01` ↔ `2026-09-16 10:21:35`)✅ ### AC-6 非白名单 / 注入串回落默认排序,且不进 SQL 对 `sortBy=depart_date; DROP TABLE order_group_batch` 连打 5 次,同时以部署面板 SSE 日志流抓取 order-v3 primary 日志(该段 444 行,含 18 条 `Preparing:` SQL)。 服务端打出回落 WARN: ``` WARN c.h.o.groupbatch.helper.GroupBatchListSortResolver [traceId=210663af876044f8] - 团期列表排序字段不在白名单,回落默认排序: sortBy=depart_date; DROP TABLE order_group_batch ``` 对应的团期列表 SQL 的排序子句为: ``` ==> Preparing: SELECT group_batch_id,product_batch_id,product_id,… FROM order_group_batch WHERE deleted_at IS NULL ORDER BY create_time DESC LIMIT ? ``` **注入串未出现在任何 `Preparing:` 行中**(含 `DROP TABLE` 的日志行共 3 行,全部是上述 `GroupBatchListSortResolver` 的 WARN 日志,其中含 `Preparing:` 的 0 行、含 `ORDER BY` 的 0 行)。全流 `ORDER BY` 子句枚举均为合法列(`create_time` / `traveler_id` / `flow_id` / `day_number` / `received_at` 等),无一含注入串 ✅ `batchLabel`(非白名单)与 `1=1 OR` 同样回落默认排序、返回 200、total 132;注入串发出后 `order_group_batch` 仍为 198 行,表未受影响 ✅ ### AC-7 老调用兼容:不传任何新参数时行集与顺序逐条一致 对部署前后同一组请求比对 `total` 与 `groupBatchId` 序列: | 用例 | total(前→后) | 行序逐条一致 | |---|---|---| | GB001 productId+opsStage+scope | 2 → 2 ✅ | ✅ | | GB001 productId only | 11 → 11 ✅ | ✅ | | GB001 DB 基底 | 132 → 132 ✅ | ✅ | | GB001 DB 基底 scope=ALL | 132 → 132 ✅ | ✅ | | GB001 DB 基底 第 2 页 | 132 → 132 ✅ | ✅ | **5/5 行集与行序与改动前逐条一致 ✅** ### AC-8 / AC-9 单测与全量 - 单测:`GroupBatchListSortResolverTest` 7 例(含注入串不命中白名单且比较器为 null)、`GroupBatchMergedRowsServiceTest` +5 例(两端含、与 month 交集、交集为空、与 scope 叠加、无出发日排除)、`GroupBatchQueryServiceTest` +2 例(区间透传 Mapper、非法日期串忽略不抛)。均为 JUnit5 + Mockito,未用 `@SpringBootTest`。 - 全量:合并前在含 #7776 的最新 dev-v3 基底上重跑 Half A(`com.hulalv.order.**`):**Tests run: 7524, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS**,514 个测试类,0 次 OOM。例数自洽:7510(基底)+ 14(本单新增)= 7524。Half B(其余包)3888 / 0 / 0 / 7。 --- ## 十、相关文档 - 差异来源:`需求分析/测试发现问题.md` 模块 1 - 原型出处:`需求分析/2026-09-11-团期模块需求分析.md` §1.2 ## 关联 / 联系人 - 后端:wx - 前端:mmg