# 早鸟优惠管理页面 v3 接口对接说明（管理后台）

- **端类型**：管理后台
- **变更类型**：接口说明（路径迁移）
- **日期**：2026-06-22
- **关联 PR**：[#4053](https://git.1814.love:8443/wx/HL/pulls/4053) / [#4064](https://git.1814.love:8443/wx/HL/pulls/4064) / [#4076](https://git.1814.love:8443/wx/HL/pulls/4076) / [#4082](https://git.1814.love:8443/wx/HL/pulls/4082)
- **负责人**：yst（腰苏图）

---

## 1. 接口背景

订单服务 v3（hl-order-service-v3，端口 8086）已完整落地早鸟优惠管理后台 6 个接口（PR #4053 / #4064 / #4076 / #4082）。

v3 的早鸟接口与 v2（hl-order-service-v2，端口 8094）的接口**入参、出参、字段语义、枚举值、交互行为完全一致**，唯一区别是**请求路径加了 /v3 前缀**。

**前端对接结论：早鸟管理页面无需任何 UI / 字段 / 交互改动，唯一需要做的是切换接口路径。**

---

## 2. 变更清单

| # | 方法 | v2 路径（旧） | v3 路径（新） | 备注 |
|---|------|--------------|--------------|------|
| 1 | GET | /admin/order/early-bird 或 /admin/order/early-bird/list | /v3/admin/order/early-bird/list | v3 只有 /list，调空路径返回 404，必须带 /list |
| 2 | GET | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 路径参数不变 |
| 3 | POST | /admin/order/early-bird | /v3/admin/order/early-bird | 仅加前缀 |
| 4 | PUT | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 路径参数不变 |
| 5 | DELETE | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 路径参数不变 |
| 6 | PUT | /admin/order/early-bird/{planId}/toggle | /v3/admin/order/early-bird/{planId}/toggle | Query 参数 enabled 不变 |

v2 早鸟列表接口历史上同时挂了空路径和 /list 两个映射。v3 只保留 /list，调空路径会返回 404。前端如果之前调的是 /admin/order/early-bird（无 /list 后缀），切换时必须同时补上 /list。

---

## 3. 接口详情

| 项目 | 说明 |
|------|------|
| **Base URL** | /v3/admin/order/early-bird |
| **认证** | 管理后台 JWT Token（Authorization: Bearer token），与 v2 一致 |
| **内容类型** | POST / PUT 请求体 Content-Type: application/json；GET 均为 Query 参数 |
| **幂等性** | POST 创建非幂等；PUT 按 planId 幂等覆盖（全字段覆盖，不支持 PATCH） |
| **限流** | 走全局网关限流，无独立限流规则 |
| **服务端口** | 测试服网关统一 9443，无需直连 8086 |

---

## 4. 接口入参

### 4.1 GET /v3/admin/order/early-bird/list — 分页列表

Query 参数（继承 PageParam）：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageNo | Integer | 是 | 页码，从 1 开始 |
| pageSize | Integer | 是 | 每页条数，建议 10 / 20 |

当前 v3 分页查询无额外筛选字段（EarlyBirdPlanPageReqVO 仅继承 PageParam），与 v2 保持一致。

### 4.2 GET /v3/admin/order/early-bird/{planId} — 详情

| 参数 | 类型 | 位置 | 必填 | 说明 |
|------|------|------|------|------|
| planId | Long（字符串） | Path | 是 | 早鸟计划 ID（雪花 ID） |

### 4.3 POST /v3/admin/order/early-bird — 创建

请求体（EarlyBirdPlanSaveReqVO）：

| 字段 | 类型 | 必填 | 校验规则 | 说明 |
|------|------|------|----------|------|
| planName | String | 是 | NotBlank | 计划名称 |
| remark | String | 否 | 无 | 优惠描述 |
| discountType | String | 否 | 枚举三选一见第6节 | 折扣方式，默认 AMOUNT_TOTAL |
| discountAmount | BigDecimal | 条件必填 | AMOUNT_PER_PERSON 或 AMOUNT_TOTAL 时必填 | 优惠金额（元） |
| discountPercent | BigDecimal | 条件必填 | PERCENT 时必填，取值 (0, 100) 开区间 | 折扣率，80=8折（不是立减 80%） |
| priority | Integer | 否 | >=0，默认 0 | 优先级，同区间多命中时大者优先 |
| minPeople | Integer | 是 | >=1 | 最低适用人数（含） |
| maxPeople | Integer | 否 | >=minPeople | 最高适用人数（含），不填=不限 |
| startDate | LocalDate | 是 | yyyy-MM-dd | 生效开始日期 |
| endDate | LocalDate | 是 | yyyy-MM-dd，需 >=startDate | 生效结束日期 |
| productIds | List<Long> | 否 | 无 | 关联产品 ID 列表，空=不限产品 |
| applicableTravelerTypes | List<String> | 否 | 枚举值见第6节 | 适用人群 code 列表，空=后端自动填充 [ADULT, CHILD, YOUNG_CHILD] |

### 4.4 PUT /v3/admin/order/early-bird/{planId} — 修改

路径参数 planId（Long/字符串）。请求体与 4.3 完全一致，全字段覆盖更新（不支持 PATCH，未传的选填字段会被重置为 null / 默认值）。

### 4.5 DELETE /v3/admin/order/early-bird/{planId} — 删除

路径参数 planId（Long/字符串）。无请求体。软删除，删除后 planId 不可再查询。

### 4.6 PUT /v3/admin/order/early-bird/{planId}/toggle — 启用 / 禁用

| 参数 | 类型 | 位置 | 必填 | 说明 |
|------|------|------|------|------|
| planId | Long | Path | 是 | 早鸟计划 ID |
| enabled | Boolean | Query | 是 | true=启用；false=禁用 |

禁用后不再参与下单时自动匹配。启用时若与已有启用计划冲突（同产品+时间段+人数区间重叠），返回 581610。

---

## 5. 出参字段

所有接口返回 Result<T> 包装，成功 code=200。

| 接口 | 出参类型 |
|------|---------|
| 分页列表 | Result<PageResult<EarlyBirdPlanVO>> |
| 详情 | Result<EarlyBirdPlanVO> |
| 创建 | Result<EarlyBirdPlanVO> |
| 修改 | Result<Void> |
| 删除 | Result<Void> |
| 启用/禁用 | Result<Void> |

### EarlyBirdPlanVO 字段表

| 字段 | 类型 | 说明 |
|------|------|------|
| planId | Long（JSON 已序列化为字符串） | 计划 ID，雪花 ID，前端用字符串类型接收防 JS 精度丢失 |
| planName | String | 计划名称 |
| remark | String / null | 优惠描述 |
| discountType | String | 折扣方式枚举 code（AMOUNT_TOTAL / AMOUNT_PER_PERSON / PERCENT） |
| discountAmount | BigDecimal（JSON 序列化为字符串） / null | 优惠金额（元）；discountType=PERCENT 时为 null |
| discountPercent | BigDecimal / null | 折扣率 (0~100)；非 PERCENT 时为 null；80=8折 |
| priority | Integer | 优先级，默认 0 |
| minPeople | Integer | 最低适用人数（含） |
| maxPeople | Integer / null | 最高适用人数（含），null=不限 |
| peopleRangeText | String | 后端拼好的人数区间文案，如 3-5 人 / 3 人及以上 |
| discountText | String | 后端拼好的折扣说明，如 每人立减 100 元 / 整单立减 500 元 / 9 折优惠 |
| startDate | String（yyyy-MM-dd） | 生效开始日期 |
| endDate | String（yyyy-MM-dd） | 生效结束日期 |
| enabled | Boolean | 是否启用 |
| applicableTravelerTypes | List<String> | 适用人群 code 列表；配置为空时后端回填并持久化 [ADULT, CHILD, YOUNG_CHILD] |
| applicableTravelerTypesText | String | 后端拼好的适用人群文案，如 成人/儿童/小童 |
| productIds | List<Long> | 关联产品 ID 列表，空数组=不限产品 |
| productNames | List<String> | 关联产品名称，下标与 productIds 对齐；产品已删除时填 产品[ID]已删除 |
| products | List<{productId:Long, name:String}> | 关联产品结构化列表，供编辑弹窗 tag 回显用 |
| createTime | String（ISO 8601） | 创建时间，如 2026-06-22T10:00:00 |
| updateTime | String（ISO 8601） | 最后更新时间 |

---

## 6. 枚举 / 数据字典

### discountType — 折扣方式

| code | 中文 | 计算公式 | 生效字段 |
|------|------|----------|---------|
| AMOUNT_TOTAL | 整单立减（默认） | 优惠额 = discountAmount | discountAmount 必填 |
| AMOUNT_PER_PERSON | 每人立减 | 优惠额 = discountAmount x 计费人数 | discountAmount 必填 |
| PERCENT | 按比例打折 | 优惠额 = 总价 x (100 - discountPercent) / 100 | discountPercent 必填 |

重要：discountPercent=80 表示打 8 折，不是优惠 80%。前端展示请转换为 X折 格式，切勿直接显示 80%。

### applicableTravelerTypes — 适用人群

| code | 中文 | 人数匹配规则 |
|------|------|------------|
| ADULT | 成人 | 计入人数口径 |
| CHILD | 儿童 | 计入人数口径 |
| YOUNG_CHILD | 小童 | 计入人数口径 |
| BABY | 婴儿 | 默认不计入；需显式配置才计入 |

人数匹配口径：统计 applicableTravelerTypes 中包含的人群类别对应人数之和，判断是否满足 minPeople / maxPeople 区间。默认排除婴儿，人数口径 = adult + child + youngChild（不含 baby）。

---

## 7. 错误码

早鸟模块错误码段位：581600 – 581699

| 错误码 | 描述 | 触发场景 |
|--------|------|---------|
| 581600 | 早鸟计划不存在 | planId 查不到记录（详情 / 修改 / 删除 / toggle） |
| 581601 | 早鸟计划已禁用 | 对禁用计划执行不允许的操作 |
| 581602 | 当前日期不在早鸟计划有效期内 | 当前日期不在 startDate ~ endDate 范围内 |
| 581603 | 人数不满足最低人数要求（{0}） | 计费人数 < minPeople，{0} 为阈值 |
| 581604 | 人数超过最高人数限制（{0}） | 计费人数 > maxPeople，{0} 为阈值 |
| 581605 | PERCENT 类型时折扣率不能为空 | discountType=PERCENT 但未传 discountPercent |
| 581606 | {0} 类型时优惠金额不能为空 | 立减类型但未传 discountAmount，{0} 为类型名 |
| 581607 | 最高人数不能小于最低人数 | maxPeople < minPeople |
| 581608 | 结束日期不能早于开始日期 | endDate < startDate |
| 581609 | 适用人群包含非法取值：{0} | applicableTravelerTypes 含未知 code |
| 581610 | 产品已存在生效时间与人数区间重叠的早鸟计划，不能重复创建或启用 | 同一产品同时间段同人数区间已有启用计划（防同订单冲突） |

---

## 8. 示例

### 8.1 典型成功 — 创建整单立减计划

请求：

    POST /v3/admin/order/early-bird
    Content-Type: application/json

请求体：

    {
      planName: 暑期早鸟立减300,
      remark: 7月出发整单立减300元,
      discountType: AMOUNT_TOTAL,
      discountAmount: 300,
      priority: 10,
      minPeople: 2,
      maxPeople: 6,
      startDate: 2026-07-01,
      endDate: 2026-07-31,
      productIds: [1900000000001, 1900000000002],
      applicableTravelerTypes: [ADULT, CHILD, YOUNG_CHILD]
    }

响应：

    {
      code: 200,
      msg: success,
      data: {
        planId: 1920000000001234,
        planName: 暑期早鸟立减300,
        discountType: AMOUNT_TOTAL,
        discountAmount: 300.00,
        discountPercent: null,
        priority: 10,
        minPeople: 2,
        maxPeople: 6,
        peopleRangeText: 2-6 人,
        discountText: 整单立减 300 元,
        startDate: 2026-07-01,
        endDate: 2026-07-31,
        enabled: true,
        applicableTravelerTypes: [ADULT, CHILD, YOUNG_CHILD],
        applicableTravelerTypesText: 成人/儿童/小童,
        productIds: [1900000000001, 1900000000002],
        productNames: [云南大理亲子7日游, 丽江雪山5日徒步],
        products: [
          {productId: 1900000000001, name: 云南大理亲子7日游},
          {productId: 1900000000002, name: 丽江雪山5日徒步}
        ],
        createTime: 2026-06-22T10:00:00
      }
    }

注意：discountAmount 在响应 JSON 中已序列化为字符串（300.00），planId 同理（1920000000001234）。前端用字符串接收，勿用 Number 解析雪花 ID。

### 8.2 边界情况 — 折扣率计划，不限人数，不限产品

maxPeople 不传=不限；productIds 不传=不限产品；applicableTravelerTypes 不传=后端自动填充默认值。

请求体：

    {
      planName: 年底全产品9折,
      discountType: PERCENT,
      discountPercent: 90,
      minPeople: 1,
      startDate: 2026-12-01,
      endDate: 2026-12-31
    }

响应（关键字段）：

    {
      code: 200,
      data: {
        planId: 1920000000001235,
        discountType: PERCENT,
        discountAmount: null,
        discountPercent: 90.00,
        maxPeople: null,
        peopleRangeText: 1 人及以上,
        discountText: 9 折优惠,
        applicableTravelerTypes: [ADULT, CHILD, YOUNG_CHILD],
        productIds: [],
        products: []
      }
    }

### 8.3 业务失败 — 产品时间区间与人数区间均重叠（错误码 581610）

同一产品在同一时间段、同一人数区间已存在启用状态的早鸟计划，新建或重新启用时返回：

请求体：

    {planName:冲突测试,discountType:AMOUNT_TOTAL,discountAmount:100,minPeople:2,maxPeople:5,startDate:2026-07-01,endDate:2026-07-15,productIds:[1900000000001]}

响应：

    {code:581610,msg:产品(ID=1900000000001)已存在生效时间与人数区间重叠的早鸟计划(planId=1920000000001234)，不能重复创建或启用,data:null}

前端按 code === 200 判断成功，失败时把 msg 直接 toast 展示即可，后端消息已是可读中文。

---

## 9. 业务边界

**适用场景**：
- 管理员新建 / 修改 / 删除 / 查询早鸟优惠配置
- 对已有计划快速启停
- 查询当前所有早鸟计划状态列表

**不适用场景**：
- 小程序 C 端用户查看早鸟优惠展示（走内部 Feign 接口，前端无需关心）
- 下单时自动匹配优惠（订单服务内部逻辑，前端无需干预）
- 已下单订单的优惠金额追溯修改（早鸟仅下单时一次性结算，改计划不追溯已有订单）

**特殊边界**：
- 同一产品在同一时间段 + 同一人数区间内，只允许存在一个启用状态的早鸟计划；禁用状态不参与冲突检测；人数区间不重叠（阶梯档）不受限制
- 修改或删除计划不影响已下单订单中已应用的早鸟优惠金额
- applicableTravelerTypes 传空或不传，后端自动填充 [ADULT, CHILD, YOUNG_CHILD] 并持久化到数据库

---

## 10. 修改前后对比（前端迁移指引）

核心结论：早鸟管理页面 UI / 字段 / 交互零改动，唯一动作 = 切换接口路径。

| 项目 | v2（旧） | v3（新） | 前端是否需要改动 |
|------|---------|---------|----------------|
| 列表接口路径 | /admin/order/early-bird 或 /admin/order/early-bird/list | /v3/admin/order/early-bird/list | 是，必须加 /v3 前缀且必须带 /list 后缀 |
| 详情接口路径 | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 是，加 /v3 前缀 |
| 创建接口路径 | /admin/order/early-bird | /v3/admin/order/early-bird | 是，加 /v3 前缀 |
| 修改接口路径 | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 是，加 /v3 前缀 |
| 删除接口路径 | /admin/order/early-bird/{planId} | /v3/admin/order/early-bird/{planId} | 是，加 /v3 前缀 |
| 启/禁用接口路径 | /admin/order/early-bird/{planId}/toggle | /v3/admin/order/early-bird/{planId}/toggle | 是，加 /v3 前缀 |
| 请求参数（入参字段） | 不变 | 完全一致 | 否 |
| 响应字段（出参字段） | 不变 | 完全一致 | 否 |
| 枚举值 / 数据字典 | 不变 | 完全一致 | 否 |
| 页面 UI / 交互逻辑 | 不变 | 完全一致 | 否 |

**前端迁移三步走**：

1. 在 v3 管理后台新建「早鸟优惠」菜单入口（页面可直接照搬 v2 早鸟优惠管理页面，UI / 字段 / 交互不变；v3 管理后台为二期独立前端工程，菜单需要新增，不会从 v2 自动带过来）
2. 将所有早鸟管理接口的请求路径加 /v3 前缀（/admin/order/early-bird* 改为 /v3/admin/order/early-bird*）
3. 列表接口如果之前调的是空路径 /admin/order/early-bird（无 /list 后缀），改为 /v3/admin/order/early-bird/list

---

## 11. 影响评估 / 回滚

| 项目 | 内容 |
|------|------|
| 破坏兼容 | 无，v3 接口为新增路径，v2 接口同期仍可访问 |
| 前端同步上线 | 无强制同步要求；v3 接口上线后可随时切换，v2 继续可用期间两者均可访问 |
| 回滚方案 | 如切换后遇到问题，将请求路径回退到 v2（去掉 /v3 前缀）即可立即恢复；后端 v2 接口未下线 |

---

## 12. 注意事项

1. 列表接口路径必须带 /list：v3 只有 /v3/admin/order/early-bird/list，调 /v3/admin/order/early-bird（无后缀）返回 404。这是 v2 到 v3 唯一的路径语义差异。
2. discountPercent 陷阱：discountPercent=80 表示打 8 折，不是优惠 80%。页面展示请转换为 8折，切勿直接显示 80%。
3. 金额字段为字符串：discountAmount、planId 在响应 JSON 中均已序列化为字符串。前端接收时用字符串类型，避免 JS 精度丢失。
4. PUT 全字段覆盖：修改接口不支持 PATCH，未传的选填字段会被重置为 null / 默认值。前端编辑弹窗提交时需确保把所有当前值一并回传。
5. toggle 启用触发冲突校验：将已禁用的计划重新启用时，若同产品 + 同时间段 + 同人数区间已有其他启用计划，接口返回 581610 并拒绝操作，前端需处理该错误码并展示 msg。
6. 早鸟不追溯已有订单：管理员修改或删除计划后，已下单订单中已结算的早鸟优惠金额不变，仅影响后续新下单的订单。
7. 需新建菜单：v3 管理后台是二期独立前端工程，早鸟优惠菜单不会从 v2 自动继承，前端需在 v3 后台手动新建「早鸟优惠」菜单入口（页面照搬 v2 即可）。

---

## 13. 关联 / 联系人

| 项目 | 内容 |
|------|------|
| PR #4053 | https://git.1814.love:8443/wx/HL/pulls/4053（早鸟 v3 基础 CRUD 落地） |
| PR #4064 | https://git.1814.love:8443/wx/HL/pulls/4064（早鸟管理接口补全） |
| PR #4076 | https://git.1814.love:8443/wx/HL/pulls/4076（早鸟管理 CRUD 6 接口完整上线） |
| PR #4082 | https://git.1814.love:8443/wx/HL/pulls/4082（早鸟 v3 路径对齐与修复） |
| 后端负责人 | yst（腰苏图） |
| 变更服务 | hl-order-service-v3（端口 8086，网关统一走 9443） |
