fix(reward-service): 添加奖励分配幂等性检查
- distributeRewards 方法添加幂等性检查,防止同一订单重复分配奖励 - distributeRewardsForExpiredContract 方法同样添加幂等性检查 - 通过 findBySourceOrderNo 检查订单是否已分配过奖励 修复问题:recovery job 重复触发导致同一订单奖励被多次分配 🤖 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
68a071cfaa
commit
e95ac15605
|
|
@ -408,7 +408,8 @@
|
|||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(contract\\): 使用合同编号代替订单号\n\n合同编号格式: accountSequence-yyyyMMddHHmm\n例如: 10001-202512251003\n\n修改内容:\n- 数据库: 添加 contract_no 字段\n- 后端: 聚合根、Repository、Service、PDF生成器支持 contractNo\n- 前端: 显示合同编号代替订单号\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(npx ts-node:*)",
|
||||
"Bash(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0xab2ecb00ef473cfbbf3df13faa7ed3ff84eea229'';\n\nasync function transfer\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function transfer\\(address to, uint256 amount\\) returns \\(bool\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n const amount = BigInt\\(1000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 1,000,000 USDT to'', TO_ADDRESS\\);\n const tx = await contract.transfer\\(TO_ADDRESS, amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const newBalance = await contract.balanceOf\\(TO_ADDRESS\\);\n console.log\\(''New balance:'', Number\\(newBalance\\) / 1e6, ''USDT''\\);\n}\n\ntransfer\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")",
|
||||
"Bash(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x17e9109ac3d2921c7731f3e72749bc13043a076c'';\n\nasync function transfer\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function transfer\\(address to, uint256 amount\\) returns \\(bool\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n const amount = BigInt\\(1000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 1,000,000 USDT to'', TO_ADDRESS\\);\n const tx = await contract.transfer\\(TO_ADDRESS, amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const newBalance = await contract.balanceOf\\(TO_ADDRESS\\);\n console.log\\(''New balance:'', Number\\(newBalance\\) / 1e6, ''USDT''\\);\n}\n\ntransfer\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")"
|
||||
"Bash(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x17e9109ac3d2921c7731f3e72749bc13043a076c'';\n\nasync function transfer\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function transfer\\(address to, uint256 amount\\) returns \\(bool\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n const amount = BigInt\\(1000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 1,000,000 USDT to'', TO_ADDRESS\\);\n const tx = await contract.transfer\\(TO_ADDRESS, amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const newBalance = await contract.balanceOf\\(TO_ADDRESS\\);\n console.log\\(''New balance:'', Number\\(newBalance\\) / 1e6, ''USDT''\\);\n}\n\ntransfer\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")",
|
||||
"Bash(powershell -Command \"\\(Get-Content ''reward-application.service.ts''\\) -replace \"\"this\\\\.logger\\\\.log\\\\\\(\\\\`Distributing rewards for order \\\\$\\\\{params\\\\.sourceOrderNo\\\\}, accountSequence=\\\\$\\\\{params\\\\.sourceAccountSequence\\\\}\\\\`\\\\\\);\\\\r?\\\\n\\\\r?\\\\n // 1\\\\. 计算所有奖励\"\", \"\"this.logger.log\\(\\\\`Distributing rewards for order \\\\$\\\\{params.sourceOrderNo\\\\}, accountSequence=\\\\$\\\\{params.sourceAccountSequence\\\\}\\\\`\\);\\\\`n\\\\`n // 幂等性检查:如果该订单已经分配过奖励,跳过处理\\\\`n const existingRewards = await this.rewardLedgerEntryRepository.findBySourceOrderNo\\(params.sourceOrderNo\\);\\\\`n if \\(existingRewards.length > 0\\) {\\\\`n this.logger.warn\\(\\\\`Order \\\\$\\\\{params.sourceOrderNo\\\\} already has \\\\$\\\\{existingRewards.length\\\\} rewards distributed, skipping \\(idempotent\\)\\\\`\\);\\\\`n return;\\\\`n }\\\\`n\\\\`n // 1. 计算所有奖励\"\" | Set-Content ''reward-application.service.ts'' -Encoding UTF8\")"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -56,6 +56,13 @@ export class RewardApplicationService {
|
|||
}): Promise<void> {
|
||||
this.logger.log(`Distributing rewards for order ${params.sourceOrderNo}, accountSequence=${params.sourceAccountSequence}`);
|
||||
|
||||
// 幂等性检查:如果该订单已经分配过奖励,跳过处理
|
||||
const existingRewards = await this.rewardLedgerEntryRepository.findBySourceOrderNo(params.sourceOrderNo);
|
||||
if (existingRewards.length > 0) {
|
||||
this.logger.warn(`Order ${params.sourceOrderNo} already has ${existingRewards.length} rewards distributed, skipping (idempotent)`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 计算所有奖励(包含考核逻辑,调用 authorization-service)
|
||||
const rewards = await this.rewardCalculationService.calculateRewards(params);
|
||||
|
||||
|
|
@ -153,6 +160,13 @@ export class RewardApplicationService {
|
|||
}): Promise<void> {
|
||||
this.logger.log(`Distributing rewards for EXPIRED contract, order ${params.sourceOrderNo}`);
|
||||
|
||||
// 幂等性检查:如果该订单已经分配过奖励,跳过处理
|
||||
const existingRewards = await this.rewardLedgerEntryRepository.findBySourceOrderNo(params.sourceOrderNo);
|
||||
if (existingRewards.length > 0) {
|
||||
this.logger.warn(`Expired contract order ${params.sourceOrderNo} already has ${existingRewards.length} rewards distributed, skipping (idempotent)`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 计算奖励,但使用系统账户作为用户权益的接收方
|
||||
const rewards = await this.rewardCalculationService.calculateRewardsForExpiredContract(params);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue