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:
parent
033f94c0c2
commit
3fe6bdbbf0
|
|
@ -4,9 +4,10 @@ import { InfrastructureModule } from '../infrastructure/infrastructure.module';
|
||||||
import { ContributionController } from './controllers/contribution.controller';
|
import { ContributionController } from './controllers/contribution.controller';
|
||||||
import { SnapshotController } from './controllers/snapshot.controller';
|
import { SnapshotController } from './controllers/snapshot.controller';
|
||||||
import { HealthController } from './controllers/health.controller';
|
import { HealthController } from './controllers/health.controller';
|
||||||
|
import { AdminController } from './controllers/admin.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ApplicationModule, InfrastructureModule],
|
imports: [ApplicationModule, InfrastructureModule],
|
||||||
controllers: [ContributionController, SnapshotController, HealthController],
|
controllers: [ContributionController, SnapshotController, HealthController, AdminController],
|
||||||
})
|
})
|
||||||
export class ApiModule {}
|
export class ApiModule {}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,4 +44,34 @@ export class InitializationController {
|
||||||
async syncContributionAccounts(@Req() req: any) {
|
async syncContributionAccounts(@Req() req: any) {
|
||||||
return this.initService.syncAllContributionAccounts(req.admin.id);
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,4 +199,100 @@ export class InitializationService {
|
||||||
return { success: false, message: error.message };
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import { InfrastructureModule } from '../infrastructure/infrastructure.module';
|
||||||
import { MiningController } from './controllers/mining.controller';
|
import { MiningController } from './controllers/mining.controller';
|
||||||
import { PriceController } from './controllers/price.controller';
|
import { PriceController } from './controllers/price.controller';
|
||||||
import { HealthController } from './controllers/health.controller';
|
import { HealthController } from './controllers/health.controller';
|
||||||
|
import { AdminController } from './controllers/admin.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ApplicationModule, InfrastructureModule],
|
imports: [ApplicationModule, InfrastructureModule],
|
||||||
controllers: [MiningController, PriceController, HealthController],
|
controllers: [MiningController, PriceController, HealthController, AdminController],
|
||||||
})
|
})
|
||||||
export class ApiModule {}
|
export class ApiModule {}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,10 @@ import { InfrastructureModule } from '../infrastructure/infrastructure.module';
|
||||||
import { TradingController } from './controllers/trading.controller';
|
import { TradingController } from './controllers/trading.controller';
|
||||||
import { TransferController } from './controllers/transfer.controller';
|
import { TransferController } from './controllers/transfer.controller';
|
||||||
import { HealthController } from './controllers/health.controller';
|
import { HealthController } from './controllers/health.controller';
|
||||||
|
import { AdminController } from './controllers/admin.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ApplicationModule, InfrastructureModule],
|
imports: [ApplicationModule, InfrastructureModule],
|
||||||
controllers: [TradingController, TransferController, HealthController],
|
controllers: [TradingController, TransferController, HealthController, AdminController],
|
||||||
})
|
})
|
||||||
export class ApiModule {}
|
export class ApiModule {}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue