From 5e698fbb8c0bd8f3b0fc7788b74aa73fb5e3c99d Mon Sep 17 00:00:00 2001 From: JiaoTianBo Date: Fri, 27 Mar 2026 11:57:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(project):=20=E6=96=B0=E5=A2=9EAI=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=88=9D=E5=A7=8B=E5=8C=96=E5=8F=8A=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增ProjectInitResult DTO,定义项目初始化的结构化数据格式 - 实现ProjectServiceImpl,支持从文本或文件生成项目数据并保存 - 集成Spring AI结构化输出能力,解析项目文档生成计划数据 - 实现项目、里程碑、任务、成员、资源、风险、时间节点数据转换及数据库保存 - 支持任务的层级关系和执行依赖关系持久化 - 新增Task实体,完善任务相关字段及数据库映射 - 添加详细日志,支持事务回滚保障数据一致性 - 新增数据库设计SQL文档,定义项目管理相关表结构及索引 --- docs/dev-ops/pgsql/sql/weform_run.sql | 8 +-- .../java/cn/yinlihupo/domain/entity/Task.java | 5 -- .../domain/entity/TaskDependency.java | 44 ++++++++++++++ .../domain/vo/ProjectInitResult.java | 3 + .../mapper/TaskDependencyMapper.java | 12 ++++ .../project/impl/ProjectServiceImpl.java | 57 +++++++++++++++---- 6 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 src/main/java/cn/yinlihupo/domain/entity/TaskDependency.java create mode 100644 src/main/java/cn/yinlihupo/mapper/TaskDependencyMapper.java diff --git a/docs/dev-ops/pgsql/sql/weform_run.sql b/docs/dev-ops/pgsql/sql/weform_run.sql index d7612e6..7ca9fc4 100644 --- a/docs/dev-ops/pgsql/sql/weform_run.sql +++ b/docs/dev-ops/pgsql/sql/weform_run.sql @@ -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); diff --git a/src/main/java/cn/yinlihupo/domain/entity/Task.java b/src/main/java/cn/yinlihupo/domain/entity/Task.java index ad6d830..874d0ce 100644 --- a/src/main/java/cn/yinlihupo/domain/entity/Task.java +++ b/src/main/java/cn/yinlihupo/domain/entity/Task.java @@ -35,11 +35,6 @@ public class Task { */ private Long milestoneId; - /** - * 父任务ID - */ - private Long parentId; - /** * 任务名称 */ diff --git a/src/main/java/cn/yinlihupo/domain/entity/TaskDependency.java b/src/main/java/cn/yinlihupo/domain/entity/TaskDependency.java new file mode 100644 index 0000000..3c00c3b --- /dev/null +++ b/src/main/java/cn/yinlihupo/domain/entity/TaskDependency.java @@ -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; +} diff --git a/src/main/java/cn/yinlihupo/domain/vo/ProjectInitResult.java b/src/main/java/cn/yinlihupo/domain/vo/ProjectInitResult.java index 1f447fd..ceed5e8 100644 --- a/src/main/java/cn/yinlihupo/domain/vo/ProjectInitResult.java +++ b/src/main/java/cn/yinlihupo/domain/vo/ProjectInitResult.java @@ -111,6 +111,9 @@ public class ProjectInitResult { @Data public static class TaskInfo { + @JsonProperty("task_id") + private String taskId; + @JsonProperty("task_name") private String taskName; diff --git a/src/main/java/cn/yinlihupo/mapper/TaskDependencyMapper.java b/src/main/java/cn/yinlihupo/mapper/TaskDependencyMapper.java new file mode 100644 index 0000000..a888839 --- /dev/null +++ b/src/main/java/cn/yinlihupo/mapper/TaskDependencyMapper.java @@ -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 { +} diff --git a/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java b/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java index 3d1d5d5..6d5b7b3 100644 --- a/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java +++ b/src/main/java/cn/yinlihupo/service/project/impl/ProjectServiceImpl.java @@ -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. 保存项目成员