D5(终态判据只保留尝试上限): - Pump.tick 内联 attempts 判定,删除 head-deadline 相关的毒丸分支与滞留告警代码 - 删除配置项 head-deadline(PipelineProps / application.yml)与 PumpDeadlineTest - PROCESSING_STARTED_AT 变为只写,注释如实说明当前无判据消费它 D1(回填放弃判据改为时间): - 暂时性故障在 R 之前只退避重试,不再按尝试次数放弃;到 R 才放弃并记 TRANSIENT_DEADLINE - backfill-max-attempts 降级为单行重试的告警阈值 D6(超期判据改用本地入队时间): - 新增 V6 迁移:PROC_STATE 加 ENQUEUED_AT(回填存量后置为非空 + 默认) - findBackfillDue 的谓词与 overdue 标记改比较 enqueued_at,不再用库方时钟的 received_at - BackfillDue 增加 overdue;收报与兼容入口显式写入本地入队时间 文档同步: - 清理 4 处 message-lifecycle.md 章节号死链(Pump/InboxService/PipelineProps/application.yml) - 关闭 G-HEAD-DEADLINE、G-BACKFILL-ABANDON-BYTIME、G-ENQUEUED-AT 三条缺口登记 - reference/user-stories/README 与实现对齐 验证:./gradlew test ⇒ 122 tests, 0 failures, 1 skipped Refs: ACM2-45
46 lines
2.1 KiB
Kotlin
46 lines
2.1 KiB
Kotlin
package com.gzzn.omms.msgexchange.ingress
|
|
|
|
import com.gzzn.omms.msgexchange.infra.persistence.CminmsgInboxRepository
|
|
import com.gzzn.omms.msgexchange.infra.persistence.ProcStateRepository
|
|
import jakarta.inject.Singleton
|
|
import java.time.Clock
|
|
import java.time.Instant
|
|
|
|
/**
|
|
* 兼容 HTTP 入口:把一条报文写进共享信箱,再在自有 PG 里入队,供联调和影子对拍使用。
|
|
* 生产的主路径是 InboxPoller 轮询,不是这里。
|
|
*
|
|
* 两步不在同一个事务里(跨库没有事务)。如果信箱写成功、PG 入队失败,原文仍然在信箱里,
|
|
* 收报轮询会按 ID 把它补进来,所以不会丢消息。
|
|
*
|
|
* 该入口**不参与水位**:它直接写 PROC_STATE,登记的行可能超出水位;主泵只领 `msgId ≤ W`,
|
|
* 因此不破坏 FIFO——这类行等水位追平后按序自然领取(`invariants.md` INV-4)。
|
|
*/
|
|
@Singleton
|
|
class InboxService(
|
|
private val inbox: CminmsgInboxRepository,
|
|
private val procState: ProcStateRepository,
|
|
private val clock: Clock,
|
|
) {
|
|
private val log = org.slf4j.LoggerFactory.getLogger(InboxService::class.java)
|
|
|
|
data class Receipt(val msgId: Long, val receivedAt: Instant)
|
|
|
|
fun accept(rawXml: String): Receipt {
|
|
val id = inbox.insertRaw(rawXml)
|
|
// 信箱接收时间缺失不能让"已经落信"的写入对外报失败(那会与"确认落信才返回 ID"的
|
|
// 契约矛盾,并让客户端重试产生额外重复行)。回退到入队时间并留痕;
|
|
// 时间源用注入的 Clock,保证与收报/回填一致、测试可确定。
|
|
val mailboxReceivedAt = inbox.receivedAtOf(id)
|
|
if (mailboxReceivedAt == null) {
|
|
log.warn("mailbox receive time missing msgId={}, falling back to enqueue time", id)
|
|
}
|
|
val now = clock.instant()
|
|
val receivedAt = mailboxReceivedAt ?: now
|
|
// 入队时间单独传本地时钟:它是超期判据 R 的比较对象,不能借用库方时间(PRE-4)。
|
|
procState.insertIfAbsent(id, receivedAt, enqueuedAt = now)
|
|
log.info("compat-accepted msgId={}", id)
|
|
return Receipt(id, receivedAt)
|
|
}
|
|
}
|