fix(planting-service): 修复合同签署任务创建的并发问题

- 将 upsert 改为 create + 重试机制
- 处理 P2002 唯一约束冲突时使用指数退避重试
- 确保并发创建时能正确返回已存在的记录

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-25 03:50:19 -08:00
parent 2607907bad
commit 2d9e1b2d03
1 changed files with 58 additions and 43 deletions

View File

@ -42,13 +42,22 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe
}); });
return this.mapToDomain(updated); return this.mapToDomain(updated);
} else { } else {
// 创建 - 使用 upsert 处理并发幂等性 // 创建 - 使用重试机制处理并发幂等性
// 如果 orderNo 已存在,则只返回现有记录(不更新) // 先尝试查询,如果不存在再创建,如果创建失败(并发冲突)则重新查询
// 额外捕获 P2002 错误以处理极端并发情况 const maxRetries = 3;
try { for (let attempt = 1; attempt <= maxRetries; attempt++) {
const result = await this.prisma.contractSigningTask.upsert({ // 先检查是否已存在
const existing = await this.prisma.contractSigningTask.findUnique({
where: { orderNo: task.orderNo }, where: { orderNo: task.orderNo },
create: { });
if (existing) {
return this.mapToDomain(existing);
}
try {
// 尝试创建
const result = await this.prisma.contractSigningTask.create({
data: {
orderNo: task.orderNo, orderNo: task.orderNo,
userId: task.userId, userId: task.userId,
accountSequence: task.accountSequence, accountSequence: task.accountSequence,
@ -67,8 +76,6 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe
status: task.status, status: task.status,
expiresAt: task.expiresAt, expiresAt: task.expiresAt,
}, },
// 如果已存在,不更新任何字段,只返回现有记录
update: {},
}); });
return this.mapToDomain(result); return this.mapToDomain(result);
} catch (error: unknown) { } catch (error: unknown) {
@ -79,17 +86,25 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe
'code' in error && 'code' in error &&
error.code === 'P2002' error.code === 'P2002'
) { ) {
// 记录已被其他并发请求创建,直接查询返回 // 记录已被其他并发请求创建,等待一小段时间后重试查询
const existing = await this.prisma.contractSigningTask.findUnique({ if (attempt < maxRetries) {
await new Promise((resolve) => setTimeout(resolve, 50 * attempt));
continue;
}
// 最后一次尝试查询
const finalExisting = await this.prisma.contractSigningTask.findUnique({
where: { orderNo: task.orderNo }, where: { orderNo: task.orderNo },
}); });
if (existing) { if (finalExisting) {
return this.mapToDomain(existing); return this.mapToDomain(finalExisting);
} }
} }
throw error; throw error;
} }
} }
// 不应该到达这里,但为了类型安全
throw new Error(`Failed to create or find signing task after ${maxRetries} attempts`);
}
} }
async findById(id: bigint): Promise<ContractSigningTask | null> { async findById(id: bigint): Promise<ContractSigningTask | null> {