feat(contribution/mining-app): add team tree API using contribution-service 2.0
Add team info and direct referrals endpoints to contribution-service, using SyncedReferral data synced via CDC. Update mining-app to use the new v2 contribution API instead of legacy referral-service. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3d6b6ae405
commit
4ec6c9f48b
|
|
@ -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<MyTeamInfoDto> {
|
||||
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<DirectReferralsResponseDto> {
|
||||
return this.getTeamTreeQuery.getDirectReferrals(
|
||||
accountSequence,
|
||||
limit ?? 100,
|
||||
offset ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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<MyTeamInfoDto> {
|
||||
// 获取个人认种棵数
|
||||
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<DirectReferralsResponseDto> {
|
||||
// 获取所有直推
|
||||
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<TeamMemberDto> {
|
||||
// 获取个人认种棵数
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,9 @@ import '../../../core/network/api_endpoints.dart';
|
|||
import '../../models/referral_model.dart';
|
||||
|
||||
abstract class ReferralRemoteDataSource {
|
||||
/// 获取当前用户推荐信息
|
||||
Future<ReferralInfoResponse> getMyReferralInfo();
|
||||
|
||||
/// 获取当前用户直推列表
|
||||
Future<DirectReferralsResponse> getDirectReferrals({
|
||||
int limit = 50,
|
||||
int offset = 0,
|
||||
/// 获取指定用户的团队信息
|
||||
Future<ReferralInfoResponse> getTeamInfo({
|
||||
required String accountSequence,
|
||||
});
|
||||
|
||||
/// 获取指定用户的直推列表(用于伞下树懒加载)
|
||||
|
|
@ -27,48 +23,22 @@ class ReferralRemoteDataSourceImpl implements ReferralRemoteDataSource {
|
|||
ReferralRemoteDataSourceImpl({required this.client});
|
||||
|
||||
@override
|
||||
Future<ReferralInfoResponse> getMyReferralInfo() async {
|
||||
Future<ReferralInfoResponse> 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<String, dynamic>;
|
||||
debugPrint('推荐信息获取成功: directReferralCount=${data['directReferralCount']}');
|
||||
debugPrint('团队信息获取成功: directReferralCount=${data['directReferralCount']}');
|
||||
return ReferralInfoResponse.fromJson(data);
|
||||
}
|
||||
|
||||
throw Exception('获取推荐信息失败');
|
||||
throw Exception('获取团队信息失败');
|
||||
} catch (e) {
|
||||
debugPrint('获取推荐信息失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DirectReferralsResponse> 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<String, dynamic>;
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> 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<String, dynamic> 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,14 +44,16 @@ class _TeamPageState extends ConsumerState<TeamPage> {
|
|||
|
||||
try {
|
||||
final dataSource = getIt<ReferralRemoteDataSource>();
|
||||
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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue