5.0 KiB
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. 运维与验证
- 验证:部署后对曾出现双行的会员再触发一次会走
mergeDaylightRecordToDatabaseInfo的查询(如 recordSearch);观察日志中consolidateDuplicateDlReservations是否合并并删除次行。 - 历史数据:长期未再走合并流程的重复行,需一次性脚本或手工按业务规则保留主行、删除/归档次行。
- 外键:若
delete次行失败(子表引用),需先迁移或删除子表记录后再删。
6. 可选后续增强(未列入当前分支必选)
- 合并匹配改为
Strings.CI.equals(忽略dl_reservation_id大小写),与接口/库实际存储一致。 - 对
mergeDaylightRecordToDatabaseInfo增加@Transactional(rollbackFor = Exception.class),使saveAll与consolidateDuplicateDlReservations同事务回滚。 - 高并发下对同一
dl_reservation_id的插入加应用层锁或依赖唯一索引 + 捕获冲突后改走更新。
文档与 daylight_1.0.0 分支中 BookingService 当前实现对应;若行号随重构变化请以方法名为准。