feat(project): 实现AI项目初始化及查询功能

- 新增ProjectService接口及实现,实现基于AI解析项目文档生成结构化项目数据
- 使用聊天机器人接口和OSS服务支持文件内容读取与AI解析
- 实现项目基本信息、里程碑、任务、成员、资源、风险及时间节点的持久化保存
- 支持任务层级关系和依赖关系的存储与管理
- 提供项目列表、项目详情、项目甘特图及项目统计接口
- 项目列表支持管理员与普通用户视角不同的数据访问权限
- 统计功能覆盖项目状态分布、本月新增、即将到期、平均进度及高风险项目数
- 项目编号自动生成,状态初始化为规划中
- 采用分页查询支持大数据量高效访问
This commit is contained in:
2026-03-28 18:53:36 +08:00
parent 32bff3aabc
commit cd56e2e987
4 changed files with 965 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import cn.yinlihupo.common.core.BaseResponse;
import cn.yinlihupo.common.page.TableDataInfo;
import cn.yinlihupo.common.util.ResultUtils;
import cn.yinlihupo.common.util.SecurityUtils;
import cn.yinlihupo.domain.vo.ProjectDetailVO;
import cn.yinlihupo.domain.vo.ProjectGanttVO;
import cn.yinlihupo.domain.vo.ProjectListVO;
import cn.yinlihupo.domain.vo.ProjectStatisticsVO;
@@ -66,6 +67,30 @@ public class ProjectQueryController {
}
}
/**
* 获取项目详情(全量信息)
* 包括基本信息、参与人员、里程碑、任务、资源、风险、时间节点等
*
* @param projectId 项目ID
* @return 项目详情
*/
@GetMapping("/{projectId}")
public BaseResponse<ProjectDetailVO> getProjectDetail(@PathVariable Long projectId) {
log.info("获取项目详情, projectId: {}", projectId);
if (projectId == null || projectId <= 0) {
return ResultUtils.error("项目ID不能为空");
}
try {
ProjectDetailVO result = projectService.getProjectDetail(projectId);
return ResultUtils.success("查询成功", result);
} catch (Exception e) {
log.error("获取项目详情失败: {}", e.getMessage(), e);
return ResultUtils.error("查询失败: " + e.getMessage());
}
}
/**
* 获取项目甘特图数据
*

View File

@@ -0,0 +1,645 @@
package cn.yinlihupo.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 项目详情VO
* 包含项目全量信息:基本信息、参与人员、预算进度、物料资源等
*/
@Data
public class ProjectDetailVO {
// ==================== 项目基本信息 ====================
/**
* 项目ID
*/
private Long id;
/**
* 项目编号
*/
private String projectCode;
/**
* 项目名称
*/
private String projectName;
/**
* 项目类型
*/
private String projectType;
/**
* 项目描述
*/
private String description;
/**
* 项目目标
*/
private String objectives;
/**
* 项目经理ID
*/
private Long managerId;
/**
* 项目经理名称
*/
private String managerName;
/**
* 项目发起人ID
*/
private Long sponsorId;
/**
* 计划开始日期
*/
private LocalDate planStartDate;
/**
* 计划结束日期
*/
private LocalDate planEndDate;
/**
* 实际开始日期
*/
private LocalDate actualStartDate;
/**
* 实际结束日期
*/
private LocalDate actualEndDate;
/**
* 项目预算
*/
private BigDecimal budget;
/**
* 已花费金额
*/
private BigDecimal cost;
/**
* 币种
*/
private String currency;
/**
* 进度百分比
*/
private Integer progress;
/**
* 状态
*/
private String status;
/**
* 优先级
*/
private String priority;
/**
* 风险等级
*/
private String riskLevel;
/**
* 可见性
*/
private Integer visibility;
/**
* 标签列表
*/
private List<String> tags;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
// ==================== 统计汇总信息 ====================
/**
* 成员数量
*/
private Integer memberCount;
/**
* 任务总数
*/
private Integer taskCount;
/**
* 已完成任务数
*/
private Integer completedTaskCount;
/**
* 里程碑总数
*/
private Integer milestoneCount;
/**
* 资源总数
*/
private Integer resourceCount;
/**
* 风险总数
*/
private Integer riskCount;
/**
* 高风险数量
*/
private Integer highRiskCount;
// ==================== 关联数据列表 ====================
/**
* 项目成员列表
*/
private List<MemberInfo> members;
/**
* 里程碑列表
*/
private List<MilestoneInfo> milestones;
/**
* 任务列表
*/
private List<TaskInfo> tasks;
/**
* 资源列表
*/
private List<ResourceInfo> resources;
/**
* 风险列表
*/
private List<RiskInfo> risks;
/**
* 时间节点列表
*/
private List<TimelineInfo> timelineNodes;
// ==================== 内部类定义 ====================
/**
* 成员信息
*/
@Data
public static class MemberInfo {
/**
* 成员记录ID
*/
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 用户名
*/
private String userName;
/**
* 真实姓名
*/
private String realName;
/**
* 头像
*/
private String avatar;
/**
* 项目角色
*/
private String roleCode;
/**
* 部门
*/
private String department;
/**
* 职责描述
*/
private String responsibility;
/**
* 每周投入小时数
*/
private BigDecimal weeklyHours;
/**
* 加入日期
*/
private LocalDate joinDate;
/**
* 状态
*/
private Integer status;
}
/**
* 里程碑信息
*/
@Data
public static class MilestoneInfo {
/**
* 里程碑ID
*/
private Long id;
/**
* 里程碑名称
*/
private String milestoneName;
/**
* 描述
*/
private String description;
/**
* 计划日期
*/
private LocalDate planDate;
/**
* 实际日期
*/
private LocalDate actualDate;
/**
* 状态
*/
private String status;
/**
* 完成进度
*/
private Integer progress;
/**
* 是否关键里程碑
*/
private Integer isKey;
/**
* 交付物列表
*/
private List<String> deliverables;
/**
* 排序
*/
private Integer sortOrder;
}
/**
* 任务信息
*/
@Data
public static class TaskInfo {
/**
* 任务ID
*/
private Long id;
/**
* 任务编号
*/
private String taskCode;
/**
* 任务名称
*/
private String taskName;
/**
* 任务描述
*/
private String description;
/**
* 任务类型
*/
private String taskType;
/**
* 所属里程碑ID
*/
private Long milestoneId;
/**
* 执行人ID
*/
private Long assigneeId;
/**
* 执行人姓名
*/
private String assigneeName;
/**
* 计划开始日期
*/
private LocalDate planStartDate;
/**
* 计划结束日期
*/
private LocalDate planEndDate;
/**
* 实际开始日期
*/
private LocalDate actualStartDate;
/**
* 实际结束日期
*/
private LocalDate actualEndDate;
/**
* 计划工时
*/
private BigDecimal planHours;
/**
* 实际工时
*/
private BigDecimal actualHours;
/**
* 进度百分比
*/
private Integer progress;
/**
* 优先级
*/
private String priority;
/**
* 状态
*/
private String status;
/**
* 排序
*/
private Integer sortOrder;
}
/**
* 资源信息
*/
@Data
public static class ResourceInfo {
/**
* 资源ID
*/
private Long id;
/**
* 资源编号
*/
private String resourceCode;
/**
* 资源类型
*/
private String resourceType;
/**
* 资源名称
*/
private String resourceName;
/**
* 资源描述
*/
private String description;
/**
* 规格型号
*/
private String specification;
/**
* 单位
*/
private String unit;
/**
* 计划数量
*/
private BigDecimal planQuantity;
/**
* 实际数量
*/
private BigDecimal actualQuantity;
/**
* 单价
*/
private BigDecimal unitPrice;
/**
* 币种
*/
private String currency;
/**
* 供应商
*/
private String supplier;
/**
* 状态
*/
private String status;
/**
* 计划到位日期
*/
private LocalDate planArriveDate;
/**
* 实际到位日期
*/
private LocalDate actualArriveDate;
/**
* 负责人ID
*/
private Long responsibleId;
/**
* 存放位置
*/
private String location;
/**
* 标签
*/
private List<String> tags;
}
/**
* 风险信息
*/
@Data
public static class RiskInfo {
/**
* 风险ID
*/
private Long id;
/**
* 风险编号
*/
private String riskCode;
/**
* 风险分类
*/
private String category;
/**
* 风险名称
*/
private String riskName;
/**
* 风险描述
*/
private String description;
/**
* 发生概率
*/
private BigDecimal probability;
/**
* 影响程度
*/
private BigDecimal impact;
/**
* 风险得分
*/
private BigDecimal riskScore;
/**
* 风险等级
*/
private String riskLevel;
/**
* 状态
*/
private String status;
/**
* 负责人ID
*/
private Long ownerId;
/**
* 缓解措施
*/
private String mitigationPlan;
/**
* 预期解决日期
*/
private LocalDate dueDate;
/**
* 发现时间
*/
private LocalDateTime discoverTime;
}
/**
* 时间节点信息
*/
@Data
public static class TimelineInfo {
/**
* 节点ID
*/
private Long id;
/**
* 节点名称
*/
private String nodeName;
/**
* 节点类型
*/
private String nodeType;
/**
* 计划日期
*/
private LocalDate planDate;
/**
* 实际日期
*/
private LocalDate actualDate;
/**
* 描述
*/
private String description;
/**
* 状态
*/
private String status;
/**
* 排序
*/
private Integer sortOrder;
/**
* 知识库范围
*/
private List<String> kbScope;
}
}

View File

@@ -1,6 +1,7 @@
package cn.yinlihupo.service.project;
import cn.yinlihupo.common.page.TableDataInfo;
import cn.yinlihupo.domain.vo.ProjectDetailVO;
import cn.yinlihupo.domain.vo.ProjectGanttVO;
import cn.yinlihupo.domain.vo.ProjectInitResult;
import cn.yinlihupo.domain.vo.ProjectListVO;
@@ -90,4 +91,12 @@ public interface ProjectService {
*/
TableDataInfo<ProjectListVO> getAllProjectList(Integer pageNum, Integer pageSize,
String keyword, String status);
/**
* 获取项目详情(全量信息)
*
* @param projectId 项目ID
* @return 项目详情
*/
ProjectDetailVO getProjectDetail(Long projectId);
}

View File

@@ -45,6 +45,7 @@ public class ProjectServiceImpl implements ProjectService {
private final ResourceMapper resourceMapper;
private final RiskMapper riskMapper;
private final ProjectTimelineMapper projectTimelineMapper;
private final SysUserMapper sysUserMapper;
/**
* 项目初始化系统提示词模板
@@ -825,4 +826,289 @@ public class ProjectServiceImpl implements ProjectService {
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;
}
}