init
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# Daylight 合并与 `dl_reservation_id` 去重说明
|
||||
|
||||
本文档说明 `BookingService` 中为减少「同一 Daylight 预订在 `bookings` 表出现多行」所做的修改及调用关系。
|
||||
|
||||
---
|
||||
|
||||
## 1. 涉及文件与入口
|
||||
|
||||
| 位置 | 说明 |
|
||||
|------|------|
|
||||
| `BookingService.java` | `mergeDaylightRecordToDatabaseInfo`、`insertDaylightBooking`、`consolidateDuplicateDlReservations` 及相关私有辅助方法 |
|
||||
| `BookingController`(recordSearch 等) | 在拉取 Daylight 列表后调用 `mergeDaylightRecordToDatabaseInfo` |
|
||||
|
||||
---
|
||||
|
||||
## 2. `mergeDaylightRecordToDatabaseInfo` 的改动要点
|
||||
|
||||
### 2.1 预查询:按 `dlReservationId` 收集(不再依赖确认号)
|
||||
|
||||
**原问题**:仅当 `confirmationNo` 非空时才把 `dlReservationId` 放进 `findByDlReservationIdIn` 的入参。若接口有 `dl_reservation_id` 但尚未返回确认号,则查不到库里已有行,会误判为「不存在」而走插入,产生重复。
|
||||
|
||||
**现逻辑**:对本次 `result`(Daylight 返回列表)中每条记录,只要 `dlReservationId` 非空白即加入 `LinkedHashSet`,再转为 `List` 做 `findByDlReservationIdIn`。
|
||||
|
||||
### 2.2 合并后物理去重:`consolidateDuplicateDlReservations`
|
||||
|
||||
在 `updateBookingList` 执行 `bookingDao.saveAll` **之后**,对本次涉及到的每个 `dl_reservation_id` 再查库:
|
||||
|
||||
- 若同一 `dl_reservation_id` 仍对应 **≥2 行**(例如一条前台 `PN`、一条同步误插入的 `N`),则:
|
||||
- 按评分规则选出 **keeper(主行)**,其余为 **duplicate(次行)**;
|
||||
- 将次行上可对齐的字段并入主行后 **`delete` 次行**。
|
||||
|
||||
**主行评分** `keeperScoreForDlDedup`(分数高者优先):
|
||||
|
||||
- 有 `player_id`:+1000
|
||||
- `approve_status == "PN"`:+100
|
||||
- 有 `confirmation_no`:+10
|
||||
- 有 `member`:+5
|
||||
|
||||
同分按 **较小 `id`** 靠前(作为 keeper)。
|
||||
|
||||
**不合并、不删除** `shouldSkipConsolidatingDuplicateRow`:
|
||||
|
||||
- 次行 `approve_status` 以 `W` 开头(候补等)
|
||||
- 次行 `revision_of` 指向 keeper(待审批修订子单)
|
||||
- keeper `revision_of` 指向次行
|
||||
|
||||
**字段合并** `mergeDuplicateBookingIntoKeeper`:
|
||||
|
||||
- 确认号、`resv_id`、Opera 状态、酒店/客源/房价码、`player_id`、`member` 与姓名字段、房/房型、`booking_type_id`、`chain_code`、`block_id` 等在主行为空或可从次行安全补齐时写入主行;**`last_opera_status`** 若次行有值则写入主行(与 Opera 同步一致)。
|
||||
|
||||
日志前缀:`[consolidateDuplicateDlReservations]`。
|
||||
|
||||
---
|
||||
|
||||
## 3. `insertDaylightBooking` 的改动要点
|
||||
|
||||
### 3.1 按 `dl_reservation_id` 兜底防插
|
||||
|
||||
在组好 `Bookings book` 并准备 `save` **之前**,若 `operaBooking.getDlReservationId()` 非空,则先 `findByDlReservationIdIn`:
|
||||
|
||||
- **若已存在任一行**:**不再 `save` 新单**,打日志 `[insertDaylightBooking] skip insert, existing dl_reservation_id=...`,并设置返回视图上的 `id` 等为已有记录,直接 `return operaBooking`。
|
||||
|
||||
避免「合并阶段漏匹配」时再次插入第二条同 `dl_reservation_id`。
|
||||
|
||||
### 3.2 按确认号防重(空列表才插入)
|
||||
|
||||
- 仅当 `confirmationNo` 非空时执行 `findByConfirmationNo`;
|
||||
- **仅当查询结果为空** 时才 `bookingDao.save(book)`;
|
||||
- 若已有同确认号记录,则复用其 `id`,**不重复插入**。
|
||||
|
||||
---
|
||||
|
||||
## 4. 根因归纳(与旧版对比)
|
||||
|
||||
| 原因 | 说明 |
|
||||
|------|------|
|
||||
| 预查漏掉无确认号的 `dl_reservation_id` | 已用 `LinkedHashSet` + 非空 `dlReservationId` 修复 |
|
||||
| 仅合并不删重复行 | 已用 `consolidateDuplicateDlReservations` 在当次同步末尾收敛 |
|
||||
| 插入路径无 `dl_reservation_id` 防重 / 确认号逻辑不当 | 已在 `insertDaylightBooking` 中补齐 |
|
||||
| 数据库无唯一约束 | **仍建议在库上对非空 `dl_reservation_id` 做过滤唯一索引**(上线前清理历史重复) |
|
||||
| 并发双写 | 当前以逻辑合并 + 完成后去重为主;多实例强一致可再加分布式锁或 DB 唯一约束 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 运维与验证
|
||||
|
||||
1. **验证**:部署后对曾出现双行的会员再触发一次会走 `mergeDaylightRecordToDatabaseInfo` 的查询(如 recordSearch);观察日志中 `consolidateDuplicateDlReservations` 是否合并并删除次行。
|
||||
2. **历史数据**:长期未再走合并流程的重复行,需一次性脚本或手工按业务规则保留主行、删除/归档次行。
|
||||
3. **外键**:若 `delete` 次行失败(子表引用),需先迁移或删除子表记录后再删。
|
||||
|
||||
---
|
||||
|
||||
## 6. 可选后续增强(未列入当前分支必选)
|
||||
|
||||
- 合并匹配改为 `Strings.CI.equals`(忽略 `dl_reservation_id` 大小写),与接口/库实际存储一致。
|
||||
- 对 `mergeDaylightRecordToDatabaseInfo` 增加 `@Transactional(rollbackFor = Exception.class)`,使 `saveAll` 与 `consolidateDuplicateDlReservations` 同事务回滚。
|
||||
- 高并发下对同一 `dl_reservation_id` 的插入加应用层锁或依赖唯一索引 + 捕获冲突后改走更新。
|
||||
|
||||
---
|
||||
|
||||
*文档与 `daylight_1.0.0` 分支中 `BookingService` 当前实现对应;若行号随重构变化请以方法名为准。*
|
||||
Reference in New Issue
Block a user