文件
hl-api-changelog/changelogs-v2/2026-09/10_7389_多夜配房释放改逐夜回补resource库存账本告警码拆分-修复-管理后台.md
T
Mimingguang 3370e7a278
changelog-filename-gate / validate (push) Failing after 3s
docs(changelog): #7389 frontmatter 回写 not_required
2026-09-10 12:06:07 +08:00

19 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 7389 多夜配房释放改为逐夜回补(不再只回补首夜);resource 房务库存账本内部一致性告警从共用 310404 拆分为 310417-310420 语义化错误码 admin wx(GIT) 修复 deployed not_required not_required mmg not_required 2026-09-10 mmg: 修复全在后端内部(order-v3 多夜释放逐夜回补 + resource 账本告警码 310404 拆 310417-310420),无对外接口/字段/VO/路由变化;changelog 自答非必须同步上线,唯一触点=前端对 310404 硬编码分支。grep 实证前端零 310404/310417-310420 命中、零 /internal/resource 库存端点直连,触点不存在。错误码由拦截器透后端 message(无中央错误码字典),旧前端不受影响。 2026-09-10 dev-v3

房务多夜配房释放改逐夜回补;resource 库存账本告警码拆分(修复)

服务: hl-order-service-v3, hl-resource-service PR: #7423 Issue: #7389 日期: 2026-09-10 影响范围: 多晚配房释放/回滚链路(order-v3 HouseDualDeductionService);resource 房务库存账本内部一致性告警码(RoomInventoryOpLogService,4 个码从共用 310404 拆分为 310417-310420)


⚠️ 关键变化

  1. 多夜配房释放此前只对首夜调用 resource 释放接口,stay_nights > 1 时后 N-1 晚的库存被永久占住、无补偿入口(方向是少卖)。改后按 stay_nights 逐夜循环释放,某一夜失败不打断其余夜(best-effort),全部夜成功才把该行标 DONE,否则留 PENDING 交 DualDeductionReconcileJob 兜底。
  2. resource 侧原本四处共用 310404("酒店ID/房型ID/日期不可为空")的内部一致性告警拆成四个语义码 310417-310420(详见"六.5、枚举")。这是前端/调用方唯一需要关心的契约变化:若有地方对 310404 做过硬编码分支处理(尤其是"同一 opId 二次请求内容不一致"这一幂等冲突场景),现在会收到 310417,不再是 310404。
  3. 本单无接口新增/修改/删除,无 Flyway、无表结构变更、无 VO 字段增删,网关零改动。以下按模板编号组织,但"三、接口详情"等节按"无对外接口契约变化、只有内部行为/错误码变化"的实际情况填写,不强行套用逐接口 VO 表格。

一、背景

house_dual_deduction_log 一行记的是"一次扣减占了 stay_nights 晚"的库存持有关系。改前的正常释放路径(HouseDualDeductionService.restoreLogRecord)只对 log.getStayDate() 这一晚调用 resource releaseRoomInventory;当 stay_nights > 1(跨夜配房)时,第 2..N 晚扣掉的库存永久没有对应的释放调用,且没有任何补偿入口能把这部分库存要回来。

同一 PR 顺带修了 resource 侧的一处诊断缺陷:RoomInventoryOpLogService 里四条内部一致性判断分支(幂等键内容冲突、拒绝结果落库失败、结果快照落库失败、结果快照缺失)此前全部抛同一个 310404「酒店ID/房型ID/日期不可为空」,排障时看到的文案和真实原因(例如"同一 opId 被另一组请求内容占用")完全对不上号,遂拆成四个语义码。


二、变更接口清单

本单不改变任何对外 HTTP 接口的方法 / 路径 / 请求体 / 响应体结构,故没有严格意义上的"变更接口"。受影响的是以下既有内部链路在特定边界场景下的行为与错误码:

# 接口/链路 方法 路径 变更类型 说明
1 配房库存释放(订单取消/换酒店/删配房时内部触发) POST /internal/resource/hotels/{hotelId}/room-types/{roomTypeId}/release 调用方(order-v3)行为变更 多夜持有的库存由"只发首夜一次调用"改为"逐夜循环调用";该内部 Feign 端点自身请求/响应体结构不变
2 库存操作账本一致性校验(deduct/release 共用的内部一致性分支) POST /internal/resource/hotels/{hotelId}/room-types/{roomTypeId}/deduct、/internal/resource/hotels/{hotelId}/room-types/{roomTypeId}/release 错误码细分 4 种内部一致性异常场景的返回码由共用 310404 拆分为 310417/310418/310419/310420;请求体/响应体结构不变

三、接口详情

