package cn.yinlihupo.service.project.impl; import cn.hutool.core.util.IdUtil; import cn.yinlihupo.common.page.TableDataInfo; import cn.yinlihupo.domain.entity.*; import cn.yinlihupo.domain.vo.*; import cn.yinlihupo.mapper.*; import cn.yinlihupo.service.oss.OssService; import cn.yinlihupo.service.project.ProjectService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.YearMonth; import java.util.*; import java.util.stream.Collectors; /** * AI项目初始化服务实现类 * 使用Spring AI结构化输出能力解析项目文档 */ @Slf4j @Service @RequiredArgsConstructor public class ProjectServiceImpl implements ProjectService { private final ChatClient chatClient; private final OssService ossService; // Mapper依赖 private final ProjectMapper projectMapper; private final ProjectMilestoneMapper projectMilestoneMapper; private final TaskMapper taskMapper; private final TaskDependencyMapper taskDependencyMapper; private final ProjectMemberMapper projectMemberMapper; private final ResourceMapper resourceMapper; private final RiskMapper riskMapper; private final ProjectTimelineMapper projectTimelineMapper; private final SysUserMapper sysUserMapper; /** * 项目初始化系统提示词模板 */ private static final String PROJECT_INIT_SYSTEM_PROMPT = """ # 角色 你是一个专业的项目管理助手,擅长从项目文档中提取结构化信息,自动生成项目计划。 # 任务 根据用户提供的项目资料,解析并生成以下结构化数据: 1. 项目基本信息(名称、周期、预算、目标) 2. 项目里程碑(关键节点) 3. 任务清单(WBS工作分解结构) 4. 项目成员及角色 5. 资源需求 6. 风险识别 # 输出格式 请严格按照以下JSON格式输出: ```json { "project": { "project_name": "项目名称", "project_type": "工程项目/研发项目/运营项目", "description": "项目描述", "objectives": "项目目标", "plan_start_date": "2024-01-01", "plan_end_date": "2024-12-31", "budget": 1000000, "currency": "CNY", "priority": "high/medium/low", "tags": ["标签1", "标签2"] }, "milestones": [ { "milestone_name": "里程碑名称", "description": "描述", "plan_date": "2024-03-01", "deliverables": "交付物", "owner_role": "负责人角色" } ], "tasks": [ { "task_id": "T001", "task_name": "任务名称", "parent_task_id": null, "description": "任务描述", "plan_start_date": "2024-01-15", "plan_end_date": "2024-02-15", "estimated_hours": 80, "priority": "high", "assignee_role": "执行者角色", "dependencies": ["T001"], "deliverables": "交付物" } ], "members": [ { "name": "姓名", "role_code": "manager/leader/member", "responsibility": "职责描述", "department": "部门", "weekly_hours": 40 } ], "resources": [ { "resource_name": "资源名称", "resource_type": "human/material/equipment/software/finance", "quantity": 1, "unit": "单位", "unit_price": 1000, "supplier": "供应商" } ], "risks": [ { "risk_name": "风险名称", "category": "technical/schedule/cost/quality/resource/external", "description": "风险描述", "probability": 60, "impact": 4, "mitigation_plan": "缓解措施" } ], "timeline_nodes": [ { "node_name": "时间节点名称", "node_type": "milestone/phase/event", "plan_date": "2024-06-01", "description": "描述", "kb_scope": ["report", "file", "risk", "ticket"] } ] } ``` # 注意事项 1. 日期格式统一使用 YYYY-MM-DD 2. 任务之间要建立合理的依赖关系 3. 里程碑应该是关键节点,不宜过多 4. 资源要考虑人力、物料、设备等 5. 风险识别要基于项目特点 6. 如果文档信息不完整,根据项目类型合理推断 """; /** * 用户提示词模板 */ private static final String USER_PROMPT_TEMPLATE = """ 请根据以下项目资料,生成完整的项目初始化结构化数据: {content} 请严格按照系统提示词中的JSON格式输出,确保所有字段都包含合理的值。 """; @Override public ProjectInitResult generateProjectFromContent(String content) { log.info("开始根据内容生成项目初始化数据"); // 构建用户提示词,直接将内容嵌入 String userPrompt = "请根据以下项目资料,生成完整的项目初始化结构化数据:\n\n" + content + "\n\n" + "请严格按照系统提示词中的JSON格式输出,确保所有字段都包含合理的值。"; return chatClient.prompt() .system(PROJECT_INIT_SYSTEM_PROMPT) .user(userPrompt) .call() .entity(ProjectInitResult.class); } @Override public ProjectInitResult generateProjectFromFile(String fileUrl, String fileType) { log.info("开始根据文件生成项目初始化数据, fileUrl: {}, fileType: {}", fileUrl, fileType); // 从OSS下载文件内容 String content = ossService.readFileAsString(fileUrl); if (content == null || content.isEmpty()) { throw new RuntimeException("无法读取文件内容: " + fileUrl); } return generateProjectFromContent(content); } @Override public ProjectInitResult generateProjectPreview(MultipartFile file) { log.info("开始上传文件并生成项目初始化预览数据, 文件名: {}", file.getOriginalFilename()); try { // 1. 上传文件到OSS String fileUrl = ossService.uploadFile(file, file.getOriginalFilename()); log.info("文件上传成功, URL: {}", fileUrl); // 2. 读取文件内容 String content = ossService.readFileAsString(fileUrl); if (content == null || content.isEmpty()) { throw new RuntimeException("无法读取文件内容: " + fileUrl); } // 3. 调用AI生成项目预览数据(不入库) return generateProjectFromContent(content); } catch (Exception e) { log.error("项目预览生成失败: {}", e.getMessage(), e); throw new RuntimeException("项目预览生成失败: " + e.getMessage(), e); } } @Override @Transactional(rollbackFor = Exception.class) public ProjectInitResult saveProjectData(ProjectInitResult result) { log.info("开始保存项目初始化数据"); if (result == null || result.getProject() == null) { throw new RuntimeException("项目数据为空"); } try { // 保存项目数据到数据库 Long projectId = saveProjectDataToDb(result); log.info("项目初始化数据保存成功, projectId: {}", projectId); return result; } catch (Exception e) { log.error("项目保存失败: {}", e.getMessage(), e); throw new RuntimeException("项目保存失败: " + e.getMessage(), e); } } /** * 保存项目数据到数据库 * * @param result AI生成的项目初始化结果 * @return 项目ID */ private Long saveProjectDataToDb(ProjectInitResult result) { if (result == null || result.getProject() == null) { throw new RuntimeException("项目数据为空"); } // 1. 保存项目基本信息 Project project = convertToProject(result.getProject()); project.setProjectCode(generateProjectCode()); project.setStatus("planning"); // 初始化后状态为规划中 project.setProgress(0); project.setVisibility(1); projectMapper.insert(project); Long projectId = project.getId(); log.info("项目基本信息保存成功, projectId: {}", projectId); // 2. 保存里程碑 Map milestoneIndexToId = new HashMap<>(); if (result.getMilestones() != null && !result.getMilestones().isEmpty()) { for (int i = 0; i < result.getMilestones().size(); i++) { ProjectInitResult.MilestoneInfo milestoneInfo = result.getMilestones().get(i); ProjectMilestone milestone = convertToMilestone(milestoneInfo, projectId); milestone.setSortOrder(i); projectMilestoneMapper.insert(milestone); milestoneIndexToId.put(i, milestone.getId()); } log.info("保存了 {} 个里程碑", result.getMilestones().size()); } // 3. 保存任务(支持层级关系) Map taskTempIdToId = new HashMap<>(); if (result.getTasks() != null && !result.getTasks().isEmpty()) { // 第一轮:保存所有任务,建立临时ID到真实ID的映射 for (int i = 0; i < result.getTasks().size(); i++) { ProjectInitResult.TaskInfo taskInfo = result.getTasks().get(i); Task task = convertToTask(taskInfo, projectId); // 设置里程碑关联(按索引匹配) // 这里简化处理,可以根据任务时间范围匹配里程碑 if (!milestoneIndexToId.isEmpty()) { // 简单策略:将任务平均分配到各个里程碑 int milestoneIndex = i % milestoneIndexToId.size(); task.setMilestoneId(milestoneIndexToId.get(milestoneIndex)); } task.setSortOrder(i); taskMapper.insert(task); // 使用task_id作为临时ID进行映射 String tempId = taskInfo.getTaskId(); if (tempId != null && !tempId.isEmpty()) { taskTempIdToId.put(tempId, task.getId()); } else { // 兼容旧格式:如果没有task_id,使用task_name taskTempIdToId.put(taskInfo.getTaskName(), task.getId()); } } // 第二轮:保存任务层级关系(parent_task_id)到 task_dependency 表 int hierarchyCount = 0; for (int i = 0; i < result.getTasks().size(); i++) { ProjectInitResult.TaskInfo taskInfo = result.getTasks().get(i); if (taskInfo.getParentTaskId() != null && !taskInfo.getParentTaskId().isEmpty()) { Long taskId = taskTempIdToId.get(taskInfo.getTaskId()); Long parentId = taskTempIdToId.get(taskInfo.getParentTaskId()); if (taskId != null && parentId != null) { TaskDependency dependency = new TaskDependency(); dependency.setTaskId(taskId); dependency.setDependsOnTaskId(parentId); dependency.setDependencyType("hierarchy"); // 层级关系 dependency.setLagDays(0); taskDependencyMapper.insert(dependency); hierarchyCount++; } } } // 第三轮:保存任务执行依赖关系(dependencies)到 task_dependency 表 int dependencyCount = 0; for (int i = 0; i < result.getTasks().size(); i++) { ProjectInitResult.TaskInfo taskInfo = result.getTasks().get(i); if (taskInfo.getDependencies() != null && !taskInfo.getDependencies().isEmpty()) { Long taskId = taskTempIdToId.get(taskInfo.getTaskId()); for (String dependsOnTaskId : taskInfo.getDependencies()) { Long dependsOnId = taskTempIdToId.get(dependsOnTaskId); if (dependsOnId != null && taskId != null) { TaskDependency dependency = new TaskDependency(); dependency.setTaskId(taskId); dependency.setDependsOnTaskId(dependsOnId); dependency.setDependencyType("finish_to_start"); // 默认依赖类型 dependency.setLagDays(0); taskDependencyMapper.insert(dependency); dependencyCount++; } } } } log.info("保存了 {} 个任务, {} 个层级关系, {} 个执行依赖关系", result.getTasks().size(), hierarchyCount, dependencyCount); } // 4. 保存项目成员 if (result.getMembers() != null && !result.getMembers().isEmpty()) { for (ProjectInitResult.MemberInfo memberInfo : result.getMembers()) { ProjectMember member = convertToMember(memberInfo, projectId); projectMemberMapper.insert(member); } log.info("保存了 {} 个成员", result.getMembers().size()); } // 5. 保存资源 if (result.getResources() != null && !result.getResources().isEmpty()) { for (ProjectInitResult.ResourceInfo resourceInfo : result.getResources()) { Resource resource = convertToResource(resourceInfo, projectId); resourceMapper.insert(resource); } log.info("保存了 {} 个资源", result.getResources().size()); } // 6. 保存风险 if (result.getRisks() != null && !result.getRisks().isEmpty()) { for (ProjectInitResult.RiskInfo riskInfo : result.getRisks()) { Risk risk = convertToRisk(riskInfo, projectId); riskMapper.insert(risk); } log.info("保存了 {} 个风险", result.getRisks().size()); } // 7. 保存时间节点 if (result.getTimelineNodes() != null && !result.getTimelineNodes().isEmpty()) { for (int i = 0; i < result.getTimelineNodes().size(); i++) { ProjectInitResult.TimelineNodeInfo nodeInfo = result.getTimelineNodes().get(i); ProjectTimeline timeline = convertToTimeline(nodeInfo, projectId); timeline.setSortOrder(i); projectTimelineMapper.insert(timeline); } log.info("保存了 {} 个时间节点", result.getTimelineNodes().size()); } return projectId; } /** * 生成项目编号 */ private String generateProjectCode() { return "PRJ" + IdUtil.fastSimpleUUID().substring(0, 12).toUpperCase(); } /** * 转换项目信息 */ private Project convertToProject(ProjectInitResult.ProjectInfo info) { Project project = new Project(); project.setProjectName(info.getProjectName()); project.setProjectType(info.getProjectType()); project.setDescription(info.getDescription()); project.setObjectives(info.getObjectives()); project.setPlanStartDate(info.getPlanStartDate()); project.setPlanEndDate(info.getPlanEndDate()); project.setBudget(info.getBudget()); project.setCurrency(info.getCurrency()); project.setPriority(info.getPriority()); project.setTags(info.getTags()); return project; } /** * 转换里程碑信息 */ private ProjectMilestone convertToMilestone(ProjectInitResult.MilestoneInfo info, Long projectId) { ProjectMilestone milestone = new ProjectMilestone(); milestone.setProjectId(projectId); milestone.setMilestoneName(info.getMilestoneName()); milestone.setDescription(info.getDescription()); milestone.setPlanDate(info.getPlanDate()); milestone.setStatus("pending"); milestone.setProgress(0); milestone.setIsKey(0); if (info.getDeliverables() != null && !info.getDeliverables().isEmpty()) { milestone.setDeliverables(Arrays.asList(info.getDeliverables().split(","))); } return milestone; } /** * 转换任务信息 */ private Task convertToTask(ProjectInitResult.TaskInfo info, Long projectId) { Task task = new Task(); task.setProjectId(projectId); task.setTaskName(info.getTaskName()); task.setDescription(info.getDescription()); task.setPlanStartDate(info.getPlanStartDate()); task.setPlanEndDate(info.getPlanEndDate()); if (info.getEstimatedHours() != null) { task.setPlanHours(BigDecimal.valueOf(info.getEstimatedHours())); } task.setPriority(info.getPriority()); task.setStatus("pending"); task.setProgress(0); return task; } /** * 转换成员信息 */ private ProjectMember convertToMember(ProjectInitResult.MemberInfo info, Long projectId) { ProjectMember member = new ProjectMember(); member.setProjectId(projectId); member.setRoleCode(info.getRoleCode()); member.setResponsibility(info.getResponsibility()); if (info.getWeeklyHours() != null) { member.setWeeklyHours(BigDecimal.valueOf(info.getWeeklyHours())); } member.setStatus(1); // 根据成员名字匹配 sys_user 用户 if (info.getName() != null && !info.getName().isEmpty()) { SysUser user = sysUserMapper.selectByRealName(info.getName()); if (user != null) { member.setUserId(user.getId()); log.debug("成员 '{}' 匹配到用户 ID: {}", info.getName(), user.getId()); } else { log.warn("成员 '{}' 未在系统中找到匹配的用户", info.getName()); } } return member; } /** * 转换资源信息 */ private Resource convertToResource(ProjectInitResult.ResourceInfo info, Long projectId) { Resource resource = new Resource(); resource.setProjectId(projectId); resource.setResourceName(info.getResourceName()); resource.setResourceType(info.getResourceType()); resource.setPlanQuantity(info.getQuantity()); resource.setUnit(info.getUnit()); resource.setUnitPrice(info.getUnitPrice()); resource.setSupplier(info.getSupplier()); resource.setStatus("planned"); resource.setCurrency("CNY"); return resource; } /** * 转换风险信息 */ private Risk convertToRisk(ProjectInitResult.RiskInfo info, Long projectId) { Risk risk = new Risk(); risk.setProjectId(projectId); risk.setRiskName(info.getRiskName()); risk.setCategory(info.getCategory()); risk.setDescription(info.getDescription()); risk.setRiskSource("ai_detection"); if (info.getProbability() != null) { risk.setProbability(BigDecimal.valueOf(info.getProbability())); } if (info.getImpact() != null) { risk.setImpact(BigDecimal.valueOf(info.getImpact())); } // 计算风险得分 if (risk.getProbability() != null && risk.getImpact() != null) { BigDecimal score = risk.getProbability() .multiply(risk.getImpact()) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); risk.setRiskScore(score); } // 根据概率和影响确定风险等级 risk.setRiskLevel(calculateRiskLevel(info.getProbability(), info.getImpact())); risk.setStatus("identified"); risk.setMitigationPlan(info.getMitigationPlan()); risk.setDiscoverTime(LocalDateTime.now()); return risk; } /** * 计算风险等级 */ private String calculateRiskLevel(Integer probability, Integer impact) { if (probability == null || impact == null) { return "low"; } int score = probability * impact; if (score >= 300) { return "critical"; } else if (score >= 200) { return "high"; } else if (score >= 100) { return "medium"; } else { return "low"; } } /** * 转换时间节点信息 */ private ProjectTimeline convertToTimeline(ProjectInitResult.TimelineNodeInfo info, Long projectId) { ProjectTimeline timeline = new ProjectTimeline(); timeline.setProjectId(projectId); timeline.setNodeName(info.getNodeName()); timeline.setNodeType(info.getNodeType()); timeline.setPlanDate(info.getPlanDate()); timeline.setDescription(info.getDescription()); timeline.setStatus("pending"); timeline.setKbScope(info.getKbScope()); return timeline; } // ==================== 项目查询相关实现 ==================== @Override public TableDataInfo getMyProjectList(Long userId, Integer pageNum, Integer pageSize, String keyword, String status) { log.info("查询我的项目列表, userId: {}, pageNum: {}, pageSize: {}", userId, pageNum, pageSize); // 使用分页插件 Page page = new Page<>(pageNum, pageSize); // 构建查询条件 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Project::getDeleted, 0); // 关键词搜索 if (keyword != null && !keyword.isEmpty()) { wrapper.and(w -> w.like(Project::getProjectName, keyword) .or() .like(Project::getProjectCode, keyword)); } // 状态筛选 if (status != null && !status.isEmpty()) { wrapper.eq(Project::getStatus, status); } // 查询我管理或参与的项目 // 先查询我参与的项目ID列表 List myProjectIds = projectMemberMapper.selectList( new LambdaQueryWrapper() .eq(ProjectMember::getUserId, userId) .eq(ProjectMember::getDeleted, 0) .eq(ProjectMember::getStatus, 1) ).stream().map(ProjectMember::getProjectId).collect(Collectors.toList()); // 查询条件:我管理的项目 或 我参与的项目 wrapper.and(w -> w.eq(Project::getManagerId, userId) .or() .in(!myProjectIds.isEmpty(), Project::getId, myProjectIds)); wrapper.orderByDesc(Project::getCreateTime); Page projectPage = projectMapper.selectPage(page, wrapper); // 转换为VO List voList = projectPage.getRecords().stream() .map(this::convertToProjectListVO) .collect(Collectors.toList()); return TableDataInfo.build(new Page(projectPage.getCurrent(), projectPage.getSize(), projectPage.getTotal()) .setRecords(voList)); } @Override public ProjectGanttVO getProjectGantt(Long projectId) { log.info("查询项目甘特图数据, projectId: {}", projectId); // 1. 查询项目基本信息 Project project = projectMapper.selectById(projectId); if (project == null || project.getDeleted() == 1) { throw new RuntimeException("项目不存在"); } ProjectGanttVO ganttVO = new ProjectGanttVO(); ganttVO.setProjectId(project.getId()); ganttVO.setProjectName(project.getProjectName()); ganttVO.setProjectStatus(project.getStatus()); ganttVO.setProjectStartDate(project.getPlanStartDate()); ganttVO.setProjectEndDate(project.getPlanEndDate()); ganttVO.setProjectProgress(project.getProgress()); // 2. 查询里程碑 List milestones = projectMilestoneMapper.selectList( new LambdaQueryWrapper() .eq(ProjectMilestone::getProjectId, projectId) .eq(ProjectMilestone::getDeleted, 0) .orderByAsc(ProjectMilestone::getSortOrder) ); List milestoneVOList = milestones.stream() .map(this::convertMilestoneToGanttTask) .collect(Collectors.toList()); ganttVO.setMilestones(milestoneVOList); // 3. 查询任务 List tasks = taskMapper.selectList( new LambdaQueryWrapper() .eq(Task::getProjectId, projectId) .eq(Task::getDeleted, 0) .orderByAsc(Task::getSortOrder) ); List taskVOList = tasks.stream() .map(this::convertTaskToGanttTask) .collect(Collectors.toList()); ganttVO.setTasks(taskVOList); return ganttVO; } @Override public ProjectStatisticsVO getProjectStatistics(Long userId) { log.info("查询项目统计信息, userId: {}", userId); ProjectStatisticsVO statistics = new ProjectStatisticsVO(); // 构建基础查询条件 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Project::getDeleted, 0); // 如果指定了用户ID,只统计该用户的项目 if (userId != null) { List myProjectIds = projectMemberMapper.selectList( new LambdaQueryWrapper() .eq(ProjectMember::getUserId, userId) .eq(ProjectMember::getDeleted, 0) .eq(ProjectMember::getStatus, 1) ).stream().map(ProjectMember::getProjectId).collect(Collectors.toList()); wrapper.and(w -> w.eq(Project::getManagerId, userId) .or() .in(!myProjectIds.isEmpty(), Project::getId, myProjectIds)); } // 1. 查询所有符合条件的项目 List projects = projectMapper.selectList(wrapper); // 2. 统计总数 statistics.setTotalCount(projects.size()); // 3. 按状态统计 Map statusCountMap = projects.stream() .collect(Collectors.groupingBy( Project::getStatus, Collectors.collectingAndThen(Collectors.counting(), Long::intValue) )); statistics.setStatusCountMap(statusCountMap); // 设置各状态数量 statistics.setDraftCount(statusCountMap.getOrDefault("draft", 0)); statistics.setPlanningCount(statusCountMap.getOrDefault("planning", 0)); statistics.setOngoingCount(statusCountMap.getOrDefault("ongoing", 0)); statistics.setPausedCount(statusCountMap.getOrDefault("paused", 0)); statistics.setCompletedCount(statusCountMap.getOrDefault("completed", 0)); statistics.setCancelledCount(statusCountMap.getOrDefault("cancelled", 0)); // 4. 统计本月新增 YearMonth thisMonth = YearMonth.now(); int newThisMonth = (int) projects.stream() .filter(p -> p.getCreateTime() != null && YearMonth.from(p.getCreateTime()).equals(thisMonth)) .count(); statistics.setNewThisMonth(newThisMonth); // 5. 统计即将到期(7天内) LocalDate now = LocalDate.now(); LocalDate sevenDaysLater = now.plusDays(7); int aboutToExpire = (int) projects.stream() .filter(p -> !"completed".equals(p.getStatus()) && !"cancelled".equals(p.getStatus())) .filter(p -> p.getPlanEndDate() != null && !p.getPlanEndDate().isBefore(now) && !p.getPlanEndDate().isAfter(sevenDaysLater)) .count(); statistics.setAboutToExpireCount(aboutToExpire); // 6. 计算平均进度 double avgProgress = projects.stream() .filter(p -> p.getProgress() != null) .mapToInt(Project::getProgress) .average() .orElse(0.0); statistics.setAverageProgress(BigDecimal.valueOf(avgProgress).setScale(2, RoundingMode.HALF_UP)); // 7. 统计高风险项目 int highRiskCount = (int) projects.stream() .filter(p -> "high".equals(p.getRiskLevel()) || "critical".equals(p.getRiskLevel())) .count(); statistics.setHighRiskCount(highRiskCount); return statistics; } @Override public TableDataInfo getAllProjectList(Integer pageNum, Integer pageSize, String keyword, String status) { log.info("查询所有项目列表, pageNum: {}, pageSize: {}", pageNum, pageSize); Page page = new Page<>(pageNum, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Project::getDeleted, 0); if (keyword != null && !keyword.isEmpty()) { wrapper.and(w -> w.like(Project::getProjectName, keyword) .or() .like(Project::getProjectCode, keyword)); } if (status != null && !status.isEmpty()) { wrapper.eq(Project::getStatus, status); } wrapper.orderByDesc(Project::getCreateTime); Page projectPage = projectMapper.selectPage(page, wrapper); List voList = projectPage.getRecords().stream() .map(this::convertToProjectListVO) .collect(Collectors.toList()); return TableDataInfo.build(new Page(projectPage.getCurrent(), projectPage.getSize(), projectPage.getTotal()) .setRecords(voList)); } /** * 转换为项目列表 VO(自动填充项目经理姓名) */ private ProjectListVO convertToProjectListVO(Project project) { ProjectListVO vo = new ProjectListVO(); vo.setId(project.getId()); vo.setProjectCode(project.getProjectCode()); vo.setProjectName(project.getProjectName()); vo.setProjectType(project.getProjectType()); vo.setManagerId(project.getManagerId()); vo.setPlanStartDate(project.getPlanStartDate()); vo.setPlanEndDate(project.getPlanEndDate()); vo.setProgress(project.getProgress()); vo.setStatus(project.getStatus()); vo.setPriority(project.getPriority()); vo.setRiskLevel(project.getRiskLevel()); vo.setTags(project.getTags()); vo.setBudget(project.getBudget()); vo.setCost(project.getCost()); vo.setCreateTime(project.getCreateTime()); // 根据 managerId 查询项目经理姓名和头像 if (project.getManagerId() != null) { SysUser manager = sysUserMapper.selectById(project.getManagerId()); if (manager != null && manager.getDeleted() == 0) { vo.setManagerName(manager.getRealName()); vo.setManagerAvatar(manager.getAvatar()); } } return vo; } /** * 转换为甘特图任务VO(里程碑) */ private GanttTaskVO convertMilestoneToGanttTask(ProjectMilestone milestone) { GanttTaskVO vo = new GanttTaskVO(); vo.setId(milestone.getId()); vo.setTaskName(milestone.getMilestoneName()); vo.setType("milestone"); vo.setStartDate(milestone.getPlanDate()); vo.setEndDate(milestone.getPlanDate()); vo.setActualStartDate(milestone.getActualDate()); vo.setActualEndDate(milestone.getActualDate()); vo.setProgress(milestone.getProgress()); vo.setStatus(milestone.getStatus()); vo.setSortOrder(milestone.getSortOrder()); return vo; } /** * 转换为甘特图任务VO(任务) */ private GanttTaskVO convertTaskToGanttTask(Task task) { GanttTaskVO vo = new GanttTaskVO(); vo.setId(task.getId()); vo.setTaskName(task.getTaskName()); vo.setType("task"); vo.setStartDate(task.getPlanStartDate()); vo.setEndDate(task.getPlanEndDate()); vo.setActualStartDate(task.getActualStartDate()); vo.setActualEndDate(task.getActualEndDate()); vo.setProgress(task.getProgress()); vo.setStatus(task.getStatus()); vo.setPriority(task.getPriority()); vo.setSortOrder(task.getSortOrder()); return vo; } // ==================== 项目详情相关实现 ==================== @Override public ProjectDetailVO getProjectDetail(Long projectId) { log.info("查询项目详情, projectId: {}", projectId); // 1. 查询项目基本信息 Project project = projectMapper.selectById(projectId); if (project == null || project.getDeleted() == 1) { throw new RuntimeException("项目不存在"); } ProjectDetailVO detailVO = new ProjectDetailVO(); // 2. 设置项目基本信息 detailVO.setId(project.getId()); detailVO.setProjectCode(project.getProjectCode()); detailVO.setProjectName(project.getProjectName()); detailVO.setProjectType(project.getProjectType()); detailVO.setDescription(project.getDescription()); detailVO.setObjectives(project.getObjectives()); detailVO.setManagerId(project.getManagerId()); detailVO.setSponsorId(project.getSponsorId()); detailVO.setPlanStartDate(project.getPlanStartDate()); detailVO.setPlanEndDate(project.getPlanEndDate()); detailVO.setActualStartDate(project.getActualStartDate()); detailVO.setActualEndDate(project.getActualEndDate()); detailVO.setBudget(project.getBudget()); detailVO.setCost(project.getCost()); detailVO.setCurrency(project.getCurrency()); detailVO.setProgress(project.getProgress()); detailVO.setStatus(project.getStatus()); detailVO.setPriority(project.getPriority()); detailVO.setRiskLevel(project.getRiskLevel()); detailVO.setVisibility(project.getVisibility()); detailVO.setTags(project.getTags()); detailVO.setCreateTime(project.getCreateTime()); detailVO.setUpdateTime(project.getUpdateTime()); // 查询项目经理名称 if (project.getManagerId() != null) { SysUser manager = sysUserMapper.selectById(project.getManagerId()); if (manager != null) { detailVO.setManagerName(manager.getRealName()); } } // 3. 查询项目成员 List members = projectMemberMapper.selectList( new LambdaQueryWrapper() .eq(ProjectMember::getProjectId, projectId) .eq(ProjectMember::getDeleted, 0) .orderByAsc(ProjectMember::getId) ); // 查询成员用户信息 List userIds = members.stream() .map(ProjectMember::getUserId) .filter(Objects::nonNull) .collect(Collectors.toList()); Map userMap = new HashMap<>(); if (!userIds.isEmpty()) { List users = sysUserMapper.selectBatchIds(userIds); userMap = users.stream() .collect(Collectors.toMap(SysUser::getId, u -> u, (a, b) -> a)); } // 转换成员信息 final Map finalUserMap = userMap; List memberVOList = members.stream() .map(member -> { ProjectDetailVO.MemberInfo memberInfo = new ProjectDetailVO.MemberInfo(); memberInfo.setId(member.getId()); memberInfo.setUserId(member.getUserId()); memberInfo.setRoleCode(member.getRoleCode()); memberInfo.setResponsibility(member.getResponsibility()); memberInfo.setWeeklyHours(member.getWeeklyHours()); memberInfo.setJoinDate(member.getJoinDate()); memberInfo.setStatus(member.getStatus()); // 填充用户信息 if (member.getUserId() != null) { SysUser user = finalUserMap.get(member.getUserId()); if (user != null) { memberInfo.setUserName(user.getUsername()); memberInfo.setRealName(user.getRealName()); memberInfo.setAvatar(user.getAvatar()); } } return memberInfo; }) .collect(Collectors.toList()); detailVO.setMembers(memberVOList); detailVO.setMemberCount(memberVOList.size()); // 4. 查询里程碑 List milestones = projectMilestoneMapper.selectList( new LambdaQueryWrapper() .eq(ProjectMilestone::getProjectId, projectId) .eq(ProjectMilestone::getDeleted, 0) .orderByAsc(ProjectMilestone::getSortOrder) ); List milestoneVOList = milestones.stream() .map(milestone -> { ProjectDetailVO.MilestoneInfo milestoneInfo = new ProjectDetailVO.MilestoneInfo(); milestoneInfo.setId(milestone.getId()); milestoneInfo.setMilestoneName(milestone.getMilestoneName()); milestoneInfo.setDescription(milestone.getDescription()); milestoneInfo.setPlanDate(milestone.getPlanDate()); milestoneInfo.setActualDate(milestone.getActualDate()); milestoneInfo.setStatus(milestone.getStatus()); milestoneInfo.setProgress(milestone.getProgress()); milestoneInfo.setIsKey(milestone.getIsKey()); milestoneInfo.setDeliverables(milestone.getDeliverables()); milestoneInfo.setSortOrder(milestone.getSortOrder()); return milestoneInfo; }) .collect(Collectors.toList()); detailVO.setMilestones(milestoneVOList); detailVO.setMilestoneCount(milestoneVOList.size()); // 5. 查询任务 List tasks = taskMapper.selectList( new LambdaQueryWrapper() .eq(Task::getProjectId, projectId) .eq(Task::getDeleted, 0) .orderByAsc(Task::getSortOrder) ); // 查询任务执行人信息 List assigneeIds = tasks.stream() .map(Task::getAssigneeId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); Map assigneeMap = new HashMap<>(); if (!assigneeIds.isEmpty()) { List assignees = sysUserMapper.selectBatchIds(assigneeIds); assigneeMap = assignees.stream() .collect(Collectors.toMap(SysUser::getId, u -> u, (a, b) -> a)); } // 转换任务信息 final Map finalAssigneeMap = assigneeMap; List taskVOList = tasks.stream() .map(task -> { ProjectDetailVO.TaskInfo taskInfo = new ProjectDetailVO.TaskInfo(); taskInfo.setId(task.getId()); taskInfo.setTaskCode(task.getTaskCode()); taskInfo.setTaskName(task.getTaskName()); taskInfo.setDescription(task.getDescription()); taskInfo.setTaskType(task.getTaskType()); taskInfo.setMilestoneId(task.getMilestoneId()); taskInfo.setAssigneeId(task.getAssigneeId()); taskInfo.setPlanStartDate(task.getPlanStartDate()); taskInfo.setPlanEndDate(task.getPlanEndDate()); taskInfo.setActualStartDate(task.getActualStartDate()); taskInfo.setActualEndDate(task.getActualEndDate()); taskInfo.setPlanHours(task.getPlanHours()); taskInfo.setActualHours(task.getActualHours()); taskInfo.setProgress(task.getProgress()); taskInfo.setPriority(task.getPriority()); taskInfo.setStatus(task.getStatus()); taskInfo.setSortOrder(task.getSortOrder()); // 填充执行人姓名 if (task.getAssigneeId() != null) { SysUser assignee = finalAssigneeMap.get(task.getAssigneeId()); if (assignee != null) { taskInfo.setAssigneeName(assignee.getRealName()); } } return taskInfo; }) .collect(Collectors.toList()); detailVO.setTasks(taskVOList); detailVO.setTaskCount(taskVOList.size()); // 统计已完成任务数 int completedCount = (int) tasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); detailVO.setCompletedTaskCount(completedCount); // 6. 查询资源 List resources = resourceMapper.selectList( new LambdaQueryWrapper() .eq(Resource::getProjectId, projectId) .eq(Resource::getDeleted, 0) .orderByAsc(Resource::getId) ); List resourceVOList = resources.stream() .map(resource -> { ProjectDetailVO.ResourceInfo resourceInfo = new ProjectDetailVO.ResourceInfo(); resourceInfo.setId(resource.getId()); resourceInfo.setResourceCode(resource.getResourceCode()); resourceInfo.setResourceType(resource.getResourceType()); resourceInfo.setResourceName(resource.getResourceName()); resourceInfo.setDescription(resource.getDescription()); resourceInfo.setSpecification(resource.getSpecification()); resourceInfo.setUnit(resource.getUnit()); resourceInfo.setPlanQuantity(resource.getPlanQuantity()); resourceInfo.setActualQuantity(resource.getActualQuantity()); resourceInfo.setUnitPrice(resource.getUnitPrice()); resourceInfo.setCurrency(resource.getCurrency()); resourceInfo.setSupplier(resource.getSupplier()); resourceInfo.setStatus(resource.getStatus()); resourceInfo.setPlanArriveDate(resource.getPlanArriveDate()); resourceInfo.setActualArriveDate(resource.getActualArriveDate()); resourceInfo.setResponsibleId(resource.getResponsibleId()); resourceInfo.setLocation(resource.getLocation()); resourceInfo.setTags(resource.getTags()); return resourceInfo; }) .collect(Collectors.toList()); detailVO.setResources(resourceVOList); detailVO.setResourceCount(resourceVOList.size()); // 7. 查询风险 List risks = riskMapper.selectList( new LambdaQueryWrapper() .eq(Risk::getProjectId, projectId) .eq(Risk::getDeleted, 0) .orderByDesc(Risk::getRiskScore) ); List riskVOList = risks.stream() .map(risk -> { ProjectDetailVO.RiskInfo riskInfo = new ProjectDetailVO.RiskInfo(); riskInfo.setId(risk.getId()); riskInfo.setRiskCode(risk.getRiskCode()); riskInfo.setCategory(risk.getCategory()); riskInfo.setRiskName(risk.getRiskName()); riskInfo.setDescription(risk.getDescription()); riskInfo.setProbability(risk.getProbability()); riskInfo.setImpact(risk.getImpact()); riskInfo.setRiskScore(risk.getRiskScore()); riskInfo.setRiskLevel(risk.getRiskLevel()); riskInfo.setStatus(risk.getStatus()); riskInfo.setOwnerId(risk.getOwnerId()); riskInfo.setMitigationPlan(risk.getMitigationPlan()); riskInfo.setDueDate(risk.getDueDate()); riskInfo.setDiscoverTime(risk.getDiscoverTime()); return riskInfo; }) .collect(Collectors.toList()); detailVO.setRisks(riskVOList); detailVO.setRiskCount(riskVOList.size()); // 统计高风险数量 int highRiskCount = (int) risks.stream() .filter(r -> "high".equals(r.getRiskLevel()) || "critical".equals(r.getRiskLevel())) .count(); detailVO.setHighRiskCount(highRiskCount); // 8. 查询时间节点 List timelines = projectTimelineMapper.selectList( new LambdaQueryWrapper() .eq(ProjectTimeline::getProjectId, projectId) .eq(ProjectTimeline::getDeleted, 0) .orderByAsc(ProjectTimeline::getSortOrder) ); List timelineVOList = timelines.stream() .map(timeline -> { ProjectDetailVO.TimelineInfo timelineInfo = new ProjectDetailVO.TimelineInfo(); timelineInfo.setId(timeline.getId()); timelineInfo.setNodeName(timeline.getNodeName()); timelineInfo.setNodeType(timeline.getNodeType()); timelineInfo.setPlanDate(timeline.getPlanDate()); timelineInfo.setActualDate(timeline.getActualDate()); timelineInfo.setDescription(timeline.getDescription()); timelineInfo.setStatus(timeline.getStatus()); timelineInfo.setSortOrder(timeline.getSortOrder()); timelineInfo.setKbScope(timeline.getKbScope()); return timelineInfo; }) .collect(Collectors.toList()); detailVO.setTimelineNodes(timelineVOList); return detailVO; } }