fix(治理): 完成任务 8 安全与通知闭环修复

- 串行化拉黑、处罚、投瓶和匹配策略检查\n- 完成异步站内通知、未读统计和偏好并发语义\n- 补齐后台查询审计、处罚恢复和隐私测试\n- 稳定 Redis 恢复、匹配锁序及超时测试
This commit is contained in:
root
2026-09-17 13:08:55 +08:00
parent ce8e1db279
commit fa0fa78312
29 changed files with 1231 additions and 153 deletions
+2 -2
View File
@@ -255,8 +255,8 @@ describe("bottles with real PostgreSQL", () => {
expiresAt: new Date(Date.now() + 60_000),
},
});
const denied = await create(randomUUID()).expect(403);
expect(denied.body.code).toBe("ACCOUNT_SANCTIONED");
const denied = await create(randomUUID()).expect(401);
expect(denied.body.code).toBe("AUTH_UNAUTHORIZED");
expect(await prisma.bottle.count()).toBe(0);
},
);
+2 -1
View File
@@ -3,9 +3,10 @@ import { AuthModule } from "../auth/auth.module.js";
import { DatabaseModule } from "../database/database.module.js";
import { BottleController } from "./bottle.controller.js";
import { BottleService } from "./bottle.service.js";
import { SafetyModule } from "../safety/safety.module.js";
@Module({
imports: [AuthModule, DatabaseModule],
imports: [AuthModule, DatabaseModule, SafetyModule],
controllers: [BottleController],
providers: [BottleService],
})
+11 -8
View File
@@ -5,20 +5,19 @@ import { Prisma } from "@prisma/client";
import { DomainException } from "../common/domain.exception.js";
import { PrismaService } from "../database/prisma.service.js";
import { utc8UsageDate } from "./usage-date.js";
import { SafetyLockService } from "../safety/safety-lock.service.js";
@Injectable()
export class BottleService {
constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {}
constructor(
@Inject(PrismaService) private readonly prisma: PrismaService,
@Inject(SafetyLockService) private readonly locks: SafetyLockService,
) {}
async create(authorId: string, requestId: string, contentText: string) {
const existing = await this.prisma.bottle.findUnique({
where: {
authorId_clientRequestId: { authorId, clientRequestId: requestId },
},
});
if (existing) return this.resolveIdempotent(existing, contentText);
try {
return await this.prisma.$transaction(async (tx) => {
await this.locks.lockAccounts(tx, [authorId]);
await tx.$executeRaw`SELECT pg_advisory_xact_lock(hashtextextended(${`${authorId}:${requestId}`}, 0))`;
const duplicate = await tx.bottle.findUnique({
where: {
@@ -27,6 +26,10 @@ export class BottleService {
});
if (duplicate) return this.resolveIdempotent(duplicate, contentText);
const now = new Date();
const account = await tx.account.findUnique({
where: { id: authorId },
select: { status: true },
});
const sanctioned = await tx.sanction.findFirst({
where: {
accountId: authorId,
@@ -37,7 +40,7 @@ export class BottleService {
},
select: { id: true },
});
if (sanctioned)
if (account?.status !== "ACTIVE" || sanctioned)
throw new DomainException(
ErrorCode.ACCOUNT_SANCTIONED,
"Account sanctioned",