本单不新增/修改/删除任何对外 VO 契约,不适用模板逐接口 VO + 入参/出参字段表结构。按"行为变更点"分两小节说明。

1. 多夜配房释放逐夜回补

涉及方法:HouseDualDeductionService.restoreLogRecord(私有方法,被 restore(assignmentId, reason) / restoreByLogId(logId, reason) / restoreByLogIdAndRound(...) 共用),无独立对外 HTTP 签名。

使用场景

订单取消、换酒店、删配房等触发释放已扣减的房型库存时调用。此前 stay_nights > 1(跨夜配房,一次扣减覆盖多晚)时只有首夜被释放。

入参字段表

不适用(内部私有方法,参数来自 HouseDualDeductionLogDO 实体字段 hotelId/roomTypeId/stayDate/stayNights/roomCount,本单未新增/删除任何列或字段)。

出参字段表

不适用。restore / restoreByLogId 对外返回 boolean:true = 全部夜释放成功且已标 DONE;false = 已标 PENDING,待 DualDeductionReconcileJob 兜底重放。

请求示例

不适用:内部私有方法调用,无 HTTP 请求体;每夜实际下发给 resource 的请求体见下方第 2 小节的请求示例。

响应示例

不适用:方法签名为 boolean,无 JSON 响应体。

空数据 / 降级响应

selectHoldingByAssignment 查无持有中记录时(如 CORE/CUSTOM 历史配房本就未扣库存),直接返回 true,视为释放成功,不发任何 resource 调用。

错误响应

不适用:本方法内部 try/catch 吞掉 resource 调用异常(转为 markReleasePending 落库标 PENDING),不对外抛出异常,无对外错误响应体。

业务边界

  • 释放循环次数 = normalizedNights(logRecord.getStayNights()):stayDate 起逐日 plusDays(i),与扣减侧 callResourceDeduct 的夜序严格镜像。
  • opId 由 buildReleaseOpId(logId, round, stayDate, date) 构造:首夜(date == stayDate)返回 release-{logId}-{round}(与改动前逐字节一致,供跨版本在途 PENDING 行重放命中同一幂等键、不重复回补);第 2..N 夜返回 release-{logId}-{round}-d{date}。全类唯一一份实现,正常释放与跨夜扣减补偿两条路径共用,有反射测试守着不许再写第二份编法。
  • best-effort:某一夜释放失败不中断循环,继续释放剩余夜;只要有一夜失败,整行走 markReleasePending(留 PENDING),不会推进为 DONE;全部夜成功才调用 markRolledBackCas 标 DONE。
  • normalizedNights 归一化规则由"null → 1"扩为"null 或 < 1 → 1",避免历史脏数据 stay_nights=0(或负数)导致循环 0 次却把行标 DONE(库存被永久判死)。
  • stay_nights 为 null 的存量行按 1 夜处理,行为与本单改动前逐字一致(不迁移不回填)。

2. resource 房务库存账本内部一致性告警码拆分

涉及类:HotelErrorCode(hl-resource-service);触发点 RoomInventoryOpLogService.execute / RoomInventoryOpLogService.replay。

使用场景

order-v3 通过 ResourceFeignClient 调用 resource 的库存扣减/释放内部端点时,resource 侧用请求携带的 opId 做幂等账本(room_inventory_op_log,唯一键 uk_op_id)。以下四种内部一致性场景改前全部返回 310404,改后各自独立返回。

入参字段表

字段 位置 类型 必填 约束 说明
date Body LocalDate 是 NotNull 入住日期;本单未改
qty Body Integer 是 NotNull, Min 1 请求变更房数;本单未改
opId Body String 是 NotBlank, Size max 256 调用方业务键 + 持有轮次 + 必要日期后缀;本单未改字段本身,但 order-v3 侧的构造规则见第 1 小节

(RoomInventoryOpReqDTO,order-v3 与 resource 两端各有一份同结构 DTO,字段名与约束逐一核对一致,本单均未改动。)

出参字段表

不适用——正常路径响应体结构(Result<HouseInventoryOpRespDTO>,字段 changed/remain/duplicated)未改;本单只影响错误态下 code/message 的取值范围。

请求示例

{
  "date": "2026-05-02",
  "qty": 2,
  "opId": "release-88001-0-d2026-05-02"
}

(内部 Feign 端点请求体,字段本身未变,仅用于对应下方错误响应示例。)

响应示例

不适用:正常路径响应结构本单未改,不重复列出。

空数据 / 降级响应

不适用(本端点无"空数据"语义;账本命中后要么返回历史快照,要么按下方错误码之一拒绝)。

错误响应

{
  "code": 310417,
  "message": "操作幂等键 release-88001-0-d2026-05-02 已存在且请求内容不一致",
  "success": false,
  "data": null
}

(HotelErrorCode.HOUSE_INVENTORY_OP_ID_CONFLICT,message 模板 操作幂等键 {0} 已存在且请求内容不一致 中的 {0} 由 MessageFormat 替换为实际 opId;hl-resource-service/src/main/java/com/hulalv/resource/errorcode/HotelErrorCode.java:216-217。触发:同一 opId 第二次到达时,账本命中行的 hotelId/roomTypeId/priceDate/quantity/opType 与本次请求任一不一致。)

业务边界

  • 310417(HOUSE_INVENTORY_OP_ID_CONFLICT,HotelErrorCode.java:216-217):同一 opId 对应的 hotelId/roomTypeId/date/qty/opType 与首次请求不一致,属确定性冲突——order-v3 侧 toResourceBusinessException 对该码做了特判:不当库存不足处理(不标 DONE 放行重扣),也不静默吞掉,而是原样上抛并记 error 日志,让失败分支走 markResourceFailed(留 NONE 阻断同轮重放),不再对该码做空转重试。
  • 310418(HOUSE_INVENTORY_LEDGER_REJECT_PERSIST_FAILED,HotelErrorCode.java:220-221):库存不足的拒绝结果回写账本影响 0 行(同一事务内账本行被并发改写),属内部一致性异常。
  • 310419(HOUSE_INVENTORY_LEDGER_SNAPSHOT_PERSIST_FAILED,HotelErrorCode.java:224-225):成功结果快照回写账本影响 0 行,同上。
  • 310420(HOUSE_INVENTORY_LEDGER_SNAPSHOT_MISSING,HotelErrorCode.java:228-229):账本按 opId 命中该行(指纹逐字段一致)但首次结果快照缺失(changed/remain 任一为 null 且非库存不足结果),无法安全重放(既不能当成功也不能当拒绝)。
  • 指纹逐字段一致的正常重放(同 opId、同 hotelId/roomTypeId/date/qty/opType)仍返回首次快照并置 duplicated=true,不受本次改动影响。

四、契约约束与正确调用方式

本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。

本单未改变任何入参校验规则或状态切换动作,无新增正确/错误 payload 对照需要记录。唯一需要调用方(order-v3 及未来其他消费 resource 内部库存端点的服务)确认的规则是:同一 opId 二次调用时,请求内容必须与首次逐字段一致(hotelId/roomTypeId/date/qty/opType),否则拒绝并返回 310417——这条判定逻辑改前也存在,只是改前返回码是 310404,本单只是把返回码改准确,未改变判定逻辑本身。

切换状态时的必要动作

无。本单不涉及任何需要调用方额外显式置空/切换的字段。


五、数据库行为

本单无 Flyway、无表结构变更、无字段增删。house_dual_deduction_log、room_inventory_op_log 两张表的列定义均未动,仅改变了写入这两张表的调用节奏(多夜释放从 1 次调用变成 N 次调用)与错误码分支的选择,不改变任何列的取值集合。


六、边界行为

  • 多夜释放某一夜网络/业务异常:不中断其余夜,整行留 PENDING 交 DualDeductionReconcileJob 兜底重放;不抛异常给上层调用方。
  • stay_nights 为 null(历史存量行):按 1 夜处理,与本单改动前逐字节一致。
  • stay_nights 为脏数据 0 或负数:按 1 夜兜底,避免循环 0 次却把行标 DONE 导致库存永久判死。
  • 首夜与非首夜的 opId 编法不同(见第一小节),跨版本在途 PENDING 行重放时首夜仍命中改动前的旧键,不会二次真实释放。
  • deduct 路径命中 310417(幂等键冲突):order-v3 不再重试,直接标记该轮扣减为失败(resourceFailed)并原样上抛,不做静默降级。

六.5、枚举 / 数据字典

