From 8c29603f5a1f1f9429fbc7dc6864d6a37255fae0 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 6 Jan 2026 21:38:13 -0800 Subject: [PATCH] fix(reporting-service): use field-level @unique for statsDate in schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: @@unique([field], name: "xxx") requires { xxx: { field } } syntax in findUnique/upsert, but code used { field } directly. Fix: Change to @unique(map: "uk_realtime_stats_date") on the field itself. This keeps the same database index name while allowing { statsDate } syntax. No migration needed - only Prisma client type generation changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../reporting-service/prisma/schema.prisma | 3 +- .../realtime-stats.repository.impl.ts | 148 ++++++------------ 2 files changed, 52 insertions(+), 99 deletions(-) diff --git a/backend/services/reporting-service/prisma/schema.prisma b/backend/services/reporting-service/prisma/schema.prisma index f4c8718b..9100bd1f 100644 --- a/backend/services/reporting-service/prisma/schema.prisma +++ b/backend/services/reporting-service/prisma/schema.prisma @@ -386,7 +386,7 @@ model RealtimeStats { id BigInt @id @default(autoincrement()) @map("stats_id") // === 统计日期 === - statsDate DateTime @map("stats_date") @db.Date + statsDate DateTime @unique(map: "uk_realtime_stats_date") @map("stats_date") @db.Date // === 认种统计 (来自 planting.order.paid) === dailyPlantingCount Int @default(0) @map("daily_planting_count") @@ -405,7 +405,6 @@ model RealtimeStats { updatedAt DateTime @updatedAt @map("updated_at") @@map("realtime_stats") - @@unique([statsDate], name: "uk_realtime_stats_date") @@index([statsDate(sort: Desc)], name: "idx_rs_date") } diff --git a/backend/services/reporting-service/src/infrastructure/persistence/repositories/realtime-stats.repository.impl.ts b/backend/services/reporting-service/src/infrastructure/persistence/repositories/realtime-stats.repository.impl.ts index fa5fd4c3..9f59120b 100644 --- a/backend/services/reporting-service/src/infrastructure/persistence/repositories/realtime-stats.repository.impl.ts +++ b/backend/services/reporting-service/src/infrastructure/persistence/repositories/realtime-stats.repository.impl.ts @@ -15,7 +15,7 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository { async getOrCreateByDate(date: Date): Promise { const statsDate = this.normalizeDate(date); - const existing = await this.prisma.realtimeStats.findFirst({ + const existing = await this.prisma.realtimeStats.findUnique({ where: { statsDate }, }); @@ -33,7 +33,7 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository { async findByDate(date: Date): Promise { const statsDate = this.normalizeDate(date); - const found = await this.prisma.realtimeStats.findFirst({ + const found = await this.prisma.realtimeStats.findUnique({ where: { statsDate }, }); @@ -54,85 +54,57 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository { `Incrementing planting: date=${statsDate.toISOString()}, trees=${safeTreeCount}, amount=${safeAmount}`, ); - // 使用 findFirst + create/update 替代 upsert,避免 Prisma 唯一约束语法问题 - const existing = await this.prisma.realtimeStats.findFirst({ + const result = await this.prisma.realtimeStats.upsert({ where: { statsDate }, + create: { + statsDate, + dailyPlantingCount: safeTreeCount, + dailyOrderCount: 1, + dailyPlantingAmount: safeAmount, + }, + update: { + dailyPlantingCount: { increment: safeTreeCount }, + dailyOrderCount: { increment: 1 }, + dailyPlantingAmount: { increment: safeAmount }, + }, }); - if (existing) { - const result = await this.prisma.realtimeStats.update({ - where: { id: existing.id }, - data: { - dailyPlantingCount: { increment: safeTreeCount }, - dailyOrderCount: { increment: 1 }, - dailyPlantingAmount: { increment: safeAmount }, - }, - }); - return this.toDomain(result); - } else { - const result = await this.prisma.realtimeStats.create({ - data: { - statsDate, - dailyPlantingCount: safeTreeCount, - dailyOrderCount: 1, - dailyPlantingAmount: safeAmount, - }, - }); - return this.toDomain(result); - } + return this.toDomain(result); } async incrementOrder(date: Date): Promise { const statsDate = this.normalizeDate(date); - const existing = await this.prisma.realtimeStats.findFirst({ + const result = await this.prisma.realtimeStats.upsert({ where: { statsDate }, + create: { + statsDate, + dailyOrderCount: 1, + }, + update: { + dailyOrderCount: { increment: 1 }, + }, }); - if (existing) { - const result = await this.prisma.realtimeStats.update({ - where: { id: existing.id }, - data: { - dailyOrderCount: { increment: 1 }, - }, - }); - return this.toDomain(result); - } else { - const result = await this.prisma.realtimeStats.create({ - data: { - statsDate, - dailyOrderCount: 1, - }, - }); - return this.toDomain(result); - } + return this.toDomain(result); } async incrementNewUser(date: Date): Promise { const statsDate = this.normalizeDate(date); this.logger.debug(`Incrementing new user: date=${statsDate.toISOString()}`); - const existing = await this.prisma.realtimeStats.findFirst({ + const result = await this.prisma.realtimeStats.upsert({ where: { statsDate }, + create: { + statsDate, + dailyNewUserCount: 1, + }, + update: { + dailyNewUserCount: { increment: 1 }, + }, }); - if (existing) { - const result = await this.prisma.realtimeStats.update({ - where: { id: existing.id }, - data: { - dailyNewUserCount: { increment: 1 }, - }, - }); - return this.toDomain(result); - } else { - const result = await this.prisma.realtimeStats.create({ - data: { - statsDate, - dailyNewUserCount: 1, - }, - }); - return this.toDomain(result); - } + return this.toDomain(result); } async incrementProvinceAuth(date: Date): Promise { @@ -141,54 +113,36 @@ export class RealtimeStatsRepository implements IRealtimeStatsRepository { `Incrementing province auth: date=${statsDate.toISOString()}`, ); - const existing = await this.prisma.realtimeStats.findFirst({ + const result = await this.prisma.realtimeStats.upsert({ where: { statsDate }, + create: { + statsDate, + dailyProvinceAuthCount: 1, + }, + update: { + dailyProvinceAuthCount: { increment: 1 }, + }, }); - if (existing) { - const result = await this.prisma.realtimeStats.update({ - where: { id: existing.id }, - data: { - dailyProvinceAuthCount: { increment: 1 }, - }, - }); - return this.toDomain(result); - } else { - const result = await this.prisma.realtimeStats.create({ - data: { - statsDate, - dailyProvinceAuthCount: 1, - }, - }); - return this.toDomain(result); - } + return this.toDomain(result); } async incrementCityAuth(date: Date): Promise { const statsDate = this.normalizeDate(date); this.logger.debug(`Incrementing city auth: date=${statsDate.toISOString()}`); - const existing = await this.prisma.realtimeStats.findFirst({ + const result = await this.prisma.realtimeStats.upsert({ where: { statsDate }, + create: { + statsDate, + dailyCityAuthCount: 1, + }, + update: { + dailyCityAuthCount: { increment: 1 }, + }, }); - if (existing) { - const result = await this.prisma.realtimeStats.update({ - where: { id: existing.id }, - data: { - dailyCityAuthCount: { increment: 1 }, - }, - }); - return this.toDomain(result); - } else { - const result = await this.prisma.realtimeStats.create({ - data: { - statsDate, - dailyCityAuthCount: 1, - }, - }); - return this.toDomain(result); - } + return this.toDomain(result); } async findRecentDays(days: number): Promise {