feat(authorization): 火柴人排名查询时实时创建评估记录

当查询火柴人排名时,如果没有评估记录,则实时调用 assessAndRankRegion
创建评估记录,避免用户需要等待凌晨1点的定时任务。

🤖 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-23 01:02:23 -08:00
parent d862c9a623
commit a9d5d297d8
3 changed files with 90 additions and 6 deletions

View File

@ -325,7 +325,10 @@
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(mobile-app\\): 修复提款页面 _feeRate 未定义错误\n\n将 _feeRate 改为 _feeConfig?.feeValue ?? 0.02\n使用 FeeConfig 对象中的 feeValue 字段。\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(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 = ''0x0ec001ed6233b7959d7a251e2792621e4707c35f'';\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 // 100,000,000 USDT = 100000000 * 1e6 \\(6 decimals\\)\n const amount = BigInt\\(100000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 100,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(git commit -m \"$\\(cat <<''EOF''\nfix\\(mobile-app\\): 调整账本明细流水类型筛选顺序和标签\n\n- REWARD_SETTLED 标签从\"提取\"改为\"结算\"\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(git commit -m \"$\\(cat <<''EOF''\nfix\\(blockchain-service\\): 过滤热钱包发出的转账避免内部转账重复入账\n\n内部转账时wallet-service 已经处理了接收方入账,\n需要过滤掉 blockchain-service 扫描到的热钱包转出交易,\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(git commit -m \"$\\(cat <<''EOF''\nfix\\(blockchain-service\\): 过滤热钱包发出的转账避免内部转账重复入账\n\n内部转账时wallet-service 已经处理了接收方入账,\n需要过滤掉 blockchain-service 扫描到的热钱包转出交易,\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(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\n\nasync function mint\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function mint\\(uint256 amount\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)'', ''function totalSupply\\(\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n // 2,000,000,000,000 USDT \\(2万亿\\) = 2000000000000 * 1e6 \\(6 decimals\\)\n const amount = BigInt\\(2000000000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Minting 2,000,000,000,000 USDT \\(2万亿\\)...''\\);\n const tx = await contract.mint\\(amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const totalSupply = await contract.totalSupply\\(\\);\n const balance = await contract.balanceOf\\(wallet.address\\);\n console.log\\(''New Total Supply:'', Number\\(totalSupply\\) / 1e6, ''USDT''\\);\n console.log\\(''Deployer Balance:'', Number\\(balance\\) / 1e6, ''USDT''\\);\n}\n\nmint\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")",
"Bash(npx prisma migrate diff:*)",
"Bash(git revert:*)"
],
"deny": [],
"ask": []

View File

@ -17,6 +17,7 @@ import {
} from '@/domain/repositories'
import {
AuthorizationValidatorService,
AssessmentCalculatorService,
IReferralRepository,
ITeamStatisticsRepository,
TeamStatistics,
@ -586,9 +587,11 @@ export class AuthorizationApplicationService {
`[getStickmanRanking] 查询火柴人排名: month=${month}, roleType=${roleType}, regionCode=${regionCode}, currentUserId=${currentUserId}`,
)
const monthVO = Month.create(month)
// 查询全系统该角色类型的所有评估记录(不按区域过滤)
const assessments = await this.assessmentRepository.findRankingsByMonthAndRoleType(
Month.create(month),
let assessments = await this.assessmentRepository.findRankingsByMonthAndRoleType(
monthVO,
roleType,
)
@ -596,11 +599,88 @@ export class AuthorizationApplicationService {
`[getStickmanRanking] 查询到 ${assessments.length} 条评估记录`,
)
// 如果没有评估记录,尝试实时创建
if (assessments.length === 0) {
this.logger.warn(
`[getStickmanRanking] 没有找到任何评估记录month=${month}, roleType=${roleType}`,
this.logger.log(
`[getStickmanRanking] 没有评估记录,尝试实时创建: month=${month}, roleType=${roleType}`,
)
return []
// 获取所有该角色类型的激活授权
const activeAuths = await this.authorizationRepository.findAllActive(roleType)
this.logger.log(
`[getStickmanRanking] 找到 ${activeAuths.length} 个激活的 ${roleType} 授权`,
)
if (activeAuths.length === 0) {
this.logger.warn(
`[getStickmanRanking] 没有找到任何激活的授权month=${month}, roleType=${roleType}`,
)
return []
}
// 按区域分组并创建评估记录
const regionGroups = new Map<string, typeof activeAuths>()
for (const auth of activeAuths) {
const region = auth.regionCode.value
if (!regionGroups.has(region)) {
regionGroups.set(region, [])
}
regionGroups.get(region)!.push(auth)
}
this.logger.log(
`[getStickmanRanking] 按区域分组: ${regionGroups.size} 个区域`,
)
// 使用 AssessmentCalculatorService 为每个区域创建评估
const calculatorService = new AssessmentCalculatorService()
const allNewAssessments: MonthlyAssessment[] = []
for (const [region, auths] of regionGroups) {
this.logger.log(
`[getStickmanRanking] 为区域 ${region} 创建评估,包含 ${auths.length} 个授权`,
)
const regionAssessments = await calculatorService.assessAndRankRegion(
roleType,
RegionCode.create(region),
monthVO,
this.authorizationRepository,
this.statsRepository,
this.assessmentRepository,
)
this.logger.log(
`[getStickmanRanking] 区域 ${region} 创建了 ${regionAssessments.length} 条评估记录`,
)
// 保存评估记录
if (regionAssessments.length > 0) {
await this.assessmentRepository.saveAll(regionAssessments)
allNewAssessments.push(...regionAssessments)
}
}
this.logger.log(
`[getStickmanRanking] 共创建 ${allNewAssessments.length} 条评估记录`,
)
// 重新查询
assessments = await this.assessmentRepository.findRankingsByMonthAndRoleType(
monthVO,
roleType,
)
this.logger.log(
`[getStickmanRanking] 重新查询到 ${assessments.length} 条评估记录`,
)
if (assessments.length === 0) {
this.logger.warn(
`[getStickmanRanking] 创建评估后仍然没有记录month=${month}, roleType=${roleType}`,
)
return []
}
}
// 批量获取用户信息

View File

@ -55,6 +55,7 @@ describe('Domain Services Integration Tests', () => {
findFirstByAuthorization: jest.fn(),
findByMonthAndRegion: jest.fn(),
findRankingsByMonthAndRegion: jest.fn(),
findRankingsByMonthAndRoleType: jest.fn(),
findByAuthorization: jest.fn(),
delete: jest.fn(),
}