resource 房务库存账本一致性错误码(HotelErrorCode,段位 310417-310420,工单 #7389 新增)

所属字段:内部 Feign 错误态响应 Result.code | 类型:Integer

值 常量名 message 模板 触发条件
310417 HOUSE_INVENTORY_OP_ID_CONFLICT 操作幂等键 {0} 已存在且请求内容不一致 同一 opId 第二次到达,账本命中行的 hotelId/roomTypeId/date/qty/opType 与本次请求任一不一致
310418 HOUSE_INVENTORY_LEDGER_REJECT_PERSIST_FAILED 库存操作拒绝结果落库失败(操作幂等键 {0}) 库存不足的拒绝结果回写账本影响 0 行
310419 HOUSE_INVENTORY_LEDGER_SNAPSHOT_PERSIST_FAILED 库存操作结果快照落库失败(操作幂等键 {0}) 成功结果快照回写账本影响 0 行
310420 HOUSE_INVENTORY_LEDGER_SNAPSHOT_MISSING 库存操作首次结果快照缺失,无法重放(操作幂等键 {0}) 账本命中但首次结果快照(changed/remain)缺失,且非库存不足结果

(hl-resource-service/src/main/java/com/hulalv/resource/errorcode/HotelErrorCode.java:214-229;{0} 为 MessageFormat 占位符,替换为实际 opId。改前这四个场景全部复用 310404 HOUSE_INVENTORY_PARAM_BLANK「酒店ID/房型ID/日期不可为空」,见同文件第 150-152 行。)

order-v3 侧的特判:仅 310417 被 HouseDualDeductionService.toResourceBusinessException 特判为不可重试(原样上抛 + error 日志,阻断同轮重放);310418/310419/310420 未做特判,走原有"其他 resource 业务异常 → 原样上抛"分支。


六.6、修改前后对比

字段级对比

字段/返回值 改前 改后
resource 内部账本一致性告警码 均为 310404(4 种场景共用) 拆分为 310417/310418/310419/310420,各自独立
端点请求/响应字段结构 无变化 无变化

行为级对比

行为 改前 改后
多夜配房释放 只对首夜调 releaseRoomInventory,stay_nights > 1 时后 N-1 晚永久少释放 按 stay_nights 逐夜循环释放,夜序与扣减侧镜像
释放 opId 编法 首夜固定 release-{logId}-{round} 首夜不变,仍是 release-{logId}-{round};第 2..N 夜新增 release-{logId}-{round}-d{date}
单夜释放失败处理 唯一一次调用失败即整行留 PENDING best-effort:某夜失败不中断其余夜释放,仍是"有失败即留 PENDING、全成功才 DONE"
normalizedNights 归一化 null → 1 null 或 < 1 → 1
resource 幂等键冲突时的重试策略(order-v3 侧) 无特判,与其他 resource 业务异常同等对待 310417 特判为确定性失败,标记 resourceFailed,不再空转重试

六.7、影响评估

  • 是否破坏向后兼容:否。对外 HTTP 接口方法/路径/请求体/响应体结构均未变化;单夜配房(stay_nights <= 1,绝大多数场景)的释放行为与 opId 与改动前逐字节一致。
  • 前端是否必须同步上线:否。本单不涉及任何前端可见的接口/字段变化;仅当调用方对 310404 做过针对"内部一致性异常"这一特定子场景的硬编码处理时,才需要知道现在会收到 310417-310420。
  • 前端 workaround 清理点:无强制清理项。如有代码对 310404 做了范围过宽的兜底判断(把"参数为空"和"幂等键冲突"混为一谈),建议改为按 310417 单独识别幂等冲突场景。

七、不影响范围

  • 仅影响:order-v3 多夜配房释放的内部循环行为(HouseDualDeductionService.restoreLogRecord);resource 房务库存账本四种内部一致性异常的错误码取值(RoomInventoryOpLogService)。
  • 零影响:
    • 无新增/修改/删除任何对外 HTTP 接口;网关路由零改动。
    • 单夜配房(stay_nights <= 1)的释放 opId 与改动前逐字节一致(release-{logId}-{round},无 -d 后缀)。
    • house_dual_deduction_log、room_inventory_op_log 表结构未变,无 Flyway、无数据迁移。
    • 库存不足码 310403、参数为空码 310404 本身未删除,310404 仍会在其原有的真正"参数为空"场景下返回,只是不再被这四个内部一致性分支借用。
    • 正常重放(指纹一致的重复请求)路径未变,仍返回首次快照并置 duplicated=true。

八、测试环境已验证

以下两条已在测试服网关实测通过,其余 AC-1/2/3/6/13 已实测通过但本 changelog 只摘录对调用方最直接可见的两条结论,不逐条复述实测数据:

三晚扣减后释放:
  三晚 stock_used 全部回到 0 [验证通过]
  room_inventory_op_log 生成三条记录, opId 为
    release-{logId}-0(首夜)、release-{logId}-0-d{date}(第2晚)、release-{logId}-0-d{date}(第3晚) [验证通过]

同一 opId 二次请求内容不一致(改 qty / 改 date)各自单独发起:
  两种场景均返回 310417 [验证通过]
  账本对应 opId 各仅 1 行(未产生第二行), 价格日历库存零变动 [验证通过]

十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @wx