fix: 修复火柴人排名显示问题
1. identity-service: 添加批量获取用户信息内部接口 - 新增 InternalController 提供 POST /internal/users/batch - repository 添加 findByUserIds 批量查询方法 2. authorization-service: 修复 cumulativeCompleted=0 问题 - assessAndRankRegion 改用 findByAccountSequence 查询团队统计 - referral-service 使用 accountSequence 作为主键 3. mobile-app: 修复火柴人UI显示问题 - 容器边距调整为16px与其他组件一致 - 行高增加到100px避免火柴人重叠 🤖 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
e4f2a61ecb
commit
5fa195e4bc
|
|
@ -328,7 +328,8 @@
|
|||
"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:*)"
|
||||
"Bash(git revert:*)",
|
||||
"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 // 1,020,000,000 USDT \\(10亿2千万\\) = 1020000000 * 1e6 \\(6 decimals\\)\n const amount = BigInt\\(1020000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 1,020,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\")"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ export class AssessmentCalculatorService {
|
|||
const assessments: MonthlyAssessment[] = []
|
||||
|
||||
for (const auth of authorizations) {
|
||||
const teamStats = await statsRepository.findByUserId(auth.userId.value)
|
||||
// 使用 accountSequence 查询团队统计(referral-service 使用 accountSequence 作为主键)
|
||||
const teamStats = await statsRepository.findByAccountSequence(auth.userId.accountSequence)
|
||||
if (!teamStats) continue
|
||||
|
||||
const assessment = await this.calculateMonthlyAssessment(
|
||||
|
|
|
|||
|
|
@ -3,15 +3,18 @@ import { UserAccountController } from './controllers/user-account.controller';
|
|||
import { AuthController } from './controllers/auth.controller';
|
||||
import { ReferralsController } from './controllers/referrals.controller';
|
||||
import { TotpController } from './controllers/totp.controller';
|
||||
import { InternalController } from './controllers/internal.controller';
|
||||
import { ApplicationModule } from '@/application/application.module';
|
||||
import { InfrastructureModule } from '@/infrastructure/infrastructure.module';
|
||||
|
||||
@Module({
|
||||
imports: [ApplicationModule],
|
||||
imports: [ApplicationModule, InfrastructureModule],
|
||||
controllers: [
|
||||
UserAccountController,
|
||||
AuthController,
|
||||
ReferralsController,
|
||||
TotpController,
|
||||
InternalController,
|
||||
],
|
||||
})
|
||||
export class ApiModule {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import { Controller, Post, Body, Logger } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { Public } from '@/shared/guards/jwt-auth.guard';
|
||||
import { UserAccountRepositoryImpl } from '@/infrastructure/persistence/repositories/user-account.repository.impl';
|
||||
import { UserId } from '@/domain/value-objects';
|
||||
|
||||
/**
|
||||
* 批量获取用户信息请求 DTO
|
||||
*/
|
||||
class BatchGetUsersDto {
|
||||
userIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户基本信息响应
|
||||
*/
|
||||
interface UserBasicInfo {
|
||||
userId: string;
|
||||
accountSequence: string;
|
||||
nickname: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部服务调用控制器
|
||||
* 用于微服务间的内部通信,不需要JWT认证
|
||||
*/
|
||||
@ApiTags('Internal')
|
||||
@Controller('internal')
|
||||
export class InternalController {
|
||||
private readonly logger = new Logger(InternalController.name);
|
||||
|
||||
constructor(
|
||||
private readonly userRepository: UserAccountRepositoryImpl,
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@Post('users/batch')
|
||||
@ApiOperation({ summary: '批量获取用户信息(内部调用)' })
|
||||
@ApiResponse({ status: 200, description: '返回用户信息列表' })
|
||||
async batchGetUsers(@Body() dto: BatchGetUsersDto): Promise<UserBasicInfo[]> {
|
||||
this.logger.debug(`[batchGetUsers] 请求用户数量: ${dto.userIds?.length || 0}`);
|
||||
|
||||
if (!dto.userIds || dto.userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const userIds = dto.userIds.map((id) => UserId.create(id));
|
||||
const users = await this.userRepository.findByUserIds(userIds);
|
||||
|
||||
const result = users.map((user) => ({
|
||||
userId: user.userId.value.toString(),
|
||||
accountSequence: user.accountSequence.value,
|
||||
nickname: user.nickname,
|
||||
avatarUrl: user.avatarUrl || undefined,
|
||||
}));
|
||||
|
||||
this.logger.debug(`[batchGetUsers] 返回用户数量: ${result.length}`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error(`[batchGetUsers] 查询失败:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +60,9 @@ export interface UserAccountRepository {
|
|||
kycStatus?: KYCStatus;
|
||||
}): Promise<number>;
|
||||
|
||||
// 批量查询
|
||||
findByUserIds(userIds: UserId[]): Promise<UserAccount[]>;
|
||||
|
||||
// 推荐相关
|
||||
findByInviterSequence(
|
||||
inviterSequence: AccountSequence,
|
||||
|
|
|
|||
|
|
@ -376,6 +376,25 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
|||
});
|
||||
}
|
||||
|
||||
// ============ 批量查询 ============
|
||||
|
||||
async findByUserIds(userIds: UserId[]): Promise<UserAccount[]> {
|
||||
if (userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await this.prisma.userAccount.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: userIds.map((id) => BigInt(id.value)),
|
||||
},
|
||||
},
|
||||
include: { devices: true, walletAddresses: true },
|
||||
});
|
||||
|
||||
return data.map((d) => this.toDomain(d));
|
||||
}
|
||||
|
||||
// ============ 推荐相关 ============
|
||||
|
||||
async findByInviterSequence(
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
|
|||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x80FFFFFF),
|
||||
|
|
@ -158,8 +158,8 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
|
|||
);
|
||||
}
|
||||
|
||||
/// 每个火柴人需要的最小高度(标签+动画+昵称)
|
||||
static const double _minTrackHeight = 85.0;
|
||||
/// 每个火柴人需要的最小高度(标签+动画+昵称+间距)
|
||||
static const double _minTrackHeight = 100.0;
|
||||
|
||||
/// 构建赛道区域
|
||||
Widget _buildRaceTrack() {
|
||||
|
|
@ -167,9 +167,9 @@ class _StickmanRaceWidgetState extends State<StickmanRaceWidget>
|
|||
final sortedRankings = List<StickmanRankingData>.from(widget.rankings)
|
||||
..sort((a, b) => b.completedCount.compareTo(a.completedCount));
|
||||
|
||||
// 根据火柴人数量动态计算赛道高度
|
||||
// 根据火柴人数量动态计算赛道高度(无上限)
|
||||
final trackCount = sortedRankings.length;
|
||||
final raceTrackHeight = (trackCount * _minTrackHeight).clamp(160.0, 500.0);
|
||||
final raceTrackHeight = (trackCount * _minTrackHeight).clamp(160.0, double.infinity);
|
||||
|
||||
return SizedBox(
|
||||
height: raceTrackHeight,
|
||||
|
|
|
|||
Loading…
Reference in New Issue