fix(server): handle existing User from syncAll in importDingTalkUsers to avoid UNIQUE constraint error
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DataSource, Repository } from 'typeorm';
|
import { DataSource, Repository } from 'typeorm';
|
||||||
import { SyncLog, SyncState, UserDingMapping, ClassStudent, ClassTeacher } from '../entities';
|
import { SyncLog, SyncState, UserDingMapping, ClassStudent, ClassTeacher, Department } from '../entities';
|
||||||
import { Class as ClassEntity } from '../entities/class.entity';
|
import { Class as ClassEntity } from '../entities/class.entity';
|
||||||
import type { SyncPlatform, SyncType, SyncStatus } from '../entities/sync-log.entity';
|
import type { SyncPlatform, SyncType, SyncStatus } from '../entities/sync-log.entity';
|
||||||
import { DingTalkService } from '../integration/dingtalk.service';
|
import { DingTalkService } from '../integration/dingtalk.service';
|
||||||
@@ -153,13 +153,32 @@ export class SyncService {
|
|||||||
let skipped = 0;
|
let skipped = 0;
|
||||||
|
|
||||||
await this.dataSource.transaction(async (manager) => {
|
await this.dataSource.transaction(async (manager) => {
|
||||||
|
// 0. 同步部门:为每个班级标记的钉钉部门创建/查找本地 Department
|
||||||
|
const deptLocalIdMap = new Map<number, number>(); // dingDeptId -> local deptId
|
||||||
|
for (const c of classItems) {
|
||||||
|
let dept = await manager.findOne(Department, {
|
||||||
|
where: { source: 'dingtalk', sourceId: String(c.deptId) },
|
||||||
|
});
|
||||||
|
if (!dept) {
|
||||||
|
dept = manager.create(Department, {
|
||||||
|
name: c.name,
|
||||||
|
type: 'department',
|
||||||
|
source: 'dingtalk',
|
||||||
|
sourceId: String(c.deptId),
|
||||||
|
});
|
||||||
|
dept = await manager.save(dept);
|
||||||
|
}
|
||||||
|
deptLocalIdMap.set(c.deptId, dept.id);
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 创建班级
|
// 1. 创建班级
|
||||||
const deptClassMap = new Map<number, number>(); // deptId -> classId
|
const deptClassMap = new Map<number, number>(); // deptId -> classId
|
||||||
for (const c of classItems) {
|
for (const c of classItems) {
|
||||||
// ponytail: Class keyword conflicts with TypeORM DeepPartial resolution
|
const deptId = deptLocalIdMap.get(c.deptId) ?? null;
|
||||||
const result = await manager.save(ClassEntity, {
|
const result = await manager.save(ClassEntity, {
|
||||||
name: c.name,
|
name: c.name,
|
||||||
code: c.code,
|
code: c.code,
|
||||||
|
departmentId: deptId,
|
||||||
classType: c.classType,
|
classType: c.classType,
|
||||||
startDate: c.startDate ?? null,
|
startDate: c.startDate ?? null,
|
||||||
endDate: c.endDate ?? null,
|
endDate: c.endDate ?? null,
|
||||||
@@ -174,75 +193,119 @@ export class SyncService {
|
|||||||
const existingMapping = await manager.findOne(UserDingMapping, {
|
const existingMapping = await manager.findOne(UserDingMapping, {
|
||||||
where: { dingUserId: u.dingUserId },
|
where: { dingUserId: u.dingUserId },
|
||||||
});
|
});
|
||||||
if (existingMapping) {
|
|
||||||
skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const username = `dd_${u.dingUserId}`;
|
|
||||||
const passwordHash = await bcrypt.hash('123456', 10);
|
|
||||||
|
|
||||||
const user = manager.create(User, {
|
|
||||||
username,
|
|
||||||
name: u.name,
|
|
||||||
passwordHash,
|
|
||||||
isActive: true,
|
|
||||||
});
|
|
||||||
await manager.save(user);
|
|
||||||
|
|
||||||
|
let userId: number | null = null;
|
||||||
let isTeacher = false;
|
let isTeacher = false;
|
||||||
|
|
||||||
if (u.roleId != null) {
|
if (existingMapping) {
|
||||||
const role = await manager.findOne(Role, { where: { id: u.roleId } });
|
skipped++;
|
||||||
if (!role) {
|
userId = existingMapping.userId;
|
||||||
throw new BadRequestException(`角色 id=${u.roleId} 不存在`);
|
// 判断是老师还是学生:查角色
|
||||||
}
|
const existingUser = await manager.findOne(User, {
|
||||||
user.roles = [role];
|
where: { id: userId },
|
||||||
await manager.save(user);
|
relations: ['roles'],
|
||||||
isTeacher = true;
|
|
||||||
teacherCount++;
|
|
||||||
} else {
|
|
||||||
const student = manager.create(Student, {
|
|
||||||
name: u.name,
|
|
||||||
phone: u.mobile || undefined,
|
|
||||||
userId: user.id,
|
|
||||||
status: 'active',
|
|
||||||
});
|
});
|
||||||
await manager.save(student);
|
isTeacher = (existingUser?.roles?.length ?? 0) > 0;
|
||||||
studentCount++;
|
} else {
|
||||||
|
// 检查是否已存在(syncAll 或历史导入),避免撞 username 唯一约束
|
||||||
|
const username = `dd_${u.dingUserId}`;
|
||||||
|
const whereConditions: Record<string, unknown>[] = [{ username }];
|
||||||
|
if (u.mobile) whereConditions.push({ username: u.mobile });
|
||||||
|
let user = await manager.findOne(User, { where: whereConditions });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
const passwordHash = await bcrypt.hash('123456', 10);
|
||||||
|
user = manager.create(User, {
|
||||||
|
username,
|
||||||
|
name: u.name,
|
||||||
|
passwordHash,
|
||||||
|
isActive: true,
|
||||||
|
});
|
||||||
|
await manager.save(user);
|
||||||
|
} else {
|
||||||
|
// 已存在:更新姓名
|
||||||
|
user.name = u.name;
|
||||||
|
await manager.save(user);
|
||||||
|
}
|
||||||
|
userId = user.id;
|
||||||
|
|
||||||
|
// 解析学生所属部门
|
||||||
|
const studentDeptId = u.roleId == null && classItems.length > 0 && u.dingDeptIds?.length > 0
|
||||||
|
? deptLocalIdMap.get(u.dingDeptIds.find((d) => deptLocalIdMap.has(d)) ?? -1) ?? undefined
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (u.roleId != null) {
|
||||||
|
const role = await manager.findOne(Role, { where: { id: u.roleId } });
|
||||||
|
if (!role) throw new BadRequestException(`角色 id=${u.roleId} 不存在`);
|
||||||
|
user.roles = [role];
|
||||||
|
await manager.save(user);
|
||||||
|
isTeacher = true;
|
||||||
|
teacherCount++;
|
||||||
|
} else {
|
||||||
|
// 学生:检查 Student 是否已存在,不存在则创建
|
||||||
|
let student = await manager.findOne(Student, { where: { userId: user.id } });
|
||||||
|
if (!student) {
|
||||||
|
student = manager.create(Student, {
|
||||||
|
name: u.name,
|
||||||
|
phone: u.mobile || undefined,
|
||||||
|
userId: user.id,
|
||||||
|
departmentId: studentDeptId,
|
||||||
|
status: 'active',
|
||||||
|
});
|
||||||
|
await manager.save(student);
|
||||||
|
}
|
||||||
|
studentCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 钉钉映射(幂等:可能已被 syncAll 创建)
|
||||||
|
const existingMappingForUser = await manager.findOne(UserDingMapping, {
|
||||||
|
where: { dingUserId: u.dingUserId },
|
||||||
|
});
|
||||||
|
if (!existingMappingForUser) {
|
||||||
|
const mapping = manager.create(UserDingMapping, {
|
||||||
|
dingUserId: u.dingUserId,
|
||||||
|
userId: user.id,
|
||||||
|
dingName: u.name,
|
||||||
|
dingMobile: u.mobile,
|
||||||
|
});
|
||||||
|
await manager.save(mapping);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 钉钉映射
|
// 3. 建立班级关联(新建/已存在用户都处理)
|
||||||
const mapping = manager.create(UserDingMapping, {
|
|
||||||
dingUserId: u.dingUserId,
|
|
||||||
userId: user.id,
|
|
||||||
dingName: u.name,
|
|
||||||
dingMobile: u.mobile,
|
|
||||||
});
|
|
||||||
await manager.save(mapping);
|
|
||||||
|
|
||||||
// 3. 建立班级关联
|
|
||||||
if (classItems.length > 0 && u.dingDeptIds?.length > 0) {
|
if (classItems.length > 0 && u.dingDeptIds?.length > 0) {
|
||||||
for (const deptId of u.dingDeptIds) {
|
for (const deptId of u.dingDeptIds) {
|
||||||
const classId = deptClassMap.get(deptId);
|
const classId = deptClassMap.get(deptId);
|
||||||
if (!classId) continue; // 该部门未标为班级
|
if (!classId) continue;
|
||||||
|
|
||||||
if (isTeacher) {
|
if (isTeacher) {
|
||||||
const ct = manager.create(ClassTeacher, {
|
const existingCT = await manager.findOne(ClassTeacher, {
|
||||||
classId,
|
where: { classId, userId },
|
||||||
userId: user.id,
|
|
||||||
roleType: 'teacher',
|
|
||||||
});
|
});
|
||||||
await manager.save(ct);
|
if (!existingCT) {
|
||||||
|
const ct = manager.create(ClassTeacher, {
|
||||||
|
classId,
|
||||||
|
userId,
|
||||||
|
roleType: 'teacher',
|
||||||
|
});
|
||||||
|
await manager.save(ct);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const studentRecord = await manager.findOne(Student, { where: { userId: user.id } });
|
const studentRecord = await manager.findOne(Student, {
|
||||||
if (!studentRecord) continue;
|
where: { userId },
|
||||||
const cs = manager.create(ClassStudent, {
|
|
||||||
classId,
|
|
||||||
studentId: studentRecord.id,
|
|
||||||
status: 'active',
|
|
||||||
});
|
});
|
||||||
await manager.save(cs);
|
if (!studentRecord) continue;
|
||||||
|
const existingCS = await manager.findOne(ClassStudent, {
|
||||||
|
where: { classId, studentId: studentRecord.id },
|
||||||
|
});
|
||||||
|
if (!existingCS) {
|
||||||
|
const cs = manager.create(ClassStudent, {
|
||||||
|
classId,
|
||||||
|
studentId: studentRecord.id,
|
||||||
|
status: 'active',
|
||||||
|
});
|
||||||
|
await manager.save(cs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user