feat(project): 新增AI项目初始化及数据持久化功能

- 新增ProjectInitResult DTO,定义项目初始化的结构化数据格式
- 实现ProjectServiceImpl,支持从文本或文件生成项目数据并保存
- 集成Spring AI结构化输出能力,解析项目文档生成计划数据
- 实现项目、里程碑、任务、成员、资源、风险、时间节点数据转换及数据库保存
- 支持任务的层级关系和执行依赖关系持久化
- 新增Task实体,完善任务相关字段及数据库映射
- 添加详细日志,支持事务回滚保障数据一致性
- 新增数据库设计SQL文档,定义项目管理相关表结构及索引
This commit is contained in:
2026-03-27 11:57:30 +08:00
parent d49bc443ce
commit 5e698fbb8c
6 changed files with 107 additions and 22 deletions

View File

@@ -405,7 +405,6 @@ CREATE TABLE task (
task_code VARCHAR(50),
project_id BIGINT NOT NULL,
milestone_id BIGINT,
parent_id BIGINT,
task_name VARCHAR(200) NOT NULL,
description TEXT,
task_type VARCHAR(50),
@@ -431,13 +430,11 @@ CREATE TABLE task (
CONSTRAINT fk_task_project FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE,
CONSTRAINT fk_task_milestone FOREIGN KEY (milestone_id) REFERENCES project_milestone(id) ON DELETE SET NULL,
CONSTRAINT fk_task_parent FOREIGN KEY (parent_id) REFERENCES task(id) ON DELETE SET NULL,
CONSTRAINT fk_task_assignee FOREIGN KEY (assignee_id) REFERENCES sys_user(id) ON DELETE SET NULL
);
CREATE INDEX idx_task_project ON task(project_id);
CREATE INDEX idx_task_milestone ON task(milestone_id);
CREATE INDEX idx_task_parent ON task(parent_id);
CREATE INDEX idx_task_assignee ON task(assignee_id);
CREATE INDEX idx_task_status ON task(status);
@@ -445,7 +442,6 @@ COMMENT ON TABLE task IS '任务表';
COMMENT ON COLUMN task.task_code IS '任务编号';
COMMENT ON COLUMN task.project_id IS '项目ID';
COMMENT ON COLUMN task.milestone_id IS '所属里程碑ID';
COMMENT ON COLUMN task.parent_id IS '父任务ID';
COMMENT ON COLUMN task.task_name IS '任务名称';
COMMENT ON COLUMN task.description IS '任务描述';
COMMENT ON COLUMN task.task_type IS '任务类型';
@@ -475,8 +471,10 @@ CREATE TABLE task_dependency (
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(task_id, depends_on_task_id),
-- 修正了下面的 fk_td_depends 约束
CONSTRAINT fk_td_task FOREIGN KEY (task_id) REFERENCES task(id) ON DELETE CASCADE,
CONSTRAINT fk_td_depends ON DELETE CASCADE
CONSTRAINT fk_td_depends FOREIGN KEY (depends_on_task_id) REFERENCES task(id) ON DELETE CASCADE
);
CREATE INDEX idx_td_depends ON task_dependency(depends_on_task_id);

View File

@@ -35,11 +35,6 @@ public class Task {
*/
private Long milestoneId;
/**
* 父任务ID
*/
private Long parentId;
/**
* 任务名称
*/

View File

@@ -0,0 +1,44 @@
package cn.yinlihupo.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 任务依赖关系实体类
* 对应数据库表: task_dependency
*/
@Data
@TableName("task_dependency")
public class TaskDependency {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 任务ID
*/
private Long taskId;
/**
* 依赖的任务ID
*/
private Long dependsOnTaskId;
/**
* 依赖类型: finish_to_start-完成-开始, start_to_start-开始-开始, finish_to_finish-完成-完成, start_to_finish-开始-完成
*/
private String dependencyType;
/**
* 滞后天数
*/
private Integer lagDays;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@@ -111,6 +111,9 @@ public class ProjectInitResult {
@Data
public static class TaskInfo {
@JsonProperty("task_id")
private String taskId;
@JsonProperty("task_name")
private String taskName;

View File

@@ -0,0 +1,12 @@
package cn.yinlihupo.mapper;
import cn.yinlihupo.domain.entity.TaskDependency;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 任务依赖关系Mapper接口
*/
@Mapper
public interface TaskDependencyMapper extends BaseMapper<TaskDependency> {
}

View File

@@ -40,6 +40,7 @@ public class ProjectServiceImpl implements ProjectService {
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;
@@ -93,6 +94,7 @@ public class ProjectServiceImpl implements ProjectService {
],
"tasks": [
{
"task_id": "T001",
"task_name": "任务名称",
"parent_task_id": null,
"description": "任务描述",
@@ -101,7 +103,7 @@ public class ProjectServiceImpl implements ProjectService {
"estimated_hours": 80,
"priority": "high",
"assignee_role": "执行者角色",
"dependencies": ["前置任务ID"],
"dependencies": ["T001"],
"deliverables": "交付物"
}
],
@@ -313,26 +315,57 @@ public class ProjectServiceImpl implements ProjectService {
task.setSortOrder(i);
taskMapper.insert(task);
// 使用任务名称作为临时ID进行映射
String tempId = taskInfo.getTaskName();
taskTempIdToId.put(tempId, task.getId());
// 使用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()) {
Task task = new Task();
task.setId(taskTempIdToId.get(taskInfo.getTaskName()));
// 查找父任务ID
Long taskId = taskTempIdToId.get(taskInfo.getTaskId());
Long parentId = taskTempIdToId.get(taskInfo.getParentTaskId());
if (parentId != null) {
task.setParentId(parentId);
taskMapper.updateById(task);
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++;
}
}
}
log.info("保存了 {} 个任务", result.getTasks().size());
// 第三轮保存任务执行依赖关系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. 保存项目成员