feat(wallet-service): allocateFunds 添加幂等性检查

防止重复分配奖励,通过检查流水表中的 orderId + accountSequence + allocationType 组合

🤖 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 09:05:44 -08:00
parent bf7f4af88d
commit f9e2d8483c
1 changed files with 44 additions and 2 deletions

View File

@ -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<boolean> {
// 查询流水表,检查是否已存在该订单的分配记录
// 流水的 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+)