feat(processing): 实现自有 PostgreSQL 运营航班权威存储与单事务闭环 (ACM2-28)

- FS1: 增加 Flyway 迁移 V1.1.0__flight_schd.sql,创建 FLIGHT_SCHD 与 SCHD_GEN
- FS2: 实现 FlightSchdRepository 接口及 JdbcFlightSchdRepository 与 StubFlightSchd,增强 JdbcOps 事务管理
- FS3: 扩展 MessageProcessor 事务 2 与按 FLID 点查视图,合并变更、事件与终态入单事务提交
- FS4: SnapshotFlow SQL 化(批处理 upsert、域内差删、SQL CAS 推进与熔断保护),JobExecutor 接入 PG 清场删除
- FS5: 彻底退役 Redis 权威与写路径,移除 FlightRedisClient、Lua 脚本、健康指示器与配置残留
- FS6: 补齐 U09/U29 不变量门禁(崩溃幂等、CAS 防并发、ADFT 存活保障、非 UTC JVM/会话时区无漂移)与 FlywayMigrationTest
- FS7: 交付影子对拍比较内核 FlightStoreDiffTool 与单元测试
- FS8: 全面回改 decision-flight-state、architecture、design、user-stories 权威文档与规范
This commit is contained in:
windyboy
2026-09-07 16:12:00 +08:00
parent dd4bbc86ca
commit 7b7b61f100
37 changed files with 2037 additions and 382 deletions
@@ -1,8 +1,6 @@
package com.gzzn.omms.msgexchange.jobs
import com.gzzn.omms.msgexchange.infra.redis.FlightRedisClient
import com.gzzn.omms.msgexchange.infra.redis.RedisScript
import com.gzzn.omms.msgexchange.infra.persistence.FlightStateRepository
import com.gzzn.omms.msgexchange.infra.persistence.FlightSchdRepository
import com.gzzn.omms.msgexchange.infra.persistence.MsgEventRepository
import com.gzzn.omms.msgexchange.infra.persistence.PumpJobRepository
import com.gzzn.omms.msgexchange.domain.MsgEvent
@@ -33,21 +31,31 @@ class JobExecutor(
*/
@Singleton
class HistorySweepJob(
private val redis: FlightRedisClient,
// TODO(阶段2): esFlightHts.saveSync(history) 返回成功集(同步写,不经事件 ack 回查)
private val flightSchd: FlightSchdRepository,
) {
companion object {
var historyPicker: ((Map<String, String>) -> Map<String, String>)? = null
var esArchiver: ((Map<String, String>) -> Set<String>)? = null
var cutoffProvider: (() -> String?)? = null
}
fun run() {
val all = redis.hgetAllFlightInfo()
val all = flightSchd.findAll()
val history = pickHistory(all) // U10/T07:saveSync 接线前判史为空集——禁止“全量可删”默认
val success = emptyList<String>() // TODO(阶段2): esFlightHts.saveSync(history)(返回成功集后再接线删除)
val success = esArchiver?.invoke(history) ?: emptySet() // 仅收集已确认写入 ES 的成功 FLID 集合
if (success.isNotEmpty()) {
redis.eval(RedisScript.BATCH_DELETE, delFields = success)
flightSchd.deleteByFlids(success) // FS2/FS4:仅删除已确认写入 ES 的成功集合,分批参数化删除
}
val cutoff = cutoffProvider?.invoke()
if (cutoff != null) {
flightSchd.deleteGenBefore(cutoff)
}
}
/** U10/T07(修订):接入 ES success 集之前的门禁——占位默认返回空集;
* 现役五条判史规则(SODT 3 天 / CNCL 1 小时 / 备降 / 离港 / 到港)golden 通过后才允许接线。 */
private fun pickHistory(all: Map<String, String>): Map<String, String> = emptyMap() // TODO(阶段2)
private fun pickHistory(all: Map<String, String>): Map<String, String> =
historyPicker?.invoke(all) ?: emptyMap()
}
/** 流程 5 runJob(ARCHIVE):3:00 归档——1 天前且仅终态可迁(矩阵 #12)。 */
@@ -63,19 +71,18 @@ class ArchiveJob(
/** 流程 7:阶段 B 投影重建——切入阶段 B 时全量重建一次,此后增量走事件。 */
@Singleton
class ProjectionRebuildJob(
private val flightState: FlightStateRepository,
private val flightSchd: FlightSchdRepository,
private val msgEvents: MsgEventRepository,
) {
fun run() {
for (day in activeDays()) {
val flights = flightState.findByDay(day)
val flights = flightSchd.findByDay(day)
flights.forEach { (flid, payload) ->
msgEvents.insertSync(
listOf(MsgEvent(target = Targets.REDIS_FLIGHT_INFO, partitionKey = flid, payloadJson = payload)),
listOf(MsgEvent(target = Targets.KAFKA_SCHD, partitionKey = flid, payloadJson = payload)),
)
}
}
// 重建完成前旧 Redis 视图继续服务(数据同源,仅短暂滞后)
}
private fun activeDays(): List<String> = emptyList() // TODO(阶段B)