fix: 修复未报备签名统计的中文编码兼容性

This commit is contained in:
hectorzhao
2026-09-21 15:39:45 +08:00
parent 001d5f2cbd
commit 28951b4fc4
9 changed files with 318 additions and 3 deletions
@@ -1,6 +1,7 @@
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { startOfDay, addDays } from './analytics-date';
import { leadingSignatureSql } from './signature-extraction';
export type ActivityDimension = {
dimensionKey: string;
@@ -114,7 +115,7 @@ export async function unreportedRows(db: PrismaService, date: string) {
}>
>(Prisma.sql`
WITH extracted AS (
SELECT m."tenantId",m."applicationId",SUBSTRING(m.content FROM '^【[^【】]+】') AS name
SELECT m."tenantId",m."applicationId",${leadingSignatureSql(Prisma.sql`m.content`)} AS name
FROM "SmsMessageRecord" m WHERE m."queuedAt">=${startOfDay(date)} AND m."queuedAt"<${startOfDay(addDays(date, 1))} AND m."signatureId" IS NULL
) SELECT jsonb_build_array(e."tenantId",e."applicationId",e.name)::text AS "dimensionKey",
e."tenantId",e."applicationId",e.name AS "signatureName",t.name AS "tenantName",a.name AS "applicationName",COUNT(*)::int AS "messageCount"
@@ -0,0 +1,8 @@
import { Prisma } from '@prisma/client';
/** Keep delimiters as whole strings: SQL_ASCII treats a Chinese character class as bytes. */
export function leadingSignatureSql(content: Prisma.Sql): Prisma.Sql {
// The lookahead excludes complete brackets without rejecting bytes shared by other Chinese characters.
// Noncapturing groups keep SUBSTRING returning the complete signature, including its brackets.
return Prisma.sql`SUBSTRING(${content} FROM '^【(?:(?!【|】).)+】')`;
}
@@ -139,7 +139,7 @@ describe('SignatureRetirementService dimensions', () => {
});
const query = prisma.$queryRaw.mock.calls[0]?.[0] as { strings?: readonly string[] };
const sql = query.strings?.join('?') ?? '';
expect(sql).toContain("SUBSTRING(message.content FROM '^【[^【】]+】')");
expect(sql).toContain("SUBSTRING(message.content FROM '^【(?:(?!【|】).)+】')");
expect(sql).toContain('message."signatureId" IS NULL');
expect(sql).toContain('FROM "SmsSignature" signature');
expect(sql).toContain('signature."applicationId" = extracted.application_id');
@@ -1,6 +1,7 @@
import { SignatureAnalyticsRead } from '../signature-analytics/analytics-read';
import { analyticsDate, analyticsPage, todayKey } from '../signature-analytics/analytics-date';
import { detectRetirement } from '../signature-analytics/retirement-batch';
import { leadingSignatureSql } from '../signature-analytics/signature-extraction';
import {
BadRequestException,
Injectable,
@@ -568,7 +569,7 @@ export class SignatureRetirementService implements OnModuleInit, OnModuleDestroy
SELECT
message."tenantId" AS tenant_id,
message."applicationId" AS application_id,
SUBSTRING(message.content FROM '^【[^【】]+】') AS signature_name
${leadingSignatureSql(Prisma.sql`message.content`)} AS signature_name
FROM "SmsMessageRecord" message
WHERE message."queuedAt" >= ${shanghaiStart(date)}
AND message."queuedAt" < ${shanghaiStart(addDays(date, 1))}