diff --git a/backend/services/contribution-service/src/api/controllers/contribution.controller.ts b/backend/services/contribution-service/src/api/controllers/contribution.controller.ts index 0b8db6a8..93ced569 100644 --- a/backend/services/contribution-service/src/api/controllers/contribution.controller.ts +++ b/backend/services/contribution-service/src/api/controllers/contribution.controller.ts @@ -4,6 +4,7 @@ import { GetContributionAccountQuery } from '../../application/queries/get-contr import { GetContributionStatsQuery } from '../../application/queries/get-contribution-stats.query'; import { GetContributionRankingQuery } from '../../application/queries/get-contribution-ranking.query'; import { GetPlantingLedgerQuery, PlantingLedgerDto } from '../../application/queries/get-planting-ledger.query'; +import { GetTeamTreeQuery, DirectReferralsResponseDto, MyTeamInfoDto } from '../../application/queries/get-team-tree.query'; import { ContributionAccountResponse, ContributionRecordsResponse, @@ -22,6 +23,7 @@ export class ContributionController { private readonly getStatsQuery: GetContributionStatsQuery, private readonly getRankingQuery: GetContributionRankingQuery, private readonly getPlantingLedgerQuery: GetPlantingLedgerQuery, + private readonly getTeamTreeQuery: GetTeamTreeQuery, ) {} @Get('stats') @@ -117,4 +119,34 @@ export class ContributionController { pageSize ?? 20, ); } + + // ========== 团队树 API ========== + + @Get('accounts/:accountSequence/team') + @ApiOperation({ summary: '获取账户团队信息' }) + @ApiParam({ name: 'accountSequence', description: '账户序号' }) + @ApiResponse({ status: 200, description: '团队信息' }) + async getMyTeamInfo( + @Param('accountSequence') accountSequence: string, + ): Promise { + return this.getTeamTreeQuery.getMyTeamInfo(accountSequence); + } + + @Get('accounts/:accountSequence/team/direct-referrals') + @ApiOperation({ summary: '获取账户直推列表(用于伞下树懒加载)' }) + @ApiParam({ name: 'accountSequence', description: '账户序号' }) + @ApiQuery({ name: 'limit', required: false, type: Number, description: '每页数量' }) + @ApiQuery({ name: 'offset', required: false, type: Number, description: '偏移量' }) + @ApiResponse({ status: 200, description: '直推列表' }) + async getDirectReferrals( + @Param('accountSequence') accountSequence: string, + @Query('limit') limit?: number, + @Query('offset') offset?: number, + ): Promise { + return this.getTeamTreeQuery.getDirectReferrals( + accountSequence, + limit ?? 100, + offset ?? 0, + ); + } } diff --git a/backend/services/contribution-service/src/application/application.module.ts b/backend/services/contribution-service/src/application/application.module.ts index ebf7c783..654da44b 100644 --- a/backend/services/contribution-service/src/application/application.module.ts +++ b/backend/services/contribution-service/src/application/application.module.ts @@ -20,6 +20,7 @@ import { GetContributionAccountQuery } from './queries/get-contribution-account. import { GetContributionStatsQuery } from './queries/get-contribution-stats.query'; import { GetContributionRankingQuery } from './queries/get-contribution-ranking.query'; import { GetPlantingLedgerQuery } from './queries/get-planting-ledger.query'; +import { GetTeamTreeQuery } from './queries/get-team-tree.query'; // Schedulers import { ContributionScheduler } from './schedulers/contribution.scheduler'; @@ -48,6 +49,7 @@ import { ContributionScheduler } from './schedulers/contribution.scheduler'; GetContributionStatsQuery, GetContributionRankingQuery, GetPlantingLedgerQuery, + GetTeamTreeQuery, // Schedulers ContributionScheduler, @@ -60,6 +62,7 @@ import { ContributionScheduler } from './schedulers/contribution.scheduler'; GetContributionStatsQuery, GetContributionRankingQuery, GetPlantingLedgerQuery, + GetTeamTreeQuery, ], }) export class ApplicationModule {} diff --git a/backend/services/contribution-service/src/application/queries/get-team-tree.query.ts b/backend/services/contribution-service/src/application/queries/get-team-tree.query.ts new file mode 100644 index 00000000..a3b5d256 --- /dev/null +++ b/backend/services/contribution-service/src/application/queries/get-team-tree.query.ts @@ -0,0 +1,121 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { + ISyncedDataRepository, + SYNCED_DATA_REPOSITORY, +} from '../../domain/repositories/synced-data.repository.interface'; + +/** + * 团队成员信息 + */ +export interface TeamMemberDto { + accountSequence: string; + personalPlantingCount: number; + teamPlantingCount: number; + directReferralCount: number; +} + +/** + * 直推列表响应 + */ +export interface DirectReferralsResponseDto { + referrals: TeamMemberDto[]; + total: number; + hasMore: boolean; +} + +/** + * 我的团队信息响应 + */ +export interface MyTeamInfoDto { + accountSequence: string; + personalPlantingCount: number; + teamPlantingCount: number; + directReferralCount: number; +} + +@Injectable() +export class GetTeamTreeQuery { + constructor( + @Inject(SYNCED_DATA_REPOSITORY) + private readonly syncedDataRepository: ISyncedDataRepository, + ) {} + + /** + * 获取当前用户的团队信息 + */ + async getMyTeamInfo(accountSequence: string): Promise { + // 获取个人认种棵数 + const personalPlantingCount = await this.syncedDataRepository.getTotalTreesByAccountSequence(accountSequence); + + // 获取直推数量 + const directReferrals = await this.syncedDataRepository.findDirectReferrals(accountSequence); + + // 获取团队认种棵数(伞下各级总和) + const teamTreesByLevel = await this.syncedDataRepository.getTeamTreesByLevel(accountSequence, 15); + let teamPlantingCount = 0; + teamTreesByLevel.forEach((count) => { + teamPlantingCount += count; + }); + + return { + accountSequence, + personalPlantingCount, + teamPlantingCount, + directReferralCount: directReferrals.length, + }; + } + + /** + * 获取指定用户的直推列表 + */ + async getDirectReferrals( + accountSequence: string, + limit: number = 100, + offset: number = 0, + ): Promise { + // 获取所有直推 + const allDirectReferrals = await this.syncedDataRepository.findDirectReferrals(accountSequence); + + // 分页 + const total = allDirectReferrals.length; + const paginatedReferrals = allDirectReferrals.slice(offset, offset + limit); + + // 获取每个直推成员的详细信息 + const referrals: TeamMemberDto[] = await Promise.all( + paginatedReferrals.map(async (ref) => { + return this.getTeamMemberInfo(ref.accountSequence); + }), + ); + + return { + referrals, + total, + hasMore: offset + limit < total, + }; + } + + /** + * 获取团队成员信息 + */ + private async getTeamMemberInfo(accountSequence: string): Promise { + // 获取个人认种棵数 + const personalPlantingCount = await this.syncedDataRepository.getTotalTreesByAccountSequence(accountSequence); + + // 获取直推数量 + const directReferrals = await this.syncedDataRepository.findDirectReferrals(accountSequence); + + // 获取团队认种棵数 + const teamTreesByLevel = await this.syncedDataRepository.getTeamTreesByLevel(accountSequence, 15); + let teamPlantingCount = 0; + teamTreesByLevel.forEach((count) => { + teamPlantingCount += count; + }); + + return { + accountSequence, + personalPlantingCount, + teamPlantingCount, + directReferralCount: directReferrals.length, + }; + } +} diff --git a/frontend/mining-app/lib/core/network/api_endpoints.dart b/frontend/mining-app/lib/core/network/api_endpoints.dart index af233be5..aec0acac 100644 --- a/frontend/mining-app/lib/core/network/api_endpoints.dart +++ b/frontend/mining-app/lib/core/network/api_endpoints.dart @@ -79,9 +79,9 @@ class ApiEndpoints { // Mining Wallet Service 2.0 (Kong路由: /api/v2/mining-wallet) static const String sharePoolBalance = '/api/v2/mining-wallet/pool-accounts/share-pool-balance'; - // Referral Service 2.0 (Kong路由: /api/v2/referral) - static const String referralMe = '/api/v2/referral/me'; - static const String referralDirects = '/api/v2/referral/me/direct-referrals'; - static String userDirectReferrals(String accountSequence) => - '/api/v2/referral/user/$accountSequence/direct-referrals'; + // Team Tree (Contribution Service 2.0) + static String teamInfo(String accountSequence) => + '/api/v2/contribution/accounts/$accountSequence/team'; + static String teamDirectReferrals(String accountSequence) => + '/api/v2/contribution/accounts/$accountSequence/team/direct-referrals'; } diff --git a/frontend/mining-app/lib/data/datasources/remote/referral_remote_datasource.dart b/frontend/mining-app/lib/data/datasources/remote/referral_remote_datasource.dart index 553498ab..ca18f0dc 100644 --- a/frontend/mining-app/lib/data/datasources/remote/referral_remote_datasource.dart +++ b/frontend/mining-app/lib/data/datasources/remote/referral_remote_datasource.dart @@ -4,13 +4,9 @@ import '../../../core/network/api_endpoints.dart'; import '../../models/referral_model.dart'; abstract class ReferralRemoteDataSource { - /// 获取当前用户推荐信息 - Future getMyReferralInfo(); - - /// 获取当前用户直推列表 - Future getDirectReferrals({ - int limit = 50, - int offset = 0, + /// 获取指定用户的团队信息 + Future getTeamInfo({ + required String accountSequence, }); /// 获取指定用户的直推列表(用于伞下树懒加载) @@ -27,48 +23,22 @@ class ReferralRemoteDataSourceImpl implements ReferralRemoteDataSource { ReferralRemoteDataSourceImpl({required this.client}); @override - Future getMyReferralInfo() async { + Future getTeamInfo({ + required String accountSequence, + }) async { try { - debugPrint('获取推荐信息...'); - final response = await client.get(ApiEndpoints.referralMe); + debugPrint('获取团队信息: accountSequence=$accountSequence'); + final response = await client.get(ApiEndpoints.teamInfo(accountSequence)); if (response.statusCode == 200) { final data = response.data as Map; - debugPrint('推荐信息获取成功: directReferralCount=${data['directReferralCount']}'); + debugPrint('团队信息获取成功: directReferralCount=${data['directReferralCount']}'); return ReferralInfoResponse.fromJson(data); } - throw Exception('获取推荐信息失败'); + throw Exception('获取团队信息失败'); } catch (e) { - debugPrint('获取推荐信息失败: $e'); - rethrow; - } - } - - @override - Future getDirectReferrals({ - int limit = 50, - int offset = 0, - }) async { - try { - debugPrint('获取直推列表...'); - final response = await client.get( - ApiEndpoints.referralDirects, - queryParameters: { - 'limit': limit, - 'offset': offset, - }, - ); - - if (response.statusCode == 200) { - final data = response.data as Map; - debugPrint('直推列表获取成功: total=${data['total']}'); - return DirectReferralsResponse.fromJson(data); - } - - throw Exception('获取直推列表失败'); - } catch (e) { - debugPrint('获取直推列表失败: $e'); + debugPrint('获取团队信息失败: $e'); rethrow; } } @@ -82,7 +52,7 @@ class ReferralRemoteDataSourceImpl implements ReferralRemoteDataSource { try { debugPrint('获取用户直推列表: accountSequence=$accountSequence'); final response = await client.get( - ApiEndpoints.userDirectReferrals(accountSequence), + ApiEndpoints.teamDirectReferrals(accountSequence), queryParameters: { 'limit': limit, 'offset': offset, diff --git a/frontend/mining-app/lib/data/models/referral_model.dart b/frontend/mining-app/lib/data/models/referral_model.dart index 0cfdc58d..d822edd5 100644 --- a/frontend/mining-app/lib/data/models/referral_model.dart +++ b/frontend/mining-app/lib/data/models/referral_model.dart @@ -1,81 +1,47 @@ -/// 推荐信息响应 (来自 referral-service) +/// 团队信息响应 (来自 contribution-service) class ReferralInfoResponse { - final String userId; - final String referralCode; - final String? referrerId; - final int referralChainDepth; - final int directReferralCount; - final int totalTeamCount; + final String accountSequence; final int personalPlantingCount; final int teamPlantingCount; - final double leaderboardScore; - final int? leaderboardRank; - final DateTime createdAt; + final int directReferralCount; ReferralInfoResponse({ - required this.userId, - required this.referralCode, - this.referrerId, - required this.referralChainDepth, - required this.directReferralCount, - required this.totalTeamCount, + required this.accountSequence, required this.personalPlantingCount, required this.teamPlantingCount, - required this.leaderboardScore, - this.leaderboardRank, - required this.createdAt, + required this.directReferralCount, }); factory ReferralInfoResponse.fromJson(Map json) { return ReferralInfoResponse( - userId: json['userId']?.toString() ?? '', - referralCode: json['referralCode'] ?? '', - referrerId: json['referrerId']?.toString(), - referralChainDepth: json['referralChainDepth'] ?? 0, - directReferralCount: json['directReferralCount'] ?? 0, - totalTeamCount: json['totalTeamCount'] ?? 0, + accountSequence: json['accountSequence']?.toString() ?? '', personalPlantingCount: json['personalPlantingCount'] ?? 0, teamPlantingCount: json['teamPlantingCount'] ?? 0, - leaderboardScore: (json['leaderboardScore'] ?? 0).toDouble(), - leaderboardRank: json['leaderboardRank'], - createdAt: json['createdAt'] != null - ? DateTime.parse(json['createdAt']) - : DateTime.now(), + directReferralCount: json['directReferralCount'] ?? 0, ); } } /// 直推成员信息 class DirectReferralInfo { - final String userId; - final String accountSequence; // 账户序列号(新格式: D + YYMMDD + 5位序号),用于显示 - final String referralCode; - final int personalPlantingCount; // 个人认种量 - final int teamPlantingCount; // 团队认种量 - final int directReferralCount; // 直推人数(用于判断是否有下级) - final DateTime joinedAt; + final String accountSequence; + final int personalPlantingCount; + final int teamPlantingCount; + final int directReferralCount; DirectReferralInfo({ - required this.userId, required this.accountSequence, - required this.referralCode, required this.personalPlantingCount, required this.teamPlantingCount, required this.directReferralCount, - required this.joinedAt, }); factory DirectReferralInfo.fromJson(Map json) { return DirectReferralInfo( - userId: json['userId']?.toString() ?? '', accountSequence: json['accountSequence']?.toString() ?? '', - referralCode: json['referralCode'] ?? '', personalPlantingCount: json['personalPlantingCount'] ?? 0, teamPlantingCount: json['teamPlantingCount'] ?? 0, directReferralCount: json['directReferralCount'] ?? 0, - joinedAt: json['joinedAt'] != null - ? DateTime.parse(json['joinedAt']) - : DateTime.now(), ); } } diff --git a/frontend/mining-app/lib/presentation/pages/profile/team_page.dart b/frontend/mining-app/lib/presentation/pages/profile/team_page.dart index d939dcbb..aff2fd2a 100644 --- a/frontend/mining-app/lib/presentation/pages/profile/team_page.dart +++ b/frontend/mining-app/lib/presentation/pages/profile/team_page.dart @@ -44,14 +44,16 @@ class _TeamPageState extends ConsumerState { try { final dataSource = getIt(); - final referralInfo = await dataSource.getMyReferralInfo(); + final teamInfo = await dataSource.getTeamInfo( + accountSequence: user.accountSequence!, + ); setState(() { _rootNode = TeamTreeNode.createRoot( accountSequence: user.accountSequence!, - personalPlantingCount: referralInfo.personalPlantingCount, - teamPlantingCount: referralInfo.teamPlantingCount, - directReferralCount: referralInfo.directReferralCount, + personalPlantingCount: teamInfo.personalPlantingCount, + teamPlantingCount: teamInfo.teamPlantingCount, + directReferralCount: teamInfo.directReferralCount, ); _isLoading = false; }); diff --git a/frontend/mining-app/lib/presentation/widgets/team_tree_widget.dart b/frontend/mining-app/lib/presentation/widgets/team_tree_widget.dart index 2f6e481f..0b73755e 100644 --- a/frontend/mining-app/lib/presentation/widgets/team_tree_widget.dart +++ b/frontend/mining-app/lib/presentation/widgets/team_tree_widget.dart @@ -4,7 +4,6 @@ import '../../data/datasources/remote/referral_remote_datasource.dart'; /// 伞下树节点数据模型 class TeamTreeNode { - final String userId; final String accountSequence; final int personalPlantingCount; final int teamPlantingCount; @@ -14,7 +13,6 @@ class TeamTreeNode { bool isLoading; TeamTreeNode({ - required this.userId, required this.accountSequence, required this.personalPlantingCount, required this.teamPlantingCount, @@ -30,7 +28,6 @@ class TeamTreeNode { /// 从 DirectReferralInfo 创建 factory TeamTreeNode.fromDirectReferralInfo(DirectReferralInfo info) { return TeamTreeNode( - userId: info.userId, accountSequence: info.accountSequence, personalPlantingCount: info.personalPlantingCount, teamPlantingCount: info.teamPlantingCount, @@ -46,7 +43,6 @@ class TeamTreeNode { required int directReferralCount, }) { return TeamTreeNode( - userId: '', accountSequence: accountSequence, personalPlantingCount: personalPlantingCount, teamPlantingCount: teamPlantingCount,