feat(ingress): 实现 JDBC 信箱轮询、入站持久化与对拍测试 (U05)

This commit is contained in:
windyboy
2026-09-07 15:11:33 +08:00
parent dc68f1e1f8
commit c7b4b527ef
51 changed files with 1005 additions and 235 deletions
@@ -0,0 +1,40 @@
package com.gzzn.omms.msgexchange.domain
/**
* ACMA-8 流程 2Handler 决策(纯函数)产物——状态与报文进,变更与事件出,
* 不直接触碰 Redis/Kafka。
*/
data class Decision(
val flightChanges: List<FlightChange> = emptyList(),
val msgNotifies: List<NotifyPayload> = emptyList(), // → MSG_EVENT(KAFKA:msg)
val schdPush: List<SchdPush> = emptyList(), // → MSG_EVENT(KAFKA:schd)PARTITION_KEY=FLID
val outboundIntents: List<OutboundIntent> = emptyList(), // → COUTMSGS(沿用既有列语义)
val refUpserts: List<RefUpsert> = emptyList(), // → 静态主数据(独立 PG reference 库,ACM2-11
)
/** 航班状态变更(阶段 A 由主泵线程 redisApply;阶段 B 落 FLIGHT_STATE 同事务)。 */
data class FlightChange(
val flid: String,
val payloadJson: String,
val maid: String? = null,
)
data class NotifyPayload(val payloadJson: String)
data class SchdPush(
val flid: String,
val fltrJson: String,
val eventSeq: Long = 0, // 由事务插入时赋 EVENT_ID 语义序,聚合取 max
)
data class OutboundIntent(
val coutmsgsXml: String,
val ackReqd: Boolean = true,
)
data class RefUpsert(
val rtype: String,
val rkey: String,
val payloadJson: String,
val source: String,
)
@@ -0,0 +1,34 @@
package com.gzzn.omms.msgexchange.domain
/**
* 解码后的入站报文(统一内部模型)。META 字段实名 SNDR/SEQN/DTTMlegacy META.javaI3 幂等键来源)。
*/
data class MetaFields(
val sndr: String,
val type: String,
val styp: String,
val seqn: Long,
val dttm: Long,
)
/** 消息分派(sealed + 穷尽 whenACMA-6 选型;取代 legacy 反射 get{TYPE}())。 */
sealed interface MsgKind {
data class Schd(val subtype: SchdSubtype) : MsgKind
data class Flop(val subtype: String) : MsgKind // 29 类 STYP,阶段 2/3 逐类翻译
enum class SchdSubtype { RESP, DNLD, ADFT }
}
data class DecodedMessage(
val meta: MetaFields,
val kind: MsgKind,
val rawXml: String,
/** 各类型载荷(vendor POJO 或 Kotlin 模型,阶段 1 后续统一)。 */
val body: Any? = null,
) {
val typeTag: String
get() = when (val k = kind) {
is MsgKind.Schd -> "SCHD-${k.subtype.name}"
is MsgKind.Flop -> "FLOP-${k.subtype}"
}
}
@@ -0,0 +1,33 @@
package com.gzzn.omms.msgexchange.domain
/**
* ACMA-8 MSG_EVENT(统一投递事件 / outbox)。
* TARGET 阶段化:KAFKA:msg、KAFKA:schd 自阶段 AES:flight_hts、REDIS:flightInfo 仅阶段 B 投影期。
*/
object Targets {
const val KAFKA_MSG = "KAFKA:msg"
const val KAFKA_SCHD = "KAFKA:schd"
const val ES_FLIGHT_HTS = "ES:flight_hts"
const val REDIS_FLIGHT_INFO = "REDIS:flightInfo"
/** 阶段 A 投递目标(仅 Kafka)——I5Delivery 阶段 A 不写 Redis。 */
val phaseA: List<String> = listOf(KAFKA_MSG, KAFKA_SCHD)
/** 阶段 B 追加投影目标。 */
val phaseB: List<String> = phaseA + listOf(ES_FLIGHT_HTS, REDIS_FLIGHT_INFO)
}
enum class EventStatus { PENDING, SENT, DEAD }
data class MsgEvent(
val eventId: Long? = null,
val target: String,
val partitionKey: String? = null, // schd 事件恒为 FLIDv4 显式声明)
val payloadJson: String,
val state: EventStatus = EventStatus.PENDING,
val attempts: Int = 0,
val nextAttemptAt: java.time.Instant? = null,
val errorClass: ErrorClass? = null,
val lastError: String? = null,
val createdAt: java.time.Instant = java.time.Instant.now(),
)
@@ -0,0 +1,25 @@
package com.gzzn.omms.msgexchange.domain
/**
* ACMA-8 数据模型 / PROC_STATE 状态机(六迁移表)。
*/
enum class ProcStatus { PENDING, FAILED, SUCCEEDED, SKIPPED, DEAD }
enum class ErrorClass { MALFORMED, CODEC_ERROR, EXHAUSTED, INFRA, UNSUPPORTED }
data class ProcState(
val cminmsgsId: Long,
val state: ProcStatus,
val identityKey: String? = null, // decode 后首次绑定;FAILED 重试不重绑(I3)
val attempts: Int = 0,
val nextAttemptAt: java.time.Instant? = null,
val errorClass: ErrorClass? = null,
val lastError: String? = null,
val updatedAt: java.time.Instant = java.time.Instant.now(),
) {
val isTerminal: Boolean
get() = state == ProcStatus.SUCCEEDED || state == ProcStatus.SKIPPED || state == ProcStatus.DEAD
/** 终态皆可归档(矩阵 #12SUCCEEDED SKIPPED DEAD);PENDING/FAILED 不迁。 */
val archivable: Boolean get() = isTerminal
}