diff --git a/backend/services/contribution-service/src/api/api.module.ts b/backend/services/contribution-service/src/api/api.module.ts index 71f3798e..269b972e 100644 --- a/backend/services/contribution-service/src/api/api.module.ts +++ b/backend/services/contribution-service/src/api/api.module.ts @@ -4,9 +4,10 @@ import { InfrastructureModule } from '../infrastructure/infrastructure.module'; import { ContributionController } from './controllers/contribution.controller'; import { SnapshotController } from './controllers/snapshot.controller'; import { HealthController } from './controllers/health.controller'; +import { AdminController } from './controllers/admin.controller'; @Module({ imports: [ApplicationModule, InfrastructureModule], - controllers: [ContributionController, SnapshotController, HealthController], + controllers: [ContributionController, SnapshotController, HealthController, AdminController], }) export class ApiModule {} diff --git a/backend/services/contribution-service/src/api/controllers/admin.controller.ts b/backend/services/contribution-service/src/api/controllers/admin.controller.ts new file mode 100644 index 00000000..1ce2b958 --- /dev/null +++ b/backend/services/contribution-service/src/api/controllers/admin.controller.ts @@ -0,0 +1,46 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service'; + +@ApiTags('Admin') +@Controller('admin') +export class AdminController { + constructor(private readonly prisma: PrismaService) {} + + @Get('accounts/sync') + @ApiOperation({ summary: '获取所有贡献值账户用于同步' }) + async getAllAccountsForSync() { + const accounts = await this.prisma.contributionAccount.findMany({ + select: { + accountSequence: true, + personalContribution: true, + totalLevelPending: true, + totalBonusPending: true, + totalUnlocked: true, + effectiveContribution: true, + hasAdopted: true, + directReferralAdoptedCount: true, + unlockedLevelDepth: true, + createdAt: true, + updatedAt: true, + }, + }); + + return { + accounts: accounts.map((acc) => ({ + accountSequence: acc.accountSequence, + personalContribution: acc.personalContribution.toString(), + teamLevelContribution: acc.totalLevelPending.toString(), + teamBonusContribution: acc.totalBonusPending.toString(), + totalContribution: acc.effectiveContribution.toString(), + effectiveContribution: acc.effectiveContribution.toString(), + hasAdopted: acc.hasAdopted, + directReferralAdoptedCount: acc.directReferralAdoptedCount, + unlockedLevelDepth: acc.unlockedLevelDepth, + createdAt: acc.createdAt, + updatedAt: acc.updatedAt, + })), + total: accounts.length, + }; + } +} diff --git a/backend/services/mining-admin-service/src/api/controllers/initialization.controller.ts b/backend/services/mining-admin-service/src/api/controllers/initialization.controller.ts index db1e33ed..fd0e524d 100644 --- a/backend/services/mining-admin-service/src/api/controllers/initialization.controller.ts +++ b/backend/services/mining-admin-service/src/api/controllers/initialization.controller.ts @@ -44,4 +44,34 @@ export class InitializationController { async syncContributionAccounts(@Req() req: any) { return this.initService.syncAllContributionAccounts(req.admin.id); } + + @Post('sync-mining-accounts') + @ApiOperation({ summary: '同步所有挖矿账户(从mining-service初始同步)' }) + async syncMiningAccounts(@Req() req: any) { + return this.initService.syncAllMiningAccounts(req.admin.id); + } + + @Post('sync-trading-accounts') + @ApiOperation({ summary: '同步所有交易账户(从trading-service初始同步)' }) + async syncTradingAccounts(@Req() req: any) { + return this.initService.syncAllTradingAccounts(req.admin.id); + } + + @Post('sync-all') + @ApiOperation({ summary: '执行完整的数据同步(用户+算力+挖矿+交易)' }) + async syncAll(@Req() req: any) { + const adminId = req.admin.id; + const results = { + users: await this.initService.syncAllUsers(adminId), + contribution: await this.initService.syncAllContributionAccounts(adminId), + mining: await this.initService.syncAllMiningAccounts(adminId), + trading: await this.initService.syncAllTradingAccounts(adminId), + }; + + return { + success: true, + message: '全部同步完成', + details: results, + }; + } } diff --git a/backend/services/mining-admin-service/src/application/services/initialization.service.ts b/backend/services/mining-admin-service/src/application/services/initialization.service.ts index 5dff6095..fb80013f 100644 --- a/backend/services/mining-admin-service/src/application/services/initialization.service.ts +++ b/backend/services/mining-admin-service/src/application/services/initialization.service.ts @@ -199,4 +199,100 @@ export class InitializationService { return { success: false, message: error.message }; } } + + async syncAllMiningAccounts(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> { + try { + const miningServiceUrl = this.configService.get('MINING_SERVICE_URL', 'http://localhost:3021'); + const response = await fetch(`${miningServiceUrl}/api/v1/admin/accounts/sync`); + + if (!response.ok) { + throw new Error(`Failed to fetch accounts: ${response.statusText}`); + } + + const { accounts } = await response.json(); + let syncedCount = 0; + + for (const account of accounts) { + try { + await this.prisma.syncedMiningAccount.upsert({ + where: { accountSequence: account.accountSequence }, + create: { + accountSequence: account.accountSequence, + totalMined: account.totalMined || 0, + availableBalance: account.availableBalance || 0, + frozenBalance: account.frozenBalance || 0, + totalContribution: account.totalContribution || 0, + }, + update: { + totalMined: account.totalMined, + availableBalance: account.availableBalance, + frozenBalance: account.frozenBalance, + totalContribution: account.totalContribution, + }, + }); + syncedCount++; + } catch (err) { + this.logger.warn(`Failed to sync mining account ${account.accountSequence}: ${err}`); + } + } + + await this.prisma.auditLog.create({ + data: { adminId, action: 'SYNC', resource: 'MINING_ACCOUNT', newValue: { syncedCount } }, + }); + + return { success: true, message: `Synced ${syncedCount} mining accounts`, syncedCount }; + } catch (error: any) { + return { success: false, message: error.message }; + } + } + + async syncAllTradingAccounts(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> { + try { + const tradingServiceUrl = this.configService.get('TRADING_SERVICE_URL', 'http://localhost:3022'); + const response = await fetch(`${tradingServiceUrl}/api/v1/admin/accounts/sync`); + + if (!response.ok) { + throw new Error(`Failed to fetch accounts: ${response.statusText}`); + } + + const { accounts } = await response.json(); + let syncedCount = 0; + + for (const account of accounts) { + try { + await this.prisma.syncedTradingAccount.upsert({ + where: { accountSequence: account.accountSequence }, + create: { + accountSequence: account.accountSequence, + shareBalance: account.shareBalance || 0, + cashBalance: account.cashBalance || 0, + frozenShares: account.frozenShares || 0, + frozenCash: account.frozenCash || 0, + totalBought: account.totalBought || 0, + totalSold: account.totalSold || 0, + }, + update: { + shareBalance: account.shareBalance, + cashBalance: account.cashBalance, + frozenShares: account.frozenShares, + frozenCash: account.frozenCash, + totalBought: account.totalBought, + totalSold: account.totalSold, + }, + }); + syncedCount++; + } catch (err) { + this.logger.warn(`Failed to sync trading account ${account.accountSequence}: ${err}`); + } + } + + await this.prisma.auditLog.create({ + data: { adminId, action: 'SYNC', resource: 'TRADING_ACCOUNT', newValue: { syncedCount } }, + }); + + return { success: true, message: `Synced ${syncedCount} trading accounts`, syncedCount }; + } catch (error: any) { + return { success: false, message: error.message }; + } + } } diff --git a/backend/services/mining-service/src/api/api.module.ts b/backend/services/mining-service/src/api/api.module.ts index 5df79702..827ee831 100644 --- a/backend/services/mining-service/src/api/api.module.ts +++ b/backend/services/mining-service/src/api/api.module.ts @@ -4,9 +4,10 @@ import { InfrastructureModule } from '../infrastructure/infrastructure.module'; import { MiningController } from './controllers/mining.controller'; import { PriceController } from './controllers/price.controller'; import { HealthController } from './controllers/health.controller'; +import { AdminController } from './controllers/admin.controller'; @Module({ imports: [ApplicationModule, InfrastructureModule], - controllers: [MiningController, PriceController, HealthController], + controllers: [MiningController, PriceController, HealthController, AdminController], }) export class ApiModule {} diff --git a/backend/services/mining-service/src/api/controllers/admin.controller.ts b/backend/services/mining-service/src/api/controllers/admin.controller.ts new file mode 100644 index 00000000..4b8daf61 --- /dev/null +++ b/backend/services/mining-service/src/api/controllers/admin.controller.ts @@ -0,0 +1,38 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service'; + +@ApiTags('Admin') +@Controller('admin') +export class AdminController { + constructor(private readonly prisma: PrismaService) {} + + @Get('accounts/sync') + @ApiOperation({ summary: '获取所有挖矿账户用于同步' }) + async getAllAccountsForSync() { + const accounts = await this.prisma.miningAccount.findMany({ + select: { + accountSequence: true, + totalMined: true, + availableBalance: true, + frozenBalance: true, + totalContribution: true, + createdAt: true, + updatedAt: true, + }, + }); + + return { + accounts: accounts.map((acc) => ({ + accountSequence: acc.accountSequence, + totalMined: acc.totalMined.toString(), + availableBalance: acc.availableBalance.toString(), + frozenBalance: acc.frozenBalance.toString(), + totalContribution: acc.totalContribution.toString(), + createdAt: acc.createdAt, + updatedAt: acc.updatedAt, + })), + total: accounts.length, + }; + } +} diff --git a/backend/services/trading-service/src/api/api.module.ts b/backend/services/trading-service/src/api/api.module.ts index 21e6c9b8..22e4d234 100644 --- a/backend/services/trading-service/src/api/api.module.ts +++ b/backend/services/trading-service/src/api/api.module.ts @@ -4,9 +4,10 @@ import { InfrastructureModule } from '../infrastructure/infrastructure.module'; import { TradingController } from './controllers/trading.controller'; import { TransferController } from './controllers/transfer.controller'; import { HealthController } from './controllers/health.controller'; +import { AdminController } from './controllers/admin.controller'; @Module({ imports: [ApplicationModule, InfrastructureModule], - controllers: [TradingController, TransferController, HealthController], + controllers: [TradingController, TransferController, HealthController, AdminController], }) export class ApiModule {} diff --git a/backend/services/trading-service/src/api/controllers/admin.controller.ts b/backend/services/trading-service/src/api/controllers/admin.controller.ts new file mode 100644 index 00000000..56450b1f --- /dev/null +++ b/backend/services/trading-service/src/api/controllers/admin.controller.ts @@ -0,0 +1,42 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service'; + +@ApiTags('Admin') +@Controller('admin') +export class AdminController { + constructor(private readonly prisma: PrismaService) {} + + @Get('accounts/sync') + @ApiOperation({ summary: '获取所有交易账户用于同步' }) + async getAllAccountsForSync() { + const accounts = await this.prisma.tradingAccount.findMany({ + select: { + accountSequence: true, + shareBalance: true, + cashBalance: true, + frozenShares: true, + frozenCash: true, + totalBought: true, + totalSold: true, + createdAt: true, + updatedAt: true, + }, + }); + + return { + accounts: accounts.map((acc) => ({ + accountSequence: acc.accountSequence, + shareBalance: acc.shareBalance.toString(), + cashBalance: acc.cashBalance.toString(), + frozenShares: acc.frozenShares.toString(), + frozenCash: acc.frozenCash.toString(), + totalBought: acc.totalBought.toString(), + totalSold: acc.totalSold.toString(), + createdAt: acc.createdAt, + updatedAt: acc.updatedAt, + })), + total: accounts.length, + }; + } +}