Files
msgexchange-v2/src/main/kotlin/com/gzzn/omms/msgexchange/domain/ProcState.kt
T
windyboy 8a380c7646 refactor(config): drop system-time defaults from domain and entry points
删除会被误用的系统时间默认值:MsgEvent.createdAt、ProcState.updatedAt、InboxPoller.pollOnce(now)、HistorySweepJob.run(now)、Identity.of(day)。Identity 的日期边界改由 MessageProcessor 按 PARAM:msgx.operation-day.zone 算出后传入(边界关闭时身份值不变);StubProcState/StubInbox 改注入 Clock(测试可传假时钟),全部调用点显式传时间。

验证:./gradlew test 145 tests / 0 fail / 0 skipped。
2026-09-12 20:56:41 +08:00

91 lines
3.6 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.gzzn.omms.msgexchange.domain
import java.time.Instant
/**
* 一条入站消息的处理记录,一行对应共享信箱 CMINMSGS 里的一条报文。
*
* 这张表同时承担四件事:
* 1. 防重复入队——主键是信箱 ID,同一条报文只会有一行;
* 2. 防业务重复——IDENTITY_KEY 唯一,同一条业务报文只处理一次;
* 3. 记录处理进度——状态、重试次数、下次重试时间和失败原因;
* 4. 记录回填进度——处理完要把"已处理"标记写回共享信箱,写成功之前一直留着待办意图。
*
* 第 4 条和终态写在同一条 UPDATE 里,所以不会出现"业务处理完了,却没人记得去回填"。
*/
enum class ProcStatus {
/** 已入队,等待处理。 */
PENDING,
/** 处理失败,等退避时间到了再重试。 */
FAILED,
/** 处理成功。 */
SUCCEEDED,
/** 判定为业务重复,跳过不处理。 */
SKIPPED,
/** 处理失败且不再重试,等人工处置。 */
DEAD,
}
/** 失败原因分类,决定失败后是重试还是直接进死信。 */
enum class ErrorClass {
/** 报文本身不合法,重试也没用,直接进死信。 */
MALFORMED,
/** 整包被拒绝(运营日冲突、声明条数不符等),整包不落地,直接进死信。 */
PROTOCOL,
/** 解码逻辑的问题;修好 codec 之后可以重放。 */
CODEC_ERROR,
/** 重试次数用尽(尝试上限);人工复核后可以重放。 */
EXHAUSTED,
/** 数据库、网络等基础设施抖动,重试通常就能过。 */
INFRA,
/** 报文类型还没有对应处理器;属于能力未实现,先退避重试等补齐。 */
UNSUPPORTED,
}
data class ProcState(
/** 信箱 CMINMSGS_ID,也是本表主键。 */
val msgId: Long,
val state: ProcStatus,
/** 业务身份 SNDR|TYPE|STYP|SEQN;解码成功后绑定一次,重试不会重绑。 */
val identityKey: String? = null,
/** 处理失败次数,用来算退避档位和判断是否已到上限。 */
val attempts: Int = 0,
/** FAILED 状态下,下次可以重试的时刻。 */
val nextAttemptAt: Instant? = null,
val errorClass: ErrorClass? = null,
/** 最近一次失败的原因(截断后落库,供排查)。 */
val lastError: String? = null,
/** 信箱里的接收时间,来自**库方时钟**、**可能为 NULL**;仅用于对账与展示,不作任何判据。 */
val receivedAt: Instant? = null,
/**
* 本地入队时间(本系统写入,非空)。超期补写期限 `R` **只比较它**:与判据用的本地
* `NOW` 同源,不受库方时钟偏斜影响(`PRE-4`)。
*/
val enqueuedAt: Instant? = null,
/** 非空表示已确认信箱行带上了处理标记。 */
val backfillAt: Instant? = null,
/** 非空表示还欠一次回填:写终态时置为当前时间,失败后退避推后。 */
val backfillNextAt: Instant? = null,
val backfillAttempts: Int = 0,
val backfillError: String? = null,
/**
* 非空表示已判定"不必再回填":信箱行不存在,或暂时性故障持续到 `R` 仍未打标。
*
* **它不等于标记已确认**`backfillAt` 仍为空,所以不满足"边界内全部行已打标"的清除条件。
* 停止重试与"已满足清除前提"是两件事,不能互相替代。
*/
val backfillAbandonedAt: Instant? = null,
/** 放弃原因(`MISSING_ROW` / `TRANSIENT_DEADLINE`),供人工对账与恢复判断。 */
val backfillAbandonedReason: String? = null,
val updatedAt: Instant,
)