feat: add report material workflows and gateway safeguards
This commit is contained in:
@@ -158,6 +158,12 @@ export interface GatewaySubmitDeadLetterDto {
|
||||
deadLetteredAt?: string;
|
||||
}
|
||||
|
||||
export interface RequeueGatewaySubmitExceptionDto {
|
||||
confirmedNotSubmitted?: boolean;
|
||||
reason?: string;
|
||||
operatorId?: string;
|
||||
}
|
||||
|
||||
export interface GatewayDownstreamRecoveryStatusDto {
|
||||
account: string;
|
||||
gatewayInstanceId?: string;
|
||||
@@ -1244,15 +1250,69 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
return updated;
|
||||
}
|
||||
|
||||
async requeueGatewaySubmitDeadLetter(id: string) {
|
||||
async requeueGatewaySubmitDeadLetter(id: string, data: RequeueGatewaySubmitExceptionDto = {}) {
|
||||
const deadLetter = await this.prisma.gatewaySubmitDeadLetter.findUnique({ where: { id } });
|
||||
if (!deadLetter) {
|
||||
throw new NotFoundException('Gateway submit dead letter not found');
|
||||
throw new NotFoundException('Gateway提交异常记录不存在');
|
||||
}
|
||||
if (!deadLetter.commandPayload || typeof deadLetter.commandPayload !== 'object') {
|
||||
throw new BadRequestException('该死信缺少可重放的 SubmitCommand');
|
||||
if (deadLetter.status !== 'pending') {
|
||||
throw new BadRequestException('该提交异常当前状态不允许重新入队');
|
||||
}
|
||||
if (!data.confirmedNotSubmitted) {
|
||||
throw new BadRequestException('请确认运营商未接收该短信后再重新入队');
|
||||
}
|
||||
const reason = String(data.reason ?? '').trim();
|
||||
if (reason.length < 5 || reason.length > 500) {
|
||||
throw new BadRequestException('请填写5至500字的重新入队原因');
|
||||
}
|
||||
if (!deadLetter.commandPayload || !isObjectRecord(deadLetter.commandPayload)) {
|
||||
throw new BadRequestException('该提交异常缺少可重新入队的SubmitCommand');
|
||||
}
|
||||
if (deadLetter.manualRetryCount >= 3) {
|
||||
throw new BadRequestException('该提交异常已达到人工重新入队次数上限');
|
||||
}
|
||||
const message = deadLetter.messageId
|
||||
? await this.prisma.smsMessageRecord.findUnique({ where: { messageId: deadLetter.messageId } })
|
||||
: null;
|
||||
if (message && (
|
||||
message.submitStatus === 'accepted'
|
||||
|| ['submitted', 'delivered', 'unknown'].includes(message.status)
|
||||
|| ['delivered', 'unknown'].includes(message.receiptStatus ?? '')
|
||||
)) {
|
||||
throw new BadRequestException('该短信已有成功或不确定的上游结果,为避免重复发送,禁止重新入队');
|
||||
}
|
||||
const commandChannelId = String(deadLetter.commandPayload.channelId ?? deadLetter.channelId ?? '').trim();
|
||||
if (!commandChannelId) {
|
||||
throw new BadRequestException('该提交异常缺少通道信息');
|
||||
}
|
||||
const channel = await this.prisma.smsChannel.findUnique({
|
||||
where: { id: commandChannelId },
|
||||
include: { connectionStates: true },
|
||||
});
|
||||
if (!channel || channel.status !== 'active') {
|
||||
throw new BadRequestException('原通道不存在或已停用,不能重新入队');
|
||||
}
|
||||
if (!channel.connectionStates.some((state) => state.status === 'connected' && state.currentConnections > 0)) {
|
||||
throw new BadRequestException('原通道当前没有可用CMPP连接,请先恢复通道');
|
||||
}
|
||||
const claimed = await this.prisma.gatewaySubmitDeadLetter.updateMany({
|
||||
where: { id, status: 'pending' },
|
||||
data: { status: 'requeueing' },
|
||||
});
|
||||
if (claimed.count !== 1) {
|
||||
throw new BadRequestException('该提交异常已被其他操作处理,请刷新后重试');
|
||||
}
|
||||
let retryStreamMessageId: string;
|
||||
try {
|
||||
const publishedStreamMessageId = await this.publishGatewaySubmitCommand(deadLetter.commandPayload);
|
||||
if (!publishedStreamMessageId) {
|
||||
throw new Error('Gateway提交异常重新入队未返回Stream消息编号');
|
||||
}
|
||||
retryStreamMessageId = publishedStreamMessageId;
|
||||
} catch (error) {
|
||||
await this.prisma.gatewaySubmitDeadLetter.update({ where: { id }, data: { status: 'pending' } });
|
||||
throw error;
|
||||
}
|
||||
const retryStreamMessageId = await this.publishGatewaySubmitCommand(deadLetter.commandPayload);
|
||||
const updated = await this.prisma.gatewaySubmitDeadLetter.update({
|
||||
where: { id },
|
||||
data: {
|
||||
@@ -1265,6 +1325,7 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
await this.prisma.operationLog.create({
|
||||
data: {
|
||||
tenantId: updated.tenantId ?? undefined,
|
||||
userId: data.operatorId,
|
||||
action: 'gateway.submit_dead_letter_requeue',
|
||||
resource: 'gateway_submit_dead_letter',
|
||||
resourceId: updated.id,
|
||||
@@ -1273,6 +1334,8 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
retryStreamMessageId,
|
||||
submitId: updated.submitId,
|
||||
messageId: updated.messageId,
|
||||
reason,
|
||||
confirmedNotSubmitted: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user