diff --git a/backend/services/wallet-service/src/application/services/wallet-application.service.ts b/backend/services/wallet-service/src/application/services/wallet-application.service.ts index 31500dd5..a6dbad93 100644 --- a/backend/services/wallet-service/src/application/services/wallet-application.service.ts +++ b/backend/services/wallet-service/src/application/services/wallet-application.service.ts @@ -898,9 +898,26 @@ export class WalletApplicationService { let totalAmount = 0; let allocatedCount = 0; + let skippedCount = 0; for (const allocation of command.allocations) { try { + // 幂等性检查:检查该订单+账户+分配类型是否已分配 + const alreadyAllocated = await this.checkAllocationExists( + command.orderId, + allocation.targetId, + allocation.allocationType, + ); + + if (alreadyAllocated) { + this.logger.warn( + `[IDEMPOTENT] Allocation already exists for order ${command.orderId}, ` + + `target ${allocation.targetId}, type ${allocation.allocationType}. Skipping.`, + ); + skippedCount++; + continue; + } + if (allocation.targetType === 'USER') { // 分配给用户钱包 await this.allocateToUserWallet(allocation, command.orderId); @@ -920,16 +937,41 @@ export class WalletApplicationService { } this.logger.log( - `Allocated ${allocatedCount}/${command.allocations.length} items, total ${totalAmount} USDT`, + `Allocated ${allocatedCount}/${command.allocations.length} items (skipped ${skippedCount}), total ${totalAmount} USDT`, ); return { - success: allocatedCount > 0, + success: allocatedCount > 0 || skippedCount > 0, allocatedCount, totalAmount, }; } + /** + * 检查分配是否已存在(幂等性检查) + * 通过查询流水表判断该订单+账户+分配类型是否已处理 + */ + private async checkAllocationExists( + orderId: string, + targetId: string, + allocationType: string, + ): Promise { + // 查询流水表,检查是否已存在该订单的分配记录 + // 流水的 payloadJson 中存储了 allocationType + const existingEntry = await this.prisma.ledgerEntry.findFirst({ + where: { + refOrderId: orderId, + accountSequence: targetId, + payloadJson: { + path: ['allocationType'], + equals: allocationType, + }, + }, + }); + + return !!existingEntry; + } + /** * 分配资金到用户钱包 * 支持用户账户 (D+日期+序号) 和系统账户 (S+序号, 7+省代码, 6+市代码, 9+省代码, 8+市代码)