fix(wallet-service): 修复流水查询使用 userId 导致记录丢失的问题

将所有流水查询从 userId 改为使用 accountSequence:
- getMyLedger: 使用 findByAccountSequence 替代 findByUserId
- getLedgerStatistics: 查询改为按 accountSequence
- getLedgerTrend: 查询改为按 accountSequence
- findByAccountSequence: 添加 HIDDEN_ENTRY_TYPES 过滤

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-08 11:16:04 -08:00
parent 53df97839d
commit 217be89c43
6 changed files with 27 additions and 22 deletions

View File

@ -22,7 +22,7 @@ export class LedgerController {
@Query() queryDto: GetMyLedgerQueryDTO, @Query() queryDto: GetMyLedgerQueryDTO,
): Promise<PaginatedLedgerResponseDTO> { ): Promise<PaginatedLedgerResponseDTO> {
const query = new GetMyLedgerQuery( const query = new GetMyLedgerQuery(
user.userId, user.accountSequence, // 使用 accountSequence 替代 userId
queryDto.page, queryDto.page,
queryDto.pageSize, queryDto.pageSize,
queryDto.entryType, queryDto.entryType,
@ -39,7 +39,7 @@ export class LedgerController {
async getStatistics( async getStatistics(
@CurrentUser() user: CurrentUserPayload, @CurrentUser() user: CurrentUserPayload,
): Promise<LedgerStatisticsResponseDTO> { ): Promise<LedgerStatisticsResponseDTO> {
return this.walletService.getLedgerStatistics(user.userId); return this.walletService.getLedgerStatistics(user.accountSequence);
} }
@Get('trend') @Get('trend')
@ -51,6 +51,6 @@ export class LedgerController {
@Query('days') days?: string, @Query('days') days?: string,
): Promise<LedgerTrendResponseDTO> { ): Promise<LedgerTrendResponseDTO> {
const numDays = days ? parseInt(days, 10) : 30; const numDays = days ? parseInt(days, 10) : 30;
return this.walletService.getLedgerTrend(user.userId, numDays); return this.walletService.getLedgerTrend(user.accountSequence, numDays);
} }
} }

View File

@ -2,7 +2,7 @@ import { LedgerEntryType, AssetType } from '@/domain/value-objects';
export class GetMyLedgerQuery { export class GetMyLedgerQuery {
constructor( constructor(
public readonly userId: string, public readonly accountSequence: string, // 使用 accountSequence 替代 userId
public readonly page?: number, public readonly page?: number,
public readonly pageSize?: number, public readonly pageSize?: number,
public readonly entryType?: LedgerEntryType, public readonly entryType?: LedgerEntryType,

View File

@ -28,7 +28,7 @@ describe('WalletApplicationService', () => {
const createMockWallet = (userId: bigint, usdtBalance = 0) => { const createMockWallet = (userId: bigint, usdtBalance = 0) => {
return WalletAccount.reconstruct({ return WalletAccount.reconstruct({
walletId: BigInt(1), walletId: BigInt(1),
accountSequence: userId, // 使用 userId 作为 accountSequence accountSequence: `D${userId.toString().padStart(11, '0')}`, // 生成 accountSequence
userId, userId,
usdtAvailable: new Decimal(usdtBalance), usdtAvailable: new Decimal(usdtBalance),
usdtFrozen: new Decimal(0), usdtFrozen: new Decimal(0),
@ -51,6 +51,8 @@ describe('WalletApplicationService', () => {
expiredTotalUsdt: new Decimal(0), expiredTotalUsdt: new Decimal(0),
expiredTotalHashpower: new Decimal(0), expiredTotalHashpower: new Decimal(0),
status: 'ACTIVE', status: 'ACTIVE',
hasPlanted: false,
version: 0,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
}); });
@ -69,6 +71,7 @@ describe('WalletApplicationService', () => {
save: jest.fn(), save: jest.fn(),
saveAll: jest.fn(), saveAll: jest.fn(),
findByUserId: jest.fn(), findByUserId: jest.fn(),
findByAccountSequence: jest.fn(),
findByRefOrderId: jest.fn(), findByRefOrderId: jest.fn(),
findByRefTxHash: jest.fn(), findByRefTxHash: jest.fn(),
}; };
@ -187,9 +190,9 @@ describe('WalletApplicationService', () => {
describe('getMyLedger', () => { describe('getMyLedger', () => {
it('should return paginated ledger entries', async () => { it('should return paginated ledger entries', async () => {
const query = new GetMyLedgerQuery('1', 1, 10); const query = new GetMyLedgerQuery('D00000000001', 1, 10);
mockLedgerRepo.findByUserId.mockResolvedValue({ mockLedgerRepo.findByAccountSequence.mockResolvedValue({
data: [], data: [],
total: 0, total: 0,
page: 1, page: 1,

View File

@ -1916,10 +1916,9 @@ export class WalletApplicationService {
} }
async getMyLedger(query: GetMyLedgerQuery): Promise<PaginatedLedgerDTO> { async getMyLedger(query: GetMyLedgerQuery): Promise<PaginatedLedgerDTO> {
const userId = BigInt(query.userId); // 使用 accountSequence 查询流水(废弃 userId 查询)
const result = await this.ledgerRepo.findByAccountSequence(
const result = await this.ledgerRepo.findByUserId( query.accountSequence,
userId,
{ {
entryType: query.entryType, entryType: query.entryType,
assetType: query.assetType, assetType: query.assetType,
@ -1934,7 +1933,7 @@ export class WalletApplicationService {
// 调试日志打印流水数据只打印前5条 // 调试日志打印流水数据只打印前5条
this.logger.debug(`[getMyLedger] ======== 流水数据调试 ========`); this.logger.debug(`[getMyLedger] ======== 流水数据调试 ========`);
this.logger.debug(`[getMyLedger] userId: ${userId}, 共 ${result.data.length} 条, 总计 ${result.total}`); this.logger.debug(`[getMyLedger] accountSequence: ${query.accountSequence}, 共 ${result.data.length} 条, 总计 ${result.total}`);
for (let i = 0; i < result.data.length && i < 5; i++) { for (let i = 0; i < result.data.length && i < 5; i++) {
const entry = result.data[i]; const entry = result.data[i];
const allocationType = (entry.payloadJson as Record<string, unknown>)?.allocationType; const allocationType = (entry.payloadJson as Record<string, unknown>)?.allocationType;
@ -2458,7 +2457,7 @@ export class WalletApplicationService {
/** /**
* *
*/ */
async getLedgerStatistics(userId: string): Promise<{ async getLedgerStatistics(accountSequence: string): Promise<{
totalIncome: number; totalIncome: number;
totalExpense: number; totalExpense: number;
netAmount: number; netAmount: number;
@ -2472,11 +2471,9 @@ export class WalletApplicationService {
startDate: string; startDate: string;
endDate: string; endDate: string;
}> { }> {
const userIdBigInt = BigInt(userId); // 使用 accountSequence 查询流水(废弃 userId 查询)
// 获取所有流水
const entries = await this.prisma.ledgerEntry.findMany({ const entries = await this.prisma.ledgerEntry.findMany({
where: { userId: userIdBigInt, assetType: 'USDT' }, where: { accountSequence, assetType: 'USDT' },
orderBy: { createdAt: 'asc' }, orderBy: { createdAt: 'asc' },
}); });
@ -2547,7 +2544,7 @@ export class WalletApplicationService {
/** /**
* *
*/ */
async getLedgerTrend(userId: string, days: number = 30): Promise<{ async getLedgerTrend(accountSequence: string, days: number = 30): Promise<{
dailyTrend: Array<{ dailyTrend: Array<{
date: string; date: string;
income: number; income: number;
@ -2560,14 +2557,14 @@ export class WalletApplicationService {
periodExpense: number; periodExpense: number;
periodNet: number; periodNet: number;
}> { }> {
const userIdBigInt = BigInt(userId); // 使用 accountSequence 查询流水(废弃 userId 查询)
const startDate = new Date(); const startDate = new Date();
startDate.setDate(startDate.getDate() - days); startDate.setDate(startDate.getDate() - days);
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
const entries = await this.prisma.ledgerEntry.findMany({ const entries = await this.prisma.ledgerEntry.findMany({
where: { where: {
userId: userIdBigInt, accountSequence,
assetType: 'USDT', assetType: 'USDT',
createdAt: { gte: startDate }, createdAt: { gte: startDate },
}, },

View File

@ -27,7 +27,7 @@ describe('WalletAccount Aggregate', () => {
let wallet: WalletAccount; let wallet: WalletAccount;
beforeEach(() => { beforeEach(() => {
wallet = WalletAccount.createNew(BigInt(1), UserId.create(1)); wallet = WalletAccount.createNew('D00000000001', UserId.create(1));
}); });
describe('createNew', () => { describe('createNew', () => {

View File

@ -133,9 +133,14 @@ export class LedgerEntryRepositoryImpl implements ILedgerEntryRepository {
filters?: LedgerFilters, filters?: LedgerFilters,
pagination?: Pagination, pagination?: Pagination,
): Promise<PaginatedResult<LedgerEntry>> { ): Promise<PaginatedResult<LedgerEntry>> {
const where: Record<string, unknown> = { accountSequence }; const where: Record<string, unknown> = {
accountSequence,
// 排除临时性流水类型
entryType: { notIn: LedgerEntryRepositoryImpl.HIDDEN_ENTRY_TYPES },
};
if (filters?.entryType) { if (filters?.entryType) {
// 如果用户指定了类型筛选,则使用用户指定的类型
where.entryType = filters.entryType; where.entryType = filters.entryType;
} }
if (filters?.assetType) { if (filters?.assetType) {