feat(sync): 添加批量同步 API 端点

- 为 contribution-service、mining-service、trading-service 添加 AdminController
- 提供 /admin/accounts/sync 端点用于批量获取账户数据
- 在 mining-admin-service 添加同步 mining/trading 账户的初始化端点
- 添加 sync-all 端点支持一键同步所有数据

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-11 21:27:35 -08:00
parent 033f94c0c2
commit 3fe6bdbbf0
8 changed files with 258 additions and 3 deletions

View File

@ -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 {}

View File

@ -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,
};
}
}

View File

@ -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,
};
}
}

View File

@ -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<string>('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<string>('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 };
}
}
}

View File

@ -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 {}

View File

@ -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,
};
}
}

View File

@ -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 {}

View File

@ -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,
};
}
}