refactor(mining-admin): remove initialization feature

System initialization is now handled by seed scripts and CDC sync,
so the manual initialization UI is no longer needed.

Removed:
- Frontend: initialization page and sidebar menu item
- Backend: InitializationController and InitializationService

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-14 02:22:23 -08:00
parent 36c3ada6a6
commit d6064294d7
6 changed files with 0 additions and 556 deletions

View File

@ -3,7 +3,6 @@ import { ApplicationModule } from '../application/application.module';
import { AuthController } from './controllers/auth.controller';
import { DashboardController } from './controllers/dashboard.controller';
import { ConfigController } from './controllers/config.controller';
import { InitializationController } from './controllers/initialization.controller';
import { AuditController } from './controllers/audit.controller';
import { HealthController } from './controllers/health.controller';
import { UsersController } from './controllers/users.controller';
@ -16,7 +15,6 @@ import { ReportsController } from './controllers/reports.controller';
AuthController,
DashboardController,
ConfigController,
InitializationController,
AuditController,
HealthController,
UsersController,

View File

@ -1,77 +0,0 @@
import { Controller, Post, Body, Req } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { InitializationService } from '../../application/services/initialization.service';
class InitMiningConfigDto {
totalShares: string;
distributionPool: string;
halvingPeriodYears: number;
burnTarget: string;
}
@ApiTags('Initialization')
@ApiBearerAuth()
@Controller('initialization')
export class InitializationController {
constructor(private readonly initService: InitializationService) {}
@Post('mining-config')
@ApiOperation({ summary: '初始化挖矿配置' })
async initMiningConfig(@Body() dto: InitMiningConfigDto, @Req() req: any) {
return this.initService.initializeMiningConfig(req.admin.id, dto);
}
@Post('system-accounts')
@ApiOperation({ summary: '初始化系统账户' })
async initSystemAccounts(@Req() req: any) {
return this.initService.initializeSystemAccounts(req.admin.id);
}
@Post('activate-mining')
@ApiOperation({ summary: '激活挖矿' })
async activateMining(@Req() req: any) {
return this.initService.activateMining(req.admin.id);
}
@Post('sync-users')
@ApiOperation({ summary: '同步所有用户数据从auth-service初始同步' })
async syncUsers(@Req() req: any) {
return this.initService.syncAllUsers(req.admin.id);
}
@Post('sync-contribution-accounts')
@ApiOperation({ summary: '同步所有算力账户从contribution-service初始同步' })
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

@ -2,7 +2,6 @@ import { Module, OnModuleInit } from '@nestjs/common';
import { InfrastructureModule } from '../infrastructure/infrastructure.module';
import { AuthService } from './services/auth.service';
import { ConfigManagementService } from './services/config.service';
import { InitializationService } from './services/initialization.service';
import { DashboardService } from './services/dashboard.service';
import { UsersService } from './services/users.service';
import { SystemAccountsService } from './services/system-accounts.service';
@ -13,7 +12,6 @@ import { DailyReportService } from './services/daily-report.service';
providers: [
AuthService,
ConfigManagementService,
InitializationService,
DashboardService,
UsersService,
SystemAccountsService,
@ -22,7 +20,6 @@ import { DailyReportService } from './services/daily-report.service';
exports: [
AuthService,
ConfigManagementService,
InitializationService,
DashboardService,
UsersService,
SystemAccountsService,

View File

@ -1,304 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
@Injectable()
export class InitializationService {
private readonly logger = new Logger(InitializationService.name);
constructor(
private readonly prisma: PrismaService,
private readonly configService: ConfigService,
) {}
async initializeMiningConfig(
adminId: string,
config: {
totalShares: string;
distributionPool: string;
halvingPeriodYears: number;
burnTarget: string;
},
): Promise<{ success: boolean; message: string }> {
const record = await this.prisma.initializationRecord.create({
data: { type: 'MINING_CONFIG', status: 'PENDING', config, executedBy: adminId },
});
try {
const miningServiceUrl = this.configService.get<string>('MINING_SERVICE_URL', 'http://localhost:3021');
const response = await fetch(`${miningServiceUrl}/api/v1/admin/initialize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
if (!response.ok) {
throw new Error('Failed to initialize mining config');
}
await this.prisma.initializationRecord.update({
where: { id: record.id },
data: { status: 'COMPLETED', executedAt: new Date() },
});
await this.prisma.auditLog.create({
data: { adminId, action: 'INIT', resource: 'MINING', resourceId: record.id, newValue: config },
});
return { success: true, message: 'Mining config initialized successfully' };
} catch (error: any) {
await this.prisma.initializationRecord.update({
where: { id: record.id },
data: { status: 'FAILED', errorMessage: error.message },
});
return { success: false, message: error.message };
}
}
async initializeSystemAccounts(adminId: string): Promise<{ success: boolean; message: string }> {
const accounts = [
{ accountType: 'OPERATION', name: '运营账户', description: '12% 运营收入' },
{ accountType: 'PROVINCE', name: '省公司账户', description: '1% 省公司收入' },
{ accountType: 'CITY', name: '市公司账户', description: '2% 市公司收入' },
];
for (const account of accounts) {
await this.prisma.systemAccount.upsert({
where: { accountType: account.accountType },
create: account,
update: { name: account.name, description: account.description },
});
}
await this.prisma.auditLog.create({
data: { adminId, action: 'INIT', resource: 'SYSTEM_ACCOUNT', newValue: accounts },
});
return { success: true, message: 'System accounts initialized successfully' };
}
async activateMining(adminId: string): Promise<{ success: boolean; message: string }> {
try {
const miningServiceUrl = this.configService.get<string>('MINING_SERVICE_URL', 'http://localhost:3021');
const response = await fetch(`${miningServiceUrl}/api/v1/admin/activate`, { method: 'POST' });
if (!response.ok) {
throw new Error('Failed to activate mining');
}
await this.prisma.auditLog.create({
data: { adminId, action: 'INIT', resource: 'MINING', newValue: { action: 'ACTIVATE' } },
});
return { success: true, message: 'Mining activated successfully' };
} catch (error: any) {
return { success: false, message: error.message };
}
}
async syncAllUsers(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> {
try {
const authServiceUrl = this.configService.get<string>('AUTH_SERVICE_URL', 'http://localhost:3024');
const response = await fetch(`${authServiceUrl}/api/v2/admin/users/sync`);
if (!response.ok) {
throw new Error(`Failed to fetch users: ${response.statusText}`);
}
const responseData = await response.json();
const users = responseData.data?.users || responseData.users || [];
let syncedCount = 0;
for (const user of users) {
try {
await this.prisma.syncedUser.upsert({
where: { accountSequence: user.accountSequence },
create: {
originalUserId: user.id || user.accountSequence,
accountSequence: user.accountSequence,
phone: user.phone,
status: user.status || 'ACTIVE',
kycStatus: user.kycStatus || 'PENDING',
realName: user.realName || null,
isLegacyUser: user.isLegacyUser || false,
createdAt: new Date(user.createdAt),
},
update: {
phone: user.phone,
status: user.status || 'ACTIVE',
kycStatus: user.kycStatus || 'PENDING',
realName: user.realName || null,
},
});
syncedCount++;
} catch (err) {
this.logger.warn(`Failed to sync user ${user.accountSequence}: ${err}`);
}
}
await this.prisma.auditLog.create({
data: { adminId, action: 'SYNC', resource: 'USER', newValue: { syncedCount } },
});
return { success: true, message: `Synced ${syncedCount} users`, syncedCount };
} catch (error: any) {
return { success: false, message: error.message };
}
}
async syncAllContributionAccounts(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> {
try {
const contributionServiceUrl = this.configService.get<string>('CONTRIBUTION_SERVICE_URL', 'http://localhost:3020');
const response = await fetch(`${contributionServiceUrl}/api/v2/admin/accounts/sync`);
if (!response.ok) {
throw new Error(`Failed to fetch accounts: ${response.statusText}`);
}
const responseData = await response.json();
const accounts = responseData.data?.accounts || responseData.accounts || [];
let syncedCount = 0;
for (const account of accounts) {
try {
await this.prisma.syncedContributionAccount.upsert({
where: { accountSequence: account.accountSequence },
create: {
accountSequence: account.accountSequence,
personalContribution: account.personalContribution || 0,
teamLevelContribution: account.teamLevelContribution || 0,
teamBonusContribution: account.teamBonusContribution || 0,
totalContribution: account.totalContribution || 0,
effectiveContribution: account.effectiveContribution || 0,
hasAdopted: account.hasAdopted || false,
directReferralCount: account.directReferralAdoptedCount || 0,
unlockedLevelDepth: account.unlockedLevelDepth || 0,
unlockedBonusTiers: account.unlockedBonusTiers || 0,
},
update: {
personalContribution: account.personalContribution,
teamLevelContribution: account.teamLevelContribution,
teamBonusContribution: account.teamBonusContribution,
totalContribution: account.totalContribution,
effectiveContribution: account.effectiveContribution,
hasAdopted: account.hasAdopted,
directReferralCount: account.directReferralAdoptedCount,
unlockedLevelDepth: account.unlockedLevelDepth,
unlockedBonusTiers: account.unlockedBonusTiers,
},
});
syncedCount++;
} catch (err) {
this.logger.warn(`Failed to sync account ${account.accountSequence}: ${err}`);
}
}
await this.prisma.auditLog.create({
data: { adminId, action: 'SYNC', resource: 'CONTRIBUTION_ACCOUNT', newValue: { syncedCount } },
});
return { success: true, message: `Synced ${syncedCount} accounts`, syncedCount };
} catch (error: any) {
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 responseData = await response.json();
const accounts = responseData.data?.accounts || responseData.accounts || [];
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 responseData = await response.json();
const accounts = responseData.data?.accounts || responseData.accounts || [];
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

@ -1,168 +0,0 @@
'use client';
import { useState } from 'react';
import { PageHeader } from '@/components/layout/page-header';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '@/lib/api/client';
import { formatDateTime } from '@/lib/utils/date';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Skeleton } from '@/components/ui/skeleton';
import { useToast } from '@/lib/hooks/use-toast';
import { Play, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
interface InitializationStatus {
initialized: boolean;
initializedAt: string | null;
initializedBy: string | null;
distributionPoolBalance: string;
blackHoleBalance: string;
circulationPoolBalance: string;
}
export default function InitializationPage() {
const queryClient = useQueryClient();
const { toast } = useToast();
const [showConfirm, setShowConfirm] = useState(false);
const { data: status, isLoading } = useQuery({
queryKey: ['initialization', 'status'],
queryFn: async () => {
const response = await apiClient.get('/initialization/status');
return response.data.data as InitializationStatus;
},
});
const initializeMutation = useMutation({
mutationFn: async () => {
const response = await apiClient.post('/initialization/initialize');
return response.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['initialization', 'status'] });
toast({ title: '初始化成功', variant: 'success' as any });
setShowConfirm(false);
},
onError: () => {
toast({ title: '初始化失败', variant: 'destructive' });
},
});
const handleInitialize = () => {
initializeMutation.mutate();
};
return (
<div className="space-y-6">
<PageHeader title="系统初始化" description="初始化挖矿系统的基础数据" />
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{isLoading ? (
<Skeleton className="h-24 w-full" />
) : (
<div className="space-y-4">
<div className="flex items-center gap-4">
{status?.initialized ? (
<>
<CheckCircle className="h-8 w-8 text-green-500" />
<div>
<p className="font-medium text-green-600"></p>
<p className="text-sm text-muted-foreground">
: {formatDateTime(status.initializedAt)}
</p>
<p className="text-sm text-muted-foreground">: {status.initializedBy}</p>
</div>
</>
) : (
<>
<AlertCircle className="h-8 w-8 text-yellow-500" />
<div>
<p className="font-medium text-yellow-600"></p>
<p className="text-sm text-muted-foreground"></p>
</div>
</>
)}
</div>
{!status?.initialized && (
<Button onClick={() => setShowConfirm(true)} className="mt-4">
<Play className="h-4 w-4 mr-2" />
</Button>
)}
</div>
)}
</CardContent>
</Card>
{status?.initialized && (
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-3 gap-6">
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-xl font-bold font-mono">{status.distributionPoolBalance}</p>
</div>
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-xl font-bold font-mono">{status.blackHoleBalance}</p>
</div>
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-xl font-bold font-mono">{status.circulationPoolBalance}</p>
</div>
</div>
</CardContent>
</Card>
)}
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="prose prose-sm max-w-none">
<p>:</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground">
<li> 2 亿</li>
<li> 0</li>
<li> 0</li>
<li> (12% )</li>
<li> (1% )</li>
<li> (2% )</li>
<li></li>
</ol>
<p className="text-yellow-600 mt-4">注意: 初始化操作只能执行一次</p>
</CardContent>
</Card>
<Dialog open={showConfirm} onOpenChange={setShowConfirm}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setShowConfirm(false)}>
</Button>
<Button onClick={handleInitialize} disabled={initializeMutation.isPending}>
{initializeMutation.isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View File

@ -11,7 +11,6 @@ import {
Building2,
FileBarChart,
ClipboardList,
Play,
ChevronLeft,
ChevronRight,
} from 'lucide-react';
@ -24,7 +23,6 @@ const menuItems = [
{ name: '系统账户', href: '/system-accounts', icon: Building2 },
{ name: '报表统计', href: '/reports', icon: FileBarChart },
{ name: '审计日志', href: '/audit-logs', icon: ClipboardList },
{ name: '初始化', href: '/initialization', icon: Play },
];
export function Sidebar() {