feat(referral): add personal/team planting counts to direct referral list

- Backend: batch query TeamStatistics to get each direct referral's
  personalPlantingCount and teamPlantingCount
- Updated DTO and query interfaces with new fields
- Frontend: parse and display "个人/团队" counts in profile page

🤖 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-10 05:57:46 -08:00
parent e1017fff46
commit 68182fd0a3
7 changed files with 74 additions and 74 deletions

View File

@ -1,65 +1,30 @@
{
"permissions": {
"allow": [
"Bash(__NEW_LINE__ echo \"=== Test /api/v1/referral/me (referral-service) ===\")",
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" push)",
"Bash(__NEW_LINE__ echo \"=== Test /api/v1/authorizations/my (authorization-service on port 3002) ===\")",
"Bash(__NEW_LINE__ echo \"=== Test /api/v1/authorizations/my (authorization-service on port 3009) ===\")",
"Bash(backend/api-gateway/kong.yml )",
"Bash(frontend/mobile-app/lib/core/constants/api_endpoints.dart )",
"Bash(frontend/mobile-app/lib/core/di/injection_container.dart )",
"Bash(frontend/mobile-app/lib/core/services/referral_service.dart )",
"Bash(frontend/mobile-app/lib/core/services/authorization_service.dart )",
"Bash(frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart )",
"Bash(backend/services/authorization-service/package.json )",
"Bash(backend/services/authorization-service/package-lock.json )",
"Bash(backend/services/authorization-service/src/shared/strategies/jwt.strategy.ts )",
"Bash(backend/services/authorization-service/src/infrastructure/persistence/repositories/system-account.repository.impl.ts)",
"Bash(DATABASE_URL=\"postgresql://rwauser:rwapass123@localhost:5432/rwa_authorization\" npx prisma migrate:*)",
"Bash(backend/services/authorization-service/.gitignore )",
"Bash(backend/services/authorization-service/prisma/migrations/00000000000000_init/migration.sql )",
"Bash(git fetch:*)",
"Bash(backend/services/blockchain-service/package.json )",
"Bash(backend/services/blockchain-service/package-lock.json )",
"Bash(backend/services/blockchain-service/src/api/api.module.ts )",
"Bash(backend/services/blockchain-service/src/api/controllers/index.ts )",
"Bash(backend/services/blockchain-service/src/api/controllers/deposit.controller.ts )",
"Bash(backend/services/blockchain-service/src/shared/index.ts )",
"Bash(backend/services/blockchain-service/src/shared/decorators/ )",
"Bash(backend/services/blockchain-service/src/shared/guards/ )",
"Bash(backend/services/blockchain-service/src/shared/strategies/ )",
"Bash(backend/services/wallet-service/src/api/api.module.ts )",
"Bash(backend/services/wallet-service/src/infrastructure/kafka/ )",
"Bash(backend/services/wallet-service/src/application/event-handlers/ )",
"Bash(backend/services/docker-compose.yml )",
"Bash(frontend/mobile-app/lib/core/services/deposit_service.dart)",
"Bash(backend/services/authorization-service/prisma/schema.prisma )",
"Bash(backend/services/authorization-service/src/application/services/ )",
"Bash(backend/services/authorization-service/src/domain/ )",
"Bash(backend/services/authorization-service/src/infrastructure/persistence/repositories/ )",
"Bash(backend/services/planting-service/src/application/services/planting-application.service.ts )",
"Bash(backend/services/planting-service/src/domain/services/fund-allocation.service.ts )",
"Bash(backend/services/planting-service/src/infrastructure/external/ )",
"Bash(backend/services/planting-service/src/infrastructure/infrastructure.module.ts )",
"Bash(backend/services/planting-service/src/infrastructure/persistence/prisma/prisma.service.ts )",
"Bash(backend/services/planting-service/src/infrastructure/persistence/unit-of-work.ts )",
"Bash(backend/services/referral-service/src/modules/api.module.ts )",
"Bash(backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts )",
"Bash(backend/services/wallet-service/package.json )",
"Bash(backend/services/wallet-service/prisma/schema.prisma )",
"Bash(backend/services/wallet-service/src/)",
"Bash(git status:*)",
"Bash(git check-ignore:*)",
"Bash(docker inspect:*)",
"Bash(python -m json.tool:*)",
"Bash(docker logs:*)",
"Bash(curl:*)",
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" add frontend/mobile-app/lib/features/home/presentation/pages/home_shell_page.dart)",
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" commit -m \"$(cat <<''EOF''\nfix(mobile): change update check interval from 24h to 30-90s random\n\nAllows faster detection of urgent updates while preventing excessive\nAPI calls with random cooldown period.\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 -C \"c:\\Users\\dong\\Desktop\\rwadurian\" commit -m \"$(cat <<''EOF''\nfix(mobile): change update check interval to 90-300s random\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 -C \"c:/Users/dong/Desktop/rwadurian\" status --short backend/services/blockchain-service/)",
"Bash(git -C \"c:/Users/dong/Desktop/rwadurian\" log --oneline -5)",
"Bash(git -C \"c:/Users/dong/Desktop/rwadurian\" status backend/services/blockchain-service/)",
"Bash(git -C \"c:/Users/dong/Desktop/rwadurian\" log --oneline --all -- backend/services/blockchain-service/src/api/controllers/deposit-repair.controller.ts)",
"Bash(git -C \"c:/Users/dong/Desktop/rwadurian\" log --oneline origin/main -10)",
"Bash(npx tsc:*)",
"Bash(flutter analyze:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push)",
"Bash(cat:*)",
"Bash(git revert:*)",
"Bash(git stash:*)"
"Bash(git push:*)",
"Bash(echo:*)",
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" add frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart)",
"Bash(git -C \"c:\\Users\\dong\\Desktop\\rwadurian\" commit -m \"$(cat <<''EOF''\nfix(mobile): reduce direct referral list item spacing\n\n- Row gap: 8px → 4px\n- Vertical padding: 12px → 8px\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 -C \"c:\\Users\\dong\\Desktop\\rwadurian\" push)",
"Bash(git checkout:*)",
"Bash(find:*)",
"Bash(docker exec:*)",
"Bash(npx prisma migrate status)",
"Bash(DATABASE_URL=\"postgresql://rwa_user:your_secure_password_here@localhost:5432/rwa_referral\" npx prisma migrate status:*)",
"Bash(DATABASE_URL=\"postgresql://rwa_user:your_secure_password_here@localhost:5432/rwa_referral\" npx prisma migrate deploy:*)",
"Bash(DATABASE_URL=\"postgresql://rwa_user:your_secure_password_here@localhost:5432/rwa_referral\" npx prisma migrate resolve:*)"
],
"deny": [],
"ask": []

View File

@ -90,8 +90,11 @@ export class DirectReferralResponseDto {
@ApiProperty({ description: '推荐码' })
referralCode: string;
@ApiProperty({ description: '个人认种量' })
personalPlantingCount: number;
@ApiProperty({ description: '团队认种量' })
teamCount: number;
teamPlantingCount: number;
@ApiProperty({ description: '加入时间' })
joinedAt: Date;

View File

@ -10,7 +10,8 @@ export interface DirectReferralResult {
userId: string;
accountSequence: number; // 8位账户序列号显示用
referralCode: string;
teamCount: number;
personalPlantingCount: number; // 个人认种量
teamPlantingCount: number; // 团队认种量
joinedAt: Date;
}

View File

@ -154,20 +154,36 @@ export class ReferralService {
*/
async getDirectReferrals(query: GetDirectReferralsQuery): Promise<DirectReferralsResult> {
const relationships = await this.referralRepo.findDirectReferrals(query.userId);
const teamStats = await this.teamStatsRepo.findByUserId(query.userId);
const directStats = teamStats?.getDirectReferralStats() ?? new Map();
// 分页
const total = relationships.length;
const paginated = relationships.slice(query.offset, query.offset + query.limit);
const referrals = paginated.map((r) => ({
userId: r.userId.toString(),
accountSequence: r.accountSequence, // 8位账户序列号用于前端显示
referralCode: r.referralCode,
teamCount: directStats.get(r.userId) ?? 0,
joinedAt: r.createdAt,
}));
// 批量查询直推用户的认种统计
const directUserIds = paginated.map((r) => r.userId);
const directStatsMap = new Map<string, { personal: number; team: number }>();
if (directUserIds.length > 0) {
const directStats = await this.teamStatsRepo.findByUserIds(directUserIds);
for (const stats of directStats) {
directStatsMap.set(stats.userId.toString(), {
personal: stats.personalPlantingCount,
team: stats.teamPlantingCount,
});
}
}
const referrals = paginated.map((r) => {
const stats = directStatsMap.get(r.userId.toString());
return {
userId: r.userId.toString(),
accountSequence: r.accountSequence,
referralCode: r.referralCode,
personalPlantingCount: stats?.personal ?? 0,
teamPlantingCount: stats?.team ?? 0,
joinedAt: r.createdAt,
};
});
return {
referrals,

View File

@ -46,7 +46,19 @@ export class TeamStatisticsRepository implements ITeamStatisticsRepository {
// 更新直推统计明细
if (data.directReferrals && data.directReferrals.length > 0) {
// 批量查询直推用户的 accountSequence
const referralIds = data.directReferrals.map((dr) => dr.referralId);
const referralRelations = await tx.referralRelationship.findMany({
where: { userId: { in: referralIds } },
select: { userId: true, accountSequence: true },
});
const sequenceMap = new Map<bigint, number>();
for (const rel of referralRelations) {
sequenceMap.set(rel.userId, rel.accountSequence);
}
for (const dr of data.directReferrals) {
const accountSequence = sequenceMap.get(dr.referralId) ?? Number(dr.referralId);
await tx.directReferral.upsert({
where: {
uk_referrer_referral: {
@ -61,7 +73,7 @@ export class TeamStatisticsRepository implements ITeamStatisticsRepository {
create: {
referrerId: data.userId,
referralId: dr.referralId,
referralSequence: dr.referralId,
referralSequence: BigInt(accountSequence),
teamPlantingCount: dr.teamCount,
},
});

View File

@ -54,14 +54,16 @@ class DirectReferralInfo {
final String userId;
final int accountSequence; // 8
final String referralCode;
final int teamCount;
final int personalPlantingCount; //
final int teamPlantingCount; //
final DateTime joinedAt;
DirectReferralInfo({
required this.userId,
required this.accountSequence,
required this.referralCode,
required this.teamCount,
required this.personalPlantingCount,
required this.teamPlantingCount,
required this.joinedAt,
});
@ -70,7 +72,8 @@ class DirectReferralInfo {
userId: json['userId']?.toString() ?? '',
accountSequence: json['accountSequence'] ?? 0,
referralCode: json['referralCode'] ?? '',
teamCount: json['teamCount'] ?? 0,
personalPlantingCount: json['personalPlantingCount'] ?? 0,
teamPlantingCount: json['teamPlantingCount'] ?? 0,
joinedAt: json['joinedAt'] != null
? DateTime.parse(json['joinedAt'])
: DateTime.now(),

View File

@ -268,8 +268,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
//
_referrals = directReferrals.referrals.map((r) => <String, dynamic>{
'serial': r.accountSequence, // 使8
'personal': 0, // API暂未返回个人认种
'team': r.teamCount,
'personal': r.personalPlantingCount, //
'team': r.teamPlantingCount, //
}).toList();
});
}