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,
): Promise<PaginatedLedgerResponseDTO> {
const query = new GetMyLedgerQuery(
user.userId,
user.accountSequence, // 使用 accountSequence 替代 userId
queryDto.page,
queryDto.pageSize,
queryDto.entryType,
@ -39,7 +39,7 @@ export class LedgerController {
async getStatistics(
@CurrentUser() user: CurrentUserPayload,
): Promise<LedgerStatisticsResponseDTO> {
return this.walletService.getLedgerStatistics(user.userId);
return this.walletService.getLedgerStatistics(user.accountSequence);
}
@Get('trend')
@ -51,6 +51,6 @@ export class LedgerController {
@Query('days') days?: string,
): Promise<LedgerTrendResponseDTO> {
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 {
constructor(
public readonly userId: string,
public readonly accountSequence: string, // 使用 accountSequence 替代 userId
public readonly page?: number,
public readonly pageSize?: number,
public readonly entryType?: LedgerEntryType,

View File

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

View File

@ -1916,10 +1916,9 @@ export class WalletApplicationService {
}
async getMyLedger(query: GetMyLedgerQuery): Promise<PaginatedLedgerDTO> {
const userId = BigInt(query.userId);
const result = await this.ledgerRepo.findByUserId(
userId,
// 使用 accountSequence 查询流水(废弃 userId 查询)
const result = await this.ledgerRepo.findByAccountSequence(
query.accountSequence,
{
entryType: query.entryType,
assetType: query.assetType,
@ -1934,7 +1933,7 @@ export class WalletApplicationService {
// 调试日志打印流水数据只打印前5条
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++) {
const entry = result.data[i];
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;
totalExpense: number;
netAmount: number;
@ -2472,11 +2471,9 @@ export class WalletApplicationService {
startDate: string;
endDate: string;
}> {
const userIdBigInt = BigInt(userId);
// 获取所有流水
// 使用 accountSequence 查询流水(废弃 userId 查询)
const entries = await this.prisma.ledgerEntry.findMany({
where: { userId: userIdBigInt, assetType: 'USDT' },
where: { accountSequence, assetType: 'USDT' },
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<{
date: string;
income: number;
@ -2560,14 +2557,14 @@ export class WalletApplicationService {
periodExpense: number;
periodNet: number;
}> {
const userIdBigInt = BigInt(userId);
// 使用 accountSequence 查询流水(废弃 userId 查询)
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
startDate.setHours(0, 0, 0, 0);
const entries = await this.prisma.ledgerEntry.findMany({
where: {
userId: userIdBigInt,
accountSequence,
assetType: 'USDT',
createdAt: { gte: startDate },
},

View File

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

View File

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