feat: scope blacklists and paginate phone segments

This commit is contained in:
hectorzhao
2026-07-10 13:42:39 +08:00
parent ff0756ff9c
commit d5e668a2e6
18 changed files with 322 additions and 86 deletions
@@ -81,6 +81,7 @@ describe('RiskReviewService', () => {
it('routes duplicate and blacklist ratio hits to manual review', async () => {
const prisma = createPrismaMock();
prisma.globalBlacklist.findMany.mockResolvedValue([{ phoneNumber: '13800000001' }]);
prisma.enterpriseBlacklist.findMany.mockResolvedValue([{ phoneNumber: '13800000002' }]);
prisma.riskRule.findMany.mockResolvedValue([
{
id: 'rule-dup',
@@ -105,6 +106,7 @@ describe('RiskReviewService', () => {
const service = new RiskReviewService(prisma as never);
const result = await service.evaluateTask({
tenantId: 'tenant-1',
applicationId: 'app-1',
content: 'hello',
phones: ['13800000001', '13800000001', '13800000002'],
});
@@ -113,11 +115,15 @@ describe('RiskReviewService', () => {
expect(prisma.smsSendTask.create).toHaveBeenCalledWith({
data: expect.objectContaining({
duplicateRatio: 0.3333,
blacklistHitRatio: 0.3333,
blacklistHitRatio: 0.6667,
status: 'pending_review',
riskDecision: 'manual_review',
}),
});
expect(prisma.enterpriseBlacklist.findMany).toHaveBeenCalledWith({
where: { tenantId: 'tenant-1', applicationId: 'app-1', phoneNumber: { in: ['13800000001', '13800000002'] }, status: 'active' },
select: { phoneNumber: true },
});
expect(prisma.riskHitRecord.createMany).toHaveBeenCalledWith({
data: expect.arrayContaining([
expect.objectContaining({ ruleCode: 'DUPLICATE_PHONE_RATIO' }),
+5 -5
View File
@@ -181,7 +181,7 @@ export class RiskReviewService {
const duplicateRatio = ratio(phoneTotal - uniquePhoneTotal, phoneTotal);
const illegalCount = phones.filter((phone) => !isMainlandMobile(phone)).length;
const illegalRatio = ratio(illegalCount, phoneTotal);
const blacklistHitCount = await this.countBlacklistHits(data.tenantId, uniquePhones);
const blacklistHitCount = await this.countBlacklistHits(data.tenantId, data.applicationId, uniquePhones);
const blacklistHitRatio = ratio(blacklistHitCount, phoneTotal);
const [application, template, rules, recentTaskCount, sensitiveWords] = await Promise.all([
data.applicationId ? this.prisma.smsApplication.findUnique({ where: { id: data.applicationId } }) : null,
@@ -324,7 +324,7 @@ export class RiskReviewService {
return [...byCode.values()].sort((a, b) => a.priority - b.priority);
}
private async countBlacklistHits(tenantId: string, phones: string[]) {
private async countBlacklistHits(tenantId: string, applicationId: string | undefined, phones: string[]) {
if (phones.length === 0) {
return 0;
}
@@ -333,10 +333,10 @@ export class RiskReviewService {
where: { phoneNumber: { in: phones }, status: 'active' },
select: { phoneNumber: true },
}),
this.prisma.enterpriseBlacklist.findMany({
where: { tenantId, phoneNumber: { in: phones }, status: 'active' },
applicationId ? this.prisma.enterpriseBlacklist.findMany({
where: { tenantId, applicationId, phoneNumber: { in: phones }, status: 'active' },
select: { phoneNumber: true },
}),
}) : Promise.resolve([]),
]);
return new Set([...globalHits, ...enterpriseHits].map((hit) => hit.phoneNumber)).size;
}