From 838d5c1d3b0e4ba699d9507994f525544b63cee7 Mon Sep 17 00:00:00 2001 From: hailin Date: Sun, 4 Jan 2026 23:10:20 -0800 Subject: [PATCH] feat(reporting): fix system account report to use wallet-service data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The system account balances were showing 0 because data was being fetched from authorization-service.system_accounts table instead of the actual wallet-service.wallet_accounts table where funds are stored. Changes: - wallet-service: Add getAllSystemAccounts() method to query all system accounts (fixed S*, province 9*, city 8*) with actual balances - wallet-service: Add /wallets/statistics/all-system-accounts API endpoint - reporting-service: Update SystemAccountReportApplicationService to fetch data from wallet-service instead of authorization-service - reporting-service: Fix default service URLs to use correct container names and ports (rwa-wallet-service:3001, rwa-reward-service:3005) - docker-compose: Add WALLET_SERVICE_URL and REWARD_SERVICE_URL env vars for reporting-service 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/services/docker-compose.yml | 3 + ...stem-account-report-application.service.ts | 168 +++++++++--------- .../reward-service/reward-service.client.ts | 2 +- .../wallet-service/wallet-service.client.ts | 56 +++++- .../controllers/internal-wallet.controller.ts | 11 ++ .../services/wallet-application.service.ts | 41 ++++- 6 files changed, 196 insertions(+), 85 deletions(-) diff --git a/backend/services/docker-compose.yml b/backend/services/docker-compose.yml index b0ba6878..28d3e136 100644 --- a/backend/services/docker-compose.yml +++ b/backend/services/docker-compose.yml @@ -501,6 +501,9 @@ services: - KAFKA_BROKERS=kafka:29092 - KAFKA_CLIENT_ID=reporting-service - KAFKA_GROUP_ID=reporting-service-group + # [2026-01-05] 新增:系统账户报表统计所需的外部服务 URL + - WALLET_SERVICE_URL=http://rwa-wallet-service:3001 + - REWARD_SERVICE_URL=http://rwa-reward-service:3005 depends_on: postgres: condition: service_healthy diff --git a/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts b/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts index 54c01dc6..c5ced78e 100644 --- a/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts +++ b/backend/services/reporting-service/src/application/services/system-account-report-application.service.ts @@ -1,12 +1,37 @@ /** * 系统账户报表应用服务 * [2026-01-04] 新增:用于聚合各服务的系统账户报表数据 + * [2026-01-05] 修改:改为从 wallet-service 获取系统账户数据(实际余额所在位置) * 回滚方式:删除此文件,并从 application.module.ts 中移除注册 */ import { Injectable, Logger } from '@nestjs/common'; -import { WalletServiceClient, OfflineSettlementSummary, SystemAccountBalance } from '../../infrastructure/external/wallet-service/wallet-service.client'; +import { WalletServiceClient, OfflineSettlementSummary, AllSystemAccountsResponse } from '../../infrastructure/external/wallet-service/wallet-service.client'; import { RewardServiceClient, ExpiredRewardsSummary } from '../../infrastructure/external/reward-service/reward-service.client'; -import { AuthorizationServiceClient, SystemAccountDTO, RegionAccountsSummary, AllAccountsSummary } from '../../infrastructure/external/authorization-service/authorization-service.client'; + +/** + * 固定系统账户信息 + */ +export interface FixedAccountInfo { + accountSequence: string; + accountType: string; + usdtBalance: string; + totalReceived: string; + totalTransferred: string; + status: string; + createdAt: string; +} + +/** + * 区域账户信息 + */ +export interface RegionAccountInfo { + accountSequence: string; + regionCode: string; + regionName: string; + usdtBalance: string; + totalReceived: string; + status: string; +} /** * 系统账户报表完整响应 @@ -14,15 +39,15 @@ import { AuthorizationServiceClient, SystemAccountDTO, RegionAccountsSummary, Al export interface SystemAccountReportResponse { // 固定系统账户 fixedAccounts: { - costAccount: SystemAccountWithBalance | null; // 成本账户 S0000000001 - operationAccount: SystemAccountWithBalance | null; // 运营账户 S0000000002 - hqCommunity: SystemAccountWithBalance | null; // 总部社区 S0000000003 - rwadPoolPending: SystemAccountWithBalance | null; // RWAD待发放池 S0000000004 - platformFee: SystemAccountWithBalance | null; // 平台手续费 S0000000005 + costAccount: FixedAccountInfo | null; // 成本账户 S0000000001 + operationAccount: FixedAccountInfo | null; // 运营账户 S0000000002 + hqCommunity: FixedAccountInfo | null; // 总部社区 S0000000003 + rwadPoolPending: FixedAccountInfo | null; // RWAD待发放池 S0000000004 + platformFee: FixedAccountInfo | null; // 平台手续费 S0000000005 }; // 省区域账户汇总 provinceSummary: { - accounts: SystemAccountDTO[]; + accounts: RegionAccountInfo[]; summary: { totalBalance: string; totalReceived: string; @@ -31,7 +56,7 @@ export interface SystemAccountReportResponse { }; // 市区域账户汇总 citySummary: { - accounts: SystemAccountDTO[]; + accounts: RegionAccountInfo[]; summary: { totalBalance: string; totalReceived: string; @@ -46,33 +71,15 @@ export interface SystemAccountReportResponse { generatedAt: string; } -/** - * 系统账户带钱包余额信息 - */ -export interface SystemAccountWithBalance extends SystemAccountDTO { - walletBalance?: number; -} - /** * 固定账户类型映射 */ -const FIXED_ACCOUNT_TYPES = { +const FIXED_ACCOUNT_TYPES: Record = { COST_ACCOUNT: 'costAccount', OPERATION_ACCOUNT: 'operationAccount', HQ_COMMUNITY: 'hqCommunity', RWAD_POOL_PENDING: 'rwadPoolPending', PLATFORM_FEE: 'platformFee', -} as const; - -/** - * 固定账户序列号映射 - */ -const FIXED_ACCOUNT_SEQUENCES = { - costAccount: 'S0000000001', - operationAccount: 'S0000000002', - hqCommunity: 'S0000000003', - rwadPoolPending: 'S0000000004', - platformFee: 'S0000000005', }; @Injectable() @@ -82,11 +89,11 @@ export class SystemAccountReportApplicationService { constructor( private readonly walletServiceClient: WalletServiceClient, private readonly rewardServiceClient: RewardServiceClient, - private readonly authorizationServiceClient: AuthorizationServiceClient, ) {} /** * 获取完整的系统账户报表 + * [2026-01-05] 修改:从 wallet-service 获取所有系统账户数据 */ async getFullReport(params?: { startDate?: string; @@ -96,26 +103,23 @@ export class SystemAccountReportApplicationService { // 并行获取所有数据 const [ - allAccountsSummary, - provinceSummary, - citySummary, + allSystemAccounts, offlineSettlement, expiredRewards, - fixedAccountsBalances, ] = await Promise.all([ - this.authorizationServiceClient.getAllAccountsSummary(), - this.authorizationServiceClient.getRegionAccountsList('province'), - this.authorizationServiceClient.getRegionAccountsList('city'), + this.walletServiceClient.getAllSystemAccounts(), this.walletServiceClient.getOfflineSettlementSummary(params), this.rewardServiceClient.getExpiredRewardsSummary(params), - this.getFixedAccountsBalances(), ]); // 组装固定账户数据 - const fixedAccounts = this.assembleFixedAccounts( - allAccountsSummary.fixedAccounts, - fixedAccountsBalances, - ); + const fixedAccounts = this.assembleFixedAccounts(allSystemAccounts.fixedAccounts); + + // 组装省账户汇总 + const provinceSummary = this.assembleRegionSummary(allSystemAccounts.provinceAccounts); + + // 组装市账户汇总 + const citySummary = this.assembleRegionSummary(allSystemAccounts.cityAccounts); const report: SystemAccountReportResponse = { fixedAccounts, @@ -133,33 +137,31 @@ export class SystemAccountReportApplicationService { /** * 获取固定账户列表(带余额) */ - async getFixedAccountsWithBalances(): Promise { - const [fixedAccounts, balances] = await Promise.all([ - this.authorizationServiceClient.getFixedAccountsList(), - this.getFixedAccountsBalances(), - ]); - - return fixedAccounts.map(account => { - const balance = balances.find(b => b.accountSequence === this.getAccountSequence(account)); - return { - ...account, - walletBalance: balance?.balance, - }; - }); + async getFixedAccountsWithBalances(): Promise { + const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts(); + return allSystemAccounts.fixedAccounts; } /** * 获取省区域账户汇总 */ - async getProvinceAccountsSummary(): Promise { - return this.authorizationServiceClient.getRegionAccountsList('province'); + async getProvinceAccountsSummary(): Promise<{ + accounts: RegionAccountInfo[]; + summary: { totalBalance: string; totalReceived: string; count: number }; + }> { + const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts(); + return this.assembleRegionSummary(allSystemAccounts.provinceAccounts); } /** * 获取市区域账户汇总 */ - async getCityAccountsSummary(): Promise { - return this.authorizationServiceClient.getRegionAccountsList('city'); + async getCityAccountsSummary(): Promise<{ + accounts: RegionAccountInfo[]; + summary: { totalBalance: string; totalReceived: string; count: number }; + }> { + const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts(); + return this.assembleRegionSummary(allSystemAccounts.cityAccounts); } /** @@ -182,20 +184,11 @@ export class SystemAccountReportApplicationService { return this.rewardServiceClient.getExpiredRewardsSummary(params); } - /** - * 获取固定账户余额 - */ - private async getFixedAccountsBalances(): Promise { - const sequences = Object.values(FIXED_ACCOUNT_SEQUENCES); - return this.walletServiceClient.getSystemAccountsBalances(sequences); - } - /** * 组装固定账户数据 */ private assembleFixedAccounts( - fixedAccounts: SystemAccountDTO[], - balances: SystemAccountBalance[], + fixedAccounts: AllSystemAccountsResponse['fixedAccounts'], ): SystemAccountReportResponse['fixedAccounts'] { const result: SystemAccountReportResponse['fixedAccounts'] = { costAccount: null, @@ -206,14 +199,9 @@ export class SystemAccountReportApplicationService { }; for (const account of fixedAccounts) { - const fieldName = FIXED_ACCOUNT_TYPES[account.accountType as keyof typeof FIXED_ACCOUNT_TYPES]; - if (fieldName) { - const sequence = FIXED_ACCOUNT_SEQUENCES[fieldName]; - const balance = balances.find(b => b.accountSequence === sequence); - result[fieldName] = { - ...account, - walletBalance: balance?.balance, - }; + const fieldName = FIXED_ACCOUNT_TYPES[account.accountType]; + if (fieldName && fieldName in result) { + (result as any)[fieldName] = account; } } @@ -221,13 +209,29 @@ export class SystemAccountReportApplicationService { } /** - * 根据账户类型获取账户序列号 + * 组装区域账户汇总 */ - private getAccountSequence(account: SystemAccountDTO): string | undefined { - const fieldName = FIXED_ACCOUNT_TYPES[account.accountType as keyof typeof FIXED_ACCOUNT_TYPES]; - if (fieldName) { - return FIXED_ACCOUNT_SEQUENCES[fieldName]; + private assembleRegionSummary( + accounts: RegionAccountInfo[], + ): { + accounts: RegionAccountInfo[]; + summary: { totalBalance: string; totalReceived: string; count: number }; + } { + let totalBalance = 0; + let totalReceived = 0; + + for (const account of accounts) { + totalBalance += parseFloat(account.usdtBalance) || 0; + totalReceived += parseFloat(account.totalReceived) || 0; } - return undefined; + + return { + accounts, + summary: { + totalBalance: totalBalance.toFixed(8), + totalReceived: totalReceived.toFixed(8), + count: accounts.length, + }, + }; } } diff --git a/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts b/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts index 16da012f..20f58cd0 100644 --- a/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts +++ b/backend/services/reporting-service/src/infrastructure/external/reward-service/reward-service.client.ts @@ -35,7 +35,7 @@ export class RewardServiceClient { ) { this.baseUrl = this.configService.get( 'REWARD_SERVICE_URL', - 'http://reward-service:3004', + 'http://rwa-reward-service:3005', ); } diff --git a/backend/services/reporting-service/src/infrastructure/external/wallet-service/wallet-service.client.ts b/backend/services/reporting-service/src/infrastructure/external/wallet-service/wallet-service.client.ts index 418b4c16..676128e6 100644 --- a/backend/services/reporting-service/src/infrastructure/external/wallet-service/wallet-service.client.ts +++ b/backend/services/reporting-service/src/infrastructure/external/wallet-service/wallet-service.client.ts @@ -27,6 +27,35 @@ export interface SystemAccountBalance { createdAt: string; } +// [2026-01-05] 新增:所有系统账户响应类型 +export interface AllSystemAccountsResponse { + fixedAccounts: Array<{ + accountSequence: string; + accountType: string; + usdtBalance: string; + totalReceived: string; + totalTransferred: string; + status: string; + createdAt: string; + }>; + provinceAccounts: Array<{ + accountSequence: string; + regionCode: string; + regionName: string; + usdtBalance: string; + totalReceived: string; + status: string; + }>; + cityAccounts: Array<{ + accountSequence: string; + regionCode: string; + regionName: string; + usdtBalance: string; + totalReceived: string; + status: string; + }>; +} + @Injectable() export class WalletServiceClient { private readonly logger = new Logger(WalletServiceClient.name); @@ -38,7 +67,7 @@ export class WalletServiceClient { ) { this.baseUrl = this.configService.get( 'WALLET_SERVICE_URL', - 'http://wallet-service:3002', + 'http://rwa-wallet-service:3001', ); } @@ -93,4 +122,29 @@ export class WalletServiceClient { return []; } } + + // [2026-01-05] 新增:获取所有系统账户(固定+区域) + /** + * 获取所有系统账户列表 + * 从 wallet-service 获取实际余额数据 + */ + async getAllSystemAccounts(): Promise { + try { + const url = `${this.baseUrl}/wallets/statistics/all-system-accounts`; + this.logger.debug(`[getAllSystemAccounts] 请求: ${url}`); + + const response = await firstValueFrom( + this.httpService.get(url), + ); + + return response.data; + } catch (error) { + this.logger.error(`[getAllSystemAccounts] 失败: ${error.message}`); + return { + fixedAccounts: [], + provinceAccounts: [], + cityAccounts: [], + }; + } + } } diff --git a/backend/services/wallet-service/src/api/controllers/internal-wallet.controller.ts b/backend/services/wallet-service/src/api/controllers/internal-wallet.controller.ts index 263b5e78..3d91972e 100644 --- a/backend/services/wallet-service/src/api/controllers/internal-wallet.controller.ts +++ b/backend/services/wallet-service/src/api/controllers/internal-wallet.controller.ts @@ -388,4 +388,15 @@ export class InternalWalletController { this.logger.log(`系统账户余额查询结果: ${result.length} 个账户`); return result; } + + @Get('statistics/all-system-accounts') + @Public() + @ApiOperation({ summary: '获取所有系统账户列表(内部API) - 用于系统账户报表' }) + @ApiResponse({ status: 200, description: '所有系统账户列表(固定+区域)' }) + async getAllSystemAccounts() { + this.logger.log(`========== statistics/all-system-accounts 请求 ==========`); + const result = await this.walletService.getAllSystemAccounts(); + this.logger.log(`系统账户查询结果: 固定=${result.fixedAccounts.length}, 省=${result.provinceAccounts.length}, 市=${result.cityAccounts.length}`); + return result; + } } diff --git a/backend/services/wallet-service/src/application/services/wallet-application.service.ts b/backend/services/wallet-service/src/application/services/wallet-application.service.ts index 3def8b6f..e0ce0193 100644 --- a/backend/services/wallet-service/src/application/services/wallet-application.service.ts +++ b/backend/services/wallet-service/src/application/services/wallet-application.service.ts @@ -2953,4 +2953,43 @@ export class WalletApplicationService { createdAt: wallet.createdAt.toISOString(), })); } -} + + // =============== 系统账户报表统计 API - 增强版 =============== + // [2026-01-05] 新增:获取所有系统账户列表(固定+区域) + // 回滚方式:删除以下方法 + + async getAllSystemAccounts(): Promise<{ + fixedAccounts: Array<{ accountSequence: string; accountType: string; usdtBalance: string; totalReceived: string; totalTransferred: string; status: string; createdAt: string }>; + provinceAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }>; + cityAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }>; + }> { + this.logger.log('[getAllSystemAccounts] 查询所有系统账户...'); + const wallets = await this.prisma.walletAccount.findMany({ + where: { OR: [{ accountSequence: { startsWith: 'S' } }, { accountSequence: { startsWith: '9' } }, { accountSequence: { startsWith: '8' } }] }, + select: { accountSequence: true, usdtAvailable: true, status: true, createdAt: true }, + }); + const accountSeqs = wallets.map(w => w.accountSequence); + const receivedStats2 = await this.prisma.ledgerEntry.groupBy({ by: ['accountSequence'], where: { accountSequence: { in: accountSeqs }, amount: { gt: 0 } }, _sum: { amount: true } }); + const transferredStats = await this.prisma.ledgerEntry.groupBy({ by: ['accountSequence'], where: { accountSequence: { in: accountSeqs }, amount: { lt: 0 } }, _sum: { amount: true } }); + const receivedMap2 = new Map(receivedStats2.map(s => [s.accountSequence, s._sum.amount])); + const transferredMap = new Map(transferredStats.map(s => [s.accountSequence, s._sum.amount])); + const fixedAccountTypes: Record = { 'S0000000001': 'COST_ACCOUNT', 'S0000000002': 'OPERATION_ACCOUNT', 'S0000000003': 'HQ_COMMUNITY', 'S0000000004': 'RWAD_POOL_PENDING', 'S0000000005': 'PLATFORM_FEE' }; + const fixedAccounts: Array<{ accountSequence: string; accountType: string; usdtBalance: string; totalReceived: string; totalTransferred: string; status: string; createdAt: string }> = []; + const provinceAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }> = []; + const cityAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }> = []; + for (const wallet of wallets) { + const seq = wallet.accountSequence; + const received = Number(receivedMap2.get(seq) || 0); + const transferred = Math.abs(Number(transferredMap.get(seq) || 0)); + if (seq.startsWith('S')) { + fixedAccounts.push({ accountSequence: seq, accountType: fixedAccountTypes[seq] || 'UNKNOWN', usdtBalance: String(wallet.usdtAvailable), totalReceived: String(received), totalTransferred: String(transferred), status: wallet.status, createdAt: wallet.createdAt.toISOString() }); + } else if (seq.startsWith('9')) { + provinceAccounts.push({ accountSequence: seq, regionCode: seq.substring(1), regionName: '省区域 ' + seq.substring(1), usdtBalance: String(wallet.usdtAvailable), totalReceived: String(received), status: wallet.status }); + } else if (seq.startsWith('8')) { + cityAccounts.push({ accountSequence: seq, regionCode: seq.substring(1), regionName: '市区域 ' + seq.substring(1), usdtBalance: String(wallet.usdtAvailable), totalReceived: String(received), status: wallet.status }); + } + } + this.logger.log('[getAllSystemAccounts] 固定: ' + fixedAccounts.length + ', 省: ' + provinceAccounts.length + ', 市: ' + cityAccounts.length); + return { fixedAccounts, provinceAccounts, cityAccounts }; + } +}