/** Server 内部消息统一信封;字段同时满足幂等、版本演进和跨进程链路追踪。 */ export interface MessageEnvelope { eventId: string; eventType: string; schemaVersion: 1; idempotencyKey: string; occurredAt: string; tradeDate: string; traceId: string; payload: TPayload; } type MessageEnvelopeInput = Omit, 'schemaVersion'> & { schemaVersion: number; }; function deepFreeze(value: TValue): Readonly { if (value && typeof value === 'object' && !Object.isFrozen(value)) { for (const child of Object.values(value as Record)) deepFreeze(child); Object.freeze(value); } return value; } /** 校验、规范化并深冻结消息信封,防止投递后字段被异步处理链路改写。 */ export function createMessageEnvelope( input: MessageEnvelopeInput, ): Readonly> { if (!input.eventId.trim()) throw new Error('eventId 不能为空'); if (!input.eventType.trim()) throw new Error('eventType 不能为空'); if (input.schemaVersion !== 1) throw new Error('schemaVersion 只支持 1'); if (!input.idempotencyKey.trim()) throw new Error('idempotencyKey 不能为空'); if (!input.occurredAt.trim() || Number.isNaN(Date.parse(input.occurredAt))) { throw new Error('occurredAt 必须为合法 ISO 时间'); } if (!/^\d{4}-\d{2}-\d{2}$/.test(input.tradeDate)) { throw new Error('tradeDate 必须为合法 YYYY-MM-DD 日期'); } const [year, month, day] = input.tradeDate.split('-').map(Number); const parsedTradeDate = new Date(Date.UTC(year, month - 1, day)); if (parsedTradeDate.toISOString().slice(0, 10) !== input.tradeDate) { throw new Error('tradeDate 必须为合法 YYYY-MM-DD 日期'); } if (!input.traceId.trim()) throw new Error('traceId 不能为空'); if (input.payload === undefined) throw new Error('payload 不能为空'); return deepFreeze({ eventId: input.eventId.trim(), eventType: input.eventType.trim(), schemaVersion: 1, idempotencyKey: input.idempotencyKey.trim(), occurredAt: new Date(input.occurredAt).toISOString(), tradeDate: input.tradeDate, traceId: input.traceId.trim(), payload: input.payload, }) as Readonly>; }