From 8ba332707417bac0ac8bce99564538c37c000f06 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 9 Jul 2026 11:07:25 +0800 Subject: [PATCH] fix: handle duplicate usernames and failed role lookups in importDingTalkUsers --- apps/server/src/sync/sync.service.spec.ts | 21 +++++++++++++++++---- apps/server/src/sync/sync.service.ts | 14 +++++++++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/apps/server/src/sync/sync.service.spec.ts b/apps/server/src/sync/sync.service.spec.ts index efdfdea..ab9f312 100644 --- a/apps/server/src/sync/sync.service.spec.ts +++ b/apps/server/src/sync/sync.service.spec.ts @@ -116,13 +116,17 @@ describe('SyncService — new methods', () => { ); }); - it('imports teacher when roleId is a number but role not found (logs warning, no roles assigned)', async () => { + it('imports teacher when roleId is a number but role not found (creates student as fallback)', async () => { roleRepo.findOne.mockResolvedValue(null); const mockUser = { id: 11 } as User; userRepo.create.mockReturnValue(mockUser); userRepo.save.mockResolvedValue(mockUser); + const mockStudent = { id: 31 } as Student; + studentRepo.create.mockReturnValue(mockStudent); + studentRepo.save.mockResolvedValue(mockStudent); + mappingRepo.findOne.mockResolvedValue(null); mappingRepo.create.mockReturnValue({} as UserDingMapping); mappingRepo.save.mockResolvedValue({} as UserDingMapping); @@ -133,12 +137,21 @@ describe('SyncService — new methods', () => { const result = await service.importDingTalkUsers(users); - expect(result.teacherCount).toBe(1); - expect(result.studentCount).toBe(0); + expect(result.teacherCount).toBe(0); + expect(result.studentCount).toBe(1); expect(result.skipped).toBe(0); - // User should be saved once (without roles assignment the second time) + // User should be saved once (no second save for role assignment) expect(userRepo.save).toHaveBeenCalledTimes(1); + // Student record should have been created as fallback + expect(studentRepo.create).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'Li Si', + userId: 11, + status: 'active', + }), + ); + expect(studentRepo.save).toHaveBeenCalled(); }); it('imports student when roleId is null', async () => { diff --git a/apps/server/src/sync/sync.service.ts b/apps/server/src/sync/sync.service.ts index 9e57f1a..008b359 100644 --- a/apps/server/src/sync/sync.service.ts +++ b/apps/server/src/sync/sync.service.ts @@ -138,7 +138,7 @@ export class SyncService { } try { - const username = u.mobile || `dd_${u.dingUserId}`; + const username = `dd_${u.dingUserId}`; const passwordHash = await bcrypt.hash('123456', 10); const user = this.userRepo.create({ @@ -155,10 +155,18 @@ export class SyncService { if (role) { user.roles = [role]; await this.userRepo.save(user); + teacherCount++; } else { - this.logger.warn(`角色 id=${u.roleId} 不存在,用户 ${u.name} 未分配角色`); + this.logger.warn(`角色 id=${u.roleId} 不存在,用户 ${u.name} 转为学生`); + const student = this.studentRepo.create({ + name: u.name, + phone: u.mobile || undefined, + userId: user.id, + status: 'active', + }); + await this.studentRepo.save(student); + studentCount++; } - teacherCount++; } else { // 学生:创建 Student 记录 const student = this.studentRepo.create({