Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
18 KiB
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 | 8665 | 配房删改与单日确认并发收口:三个写口新增 808932 并发冲突码 | admin | wx(GIT) | 修改接口 | deployed | not_required | not_required | 2026-10-02 | dev-v3 |
配房删改与单日确认并发收口:新增 808932 并发状态码
存放目录: 二期 →
changelogs-v2/2026-10/服务: hl-order-service-v3 Issue: #8665 日期: 2026-10-02 影响范围: 管理后台房务配房操作:删除、修改、单日确认三个写口,并发交错时新增返回错误码 808932
⚠️ 关键变化
- 新增错误码 808932(房务状态已被并发修改,请刷新后重试):删除配房、修改配房、单日确认三个写口在并发交错时返回,不再产生孤儿应付台账行。
- 请求/响应结构无变化:三个接口的方法、路径、入参字段均未变,仅错误码表新增一条。
- 808932 与其他业务错误码走同一套契约:HTTP 200 +
code+message,按code区分即可,无需为该码新增专门的前端处理分支;hl-ui v2.1 现有的通用业务错误拦截器(src/utils/request.js的业务错误分支 +errorBus.js)会把后端返回的message原样呈现,不要求前端硬编码该文案。
一、背景
配房行的删除与修改两个写口(DELETE /assignments/{id} 与 PUT /assignments/{id})原无互斥锁,与单日确认(confirmDayPersist)并发交错时,可能产生「配房行已软删,但应付台账仍留下可付款行」的孤儿记录。工单 #8665 为三个写口补充行级锁定读与版本控制,在并发修改被检测时返回 808932,整体回滚包括该行的任何台账产出。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 删除配房 | DELETE | /v3/admin/order/assignments/{id} |
修改 | 新增错误码 808932(并发冲突) |
| 2 | 修改配房 | PUT | /v3/admin/order/assignments/{id} |
修改 | 新增错误码 808932(并发冲突) |
| 3 | 单日确认 | POST | /v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}/confirm |
修改 | 新增错误码 808932(并发冲突) |
三、接口详情
本节示例 JSON 的字段名、类型、错误码与文案逐一取自源码;ID、日期等取值为说明用的构造值。所有接口统一返回 Result 信封(code / message / data / traceId / success),示例省略 traceId;业务失败与入参校验失败均为 HTTP 200,靠 code 区分。
1. 删除配房 DELETE /v3/admin/order/assignments/{id}
VO: 无请求体 → Void
使用场景
房务管理员在管理后台删除已配置的某条配房行,通常在需要重新调整房型或取消某晚房务时使用。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | ✅ | - | 配房行 ID |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| (无数据体) | - | 删除成功时 Result.data 为 null |
请求示例
DELETE /v3/admin/order/assignments/2105709698592440321
响应示例
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
空数据 / 降级响应
- 若配房行不存在(已被删除或 ID 无效):返回业务错误码 808120「配房不存在」。
错误响应
{
"code": 808932,
"message": "房务状态已被并发修改,请刷新后重试",
"data": null,
"success": false
}
业务边界
- 触发 808932 的场景:该配房行在读取后被并发确认(单日确认的保留行流程)或被并发修改(改配房),版本对不上或已被软删,带版本谓词的软删(
softDeleteWithVersion)影响 0 行。 - 建议的前端处理:收到 808932 时提示用户刷新该订单的配房列表后重试;无需为该码单独编码重试逻辑,交由通用错误提示呈现即可。
- 串行无 808932:若删除在单日确认完全提交之后才发起,行已稳定,返回 200;若确认在删除完全提交之后才查询候选,会命中更早的 808118(该天无可确认的询房中候选),不会到达锁定复读分支。
2. 修改配房 PUT /v3/admin/order/assignments/{id}
VO: AssignmentUpdateReqVO → Void
使用场景
房务管理员修改已配置配房行的协议价、结算价、支付方式、备注、早餐、酒店主数据快照同步开关,不支持修改酒店、房型、间数(需要换酒店/换房型走 §2.3b 调整配房端点,或删除后用 §2.2 重新创建)。EXCEPTION 异常态下的配房仍允许走本接口改价,用于异常桶的人工处置(如与酒店协商退款后的补偿调整)。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | ✅ | - | 配房行 ID |
| protoPrice | Body | BigDecimal | ❌ | ≥0.00 | 协议价 |
| settlementPrice | Body | BigDecimal | ❌ | ≥0.00 | 结算价 |
| settleType | Body | String | ❌ | 正则 cash|sign|company |
结算方式 |
| syncProtocolPrice | Body | Boolean | ❌ | - | 是否同步协议价到酒店主数据快照 |
| syncSettlementPrice | Body | Boolean | ❌ | - | 是否同步结算价到酒店主数据快照 |
| syncSettleType | Body | Boolean | ❌ | - | 是否同步结算方式到酒店主数据快照 |
| remark | Body | String | ❌ | 无长度校验注解 | 备注 |
| syncHotelSnapshot | Body | Boolean | ❌ | - | 是否整体同步酒店主数据快照 |
| breakfast | Body | String | ❌ | 正则(HouseBreakfast.VALUE_REGEX,即 INCLUDED/EXCLUDED/PENDING) |
早餐 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| (无数据体) | - | 修改成功时 Result.data 为 null |
请求示例
{
"settlementPrice": "320.50",
"remark": "与酒店协商调整"
}
响应示例
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
空数据 / 降级响应
- 若配房行不存在:返回 808120「配房不存在」。
错误响应
{
"code": 808932,
"message": "房务状态已被并发修改,请刷新后重试",
"data": null,
"success": false
}
业务边界
- 触发 808932 的场景:该配房行在读取后被并发删除或被并发修改(版本漂移),带
@Version校验的updateById影响 0 行。 - EXCEPTION 态改价:本接口不经过「需求级写锁 + EXCEPTION 写闸」(
assertWritableForAssignment)那条校验链,因此配房所属需求处于 EXCEPTION(异常态)时仍可调用本接口改价,用于异常桶的人工处置;并发删除/修改仍会返 808932。 - 建议的前端处理:收到 808932 时提示用户刷新该配房行详情后重试,无需额外编码特殊逻辑。
3. 单日确认 POST /v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}/confirm
VO: AssignmentDayConfirmReqVO → Void
使用场景
房务确认某个住宿需求的某一晚配房方案,触发应付台账推送和库存更新。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| requirementId | Path | Long | ✅ | - | 住宿需求 ID |
| dayNumber | Path | Integer | ✅ | 超出该需求行程晚数上界返 808102,无下界校验注解 | 第几晚(从 1 开始计数) |
| keepAssignmentIds | Body | List | ❌ | 非空时每个 id 须属于该晚询房中候选集,否则返 808123 | 保留的配房行 ID 清单(待翻 CONFIRMED);整个请求体、本字段均可省略,或传空数组——两者语义相同,均表示保留该晚全部询房中候选(全部翻 CONFIRMED),不传则不会软删任何候选行;显式传非空列表时,列表外的该晚候选行才会被软删 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| (无数据体) | - | 确认成功时 Result.data 为 null |
请求示例
{
"keepAssignmentIds": [2105709698592440321]
}
响应示例
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
空数据 / 降级响应
- 若该晚无询房中的候选(已被删除或已确认):返回 808118「该天无可确认的询房中候选, 请先配房再确认」。
- 若该需求所属订单已取消或处于异常处置中(
house_status=EXCEPTION):返回 808119「订单已取消或异常处置中, 该需求不可新增或确认配房, 请在异常桶处理」。 - 若
keepAssignmentIds非空且其中存在不属于该晚询房中候选的 id:返回 808123「保留的配房行不属于该天询房中候选(可能配房已更新), 请刷新后重试」。
错误响应
{
"code": 808932,
"message": "房务状态已被并发修改,请刷新后重试",
"data": null,
"success": false
}
业务边界
- 触发 808932 的场景:确认流程中保留行(
keepAssignmentIds对应的候选行)在候选列表读取之后被并发删除或修改,锁定复读时发现该行已不存在、已非询房中状态、版本不一致,或翻 CONFIRMED 的updateById影响 0 行。 - 整体回滚:落选候选先软删、保留候选再逐行锁定复读,任一保留行复读发现不一致(抛 808932)时整个事务回滚——包括同一事务内已执行的落选行软删——该确认涉及的应付台账均不推送,确保终态一致,不留半截状态。
- 建议的前端处理:收到 808932 时提示用户刷新该晚配房列表后重试,无需额外编码特殊逻辑。
- 串行与并发的区别:若删除完全提交在确认发起之前,确认查询该晚候选时已无询房中的行,会命中更早的 808118(候选预检),不会到达锁定复读分支(808932 仅针对确认读取候选之后、锁定复读之前这段并发窗口);两条路径的共同效果一致:均不推送应付台账。
四、契约约束与正确调用方式
- 三个接口的请求方式、路径、参数均无变化,仅错误码表补充。
- 808932 与其他业务错误码(如 808118、808119、808120、808123)一样走 HTTP 200 + 业务码的行为,前端按
code区分即可;hl-ui v2.1 的通用业务错误拦截器会将后端message原样呈现,不要求为 808932 单独编码处理分支。 - 单日确认(接口 3)请求体可以整体省略:省略或
keepAssignmentIds传空数组语义相同,均表示保留该晚全部询房中候选;若需要落选部分候选行,必须显式传入要保留的 id 列表。
五、数据库行为
- 无表结构变更、无 Flyway 迁移(本次合并仅涉及 Service/测试代码与 API 文档,对比 #8665 合并提交的文件清单确认无
db/migration变更)。 - 配房表
house_hotel_assignment的version列与实体上的@Version注解系已有能力(早于本次改动),本次复用它为删除、修改、单日确认三个写口补齐「锁定读(SELECT...FOR UPDATE)+ 带版本更新/软删」的组合:先用锁定读拿到最新行与其version,再执行带版本谓词的写操作(updateById走 MyBatis-Plus 全局注册的乐观锁拦截器自动拼version条件;软删复用既有的softDeleteWithVersion(id, version)),任一写操作影响 0 行即判定为并发冲突并抛 808932,整体事务回滚。
六、边界行为
- 离线与网络异常:808932 不涉及网络/超时,纯业务并发码,离线时前端仍会收到错误响应。
- 灰度与开关:本次改动无灰度开关,所有测试环境已包含该并发收口逻辑。
- 下游链路:应付台账推送、库存扣减、其他异步链路仅当确认成功(code=200)才执行,808932 回滚后不产生任何账务记录。
六.5 枚举
不涉及新增枚举值;配房状态(INQUIRING/CONFIRMED/EXCEPTION 等)无变化。
六.6、修改前后对比
并发行为对比
| 项 | 改前 | 改后 |
|---|---|---|
| 删除配房并发于确认 | 可能双 200,留下孤儿应付台账行 | 后提交方返 808932,回滚,无台账产出 |
| 修改配房并发于确认 | 可能按过期快照推台账 | 后提交方返 808932,回滚,无台账产出 |
| 单日确认保留行被删 | 静默跳过已删行,继续推台账 | 锁定复读检测到行已删,返 808932,回滚,无台账产出 |
| 返回的错误码 | 无 808932 | 新增 808932(仅在并发窗口内返回) |
六.7、影响评估
- 是否破坏向后兼容:否。新增错误码不影响其他业务流程,正常序列的删除、修改、确认仍返回 200。
- 前端是否必须同步上线:否。hl-ui v2.1 现有的通用业务错误拦截器会把后端返回的
message原样呈现,808932 走的是这条通用路径,不需要新增前端代码。 - 前端 workaround 清理点:无。本次改动前 808932 这个码不存在,故前端不会有针对它的既有 workaround 需要清理。
影响范围说明:
- 前端 hl-ui:请求/响应字段无改动;808932 走通用业务错误展示路径,后端返回的
message即为用户看到的提示文案,无需前端硬编码该文案。 - 管理后台网关:路由无变更,仅错误响应码增加,不影响路由规则和鉴权。
- 其他服务:本次改动是 order-v3 内部的行级锁定读 + 乐观锁版本校验,不新增分布式锁、不改 Feign 契约;finance、resource 等消费方零感知。
- 数据一致性:通过锁定读 + 版本控制,堵住了孤儿应付台账的产生根源,后续应付台账取消、对账等链路无需额外补丁。
七、不影响范围
- EXCEPTION 异常态下调用修改配房(接口 2)改价仍被允许——该接口本就不经过
assertWritableForAssignment这条 EXCEPTION 写闸——未受本次新增版本控制的影响;本次改动只新增 808932 这一个并发冲突码,不改变任何既有的状态校验逻辑。 - 其他写口(如转房
changeId、取消级联删除等)未涉及本次改动,仍按既有逻辑。 - 只读接口(查询配房列表、查询单日候选等)逻辑无变化。
八、测试环境已验证
部署状态:
- hl-order-service-v3 @cd82a19ab(origin/dev-v3 的祖先,#8665 的合并提交 f8379ddfc3 已包含)
- hl-gateway @71def6dc5(网关落后 dev-v3 156 个提交,本次无路由变化,不影响)
- 部署时刻:2026-10-01 22:50:33
测试链路 A:确认第 1 晚 → 删除该行
- 操作序列:
POST /confirm返回 200 →DELETE /assignments/{id}返回 200 - 终态:配房行
deleted_at非空;应付台账该行已软删(无活跃 NORMAL 记录);日历库存回复 - 结论:PASS(两步均 200,无 808932)
测试链路 B:删除该行 → 确认第 2 晚
- 操作序列:
DELETE /assignments/{id}返回 200 →POST .../days/2/confirm返回 808118(严格串行,无并发窗口) - 终态:应付台账对该行零产出(
fin_payable_line WHERE source_ref_id=<该行id>为 0 行) - 结论:PASS——判据是「第二步不再为该行产生台账行」,不要求第二步返回 200;完全串行操作下,confirmDay 在进入锁定复读之前先按该晚候选集过滤,发现该晚询房中候选集已为空,命中更早的 808118(该天无可确认的询房中候选),而不是 #8665 新增的 808932(锁定复读检查需要先有候选才会走到)。
并发窗口说明:
- 808932 是为「删除/修改读取之后、带版本的写操作之前」这段时间窗口的并发交错设计的,靠锁定读 + 乐观锁版本校验堵住。
- 完全串行操作(一方提交完成后另一方才查询)会先命中 808118(无候选)或 808120(配房不存在),不会到达锁定复读检查点;这两条路径的共同效果都是零台账产出。
- 测试环境部署状态:hl-order-service-v3 对 origin/dev-v3 零落后,#8665 的合并提交已在部署字节内,以上两条测试链路均为该部署字节下的实测结果。
十、相关文档
- API 规范:
docs/order-v3/api/API-SPEC-HOUSE-V1.1.html(v1.1.20,2026-10-01)- §2.2b 单日确认 / §2.3 修改配房 / §2.4 删除配房错误码表新增 808932
- §11.9 内部接口 + 跨服务(808900-808999)全表补 808932「房务状态已被并发修改,请刷新后重试」
- 工单正文:Gitea #8665(设计、验收、口径定案)
- 单测覆盖:
HouseAssignmentServiceTest#confirmDayPersist_keepUpdateAffectsZeroRows_throws808932AndNoPayablePush:confirmDayPersist 中某 keep 行 updateById 影响 0 行时抛 808932,整体回滚,零推台账,落选不回补库存HouseAssignmentServiceTest#delete_softDeleteAffectsZeroRows_throws808932NoPayableNoRestore:delete() 在 softDeleteWithVersion 返 0 时抛 808932,不碰台账、不回补库存HouseAssignmentServiceTest#updateTx_updateAffectsZeroRows_throws808932NoPayableRewrite:updateTx() 乐观锁写库影响 0 行时抛 808932,不判台账、不作废不重推HouseAssignmentDeleteConfirmDayOrderingIT:固定时序交错 IT,覆盖「确认快照之后删配房提交→确认返 808932」与「删配房持行锁期间确认进入事务→确认被挡住随后 808932」两条交错,均断言无孤儿台账
关联 / 联系人
联系人
- 后端负责人: @wx