docs(kdoc): V2 迁移与处理/持久化层剩余注释改为直白说明
接上一提交,把本次工作范围内还没改到的注释补齐:V2 迁移脚本的注释原文几乎全是 段落编号引用,处理层与持久化层还留着一批 `(§3.3/§5)` 形式的行内注释。 V2__inbox_lifecycle.sql:文件头改为先讲清这次迁移解决的三个问题(记住收报读到哪里、 回填记录并进 PROC_STATE 而不是单开一张表、记下收信时间做什么用),再落到每个字段; 字段级注释补上"这个值非空代表什么"。SQL 语句一字未动(已用去注释后比对确认)。 processing:Pump 的分派与终态注释、ScheduleProcessor 的重放判定与运营日冲突、 DynamicProcessors 的删除通知与重新激活,都改成说明白"这一步在做什么、为什么这么做"。 infra/persistence:Repositories.kt 的仓储约定、航班状态读写、待发事件与请求跟踪接口, JdbcPgRepositories 的锁、整态合并、历史清理判据、明细表映射,StubRepositories 的 对应实现,一律先说清用途再谈规则。 测试:8 个测试类里的行内引用改为说明这条断言在守什么。 注释里保留的文档指向只在需要延伸阅读时出现,不再作为解释本身。 全部为注释改动,测试仍为 78 passed / 1 skipped。
This commit is contained in:
@@ -14,17 +14,21 @@ import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
|
||||
// =====================================================================
|
||||
// 仓储契约(docs/flight-state.md §2 权威模型/§3 写入语义,design.md §2.1)。
|
||||
// 决策层写路径约定:所有 FLIGHT_SCHD 及明细写操作必须发生在
|
||||
// 「持有 PIPELINE_LOCK 的同一事务」内(§1/§4:锁只串行化 DB 事务)。
|
||||
// 仓储接口定义。
|
||||
//
|
||||
// 有一条约定贯穿航班相关的写操作:改航班状态(FLIGHT_SCHD 和明细表)必须发生在
|
||||
// 同一个事务里,并且进事务后第一件事就是拿 PIPELINE_LOCK 这把单行锁。锁只负责让
|
||||
// 写事务排队,不负责选主或故障切换。
|
||||
//
|
||||
// 航班模型与合并规则的完整说明见 docs/flight-state.md。
|
||||
// =====================================================================
|
||||
|
||||
/** 自有 PG 单事务原子保障(flight-state.md §1/§4:状态、事件、处理终态同事务提交)。 */
|
||||
/** 把一段代码包进一个数据库事务:航班状态、待发事件、处理终态要么一起提交,要么一起回滚。 */
|
||||
interface PipelineTransactionManager {
|
||||
fun <T> inTransaction(block: () -> T): T
|
||||
}
|
||||
|
||||
/** 单行锁 PIPELINE_LOCK:事务内第一步 SELECT ... FOR UPDATE,串行化状态写事务(§1/§4)。 */
|
||||
/** 单行锁:进事务后先 `SELECT ... FOR UPDATE`,让并发的状态写事务排队执行。 */
|
||||
interface PipelineLockRepository {
|
||||
fun lock()
|
||||
}
|
||||
@@ -117,7 +121,12 @@ data class BackfillDue(val msgId: Long, val attempts: Int)
|
||||
*/
|
||||
data class Backlog(val unfinished: Int, val oldestReceivedAt: Instant?, val unmarkedTerminal: Int)
|
||||
|
||||
/** MSG_EVENT outbox(flight-state.md §5)。KAFKA_SCHD 合并同 FLID 未发事件按最新 STATE_VERSION 输出。 */
|
||||
/**
|
||||
* 待发事件(outbox)表:业务提交时把要发的事件一起写进来,投递线程再从这张表往外发,
|
||||
* 这样发送失败不会影响业务事务。
|
||||
*
|
||||
* 其中 KAFKA_SCHD 是"最新状态"通知:同一条航班的多个未发事件只留最新的那个版本。
|
||||
*/
|
||||
interface MsgEventRepository {
|
||||
fun insertAll(events: List<MsgEvent>): List<Long>
|
||||
|
||||
@@ -125,7 +134,7 @@ interface MsgEventRepository {
|
||||
|
||||
fun claimBatch(target: String, limit: Int): List<MsgEvent>
|
||||
|
||||
/** 同 FLID 未发 KAFKA_SCHD 事件合并:每 FLID 取最新 STATE_VERSION 一条(§5)。 */
|
||||
/** 挑出待发的整态事件,同一条航班只取版本最高的那一条(中间的版本不用发)。 */
|
||||
fun mergePendingSchd(limit: Int): List<MsgEvent>
|
||||
|
||||
fun markSent(eventId: Long)
|
||||
@@ -137,63 +146,74 @@ interface MsgEventRepository {
|
||||
fun markDead(eventId: Long, errorClass: ErrorClass, lastError: String, attempts: Int? = null)
|
||||
}
|
||||
|
||||
/** 完整态落库结果:DAY_GUARD_VIOLATION = OPERATION_DAY 不可变条件更新未命中(§2.1)。 */
|
||||
/**
|
||||
* 落库结果。
|
||||
* [DAY_GUARD_VIOLATION] 表示这次写入被"运营日不可变"的条件挡住:库里已有运营日,
|
||||
* 和这次的报文对不上,说明这条报文可能串了日子。
|
||||
*/
|
||||
enum class PersistOutcome { INSERTED, UPDATED, DAY_GUARD_VIOLATION }
|
||||
|
||||
/**
|
||||
* 航班当前态权威(§2 FLIGHT_SCHD + 9 张明细表)。
|
||||
* 唯一写路径 = persistFullState:写入前在内存生成完整新状态(引擎产物)再落库,
|
||||
* 明细集合按组先删后插(§2.2 完整合并结果为准)。
|
||||
* 航班当前态的读写入口:主表 FLIGHT_SCHD 加 9 张明细表。
|
||||
*
|
||||
* 写只有一个入口 [persistFullState]:先在内存里算出合并后的完整状态,再整体落库。
|
||||
* 明细数据按组先删后插,所以入库的永远是"合并后的完整结果",不依赖增量更新。
|
||||
*/
|
||||
interface FlightStateRepository {
|
||||
/** 主行点查(身份/版本判定;批量用于日计划归属校验 §2.1)。 */
|
||||
/** 查航班主行,用于判断它是否存在、属于哪个运营日、当前版本是多少。 */
|
||||
fun findMainRow(flid: String): FlightMainRow?
|
||||
|
||||
fun findMainRows(flids: Collection<String>): Map<String, FlightMainRow>
|
||||
|
||||
/** 完整当前态:主行 + 全部明细(一致性读边界由调用方事务保证,§5)。 */
|
||||
/** 读一条航班的完整当前态(主行加全部明细)。要读得一致,得由调用方放在事务里读。 */
|
||||
fun loadFullSnapshot(flid: String): FlightSnapshot?
|
||||
|
||||
/**
|
||||
* 完整当前态落库(§3):主行 upsert + 全部明细按组先删后插;
|
||||
* STATE_VERSION 以 snapshot.stateVersion 落库。
|
||||
* SCHD(§3.1)、FLOP/ADFT(§3.2/§3.3)合并后的全量结果共用此唯一写路径。
|
||||
* 条件更新带 `WHERE operation_day IS NULL OR operation_day = :day`(§2.1 不可变强化)。
|
||||
* 把合并后的完整状态写库:主行做 upsert,明细按组先删后插,
|
||||
* 版本号用 [snapshot] 里算好的那个。
|
||||
*
|
||||
* 日计划、动态事件、异常航班的合并结果都走这一个入口。
|
||||
* 写入带运营日条件(`WHERE operation_day IS NULL OR operation_day = :day`):
|
||||
* 一旦航班已经归属某个运营日,别的日子的报文就写不进来。
|
||||
*/
|
||||
fun persistFullState(snapshot: FlightSnapshot, msgId: Long, now: Instant): PersistOutcome
|
||||
|
||||
/**
|
||||
* FDEL(§3.3):ACTIVE → 置 DELETED、推进版本、明细保留、返回 true(发布删除事件);
|
||||
* 已 DELETED 或不存在 → 返回 false(幂等成功,不推进版本不重复发布)。
|
||||
* 删除航班:把在用的航班标成 DELETED、版本号加一,明细数据保留不删。
|
||||
*
|
||||
* 返回 true 表示这次真的删了(调用方据此发一次删除通知);返回 false 表示航班已经
|
||||
* 是删除状态或根本不存在,按成功处理,不推进版本也不重复发通知。
|
||||
*/
|
||||
fun markDeleted(flid: String, msgId: Long, now: Instant): Boolean
|
||||
|
||||
/** ADFT 生命周期重激活(§3.3):DELETED → ACTIVE,推进版本;非 DELETED 返回 false。 */
|
||||
/** 把已删除的航班重新激活(ADFT 报文触发):DELETED → ACTIVE、版本号加一;不是删除状态则返回 false。 */
|
||||
fun revive(flid: String, msgId: Long, now: Instant): Boolean
|
||||
|
||||
/** §6:按保留期与终态/静默判据选出历史候选(含 DELETED;窗口按机场时区折算)。 */
|
||||
/** 按保留期挑出可以归档清理的航班(含已删除的)。时间窗口按机场时区计算。 */
|
||||
fun findHistoryCandidates(rules: HistoryRules, zone: ZoneId, now: Instant): List<HistoryCandidate>
|
||||
|
||||
/**
|
||||
* §6:物理删除主行与明细(仅历史存储确认成功后调用;
|
||||
* 历史存储未接通时调用方必须传空集合——删 0 条)。
|
||||
* 物理删除主行与明细。**只能在历史存储确认归档成功之后调用**;
|
||||
* 历史存储没接通时,调用方必须传空集合,也就是一条都不删。
|
||||
*/
|
||||
fun purgeArchived(flids: Collection<String>): Int
|
||||
|
||||
/** §2.1 观测:OPERATION_DAY 仍为 NULL 的航班数(只增不删,终止规则未定 §6)。 */
|
||||
/** 运营日还没定下来的航班有多少条(观测用;这些航班暂时不参与清理)。 */
|
||||
fun countOperationDayNull(): Int
|
||||
}
|
||||
|
||||
/** SCHD_SNAP_LOG(design.md §6.2):事务外追加留痕,不参与决策;一行 = 一次尝试(重放也记)。 */
|
||||
/** 日计划处理留痕:只追加、不参与业务判断,一次处理(含重放)记一行,写失败不影响业务。 */
|
||||
interface SnapshotLogRepository {
|
||||
fun append(entry: SnapshotLogEntry)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 请求状态机 REQ_TRACK(design.md §4.2 目标机制,尚无运行时协调器):
|
||||
* 只有 RESP 完成 RQFD 请求,按(运营日、发送方、请求类型)匹配最新一条 PENDING;
|
||||
* 同类请求只留一条有效,新请求置旧为 EXPIRED。
|
||||
* 上游请求的跟踪表:记录我们发出去的请求、以及对方回来的应答。
|
||||
*
|
||||
* 目前只有表和读写方法,**还没有运行时协调器**(出站写信箱、超时、应答匹配都没实现)。
|
||||
* 设计意图是:RESP 报文按(运营日、发送方、请求类型)匹配最近一条待应答的请求;
|
||||
* 同一类请求同时只保留一条有效,发新请求时把旧的置为已过期。
|
||||
*/
|
||||
interface ReqTrackRepository {
|
||||
enum class ReqState { PENDING, SENT, DONE, EXPIRED }
|
||||
|
||||
+17
-13
@@ -38,9 +38,10 @@ import java.util.Locale
|
||||
import javax.sql.DataSource
|
||||
|
||||
// =====================================================================
|
||||
// 自有 PostgreSQL 仓储实现(docs/flight-state.md §2 权威模型/§3 写入语义;schema 见
|
||||
// db/migration/V1__flight_state_baseline.sql)。全部写路径约定在
|
||||
// withTransaction + PIPELINE_LOCK 内调用(§1/§4)。
|
||||
// 自有 PostgreSQL 的仓储实现,表结构见 db/migration/V1__flight_state_baseline.sql。
|
||||
//
|
||||
// 航班相关的写操作都要求调用方先开事务、再拿 PIPELINE_LOCK 单行锁(见 Repositories.kt
|
||||
// 顶部说明)。航班模型与合并规则见 docs/flight-state.md。
|
||||
// =====================================================================
|
||||
|
||||
@Singleton
|
||||
@@ -58,7 +59,7 @@ class JdbcPipelineTransactionManager(
|
||||
class JdbcPipelineLockRepository(
|
||||
private val ds: DataSource,
|
||||
) : PipelineLockRepository {
|
||||
/** 事务内第一步:单行 FOR UPDATE 串行化状态写事务(§1/§4 PIPELINE_LOCK)。 */
|
||||
/** 进事务后的第一步:对锁行 `FOR UPDATE`,让并发的状态写事务排队。 */
|
||||
override fun lock() {
|
||||
ds.queryOne("SELECT lock_id FROM pipeline_lock WHERE lock_id = 1 FOR UPDATE", {}) { 1 } ?: error("PIPELINE_LOCK row missing")
|
||||
}
|
||||
@@ -340,7 +341,7 @@ class JdbcMsgEventRepository(
|
||||
::mapEvent,
|
||||
)
|
||||
|
||||
/** §5:同 FLID 未发事件按最新 STATE_VERSION 合并;PG DISTINCT ON 方言(注释明示)。 */
|
||||
/** 同一条航班只取版本最高的待发整态事件;用了 PostgreSQL 的 DISTINCT ON 语法。 */
|
||||
override fun mergePendingSchd(limit: Int): List<MsgEvent> =
|
||||
ds.query(
|
||||
"""
|
||||
@@ -477,7 +478,7 @@ class JdbcFlightStateRepository(
|
||||
},
|
||||
)
|
||||
if (updated == 0) {
|
||||
// §2.1 不可变强化:条件更新未命中 = 归属日冲突
|
||||
// 一行都没更新到,说明被运营日条件挡住了:这条航班已经属于别的运营日
|
||||
return if (existed != null) PersistOutcome.DAY_GUARD_VIOLATION else PersistOutcome.UPDATED
|
||||
}
|
||||
replaceDetails(snapshot, now)
|
||||
@@ -499,7 +500,7 @@ class JdbcFlightStateRepository(
|
||||
) == 1
|
||||
|
||||
override fun findHistoryCandidates(rules: HistoryRules, zone: ZoneId, now: Instant): List<HistoryCandidate> {
|
||||
// §6 四条判定在应用层执行(SIS 时间串解析无法下推 SQL);
|
||||
// 四条清理判据都在应用层算:里面的时间字段是 SIS 格式的字符串,没法交给 SQL 比较;
|
||||
// 不按 updated_at 粗筛——取消/终态时间可能早于最近一次更新,粗筛会漏删。
|
||||
val rows = ds.query(
|
||||
"SELECT flid, state, state_version, operation_day, cncl, naat, neat, updated_at FROM flight_schd",
|
||||
@@ -528,7 +529,7 @@ class JdbcFlightStateRepository(
|
||||
r.updatedAt < now.minus(Duration.ofHours(rules.idleHours)) -> true
|
||||
else -> false
|
||||
}
|
||||
// NAAT/NEAT 业务含义待术语表确认(§6 开放项):解析失败视为不命中,不误删
|
||||
// NAAT/NEAT 的业务含义还没确认,所以解析不出来时一律当成"不满足条件",宁可漏删
|
||||
if (hit) HistoryCandidate(r.flid, r.state, r.version, wasNeverFdel = r.state == FlightState.ACTIVE) else null
|
||||
}
|
||||
}
|
||||
@@ -585,7 +586,7 @@ class JdbcFlightStateRepository(
|
||||
items.forEachIndexed { ordinal, item ->
|
||||
val cols = mutableListOf("flid", "ordinal", "source_seq")
|
||||
val vals = mutableListOf<Any?>(flid)
|
||||
vals.add(ordinal + 1) // ORDINAL 保留输入顺序(§2.2)
|
||||
vals.add(ordinal + 1) // 序号按报文里的先后顺序编,读回来顺序不变
|
||||
vals.add(item[spec.seqAttr])
|
||||
spec.columns.forEach { col ->
|
||||
cols.add(col)
|
||||
@@ -617,7 +618,7 @@ class JdbcFlightStateRepository(
|
||||
|
||||
private fun loadDetails(flid: String, key: String, spec: DetailSpec): List<Map<String, String>> {
|
||||
val sql = if (spec.routeKind != null) {
|
||||
// ROUT/ERUT 共表:按 ROUTE_KIND 过滤(§2.2)
|
||||
// ROUT 和 ERUT 存在同一张表里,靠 ROUTE_KIND 区分,读的时候要带上这个条件
|
||||
"SELECT * FROM ${spec.table} WHERE flid = ? AND route_kind = ? ORDER BY ordinal ASC"
|
||||
} else {
|
||||
"SELECT * FROM ${spec.table} WHERE flid = ? ORDER BY ordinal ASC"
|
||||
@@ -673,7 +674,7 @@ class JdbcFlightStateRepository(
|
||||
"flight_delay", "flight_bridge_op", "flight_chock_op", "flight_route_point",
|
||||
)
|
||||
|
||||
/** 10 类集合 ↔ 明细表/列映射(§2.2;列名与基线一致)。 */
|
||||
/** 报文里的 10 类集合分别存到哪张表、哪些列(列名与基线脚本一致)。 */
|
||||
internal val COLLECTIONS: Map<String, DetailSpec> = mapOf(
|
||||
"GTDT" to DetailSpec("flight_gate", listOf("gate", "pgot", "pgct", "gotm", "gctm", "gtyp"), "GTNO"),
|
||||
"CKDT" to DetailSpec("flight_checkin", listOf("chkc", "ccls", "pcot", "pcct", "cotm", "cctm", "ctyp"), "CKNO"),
|
||||
@@ -695,7 +696,7 @@ class JdbcFlightStateRepository(
|
||||
class JdbcSnapshotLogRepository(
|
||||
private val ds: DataSource,
|
||||
) : SnapshotLogRepository {
|
||||
/** design.md §6.2:只追加;写失败由调用方捕获记指标(append 自身不抛出)。 */
|
||||
/** 只追加写;这里不抛异常,写失败由调用方捕获并记为指标。 */
|
||||
override fun append(entry: SnapshotLogEntry) {
|
||||
ds.update(
|
||||
"""
|
||||
@@ -757,7 +758,10 @@ class JdbcReqTrackRepository(
|
||||
)
|
||||
}
|
||||
|
||||
/** design.md §4.2 RESP 完成请求:匹配最新一条 PENDING/SENT;无匹配返回 false(迟到不报错)。 */
|
||||
/**
|
||||
* 应答报文到达时,把对应请求标记为已完成:找最近一条待应答的请求(PENDING 或 SENT)。
|
||||
* 找不到就返回 false——迟到或多余的应答不算错误。
|
||||
*/
|
||||
override fun completeLatest(reqType: String, operationDay: LocalDate, sender: String): Boolean =
|
||||
ds.update(
|
||||
"""
|
||||
|
||||
@@ -199,7 +199,7 @@ class StubMsgEvents : MsgEventRepository {
|
||||
override fun claimBatch(target: String, limit: Int): List<MsgEvent> =
|
||||
rows.values.filter { it.target == target && it.state == EventStatus.PENDING }.sortedBy { it.eventId!! }.take(limit)
|
||||
|
||||
/** §5:同 FLID 取最新 STATE_VERSION,按事件序输出。 */
|
||||
/** 同一条航班只取版本最高的待发整态事件,按事件先后输出。 */
|
||||
override fun mergePendingSchd(limit: Int): List<MsgEvent> =
|
||||
rows.values
|
||||
.filter { it.target == "KAFKA:schd" && it.state == EventStatus.PENDING }
|
||||
@@ -245,7 +245,7 @@ class StubFlightState : FlightStateRepository {
|
||||
|
||||
override fun loadFullSnapshot(flid: String): FlightSnapshot? = snapshots[flid]
|
||||
|
||||
/** §2.1 不可变条件:已有非空 OPERATION_DAY 且与新值不同 → DAY_GUARD_VIOLATION。 */
|
||||
/** 运营日不可变:库里已有运营日且与新值不同时,返回 DAY_GUARD_VIOLATION。 */
|
||||
override fun persistFullState(snapshot: FlightSnapshot, msgId: Long, now: Instant): PersistOutcome {
|
||||
val existing = mains[snapshot.flid]
|
||||
if (existing?.operationDay != null && existing.operationDay != snapshot.operationDay) {
|
||||
@@ -327,7 +327,7 @@ class StubReqTrack : ReqTrackRepository {
|
||||
fun clear() = rows.clear()
|
||||
|
||||
override fun insert(reqType: String, operationDay: LocalDate, sender: String): Long {
|
||||
// design.md §4.2:同类(类型+运营日+发送方)旧有效请求先置 EXPIRED
|
||||
// 同一类请求(类型 + 运营日 + 发送方)只保留一条有效,旧的先置为已过期
|
||||
rows.values.filter {
|
||||
it.reqType == reqType && it.operationDay == operationDay && it.sender == sender &&
|
||||
(it.state == ReqTrackRepository.ReqState.PENDING || it.state == ReqTrackRepository.ReqState.SENT)
|
||||
|
||||
@@ -78,7 +78,7 @@ class FdelProcessor(
|
||||
val deleted = flightState.markDeleted(payload.flid, msgId = head.msgId, now = Instant.now())
|
||||
if (deleted) {
|
||||
val current = flightState.loadFullSnapshot(payload.flid)
|
||||
// tombstone 仅在 ACTIVE→DELETED 时登记(§3.3/§5),与删除同事务
|
||||
// 只有"在用 → 删除"这一步才发删除通知,而且和状态变更写在同一个事务里
|
||||
msgEvents.insertAll(
|
||||
listOf(
|
||||
MsgEvent(
|
||||
@@ -105,7 +105,7 @@ class FdelProcessor(
|
||||
),
|
||||
)
|
||||
}
|
||||
procState.markTerminal(head.msgId, ProcStatus.SUCCEEDED) // 未命中 = 迟到/重复,幂等成功(§3.3)
|
||||
procState.markTerminal(head.msgId, ProcStatus.SUCCEEDED) // 没删到东西说明是迟到或重复报文,照样算成功
|
||||
ApplyResult.Succeeded
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class AdftProcessor(
|
||||
lock.lock()
|
||||
val main = flightState.findMainRow(record.flid)
|
||||
if (main != null && main.state == FlightState.DELETED) {
|
||||
// §3.3 重激活:DELETED → ACTIVE,推进版本,登记状态事件
|
||||
// 已删除的航班重新激活:状态改回 ACTIVE、版本号加一,并登记状态事件
|
||||
if (flightState.revive(record.flid, msgId = head.msgId, now = Instant.now())) {
|
||||
val current = flightState.loadFullSnapshot(record.flid)
|
||||
if (current != null) {
|
||||
@@ -152,11 +152,11 @@ class AdftProcessor(
|
||||
|
||||
val current = flightState.loadFullSnapshot(record.flid)
|
||||
val next: FlightSnapshot = if (current == null) {
|
||||
// 新实例建立:ADFT 含 SODT 时直接计算运营日(§2.1),不可算则置 null 待日计划收录
|
||||
// 新建航班:带了计划时间就算出运营日,算不出来先留空,等日计划报文来收录
|
||||
val day = opDay.compute(record.scalars["SODT"])
|
||||
FlightSnapshot(
|
||||
flid = record.flid,
|
||||
operationDay = day, // §2.1:不可算时不得默认写接收日
|
||||
operationDay = day, // 算不出来就留空,不能默认拿收报当天顶上
|
||||
state = FlightState.ACTIVE,
|
||||
stateVersion = 1L,
|
||||
scalars = record.scalars,
|
||||
|
||||
@@ -129,8 +129,8 @@ class MessageProcessor(
|
||||
log.warn("processOne unexpected failure msgId={} ec=INFRA msg={}", head.msgId, e.message ?: e.javaClass.simpleName)
|
||||
procFailure.fail(head, ErrorClass.INFRA, e.message ?: e.javaClass.simpleName)
|
||||
}
|
||||
// 终态已提交:立即尝试一次回填;失败留待回填扫描按退避重试(意图已在终态事务内登记)。
|
||||
// 中间态(PENDING/FAILED)不适用(message-lifecycle §5.2)。
|
||||
// 终态已经写好了,马上试一次回填;写不进去也没关系,回填扫描会按退避继续重试
|
||||
// (待办在写终态时就一起登记了)。还没处理完的消息不打标记。
|
||||
if (terminal) backfill.attempt(head.msgId)
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,7 @@ class MessageProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
// 处理器分派:SCHD 日计划主链路(§3.1)/ FLOP / FDEL / ADFT;缺载荷按 MALFORMED 终态
|
||||
// 按报文类型分派:日计划走 SCHD,其余走 FLOP / FDEL / ADFT;报文缺载荷直接判为非法报文的死信
|
||||
val result: ApplyResult = when (val kind = decoded.kind) {
|
||||
is MsgKind.Schd -> {
|
||||
val body = decoded.body as? ScheduleBody
|
||||
@@ -204,14 +204,14 @@ class MessageProcessor(
|
||||
flopProcessor.apply(head, decoded, payload)
|
||||
}
|
||||
is MsgKind.Unsupported -> {
|
||||
// design.md §2.3:未支持类型 → FAILED(UNSUPPORTED) 退避重试,达阈值转 DEAD;绝不写终态
|
||||
// 还没有对应处理器的报文类型:先按可重试的失败处理,等能力补齐,不直接判死
|
||||
log.warn("unsupported type -> FAILED(UNSUPPORTED) msgId={} tag={}", head.msgId, kind.tag)
|
||||
return procFailure.fail(head, ErrorClass.UNSUPPORTED, "no-handler:${kind.tag}")
|
||||
}
|
||||
}
|
||||
|
||||
if (result is ApplyResult.DeadProtocol) {
|
||||
// design.md §2.3:整包拒绝 DEAD(PROTOCOL),立即释放队头,交人工确认
|
||||
// 整包被拒绝:不重试、立刻放掉队头,等人工确认
|
||||
log.error("DEAD(PROTOCOL) msgId={} reason={} flags={}", head.msgId, result.reason, result.flags)
|
||||
procState.markTerminal(
|
||||
head.msgId, ProcStatus.DEAD,
|
||||
|
||||
@@ -76,7 +76,7 @@ class ScheduleProcessor(
|
||||
val body = msg.body as? ScheduleBody ?: return ApplyResult.DeadProtocol("missing-schd-body")
|
||||
val started = System.nanoTime()
|
||||
|
||||
// 重放判定:MSG_ID 已有成功终态 → 幂等成功(flight-state.md §4),重放也记留痕
|
||||
// 这条消息以前处理成功过:直接算成功,不重复写数据(留痕照样记一条)
|
||||
if (procState.findSuccessTerminal(head.msgId)) {
|
||||
logSnapshot(head, body, SnapshotResult.REPLAY_SKIPPED, upserted = 0, flags = emptySet(), started)
|
||||
return ApplyResult.ReplaySkipped
|
||||
@@ -123,7 +123,7 @@ class ScheduleProcessor(
|
||||
val record = body.records.first { it.flid == flid }
|
||||
val existingMain = mains[flid]
|
||||
val keepDeleted = existingMain?.state == FlightState.DELETED
|
||||
if (keepDeleted) flags.add(SnapshotFlag.SCHD_REVIVE_CONFLICT) // §3.3:日计划不复活 DELETED
|
||||
if (keepDeleted) flags.add(SnapshotFlag.SCHD_REVIVE_CONFLICT) // 日计划不会让已删除的航班复活
|
||||
val current = if (existingMain != null) flightState.loadFullSnapshot(flid) else null
|
||||
val next = FlightStateEngine.snapshotState(
|
||||
current = current,
|
||||
@@ -151,7 +151,7 @@ class ScheduleProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
/** 状态事件:KAFKA_SCHD 整态 + KAFKA_MSG 变化通知(flight-state.md §3.1 登记、§5 语义)。 */
|
||||
/** 每次航班状态变化登记两个事件:KAFKA_SCHD 发完整状态,KAFKA_MSG 发一条变更通知。 */
|
||||
private fun snapshotEvents(next: FlightSnapshot): List<MsgEvent> {
|
||||
val payload = linkedMapOf<String, Any>(
|
||||
"flid" to next.flid,
|
||||
|
||||
Reference in New Issue
Block a user