Files
ylhp-ai-project-manager/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java
JiaoTianBo 5f2aedf57e feat(project): 转换项目列表 VO 时自动填充项目经理姓名
- 在 convertToProjectListVO 方法中添加逻辑,根据 managerId 查询项目经理姓名
- 查询到有效项目经理时,将其真实姓名设置到 VO 的 managerName 字段
- 增强项目列表展示信息的完整性和可读性
2026-03-31 18:22:15 +08:00

1136 lines
48 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<Integer, Long> 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<String, Long> 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<ProjectListVO> getMyProjectList(Long userId, Integer pageNum, Integer pageSize,
String keyword, String status) {
log.info("查询我的项目列表, userId: {}, pageNum: {}, pageSize: {}", userId, pageNum, pageSize);
// 使用分页插件
Page<Project> page = new Page<>(pageNum, pageSize);
// 构建查询条件
LambdaQueryWrapper<Project> 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<Long> myProjectIds = projectMemberMapper.selectList(
new LambdaQueryWrapper<ProjectMember>()
.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<Project> projectPage = projectMapper.selectPage(page, wrapper);
// 转换为VO
List<ProjectListVO> voList = projectPage.getRecords().stream()
.map(this::convertToProjectListVO)
.collect(Collectors.toList());
return TableDataInfo.build(new Page<ProjectListVO>(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<ProjectMilestone> milestones = projectMilestoneMapper.selectList(
new LambdaQueryWrapper<ProjectMilestone>()
.eq(ProjectMilestone::getProjectId, projectId)
.eq(ProjectMilestone::getDeleted, 0)
.orderByAsc(ProjectMilestone::getSortOrder)
);
List<GanttTaskVO> milestoneVOList = milestones.stream()
.map(this::convertMilestoneToGanttTask)
.collect(Collectors.toList());
ganttVO.setMilestones(milestoneVOList);
// 3. 查询任务
List<Task> tasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.eq(Task::getProjectId, projectId)
.eq(Task::getDeleted, 0)
.orderByAsc(Task::getSortOrder)
);
List<GanttTaskVO> 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<Project> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Project::getDeleted, 0);
// 如果指定了用户ID只统计该用户的项目
if (userId != null) {
List<Long> myProjectIds = projectMemberMapper.selectList(
new LambdaQueryWrapper<ProjectMember>()
.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<Project> projects = projectMapper.selectList(wrapper);
// 2. 统计总数
statistics.setTotalCount(projects.size());
// 3. 按状态统计
Map<String, Integer> 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<ProjectListVO> getAllProjectList(Integer pageNum, Integer pageSize,
String keyword, String status) {
log.info("查询所有项目列表, pageNum: {}, pageSize: {}", pageNum, pageSize);
Page<Project> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Project> 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<Project> projectPage = projectMapper.selectPage(page, wrapper);
List<ProjectListVO> voList = projectPage.getRecords().stream()
.map(this::convertToProjectListVO)
.collect(Collectors.toList());
return TableDataInfo.build(new Page<ProjectListVO>(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<ProjectMember> members = projectMemberMapper.selectList(
new LambdaQueryWrapper<ProjectMember>()
.eq(ProjectMember::getProjectId, projectId)
.eq(ProjectMember::getDeleted, 0)
.orderByAsc(ProjectMember::getId)
);
// 查询成员用户信息
List<Long> userIds = members.stream()
.map(ProjectMember::getUserId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
Map<Long, SysUser> userMap = new HashMap<>();
if (!userIds.isEmpty()) {
List<SysUser> users = sysUserMapper.selectBatchIds(userIds);
userMap = users.stream()
.collect(Collectors.toMap(SysUser::getId, u -> u, (a, b) -> a));
}
// 转换成员信息
final Map<Long, SysUser> finalUserMap = userMap;
List<ProjectDetailVO.MemberInfo> 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<ProjectMilestone> milestones = projectMilestoneMapper.selectList(
new LambdaQueryWrapper<ProjectMilestone>()
.eq(ProjectMilestone::getProjectId, projectId)
.eq(ProjectMilestone::getDeleted, 0)
.orderByAsc(ProjectMilestone::getSortOrder)
);
List<ProjectDetailVO.MilestoneInfo> 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<Task> tasks = taskMapper.selectList(
new LambdaQueryWrapper<Task>()
.eq(Task::getProjectId, projectId)
.eq(Task::getDeleted, 0)
.orderByAsc(Task::getSortOrder)
);
// 查询任务执行人信息
List<Long> assigneeIds = tasks.stream()
.map(Task::getAssigneeId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
Map<Long, SysUser> assigneeMap = new HashMap<>();
if (!assigneeIds.isEmpty()) {
List<SysUser> assignees = sysUserMapper.selectBatchIds(assigneeIds);
assigneeMap = assignees.stream()
.collect(Collectors.toMap(SysUser::getId, u -> u, (a, b) -> a));
}
// 转换任务信息
final Map<Long, SysUser> finalAssigneeMap = assigneeMap;
List<ProjectDetailVO.TaskInfo> 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<Resource> resources = resourceMapper.selectList(
new LambdaQueryWrapper<Resource>()
.eq(Resource::getProjectId, projectId)
.eq(Resource::getDeleted, 0)
.orderByAsc(Resource::getId)
);
List<ProjectDetailVO.ResourceInfo> 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<Risk> risks = riskMapper.selectList(
new LambdaQueryWrapper<Risk>()
.eq(Risk::getProjectId, projectId)
.eq(Risk::getDeleted, 0)
.orderByDesc(Risk::getRiskScore)
);
List<ProjectDetailVO.RiskInfo> 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<ProjectTimeline> timelines = projectTimelineMapper.selectList(
new LambdaQueryWrapper<ProjectTimeline>()
.eq(ProjectTimeline::getProjectId, projectId)
.eq(ProjectTimeline::getDeleted, 0)
.orderByAsc(ProjectTimeline::getSortOrder)
);
List<ProjectDetailVO.TimelineInfo> 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;
}
}