|
|
@@ -21,8 +21,7 @@ import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
+import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@Service
|
|
|
@@ -59,23 +58,78 @@ public class ProjectServiceImpl implements ProjectService {
|
|
|
return voPage;
|
|
|
}
|
|
|
|
|
|
- private Map<Long, Long> countResourcesByProject(List<Long> projectIds) {
|
|
|
- if (projectIds.isEmpty()) {
|
|
|
- return Map.of();
|
|
|
+ @Override
|
|
|
+ public List<ProjectVO> tree(ProjectQuery query) {
|
|
|
+ LambdaQueryWrapper<Project> wrapper = new LambdaQueryWrapper<Project>()
|
|
|
+ .like(query.getName() != null && !query.getName().isBlank(), Project::getName, query.getName())
|
|
|
+ .eq(query.getOwnerId() != null, Project::getOwnerId, query.getOwnerId())
|
|
|
+ .eq(query.getStatus() != null, Project::getStatus, query.getStatus());
|
|
|
+
|
|
|
+ List<Project> all = projectMapper.selectList(wrapper);
|
|
|
+ if (all.isEmpty()) return List.of();
|
|
|
+
|
|
|
+ List<Long> ids = all.stream().map(Project::getId).toList();
|
|
|
+ Map<Long, Long> directCount = countResourcesByProject(ids);
|
|
|
+ Map<Long, String> ownerNameMap = sysUserService.getUserNameMap(
|
|
|
+ all.stream().map(Project::getOwnerId).distinct().toList());
|
|
|
+
|
|
|
+ Map<Long, List<Project>> childrenMap = all.stream()
|
|
|
+ .filter(p -> p.getParentId() != null && p.getParentId() != 0)
|
|
|
+ .collect(Collectors.groupingBy(Project::getParentId));
|
|
|
+
|
|
|
+ List<ProjectVO> roots = new ArrayList<>();
|
|
|
+ for (Project p : all) {
|
|
|
+ if (p.getParentId() == null || p.getParentId() == 0) {
|
|
|
+ roots.add(toNode(p, ownerNameMap, directCount, childrenMap, 1));
|
|
|
+ }
|
|
|
}
|
|
|
- List<ProjectResource> relations = projectResourceMapper.selectList(
|
|
|
- new LambdaQueryWrapper<ProjectResource>().in(ProjectResource::getProjectId, projectIds));
|
|
|
- return relations.stream().collect(Collectors.groupingBy(ProjectResource::getProjectId, Collectors.counting()));
|
|
|
+ boolean hasFilter = (query.getName() != null && !query.getName().isBlank())
|
|
|
+ || query.getOwnerId() != null || query.getStatus() != null;
|
|
|
+ if (hasFilter) {
|
|
|
+ roots.removeIf(r -> !subtreeMatches(r, query));
|
|
|
+ }
|
|
|
+ return roots;
|
|
|
}
|
|
|
|
|
|
- private ProjectVO toBaseVO(Project p, Map<Long, String> ownerNameMap) {
|
|
|
+ private boolean subtreeMatches(ProjectVO vo, ProjectQuery q) {
|
|
|
+ if (nodeMatches(vo, q)) return true;
|
|
|
+ if (vo.getChildren() != null) {
|
|
|
+ for (ProjectVO c : vo.getChildren()) if (subtreeMatches(c, q)) return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean nodeMatches(ProjectVO vo, ProjectQuery q) {
|
|
|
+ if (q.getName() != null && !q.getName().isBlank() && !vo.getName().contains(q.getName())) return false;
|
|
|
+ if (q.getOwnerId() != null && !q.getOwnerId().equals(vo.getOwnerId())) return false;
|
|
|
+ if (q.getStatus() != null && !q.getStatus().equals(vo.getStatus())) return false;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ProjectVO toNode(Project p, Map<Long, String> ownerNameMap, Map<Long, Long> directCount,
|
|
|
+ Map<Long, List<Project>> childrenMap, int level) {
|
|
|
ProjectVO vo = new ProjectVO();
|
|
|
vo.setId(p.getId());
|
|
|
vo.setName(p.getName());
|
|
|
+ vo.setParentId(p.getParentId());
|
|
|
vo.setOwnerId(p.getOwnerId());
|
|
|
vo.setOwnerName(ownerNameMap.get(p.getOwnerId()));
|
|
|
vo.setStatus(p.getStatus());
|
|
|
vo.setRemark(p.getRemark());
|
|
|
+ List<Project> children = childrenMap.getOrDefault(p.getId(), List.of());
|
|
|
+ boolean leaf = children.isEmpty();
|
|
|
+ vo.setLevel(level);
|
|
|
+ vo.setIsLeaf(leaf);
|
|
|
+ if (leaf) {
|
|
|
+ vo.setResourceCount(directCount.getOrDefault(p.getId(), 0L).intValue());
|
|
|
+ vo.setChildren(List.of());
|
|
|
+ } else {
|
|
|
+ List<ProjectVO> childVos = children.stream()
|
|
|
+ .map(c -> toNode(c, ownerNameMap, directCount, childrenMap, level + 1)).toList();
|
|
|
+ int agg = childVos.stream().mapToInt(ProjectVO::getResourceCount).sum();
|
|
|
+ vo.setResourceCount(agg);
|
|
|
+ vo.setChildren(childVos);
|
|
|
+ }
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
@@ -86,15 +140,49 @@ public class ProjectServiceImpl implements ProjectService {
|
|
|
throw new BusinessException("项目不存在");
|
|
|
}
|
|
|
Map<Long, String> ownerNameMap = sysUserService.getUserNameMap(List.of(project.getOwnerId()));
|
|
|
- ProjectVO vo = toBaseVO(project, ownerNameMap);
|
|
|
|
|
|
- List<Long> resourceIds = projectResourceMapper.selectList(
|
|
|
- new LambdaQueryWrapper<ProjectResource>().eq(ProjectResource::getProjectId, id))
|
|
|
- .stream().map(ProjectResource::getResourceId).toList();
|
|
|
+ ProjectVO vo = new ProjectVO();
|
|
|
+ vo.setId(project.getId());
|
|
|
+ vo.setName(project.getName());
|
|
|
+ vo.setParentId(project.getParentId());
|
|
|
+ vo.setOwnerId(project.getOwnerId());
|
|
|
+ vo.setOwnerName(ownerNameMap.get(project.getOwnerId()));
|
|
|
+ vo.setStatus(project.getStatus());
|
|
|
+ vo.setRemark(project.getRemark());
|
|
|
+ vo.setLevel(project.getParentId() == null || project.getParentId() == 0 ? 1 : 2);
|
|
|
+
|
|
|
+ List<Project> children = projectMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Project>().eq(Project::getParentId, id));
|
|
|
+ vo.setIsLeaf(children.isEmpty());
|
|
|
+
|
|
|
+ // 资源只挂在叶子(子项目); 父项目聚合所有叶子后代的资源
|
|
|
+ List<Long> leafIds = children.isEmpty() ? List.of(id) : children.stream().map(Project::getId).toList();
|
|
|
+ Map<Long, Long> directCount = countResourcesByProject(leafIds);
|
|
|
|
|
|
- List<ResourceVO> resourceVOs = resourceService.listByIds(resourceIds);
|
|
|
- vo.setResources(resourceVOs);
|
|
|
- vo.setResourceCount(resourceVOs.size());
|
|
|
+ if (!children.isEmpty()) {
|
|
|
+ vo.setChildren(children.stream().map(c -> {
|
|
|
+ ProjectVO cv = new ProjectVO();
|
|
|
+ cv.setId(c.getId());
|
|
|
+ cv.setName(c.getName());
|
|
|
+ cv.setParentId(c.getParentId());
|
|
|
+ cv.setOwnerId(c.getOwnerId());
|
|
|
+ cv.setOwnerName(ownerNameMap.get(c.getOwnerId()));
|
|
|
+ cv.setStatus(c.getStatus());
|
|
|
+ cv.setRemark(c.getRemark());
|
|
|
+ cv.setLevel(2);
|
|
|
+ cv.setIsLeaf(true);
|
|
|
+ cv.setChildren(List.of());
|
|
|
+ cv.setResourceCount(directCount.getOrDefault(c.getId(), 0L).intValue());
|
|
|
+ return cv;
|
|
|
+ }).toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Long> resIds = projectResourceMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProjectResource>().in(ProjectResource::getProjectId, leafIds))
|
|
|
+ .stream().map(ProjectResource::getResourceId).toList();
|
|
|
+ List<ResourceVO> resources = resourceService.listByIds(resIds);
|
|
|
+ vo.setResources(resources);
|
|
|
+ vo.setResourceCount(resources.size());
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
@@ -106,7 +194,31 @@ public class ProjectServiceImpl implements ProjectService {
|
|
|
if (!isNew && project == null) {
|
|
|
throw new BusinessException("项目不存在");
|
|
|
}
|
|
|
+
|
|
|
+ Long parentId = dto.getParentId() == null ? 0L : dto.getParentId();
|
|
|
+ if (parentId != 0L) {
|
|
|
+ Project parent = projectMapper.selectById(parentId);
|
|
|
+ if (parent == null) {
|
|
|
+ throw new BusinessException("父项目不存在");
|
|
|
+ }
|
|
|
+ if (parent.getParentId() != null && parent.getParentId() != 0L) {
|
|
|
+ throw new BusinessException("父项目必须是顶层项目(仅支持两层结构)");
|
|
|
+ }
|
|
|
+ if (!isNew && parentId.equals(dto.getId())) {
|
|
|
+ throw new BusinessException("不能选择自己作为父项目");
|
|
|
+ }
|
|
|
+ // 若当前项目已有子项目, 不能再设为别人的子项目(会导致三层)
|
|
|
+ if (!isNew) {
|
|
|
+ long childCount = projectMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<Project>().eq(Project::getParentId, dto.getId()));
|
|
|
+ if (childCount > 0) {
|
|
|
+ throw new BusinessException("该项目下还有子项目, 不能设为子项目");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
project.setName(dto.getName());
|
|
|
+ project.setParentId(parentId);
|
|
|
project.setOwnerId(dto.getOwnerId());
|
|
|
project.setStatus(dto.getStatus() != null ? dto.getStatus() : ProjectStatus.ACTIVE.getCode());
|
|
|
project.setRemark(dto.getRemark());
|
|
|
@@ -123,14 +235,59 @@ public class ProjectServiceImpl implements ProjectService {
|
|
|
@Override
|
|
|
@Transactional
|
|
|
public void delete(Long id) {
|
|
|
- projectMapper.deleteById(id);
|
|
|
+ Project project = projectMapper.selectById(id);
|
|
|
+ if (project == null) {
|
|
|
+ throw new BusinessException("项目不存在");
|
|
|
+ }
|
|
|
+ // 级联收集: 自身 + 所有子项目(仅两层)
|
|
|
+ List<Long> ids = new ArrayList<>();
|
|
|
+ ids.add(id);
|
|
|
+ List<Project> children = projectMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Project>().eq(Project::getParentId, id));
|
|
|
+ children.forEach(c -> ids.add(c.getId()));
|
|
|
+
|
|
|
projectResourceMapper.delete(
|
|
|
- new LambdaQueryWrapper<ProjectResource>().eq(ProjectResource::getProjectId, id));
|
|
|
- operationLogService.record("项目", "删除", id, "项目id: " + id);
|
|
|
+ new LambdaQueryWrapper<ProjectResource>().in(ProjectResource::getProjectId, ids));
|
|
|
+ projectMapper.deleteBatchIds(ids);
|
|
|
+ operationLogService.record("项目", "删除", id, "项目id: " + id + "(含子项目" + children.size() + "个)");
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public List<Project> listActive() {
|
|
|
- return projectMapper.selectList(new LambdaQueryWrapper<Project>().eq(Project::getStatus, ProjectStatus.ACTIVE.getCode()));
|
|
|
+ // 资源只挂在叶子(子项目), 故下拉只返回叶子
|
|
|
+ List<Project> all = projectMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Project>().eq(Project::getStatus, ProjectStatus.ACTIVE.getCode()));
|
|
|
+ Set<Long> parentIds = all.stream()
|
|
|
+ .map(Project::getParentId)
|
|
|
+ .filter(pid -> pid != null && pid != 0L)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ return all.stream().filter(p -> !parentIds.contains(p.getId())).toList();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Project> listTopLevel() {
|
|
|
+ return projectMapper.selectList(new LambdaQueryWrapper<Project>()
|
|
|
+ .eq(Project::getStatus, ProjectStatus.ACTIVE.getCode())
|
|
|
+ .and(w -> w.eq(Project::getParentId, 0L).or().isNull(Project::getParentId)));
|
|
|
+ }
|
|
|
+
|
|
|
+ private ProjectVO toBaseVO(Project p, Map<Long, String> ownerNameMap) {
|
|
|
+ ProjectVO vo = new ProjectVO();
|
|
|
+ vo.setId(p.getId());
|
|
|
+ vo.setName(p.getName());
|
|
|
+ vo.setOwnerId(p.getOwnerId());
|
|
|
+ vo.setOwnerName(ownerNameMap.get(p.getOwnerId()));
|
|
|
+ vo.setStatus(p.getStatus());
|
|
|
+ vo.setRemark(p.getRemark());
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Long, Long> countResourcesByProject(List<Long> projectIds) {
|
|
|
+ if (projectIds.isEmpty()) {
|
|
|
+ return Map.of();
|
|
|
+ }
|
|
|
+ List<ProjectResource> relations = projectResourceMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProjectResource>().in(ProjectResource::getProjectId, projectIds));
|
|
|
+ return relations.stream().collect(Collectors.groupingBy(ProjectResource::getProjectId, Collectors.counting()));
|
|
|
}
|
|
|
}
|