feat: add carrier-aware signature retirement alerts
This commit is contained in:
@@ -226,7 +226,8 @@ export function getNonNegativeConfigInteger(config: unknown, key: string, fallba
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function isCarrierCompatible(channelCarrier: string | null | undefined, targetCarrier: string) {
|
||||
export function isCarrierCompatible(channelCarrier: string | null | undefined, targetCarrier: string, channelCarriers?: string[] | null) {
|
||||
if (channelCarriers?.length) return channelCarriers.map(normalizeCarrier).includes(normalizeCarrier(targetCarrier));
|
||||
const normalized = normalizeCarrier(channelCarrier);
|
||||
return normalized === 'all' || normalized === targetCarrier;
|
||||
}
|
||||
@@ -448,6 +449,7 @@ export type ChannelCandidate = {
|
||||
province?: string | null;
|
||||
channel: {
|
||||
carrier?: string | null;
|
||||
carriers?: string[] | null;
|
||||
sendRegion?: string | null;
|
||||
status: string;
|
||||
connectionStates?: Array<{ status: string; currentConnections: number; desiredConnections: number }>;
|
||||
@@ -481,7 +483,7 @@ export function selectChannelCandidate<T extends ChannelCandidate>(
|
||||
!options.excludedChannelIds.has(item.channelId)
|
||||
&& options.approvedChannelIds.has(item.channelId)
|
||||
&& normalizeCarrier(item.carrier) === options.carrier
|
||||
&& isCarrierCompatible(item.channel.carrier, options.carrier),
|
||||
&& isCarrierCompatible(item.channel.carrier, options.carrier, item.channel.carriers),
|
||||
);
|
||||
const provinceCandidates = options.forceNational
|
||||
? []
|
||||
|
||||
@@ -770,8 +770,9 @@ export class SendChainService implements OnModuleInit, OnModuleDestroy {
|
||||
signature?: { id?: string | null; name?: string | null } | null;
|
||||
},
|
||||
channelId: string,
|
||||
carrier: string,
|
||||
) {
|
||||
return this.submission.ensureSignatureReportedForChannel(message, channelId);
|
||||
return this.submission.ensureSignatureReportedForChannel(message, channelId, carrier);
|
||||
}
|
||||
|
||||
private async resolveMessageSignatureId(message: { templateId?: string | null; signatureId?: string | null; template?: { signature?: { id?: string | null } | null } | null; signature?: { id?: string | null } | null }) {
|
||||
|
||||
@@ -162,7 +162,7 @@ async submitMessageToGateway(
|
||||
) {
|
||||
const channel = routed.channel;
|
||||
const upstreamSrcId = composeUpstreamSrcId(channel.srcId, message.applicationExtension);
|
||||
await this.facade.ensureSignatureReportedForChannel(message, channel.id);
|
||||
await this.facade.ensureSignatureReportedForChannel(message, channel.id, routed.carrier);
|
||||
await this.facade.waitForChannelRateLimit(channel.id, channel.rateLimitPerSecond);
|
||||
const submitId = `SUB-${randomUUID()}`;
|
||||
try {
|
||||
@@ -321,7 +321,13 @@ async selectChannelForMessage(
|
||||
const signatureId = await this.facade.resolveMessageSignatureId(message);
|
||||
if (!signatureId) throw new BadRequestException('短信签名未配置,无法选择已报备通道');
|
||||
const approvedTasks = await this.prisma.channelSignatureReportTask.findMany({
|
||||
where: { signatureId, reportType: 'signature', status: 'approved', channelId: { in: route.group.items.map((item) => item.channelId) } },
|
||||
where: {
|
||||
signatureId,
|
||||
reportType: 'signature',
|
||||
status: 'approved',
|
||||
channelId: { in: route.group.items.map((item) => item.channelId) },
|
||||
OR: signatureReportApprovalScopes(carrier),
|
||||
},
|
||||
select: { channelId: true },
|
||||
});
|
||||
const approvedChannelIds = new Set(approvedTasks.map((task) => task.channelId));
|
||||
@@ -386,13 +392,20 @@ async ensureSignatureReportedForChannel(
|
||||
signature?: { id?: string | null; name?: string | null } | null;
|
||||
},
|
||||
channelId: string,
|
||||
carrier: string,
|
||||
) {
|
||||
const signatureId = await this.facade.resolveMessageSignatureId(message);
|
||||
if (!signatureId) {
|
||||
throw new BadRequestException('短信签名未配置,不能提交到通道');
|
||||
}
|
||||
const reportTask = await this.prisma.channelSignatureReportTask.findFirst({
|
||||
where: { signatureId, channelId, reportType: 'signature', status: 'approved' },
|
||||
where: {
|
||||
signatureId,
|
||||
channelId,
|
||||
reportType: 'signature',
|
||||
status: 'approved',
|
||||
OR: signatureReportApprovalScopes(carrier),
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
if (!reportTask) {
|
||||
@@ -489,3 +502,11 @@ return streamId`,
|
||||
return typeof result === 'string' ? result : String(result ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
function signatureReportApprovalScopes(carrier: string) {
|
||||
const exact = { carrier, approvalScope: 'carrier_specific' };
|
||||
// 迁移期默认双读;全部历史数据人工拆分后可通过环境开关进入严格运营商口径。
|
||||
return process.env.SIGNATURE_REPORT_STRICT_CARRIER === 'true'
|
||||
? [exact]
|
||||
: [exact, { carrier: null, approvalScope: 'legacy_channel' }];
|
||||
}
|
||||
|
||||
@@ -277,8 +277,9 @@ async ensureSignatureReportedForChannel(
|
||||
signature?: { id?: string | null; name?: string | null } | null;
|
||||
},
|
||||
channelId: string,
|
||||
carrier: string,
|
||||
) {
|
||||
return this.gatewaySubmit.ensureSignatureReportedForChannel(message, channelId);
|
||||
return this.gatewaySubmit.ensureSignatureReportedForChannel(message, channelId, carrier);
|
||||
}
|
||||
|
||||
async resolveMessageSignatureId(message: { templateId?: string | null; signatureId?: string | null; template?: { signature?: { id?: string | null } | null } | null; signature?: { id?: string | null } | null }) {
|
||||
|
||||
Reference in New Issue
Block a user