docs(changelog): 团期列表 #7769 出参补 daysToDepart/minToForm、#7770 入参补日期区间与排序
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
- 16_7769:GB-ADM-001 出参新增 daysToDepart / minToForm,GB-ADM-009 出参新增 minToForm / maxRooms - 16_7770:GB-ADM-001 入参新增 departFrom / departTo / sortBy / sortOrder 两单均已合入 dev-v3(4559f35d0 / 791506985)并部署 TEST(791506985), AC 已在 TEST 逐条取证:请求响应、SQL 对照、注入串回落日志、前后 diff 均留证。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,327 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "7769"
|
||||||
|
title: "团期列表与看板统计条补距出团天数与成团户数门槛"
|
||||||
|
consumer: "admin"
|
||||||
|
author: "wx(GIT)"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "not_required"
|
||||||
|
frontend_status: "pending"
|
||||||
|
frontend_owner: "mmg"
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-09-16"
|
||||||
|
status_note: "GB-ADM-001 出参新增 daysToDepart / minToForm,GB-ADM-009 出参新增 minToForm / maxRooms,均为纯新增字段,路径与入参不变,老调用响应除新增键外逐字不变。PR #7776 已合入 dev-v3(merge commit 4559f35d0)。"
|
||||||
|
updated_at: "2026-09-16"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 团期列表与看板统计条补距出团天数与成团户数门槛(修改接口)
|
||||||
|
|
||||||
|
> **服务**: hl-order-service-v3
|
||||||
|
> **PR**: #7776
|
||||||
|
> **Issue**: #7769
|
||||||
|
> **日期**: 2026-09-16
|
||||||
|
> **影响范围**: 两个既有 GET 读接口的出参新增字段;无新端点、无路由变化、无 DDL、无新增错误码
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
🔵 **纯新增出参字段,老调用不受影响。** 两个接口的路径、入参、`Result` 信封、既有字段的取值与顺序全部不变,只是各多返回两个键。
|
||||||
|
|
||||||
|
前端团期列表页此前做不出原型的「距出团 N 天」与统计条副标题「满 N 户成团 · 满 M 户满团」,根因就是接口不给这三个值,现已补齐。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | 团期分页列表 | GET | `/v3/admin/order/group-batch` | 修改接口 | 出参新增 `daysToDepart`、`minToForm` |
|
||||||
|
| 2 | 团期看板统计条 | GET | `/v3/admin/order/group-batch/summary` | 修改接口 | 出参新增 `minToForm`、`maxRooms` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情
|
||||||
|
|
||||||
|
### 1. 团期分页列表 `GET /v3/admin/order/group-batch`
|
||||||
|
|
||||||
|
**VO**: `GroupBatchPageItemRespVO`
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
团期订单列表页逐行渲染。本次新增的两个字段供「距出团 N 天」倒计时与「满 N 户成团」成团标准展示。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| productId | query | string | 否 | 雪花 ID 字符串 | 按产品筛选;有值时走合并基底 |
|
||||||
|
| opsStage | query | string | 否 | 八桶 code | 运营阶段筛选 |
|
||||||
|
| scope | query | string | 否 | ONGOING/FINISHED/ALL | 班期范围 |
|
||||||
|
| pageNo | query | integer | 是 | ≥1 | 页码 |
|
||||||
|
| pageSize | query | integer | 是 | 1~100 | 每页条数 |
|
||||||
|
|
||||||
|
> 入参本次**无任何变化**,上表仅列与新增出参相关的常用项,完整入参见既有契约。
|
||||||
|
|
||||||
|
#### 出参
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| daysToDepart | integer | **新增**。距出发剩余天数 = 出发日 − 服务端当天;**负数表示出发日已过**,不截断为 0;出发日为空时为 null |
|
||||||
|
| minToForm | integer | **新增**。成团闸的最低成团**户数**门槛(户=订单=房);0 或 null 表示未设,前端不显示成团标准 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/group-batch?pageNo=1&pageSize=10&productId=2056947670512971778&opsStage=FORMED&scope=ONGOING
|
||||||
|
Authorization: Bearer <admin token>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"groupBatchId": "2096495107078328322",
|
||||||
|
"batchNo": "Q202612202096494989117677570",
|
||||||
|
"batchLabel": "12",
|
||||||
|
"batchStatus": "RESOURCE_PREPARING",
|
||||||
|
"opsStage": "FORMED",
|
||||||
|
"departDate": "2026-12-20",
|
||||||
|
"endDate": "2026-12-21",
|
||||||
|
"daysToDepart": 95,
|
||||||
|
"minGroupPeople": 0,
|
||||||
|
"minToForm": 6,
|
||||||
|
"maxRooms": 9,
|
||||||
|
"enrolledRooms": 2,
|
||||||
|
"remainRooms": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 10
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
命中为空时 `records` 为空数组、`total` 为 0,两个新字段不出现在任何行上;不返回 null 列表。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 200, "message": "成功", "data": { "records": [], "total": 0, "page": 1, "pageSize": 10 }, "success": true }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 401, "message": "Token无效或已过期", "data": null, "success": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
网关与业务错误一律 HTTP 200,判定看 body 的 `code`。
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- `daysToDepart` 以**服务端当天**为基准,前端不要用本地时间自行计算,否则跨时区/跨零点会与列表不一致。
|
||||||
|
- 「出行中 / 已返团」由前端用 `daysToDepart` 配合 `endDate` 自行判定,后端不再另给中文态。
|
||||||
|
- `minToForm` 在 `productId` 有值时取产品域实时值,与团期详情 GB-ADM-002 的同名字段同源同值;`productId` 缺省或产品侧班期已删(孤儿行)时回落订单域建团快照,此时可能与详情的实时值不同。
|
||||||
|
- `minToForm` 与 `minGroupPeople` 是两回事:前者是**户数**门槛,后者是**人数**门槛,别混用。
|
||||||
|
|
||||||
|
### 2. 团期看板统计条 `GET /v3/admin/order/group-batch/summary`
|
||||||
|
|
||||||
|
**VO**: `GroupBatchSummaryVO`
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
团期列表页顶部「班期总览」统计卡。本次新增两个字段供副标题「成团标准:满 N 户成团 · 满 M 户满团」。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| productId | query | string | 否 | 雪花 ID 字符串 | 按产品筛选;决定两个新字段是否有值 |
|
||||||
|
| month | query | string | 否 | yyyy-MM | 出团月份 |
|
||||||
|
| keyword | query | string | 否 | — | 编号/名称模糊 |
|
||||||
|
| scope | query | string | 否 | ONGOING/FINISHED/ALL | 班期范围 |
|
||||||
|
|
||||||
|
> 入参本次**无任何变化**。
|
||||||
|
|
||||||
|
#### 出参
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| minToForm | integer | **新增**。最低成团户数门槛;仅 `productId` 有值且命中行门槛取值唯一时给值,否则 null |
|
||||||
|
| maxRooms | integer | **新增**。房间容量(满团标准);取值规则同 `minToForm` |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /v3/admin/order/group-batch/summary?productId=2056947670512971778&scope=ONGOING
|
||||||
|
Authorization: Bearer <admin token>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"total": 11,
|
||||||
|
"buckets": { "RECRUIT": 5, "FORMED": 2, "PENDING_TRIP": 0, "TRAVELLING": 0, "TRIP_FINISHED": 0, "AUDITING": 0, "CHECKED": 0, "DISBANDED": 4 },
|
||||||
|
"subOrderCount": 3,
|
||||||
|
"minToForm": 6,
|
||||||
|
"maxRooms": 9
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
不带 `productId`(跨产品)时门槛不唯一,两个字段均返回 null,八桶与计数照常返回。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 200, "message": "成功", "data": { "total": 0, "buckets": { "RECRUIT": 0, "FORMED": 0, "PENDING_TRIP": 0, "TRAVELLING": 0, "TRIP_FINISHED": 0, "AUDITING": 0, "CHECKED": 0, "DISBANDED": 0 }, "subOrderCount": 0, "minToForm": null, "maxRooms": null }, "success": true }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 401, "message": "Token无效或已过期", "data": null, "success": false }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- 同一产品各班期理论上可配不同门槛,命中行取值**不唯一时返回 null**,后端不取第一条冒充全体;前端据此不显示副标题。
|
||||||
|
- 该字段是产品级常量的投影,不参与八桶计数,也不影响 `total` / `subOrderCount`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、契约约束与正确调用方式
|
||||||
|
|
||||||
|
- 两个接口均为 GET 只读,路径与入参不变,**前端可按需增量接入,不接也不会坏**。
|
||||||
|
- `daysToDepart` 是整数且可为负,前端渲染「距出团 N 天」前先判 `> 0`;`= 0` 是当天出发;`< 0` 表示已过出发日。
|
||||||
|
- 成团标准优先用 `minToForm`(户),不要用 `minGroupPeople`(人)去拼「满 N 户成团」。
|
||||||
|
- 统计条副标题只在 `minToForm` 与 `maxRooms` 同时非 null 时展示。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、边界行为
|
||||||
|
|
||||||
|
- 出发日为空的团期:`daysToDepart` 为 null,不是 0。
|
||||||
|
- 容量为 0/null 按「不限」语义:`remainRooms` / `remainParticipants` 仍按既有规则返回 null,本次未改。
|
||||||
|
- 合并基底(`productId` 有值)下,行的出发日以产品域权威值为准,`daysToDepart` 按该权威出发日计算,不用建团快照日期。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.6、修改前后对比
|
||||||
|
|
||||||
|
| 接口 | 改前 | 改后 |
|
||||||
|
|---|---|---|
|
||||||
|
| GB-ADM-001 | 无距出团天数;无户数门槛,只有人数门槛 `minGroupPeople` | 新增 `daysToDepart`、`minToForm`,其余字段逐字不变 |
|
||||||
|
| GB-ADM-009 | 只有 `total` / `buckets` / `subOrderCount` | 新增 `minToForm`、`maxRooms`,其余字段逐字不变 |
|
||||||
|
|
||||||
|
老调用不读新键时,两个接口的响应与改前**逐字一致**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六.7、影响评估
|
||||||
|
|
||||||
|
- **前端**:纯增量,不接入无影响;接入后可做出原型的倒计时与成团标准。
|
||||||
|
- **后端**:`minToForm` 复用合并基底已批量拉回的产品班期数据,**不产生逐行 Feign 调用**;单页 100 条时的远程调用次数与改前一致。
|
||||||
|
- **数据**:无表变更、无 Flyway、无写链路。
|
||||||
|
- **兼容性**:无字段删除或改名,无枚举值变化,无错误码新增。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- 团期详情 GB-ADM-002、子订单列表 GB-ADM-003、六芯片明细 GB-ADM-090~095 均未改动。
|
||||||
|
- 列表入参、分页语义、排序、权限码、网关路由均未变化。
|
||||||
|
- 小程序端接口零影响。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试环境已验证
|
||||||
|
|
||||||
|
**部署**:dev-v3 @ `791506985`(PR #7776 合并提交 `4559f35d0` 已在其中),2026-09-16 11:47:47~11:49:18 滚动部署双实例(8186/8086)成功,task `e68e54cd`,exit_code=0。
|
||||||
|
|
||||||
|
**验证环境**:`https://api.test.1814.love:9443`(真实网关,admin token)。部署前已先采集同请求的「改动前」基线用于逐字对比。
|
||||||
|
|
||||||
|
### AC-1 `daysToDepart` = departDate − 服务端当天(基准日 2026-09-16)
|
||||||
|
|
||||||
|
| 情形 | batchNo | departDate | daysToDepart | 期望 | 结果 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 出发日未到 | Q202610262100047410968346625 | 2026-10-26 | 40 | 40 | ✅ |
|
||||||
|
| 出发日已过 | Q202610152099543532271267841 | 2026-09-14 | **−2** | −2 | ✅ 负数未截断为 0 |
|
||||||
|
| 出发日=当天 | Q202609162099715421815926785 | 2026-09-16 | 0 | 0 | ✅ |
|
||||||
|
| departDate 为空 | — | — | — | null | ⚠️ **TEST 环境无此数据**(`order_group_batch` 共 198 行,`depart_date IS NULL` 为 0 行),未能实测;该分支由单测 `GroupBatchConverterTest.Issue7769ListRespFields` 覆盖 |
|
||||||
|
|
||||||
|
### AC-2 GB-ADM-001 与 GB-ADM-002 的 `minToForm` 逐字相等
|
||||||
|
|
||||||
|
团期 `groupBatchId=2096495107078328322`:列表 `minToForm=6`,详情 `minToForm=6`,**逐字相等 ✅**。
|
||||||
|
|
||||||
|
### AC-3 GB-ADM-009 的 `minToForm` / `maxRooms`
|
||||||
|
|
||||||
|
| 请求 | minToForm | maxRooms | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `productId=2043696114145644545` | 0 | null | 命中 37 行的 `minToForm` 取值集合为 `{0}`(唯一)故给值;`maxRooms` 取值集合为 `{0, 10}`(**不唯一**)故按契约返回 null |
|
||||||
|
| `productId=2056947670512971778` | null | null | 该产品门槛高度分散(`min_to_form` 7 种取值),两者均不唯一 |
|
||||||
|
| 不带 `productId` | null | null | 跨产品,符合契约 |
|
||||||
|
|
||||||
|
三次实测与「取值唯一才给值、否则 null」的契约**完全一致 ✅**。
|
||||||
|
|
||||||
|
### AC-4 老调用兼容
|
||||||
|
|
||||||
|
对部署前后同一组 7 个请求逐字对比(剔除新增键后做 JSON 全量比较):
|
||||||
|
|
||||||
|
| 用例 | 除新增键外一致 | 新增键 |
|
||||||
|
|---|---|---|
|
||||||
|
| GB001 productId+opsStage+scope | ✅ | daysToDepart, minToForm |
|
||||||
|
| GB001 productId only | ✅ | daysToDepart, minToForm |
|
||||||
|
| GB001 DB 基底 | ✅ | daysToDepart, minToForm |
|
||||||
|
| GB001 DB 基底 scope=ALL | ✅ | daysToDepart, minToForm |
|
||||||
|
| GB001 DB 基底 第 2 页 | ✅ | daysToDepart, minToForm |
|
||||||
|
| GB009 带 productId | ✅ | maxRooms, minToForm |
|
||||||
|
| GB009 不带 productId | ✅ | maxRooms, minToForm |
|
||||||
|
|
||||||
|
**7/7 全部逐字一致 ✅**,新增键恰为本次声明的字段,无其他差异。
|
||||||
|
|
||||||
|
### AC-5 单页 100 条不产生逐行产品域 Feign
|
||||||
|
|
||||||
|
对 `pageSize=100&productId=2056947670512971778&scope=ALL` 连打 5 次,同时以部署面板 SSE 日志流抓取 order-v3 primary 实例日志。该请求段共 276 行日志,确含 6 条 `Preparing:` SQL 与 5 条 `Total:` 结果行(证明调用确被记录,非空日志):
|
||||||
|
|
||||||
|
```
|
||||||
|
==> Preparing: SELECT group_batch_id,product_batch_id,enrolled_people,enrolled_rooms
|
||||||
|
FROM order_group_batch WHERE deleted_at IS NULL AND (product_batch_id IN (?,?,?,?,?,?,?,?,?,?))
|
||||||
|
==> Preparing: SELECT product_batch_id,adult_count,child_count,young_child_count,baby_count
|
||||||
|
FROM order_main WHERE deleted_at IS NULL AND (product_batch_id IN (?,?,?,?,?,?,?,?,?,?) AND order_status <> ?)
|
||||||
|
```
|
||||||
|
|
||||||
|
全部走 `product_batch_id IN (?,?,…)` **批量 IN 查询**;`feign` / `ProductFeign` / `batch-stats` / `group-tour-batch` / `product-service` / `RestTemplate` / `WebClient` 七个关键词在整段日志中**出现次数均为 0**。单页 100 条仅产生 6 条 SQL,远低于「逐行调用应 ≥100」的阈值 ✅
|
||||||
|
|
||||||
|
### AC-6 / AC-7 单测与全量
|
||||||
|
|
||||||
|
- 边界单测:`GroupBatchConverterTest.Issue7769ListRespFields` 新增 11 例,覆盖 `departDate` 为空、出发日已过、跨产品门槛为 null;`GroupBatchBoardStatsServiceTest` 新增 3 例。均为 JUnit5 + Mockito,未用 `@SpringBootTest`。
|
||||||
|
- 全量:`hl-order-service-v3` Half A(`com.hulalv.order.**`)**Tests run: 7510, Failures: 0, Errors: 0, Skipped: 0, BUILD SUCCESS**;Half B(其余包)3888 / 0 / 0 / 7。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- 差异来源:`需求分析/测试发现问题.md` 模块 1
|
||||||
|
- 原型出处:`需求分析/2026-09-11-团期模块需求分析.md` §1.2、§2.2.1、§2.2.4
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
- 后端:wx
|
||||||
|
- 前端:mmg
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "7770"
|
||||||
|
title: "团期列表补出团日期区间筛选与排序参数"
|
||||||
|
consumer: "admin"
|
||||||
|
author: "wx(GIT)"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "not_required"
|
||||||
|
frontend_status: "pending"
|
||||||
|
frontend_owner: "mmg"
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: "2026-09-16"
|
||||||
|
status_note: "GB-ADM-001 入参新增 departFrom / departTo / sortBy / sortOrder,均为可选;不传时行集与行序与改前逐条一致。出参不变。PR #7777 已合入 dev-v3(合并提交 791506985),已部署 TEST 并逐条取证。"
|
||||||
|
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 <admin token>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```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
|
||||||
在新工单中引用
屏蔽一个用户