feat: add carrier-aware signature retirement alerts

This commit is contained in:
hectorzhao
2026-08-10 20:54:05 +08:00
parent 232d1c22a3
commit 55aa054005
52 changed files with 3074 additions and 152 deletions
+4 -2
View File
@@ -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
? []
+2 -1
View File
@@ -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 }) {