From 2d9e1b2d0329a35b7411cedd03ac1c8149fda798 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 25 Dec 2025 03:50:19 -0800 Subject: [PATCH] =?UTF-8?q?fix(planting-service):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=90=88=E5=90=8C=E7=AD=BE=E7=BD=B2=E4=BB=BB=E5=8A=A1=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E7=9A=84=E5=B9=B6=E5=8F=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 upsert 改为 create + 重试机制 - 处理 P2002 唯一约束冲突时使用指数退避重试 - 确保并发创建时能正确返回已存在的记录 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../contract-signing-task.repository.impl.ts | 101 ++++++++++-------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts b/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts index 84f59741..095cd88b 100644 --- a/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts +++ b/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts @@ -42,53 +42,68 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe }); return this.mapToDomain(updated); } else { - // 创建 - 使用 upsert 处理并发幂等性 - // 如果 orderNo 已存在,则只返回现有记录(不更新) - // 额外捕获 P2002 错误以处理极端并发情况 - try { - const result = await this.prisma.contractSigningTask.upsert({ + // 创建 - 使用重试机制处理并发幂等性 + // 先尝试查询,如果不存在再创建,如果创建失败(并发冲突)则重新查询 + const maxRetries = 3; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + // 先检查是否已存在 + const existing = await this.prisma.contractSigningTask.findUnique({ where: { orderNo: task.orderNo }, - create: { - orderNo: task.orderNo, - userId: task.userId, - accountSequence: task.accountSequence, - templateId: task.templateId, - contractVersion: task.contractVersion, - contractContent: task.contractContent, - userPhoneNumber: task.userPhoneNumber, - userRealName: task.userRealName, - userIdCardNumber: task.userIdCardNumber, - treeCount: task.treeCount, - totalAmount: new Prisma.Decimal(task.totalAmount), - provinceCode: task.provinceCode, - provinceName: task.provinceName, - cityCode: task.cityCode, - cityName: task.cityName, - status: task.status, - expiresAt: task.expiresAt, - }, - // 如果已存在,不更新任何字段,只返回现有记录 - update: {}, }); - return this.mapToDomain(result); - } catch (error: unknown) { - // 处理并发创建时的唯一约束冲突 (Prisma P2002) - if ( - error && - typeof error === 'object' && - 'code' in error && - error.code === 'P2002' - ) { - // 记录已被其他并发请求创建,直接查询返回 - const existing = await this.prisma.contractSigningTask.findUnique({ - where: { orderNo: task.orderNo }, - }); - if (existing) { - return this.mapToDomain(existing); - } + if (existing) { + return this.mapToDomain(existing); + } + + try { + // 尝试创建 + const result = await this.prisma.contractSigningTask.create({ + data: { + orderNo: task.orderNo, + userId: task.userId, + accountSequence: task.accountSequence, + templateId: task.templateId, + contractVersion: task.contractVersion, + contractContent: task.contractContent, + userPhoneNumber: task.userPhoneNumber, + userRealName: task.userRealName, + userIdCardNumber: task.userIdCardNumber, + treeCount: task.treeCount, + totalAmount: new Prisma.Decimal(task.totalAmount), + provinceCode: task.provinceCode, + provinceName: task.provinceName, + cityCode: task.cityCode, + cityName: task.cityName, + status: task.status, + expiresAt: task.expiresAt, + }, + }); + return this.mapToDomain(result); + } catch (error: unknown) { + // 处理并发创建时的唯一约束冲突 (Prisma P2002) + if ( + error && + typeof error === 'object' && + 'code' in error && + error.code === 'P2002' + ) { + // 记录已被其他并发请求创建,等待一小段时间后重试查询 + if (attempt < maxRetries) { + await new Promise((resolve) => setTimeout(resolve, 50 * attempt)); + continue; + } + // 最后一次尝试查询 + const finalExisting = await this.prisma.contractSigningTask.findUnique({ + where: { orderNo: task.orderNo }, + }); + if (finalExisting) { + return this.mapToDomain(finalExisting); + } + } + throw error; } - throw error; } + // 不应该到达这里,但为了类型安全 + throw new Error(`Failed to create or find signing task after ${maxRetries} attempts`); } }