fix: resolve 9 P0+P1 review findings (bills decorators, campus isolation bypass, ding raw crash, batch import counter, SSE multi-tab, interval leak, dept permissions, cycle prevention, tree depth)
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
AssignUserDto,
|
||||
} from './dto/department.dto';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('departments')
|
||||
@@ -23,26 +24,31 @@ export class DepartmentsController {
|
||||
constructor(private readonly departmentsService: DepartmentsService) {}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('department:view')
|
||||
findAll() {
|
||||
return this.departmentsService.findAll();
|
||||
}
|
||||
|
||||
@Get('tree')
|
||||
@RequirePermission('department:view')
|
||||
findTree() {
|
||||
return this.departmentsService.findTree();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RequirePermission('department:view')
|
||||
findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.departmentsService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RequirePermission('department:edit')
|
||||
create(@Body() dto: CreateDepartmentDto) {
|
||||
return this.departmentsService.create(dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@RequirePermission('department:edit')
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: UpdateDepartmentDto,
|
||||
@@ -51,16 +57,19 @@ export class DepartmentsController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('department:delete')
|
||||
remove(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.departmentsService.remove(id);
|
||||
}
|
||||
|
||||
@Get(':id/users')
|
||||
@RequirePermission('department:view')
|
||||
getUsers(@Param('id', ParseIntPipe) id: number) {
|
||||
return this.departmentsService.getUsers(id);
|
||||
}
|
||||
|
||||
@Post(':id/users')
|
||||
@RequirePermission('department:edit')
|
||||
assignUser(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() dto: AssignUserDto,
|
||||
@@ -69,6 +78,7 @@ export class DepartmentsController {
|
||||
}
|
||||
|
||||
@Delete(':id/users/:userId')
|
||||
@RequirePermission('department:delete')
|
||||
removeUser(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Param('userId', ParseIntPipe) userId: number,
|
||||
|
||||
@@ -22,12 +22,33 @@ export class DepartmentsService {
|
||||
}
|
||||
|
||||
async findTree(): Promise<Department[]> {
|
||||
// Load ALL departments flat (no relations — avoids N+1 and only loads 1 level),
|
||||
// then build the full tree in memory.
|
||||
const all = await this.deptRepo.find({
|
||||
where: { status: 'active' },
|
||||
order: { sortOrder: 'ASC', name: 'ASC' },
|
||||
relations: ['children'],
|
||||
});
|
||||
return all.filter((d) => d.parentId === null);
|
||||
|
||||
const byParent = new Map<number | null, Department[]>();
|
||||
for (const dept of all) {
|
||||
const key = dept.parentId ?? null;
|
||||
const list = byParent.get(key);
|
||||
if (list) {
|
||||
list.push(dept);
|
||||
} else {
|
||||
byParent.set(key, [dept]);
|
||||
}
|
||||
}
|
||||
|
||||
const attachChildren = (dept: Department): void => {
|
||||
const children = byParent.get(dept.id) ?? [];
|
||||
dept.children = children;
|
||||
for (const child of children) attachChildren(child);
|
||||
};
|
||||
|
||||
const roots = byParent.get(null) ?? [];
|
||||
for (const root of roots) attachChildren(root);
|
||||
return roots;
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<Department> {
|
||||
@@ -43,6 +64,19 @@ export class DepartmentsService {
|
||||
|
||||
async update(id: number, dto: UpdateDepartmentDto): Promise<Department> {
|
||||
const dept = await this.findOne(id);
|
||||
|
||||
// Prevent parent cycles: parentId must not be the dept itself or one of its descendants
|
||||
if (dto.parentId !== undefined && dto.parentId !== null) {
|
||||
const newParentId = dto.parentId;
|
||||
if (newParentId === id) {
|
||||
throw new ConflictException('不能将部门的上级设为自身');
|
||||
}
|
||||
const descendantIds = await this.getDescendantIds(id);
|
||||
if (descendantIds.includes(newParentId)) {
|
||||
throw new ConflictException('不能将部门的上级设为其子部门,会形成循环');
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(dept, dto);
|
||||
return this.deptRepo.save(dept);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user