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