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:
parent
2607907bad
commit
2d9e1b2d03
|
|
@ -42,53 +42,68 @@ 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: {
|
|
||||||
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);
|
if (existing) {
|
||||||
} catch (error: unknown) {
|
return this.mapToDomain(existing);
|
||||||
// 处理并发创建时的唯一约束冲突 (Prisma P2002)
|
}
|
||||||
if (
|
|
||||||
error &&
|
try {
|
||||||
typeof error === 'object' &&
|
// 尝试创建
|
||||||
'code' in error &&
|
const result = await this.prisma.contractSigningTask.create({
|
||||||
error.code === 'P2002'
|
data: {
|
||||||
) {
|
orderNo: task.orderNo,
|
||||||
// 记录已被其他并发请求创建,直接查询返回
|
userId: task.userId,
|
||||||
const existing = await this.prisma.contractSigningTask.findUnique({
|
accountSequence: task.accountSequence,
|
||||||
where: { orderNo: task.orderNo },
|
templateId: task.templateId,
|
||||||
});
|
contractVersion: task.contractVersion,
|
||||||
if (existing) {
|
contractContent: task.contractContent,
|
||||||
return this.mapToDomain(existing);
|
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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue