From ad82e3ee4485b83c55822a5a4058c29355423bac Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 12 Dec 2025 19:49:11 -0800 Subject: [PATCH] =?UTF-8?q?fix(referral-service):=20=E4=BB=8E=E9=A2=86?= =?UTF-8?q?=E5=9F=9F=E5=B1=82=E4=BF=AE=E5=A4=8D=20usedReferralCode=20?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - 创建推荐关系时,used_referral_code 字段错误地存储了用户自己的推荐码 - 而不是用户使用的推荐人的推荐码 根本修复(从领域层): 1. ReferralRelationshipProps: 添加 usedReferralCode 字段 2. ReferralRelationship.create(): 新增 referrerReferralCode 参数 3. ReferralRelationship: 添加 _usedReferralCode 私有属性和 getter 4. toPersistence(): 输出 usedReferralCode 5. reconstitute(): 正确重建 usedReferralCode 调用链路修复: - referral.service.ts: 在查询推荐人时获取其 referralCode, 并在调用 ReferralRelationship.create() 时传入 - repository: 直接使用 data.usedReferralCode 而不是额外查询 测试更新: - 更新单元测试和集成测试以支持新的 create() 签名 - 新增 usedReferralCode 相关测试用例 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../application/services/referral.service.ts | 7 +- .../referral-relationship.aggregate.ts | 17 ++++- .../referral-relationship.repository.ts | 4 +- .../referral-relationship.aggregate.spec.ts | 64 ++++++++++++++----- .../test/integration/mocks/prisma.mock.ts | 2 + ...elationship.repository.integration.spec.ts | 48 +++++++------- 6 files changed, 100 insertions(+), 42 deletions(-) diff --git a/backend/services/referral-service/src/application/services/referral.service.ts b/backend/services/referral-service/src/application/services/referral.service.ts index fcdf32bf..2e354abf 100644 --- a/backend/services/referral-service/src/application/services/referral.service.ts +++ b/backend/services/referral-service/src/application/services/referral.service.ts @@ -43,6 +43,7 @@ export class ReferralService { } let referrerId: bigint | null = null; + let referrerReferralCode: string | null = null; // 推荐人的推荐码 let parentChain: bigint[] = []; // 优先通过推荐码查找推荐人 @@ -52,6 +53,7 @@ export class ReferralService { throw new NotFoundException('推荐码不存在'); } referrerId = referrer.userId; + referrerReferralCode = referrer.referralCode; // 获取推荐人的推荐码 parentChain = referrer.referralChain; // 验证推荐链 @@ -64,12 +66,14 @@ export class ReferralService { const referrer = await this.referralRepo.findByAccountSequence(command.inviterAccountSequence); if (referrer) { referrerId = referrer.userId; + referrerReferralCode = referrer.referralCode; // 获取推荐人的推荐码 parentChain = referrer.referralChain; // 验证推荐链 if (!this.referralChainService.validateChain(parentChain, command.userId)) { this.logger.warn(`Invalid referral chain for user ${command.userId}, ignoring inviter`); referrerId = null; + referrerReferralCode = null; parentChain = []; } } else { @@ -77,11 +81,12 @@ export class ReferralService { } } - // 创建推荐关系 + // 创建推荐关系(传入推荐人的推荐码) const relationship = ReferralRelationship.create( command.userId, command.accountSequence, referrerId, + referrerReferralCode, // 推荐人的推荐码 parentChain, ); const saved = await this.referralRepo.save(relationship); diff --git a/backend/services/referral-service/src/domain/aggregates/referral-relationship/referral-relationship.aggregate.ts b/backend/services/referral-service/src/domain/aggregates/referral-relationship/referral-relationship.aggregate.ts index 73e2b316..d985e827 100644 --- a/backend/services/referral-service/src/domain/aggregates/referral-relationship/referral-relationship.aggregate.ts +++ b/backend/services/referral-service/src/domain/aggregates/referral-relationship/referral-relationship.aggregate.ts @@ -6,7 +6,8 @@ export interface ReferralRelationshipProps { userId: bigint; accountSequence: string; // 格式: D + YYMMDD + 5位序号 referrerId: bigint | null; - referralCode: string; + referralCode: string; // 用户自己的推荐码 + usedReferralCode: string | null; // 用户使用的推荐码(推荐人的推荐码) referralChain: bigint[]; createdAt: Date; updatedAt: Date; @@ -29,6 +30,7 @@ export class ReferralRelationship { private readonly _accountSequence: string, // 格式: D + YYMMDD + 5位序号 private readonly _referrerId: UserId | null, private readonly _referralCode: ReferralCode, + private readonly _usedReferralCode: ReferralCode | null, // 用户使用的推荐码(推荐人的推荐码) private readonly _referralChain: ReferralChain, private readonly _createdAt: Date, private _updatedAt: Date, @@ -50,6 +52,9 @@ export class ReferralRelationship { get referralCode(): string { return this._referralCode.value; } + get usedReferralCode(): string | null { + return this._usedReferralCode?.value ?? null; + } get referralChain(): bigint[] { return this._referralChain.toArray(); } @@ -65,16 +70,23 @@ export class ReferralRelationship { /** * 创建新的推荐关系 (用户注册时) + * @param userId 新用户ID + * @param accountSequence 新用户账户序号 + * @param referrerId 推荐人ID(可选) + * @param referrerReferralCode 推荐人的推荐码(可选,如果有推荐人则必须提供) + * @param parentReferralChain 推荐人的推荐链 */ static create( userId: bigint, accountSequence: string, // 格式: D + YYMMDD + 5位序号 referrerId: bigint | null, + referrerReferralCode: string | null, // 推荐人的推荐码 parentReferralChain: bigint[] = [], ): ReferralRelationship { const userIdVo = UserId.create(userId); const referrerIdVo = referrerId ? UserId.create(referrerId) : null; const referralCode = ReferralCode.generate(userId); + const usedReferralCode = referrerReferralCode ? ReferralCode.create(referrerReferralCode) : null; const referralChain = ReferralChain.create(referrerId, parentReferralChain); const now = new Date(); @@ -84,6 +96,7 @@ export class ReferralRelationship { accountSequence, referrerIdVo, referralCode, + usedReferralCode, referralChain, now, now, @@ -112,6 +125,7 @@ export class ReferralRelationship { props.accountSequence, props.referrerId ? UserId.create(props.referrerId) : null, ReferralCode.create(props.referralCode), + props.usedReferralCode ? ReferralCode.create(props.usedReferralCode) : null, ReferralChain.fromArray(props.referralChain), props.createdAt, props.updatedAt, @@ -163,6 +177,7 @@ export class ReferralRelationship { accountSequence: this._accountSequence, referrerId: this._referrerId?.value ?? null, referralCode: this._referralCode.value, + usedReferralCode: this._usedReferralCode?.value ?? null, referralChain: this._referralChain.toArray(), createdAt: this._createdAt, updatedAt: this._updatedAt, diff --git a/backend/services/referral-service/src/infrastructure/repositories/referral-relationship.repository.ts b/backend/services/referral-service/src/infrastructure/repositories/referral-relationship.repository.ts index b1a355f0..7e956197 100644 --- a/backend/services/referral-service/src/infrastructure/repositories/referral-relationship.repository.ts +++ b/backend/services/referral-service/src/infrastructure/repositories/referral-relationship.repository.ts @@ -27,7 +27,7 @@ export class ReferralRelationshipRepository implements IReferralRelationshipRepo accountSequence: data.accountSequence, referrerId: data.referrerId, myReferralCode: data.referralCode, - usedReferralCode: data.referrerId ? data.referralCode : null, + usedReferralCode: data.usedReferralCode, // 从领域模型获取推荐人的推荐码 ancestorPath: data.referralChain, depth: data.referralChain.length, rootUserId: data.referralChain.length > 0 ? data.referralChain[data.referralChain.length - 1] : null, @@ -103,6 +103,7 @@ export class ReferralRelationshipRepository implements IReferralRelationshipRepo accountSequence: string; // 格式: D + YYMMDD + 5位序号 referrerId: bigint | null; myReferralCode: string; + usedReferralCode: string | null; // 用户使用的推荐码(推荐人的推荐码) ancestorPath: bigint[]; createdAt: Date; updatedAt: Date; @@ -113,6 +114,7 @@ export class ReferralRelationshipRepository implements IReferralRelationshipRepo accountSequence: record.accountSequence, referrerId: record.referrerId, referralCode: record.myReferralCode, + usedReferralCode: record.usedReferralCode, referralChain: record.ancestorPath, createdAt: record.createdAt, updatedAt: record.updatedAt, diff --git a/backend/services/referral-service/test/domain/aggregates/referral-relationship.aggregate.spec.ts b/backend/services/referral-service/test/domain/aggregates/referral-relationship.aggregate.spec.ts index 5d7c3e3e..563977bd 100644 --- a/backend/services/referral-service/test/domain/aggregates/referral-relationship.aggregate.spec.ts +++ b/backend/services/referral-service/test/domain/aggregates/referral-relationship.aggregate.spec.ts @@ -4,27 +4,29 @@ import { ReferralRelationshipCreatedEvent } from '../../../src/domain/events'; describe('ReferralRelationship Aggregate', () => { describe('create', () => { it('should create referral relationship without referrer', () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); expect(relationship.userId).toBe(100n); - expect(relationship.accountSequence).toBe(12345678); + expect(relationship.accountSequence).toBe('D2512120001'); expect(relationship.referrerId).toBeNull(); + expect(relationship.usedReferralCode).toBeNull(); expect(relationship.referralCode).toMatch(/^RWA/); expect(relationship.referralChain).toEqual([]); }); it('should create referral relationship with referrer', () => { const parentChain = [200n, 300n]; - const relationship = ReferralRelationship.create(100n, 12345678, 50n, parentChain); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345', parentChain); expect(relationship.userId).toBe(100n); - expect(relationship.accountSequence).toBe(12345678); + expect(relationship.accountSequence).toBe('D2512120001'); expect(relationship.referrerId).toBe(50n); + expect(relationship.usedReferralCode).toBe('RWAREF12345'); expect(relationship.referralChain).toEqual([50n, 200n, 300n]); }); it('should emit ReferralRelationshipCreatedEvent', () => { - const relationship = ReferralRelationship.create(100n, 12345678, 50n); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345'); expect(relationship.domainEvents.length).toBe(1); expect(relationship.domainEvents[0]).toBeInstanceOf(ReferralRelationshipCreatedEvent); @@ -40,9 +42,10 @@ describe('ReferralRelationship Aggregate', () => { const props = { id: 1n, userId: 100n, - accountSequence: 12345678, + accountSequence: 'D2512120001', referrerId: 50n, referralCode: 'RWATEST123', + usedReferralCode: 'RWAREF12345', referralChain: [50n, 200n], createdAt: new Date('2024-01-01'), updatedAt: new Date('2024-01-02'), @@ -52,28 +55,48 @@ describe('ReferralRelationship Aggregate', () => { expect(relationship.id).toBe(1n); expect(relationship.userId).toBe(100n); - expect(relationship.accountSequence).toBe(12345678); + expect(relationship.accountSequence).toBe('D2512120001'); expect(relationship.referrerId).toBe(50n); expect(relationship.referralCode).toBe('RWATEST123'); + expect(relationship.usedReferralCode).toBe('RWAREF12345'); expect(relationship.referralChain).toEqual([50n, 200n]); }); + + it('should reconstitute without used referral code', () => { + const props = { + id: 1n, + userId: 100n, + accountSequence: 'D2512120001', + referrerId: null, + referralCode: 'RWATEST123', + usedReferralCode: null, + referralChain: [], + createdAt: new Date('2024-01-01'), + updatedAt: new Date('2024-01-02'), + }; + + const relationship = ReferralRelationship.reconstitute(props); + + expect(relationship.referrerId).toBeNull(); + expect(relationship.usedReferralCode).toBeNull(); + }); }); describe('getDirectReferrer', () => { it('should return direct referrer', () => { - const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n]); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345', [200n]); expect(relationship.getDirectReferrer()).toBe(50n); }); it('should return null when no referrer', () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); expect(relationship.getDirectReferrer()).toBeNull(); }); }); describe('getReferrerAtLevel', () => { it('should return referrer at specific level', () => { - const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n, 300n]); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345', [200n, 300n]); expect(relationship.getReferrerAtLevel(0)).toBe(50n); expect(relationship.getReferrerAtLevel(1)).toBe(200n); @@ -83,21 +106,21 @@ describe('ReferralRelationship Aggregate', () => { describe('getAllAncestorIds', () => { it('should return all ancestor IDs', () => { - const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n, 300n]); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345', [200n, 300n]); expect(relationship.getAllAncestorIds()).toEqual([50n, 200n, 300n]); }); }); describe('getChainDepth', () => { it('should return chain depth', () => { - const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n, 300n]); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345', [200n, 300n]); expect(relationship.getChainDepth()).toBe(3); }); }); describe('clearDomainEvents', () => { it('should clear domain events', () => { - const relationship = ReferralRelationship.create(100n, 12345678, 50n); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345'); expect(relationship.domainEvents.length).toBe(1); relationship.clearDomainEvents(); @@ -106,15 +129,24 @@ describe('ReferralRelationship Aggregate', () => { }); describe('toPersistence', () => { - it('should convert to persistence format', () => { - const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n]); + it('should convert to persistence format with referrer', () => { + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345', [200n]); const data = relationship.toPersistence(); expect(data.userId).toBe(100n); - expect(data.accountSequence).toBe(12345678); + expect(data.accountSequence).toBe('D2512120001'); expect(data.referrerId).toBe(50n); expect(data.referralCode).toMatch(/^RWA/); + expect(data.usedReferralCode).toBe('RWAREF12345'); expect(data.referralChain).toEqual([50n, 200n]); }); + + it('should convert to persistence format without referrer', () => { + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); + const data = relationship.toPersistence(); + + expect(data.referrerId).toBeNull(); + expect(data.usedReferralCode).toBeNull(); + }); }); }); diff --git a/backend/services/referral-service/test/integration/mocks/prisma.mock.ts b/backend/services/referral-service/test/integration/mocks/prisma.mock.ts index 877057ef..0bc40df7 100644 --- a/backend/services/referral-service/test/integration/mocks/prisma.mock.ts +++ b/backend/services/referral-service/test/integration/mocks/prisma.mock.ts @@ -6,6 +6,7 @@ type MockReferralRelationship = { id: bigint; userId: bigint; + accountSequence: string; // 格式: D + YYMMDD + 5位序号 referrerId: bigint | null; rootUserId: bigint | null; myReferralCode: string; @@ -65,6 +66,7 @@ export class MockPrismaService { const created: MockReferralRelationship = { id: this.idCounter++, userId: args.where.userId, + accountSequence: args.create.accountSequence ?? `D${Date.now()}`, referrerId: args.create.referrerId ?? null, rootUserId: args.create.rootUserId ?? null, myReferralCode: args.create.myReferralCode!, diff --git a/backend/services/referral-service/test/integration/repositories/referral-relationship.repository.integration.spec.ts b/backend/services/referral-service/test/integration/repositories/referral-relationship.repository.integration.spec.ts index 87872b26..be0bb102 100644 --- a/backend/services/referral-service/test/integration/repositories/referral-relationship.repository.integration.spec.ts +++ b/backend/services/referral-service/test/integration/repositories/referral-relationship.repository.integration.spec.ts @@ -29,53 +29,55 @@ describe('ReferralRelationshipRepository (Integration)', () => { }); describe('save', () => { - it('should save a new referral relationship', async () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + it('should save a new referral relationship without referrer', async () => { + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); const saved = await repository.save(relationship); expect(saved).toBeDefined(); expect(saved.userId).toBe(100n); - expect(saved.accountSequence).toBe(12345678); + expect(saved.accountSequence).toBe('D2512120001'); expect(saved.referralCode).toBeDefined(); + expect(saved.usedReferralCode).toBeNull(); expect(saved.referralChain).toEqual([]); }); it('should save referral relationship with referrer', async () => { // First create the referrer - const referrer = ReferralRelationship.create(50n, 11111111, null); - await repository.save(referrer); + const referrer = ReferralRelationship.create(50n, 'D2512110001', null, null); + const savedReferrer = await repository.save(referrer); - // Then create with referrer - const relationship = ReferralRelationship.create(100n, 12345678, 50n, []); + // Then create with referrer, passing the referrer's referral code + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, savedReferrer.referralCode, []); const saved = await repository.save(relationship); expect(saved.userId).toBe(100n); - expect(saved.accountSequence).toBe(12345678); + expect(saved.accountSequence).toBe('D2512120001'); expect(saved.referrerId).toBe(50n); + expect(saved.usedReferralCode).toBe(savedReferrer.referralCode); expect(saved.referralChain).toContain(50n); }); it('should update existing referral relationship', async () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); await repository.save(relationship); // Save again should update const updated = await repository.save(relationship); expect(updated.userId).toBe(100n); - expect(updated.accountSequence).toBe(12345678); + expect(updated.accountSequence).toBe('D2512120001'); }); }); describe('findByUserId', () => { it('should find relationship by user ID', async () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); await repository.save(relationship); const found = await repository.findByUserId(100n); expect(found).toBeDefined(); expect(found!.userId).toBe(100n); - expect(found!.accountSequence).toBe(12345678); + expect(found!.accountSequence).toBe('D2512120001'); }); it('should return null for non-existent user', async () => { @@ -86,14 +88,14 @@ describe('ReferralRelationshipRepository (Integration)', () => { describe('findByReferralCode', () => { it('should find relationship by referral code', async () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); const saved = await repository.save(relationship); const found = await repository.findByReferralCode(saved.referralCode); expect(found).toBeDefined(); expect(found!.userId).toBe(100n); - expect(found!.accountSequence).toBe(12345678); + expect(found!.accountSequence).toBe('D2512120001'); }); it('should return null for non-existent code', async () => { @@ -105,12 +107,12 @@ describe('ReferralRelationshipRepository (Integration)', () => { describe('findDirectReferrals', () => { it('should find all direct referrals', async () => { // Create referrer - const referrer = ReferralRelationship.create(50n, 11111111, null); - await repository.save(referrer); + const referrer = ReferralRelationship.create(50n, 'D2512110001', null, null); + const savedReferrer = await repository.save(referrer); - // Create direct referrals - const ref1 = ReferralRelationship.create(100n, 12345678, 50n, []); - const ref2 = ReferralRelationship.create(101n, 12345679, 50n, []); + // Create direct referrals with referrer's code + const ref1 = ReferralRelationship.create(100n, 'D2512120001', 50n, savedReferrer.referralCode, []); + const ref2 = ReferralRelationship.create(101n, 'D2512120002', 50n, savedReferrer.referralCode, []); await repository.save(ref1); await repository.save(ref2); @@ -122,7 +124,7 @@ describe('ReferralRelationshipRepository (Integration)', () => { }); it('should return empty array for user with no referrals', async () => { - const referrer = ReferralRelationship.create(50n, 11111111, null); + const referrer = ReferralRelationship.create(50n, 'D2512110001', null, null); await repository.save(referrer); const directReferrals = await repository.findDirectReferrals(50n); @@ -132,7 +134,7 @@ describe('ReferralRelationshipRepository (Integration)', () => { describe('existsByReferralCode', () => { it('should return true for existing code', async () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); const saved = await repository.save(relationship); const exists = await repository.existsByReferralCode(saved.referralCode); @@ -147,7 +149,7 @@ describe('ReferralRelationshipRepository (Integration)', () => { describe('existsByUserId', () => { it('should return true for existing user', async () => { - const relationship = ReferralRelationship.create(100n, 12345678, null); + const relationship = ReferralRelationship.create(100n, 'D2512120001', null, null); await repository.save(relationship); const exists = await repository.existsByUserId(100n); @@ -163,7 +165,7 @@ describe('ReferralRelationshipRepository (Integration)', () => { describe('getReferralChain', () => { it('should return referral chain', async () => { const parentChain = [200n, 300n]; - const relationship = ReferralRelationship.create(100n, 12345678, 50n, parentChain); + const relationship = ReferralRelationship.create(100n, 'D2512120001', 50n, 'RWAREF12345', parentChain); await repository.save(relationship); const chain = await repository.getReferralChain(100n);