feat(project): 实现AI项目初始化功能
- 新增项目初始化控制器,支持文件上传生成项目结构化数据 - 定义项目初始化结果DTO,包含项目、里程碑、任务、成员、资源、风险和时间节点等信息 - 实现项目初始化服务接口及其实现类,集成Spring AI结构化输出能力 - 支持根据内容或文件生成项目初始化数据,并保存到数据库 - 增加项目、里程碑、任务、成员、资源、风险及时间节点实体及对应Mapper - 实现文件上传到OSS及项目初始化记录功能,记录解析状态及结果 - 添加PostgreSQL JSONB类型处理器,支持JSON对象字段存储 - 修改开发环境数据库配置,更新连接的数据库名称为aiprojectmanager
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package cn.yinlihupo.common.handler;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.postgresql.util.PGobject;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* PostgreSQL JSONB 类型处理器
|
||||
* 用于处理 Java 对象与 PostgreSQL jsonb 类型之间的转换
|
||||
*/
|
||||
public class JsonbTypeHandler extends BaseTypeHandler<Object> {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static final String JSONB_TYPE = "jsonb";
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
|
||||
PGobject jsonObject = new PGobject();
|
||||
jsonObject.setType(JSONB_TYPE);
|
||||
try {
|
||||
jsonObject.setValue(objectMapper.writeValueAsString(parameter));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new SQLException("Error converting object to JSONB", e);
|
||||
}
|
||||
ps.setObject(i, jsonObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
String json = rs.getString(columnName);
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(json, Object.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new SQLException("Error parsing JSONB from column " + columnName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
String json = rs.getString(columnIndex);
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(json, Object.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new SQLException("Error parsing JSONB from column index " + columnIndex, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
String json = cs.getString(columnIndex);
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(json, Object.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new SQLException("Error parsing JSONB from callable statement column index " + columnIndex, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package cn.yinlihupo.controller.project;
|
||||
|
||||
import cn.yinlihupo.common.result.Result;
|
||||
import cn.yinlihupo.domain.dto.ProjectInitRequest;
|
||||
import cn.yinlihupo.domain.dto.ProjectInitResult;
|
||||
import cn.yinlihupo.domain.vo.ProjectInitResult;
|
||||
import cn.yinlihupo.service.oss.OssService;
|
||||
import cn.yinlihupo.service.project.ProjectService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -38,14 +37,8 @@ public class ProjectController {
|
||||
}
|
||||
|
||||
try {
|
||||
// 上传文件到OSS
|
||||
String fileUrl = ossService.uploadFile(file, file.getOriginalFilename());
|
||||
log.info("文件上传成功,URL: {}", fileUrl);
|
||||
|
||||
// 根据文件生成项目初始化数据
|
||||
String fileType = getFileExtension(file.getOriginalFilename());
|
||||
ProjectInitResult result = projectService.generateProjectFromFile(fileUrl, fileType);
|
||||
|
||||
// 上传文件、生成项目初始化数据并保存到数据库
|
||||
ProjectInitResult result = projectService.generateAndSaveProject(file);
|
||||
return Result.success("项目初始化成功", result);
|
||||
} catch (Exception e) {
|
||||
log.error("项目初始化失败: {}", e.getMessage(), e);
|
||||
|
||||
157
src/main/java/cn/yinlihupo/domain/entity/Project.java
Normal file
157
src/main/java/cn/yinlihupo/domain/entity/Project.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目实体类
|
||||
* 对应数据库表: project
|
||||
*/
|
||||
@Data
|
||||
@TableName("project")
|
||||
public class Project {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目编号
|
||||
*/
|
||||
private String projectCode;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
|
||||
/**
|
||||
* 项目类型
|
||||
*/
|
||||
private String projectType;
|
||||
|
||||
/**
|
||||
* 项目描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 项目目标
|
||||
*/
|
||||
private String objectives;
|
||||
|
||||
/**
|
||||
* 项目经理ID
|
||||
*/
|
||||
private Long managerId;
|
||||
|
||||
/**
|
||||
* 项目发起人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;
|
||||
|
||||
/**
|
||||
* 状态: draft-草稿, planning-规划中, ongoing-进行中, paused-暂停, completed-已完成, cancelled-已取消
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 优先级: critical-关键, high-高, medium-中, low-低
|
||||
*/
|
||||
private String priority;
|
||||
|
||||
/**
|
||||
* 风险等级: high-高, medium-中, low-低
|
||||
*/
|
||||
private String riskLevel;
|
||||
|
||||
/**
|
||||
* 可见性: 1-公开, 2-部门内, 3-项目成员
|
||||
*/
|
||||
private Integer visibility;
|
||||
|
||||
/**
|
||||
* 标签列表
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object extraData;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
105
src/main/java/cn/yinlihupo/domain/entity/ProjectInitRecord.java
Normal file
105
src/main/java/cn/yinlihupo/domain/entity/ProjectInitRecord.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目初始化记录实体类
|
||||
* 对应数据库表: project_init_record
|
||||
*/
|
||||
@Data
|
||||
@TableName("project_init_record")
|
||||
public class ProjectInitRecord {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 上传的文件列表[{name, path, type, size}]
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<InputFile> inputFiles;
|
||||
|
||||
/**
|
||||
* 用户输入的项目描述
|
||||
*/
|
||||
private String inputText;
|
||||
|
||||
/**
|
||||
* 状态: pending-待解析, processing-解析中, completed-已完成, failed-失败
|
||||
*/
|
||||
private String parseStatus;
|
||||
|
||||
/**
|
||||
* 解析结果(结构化数据)
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object parseResult;
|
||||
|
||||
/**
|
||||
* 生成的里程碑数量
|
||||
*/
|
||||
private Integer generatedMilestones;
|
||||
|
||||
/**
|
||||
* 生成的任务数量
|
||||
*/
|
||||
private Integer generatedTasks;
|
||||
|
||||
/**
|
||||
* 生成的成员数量
|
||||
*/
|
||||
private Integer generatedMembers;
|
||||
|
||||
/**
|
||||
* 生成的资源数量
|
||||
*/
|
||||
private Integer generatedResources;
|
||||
|
||||
/**
|
||||
* 使用的AI模型
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 消耗的Token数
|
||||
*/
|
||||
private Integer tokensUsed;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 输入文件信息内部类
|
||||
*/
|
||||
@Data
|
||||
public static class InputFile {
|
||||
private String name;
|
||||
private String path;
|
||||
private String type;
|
||||
private Long size;
|
||||
}
|
||||
}
|
||||
83
src/main/java/cn/yinlihupo/domain/entity/ProjectMember.java
Normal file
83
src/main/java/cn/yinlihupo/domain/entity/ProjectMember.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 项目成员实体类
|
||||
* 对应数据库表: project_member
|
||||
*/
|
||||
@Data
|
||||
@TableName("project_member")
|
||||
public class ProjectMember {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 项目角色: manager-项目经理, leader-负责人, member-成员, observer-观察者
|
||||
*/
|
||||
private String roleCode;
|
||||
|
||||
/**
|
||||
* 加入日期
|
||||
*/
|
||||
private LocalDate joinDate;
|
||||
|
||||
/**
|
||||
* 离开日期
|
||||
*/
|
||||
private LocalDate leaveDate;
|
||||
|
||||
/**
|
||||
* 职责描述
|
||||
*/
|
||||
private String responsibility;
|
||||
|
||||
/**
|
||||
* 每周投入小时数
|
||||
*/
|
||||
private BigDecimal weeklyHours;
|
||||
|
||||
/**
|
||||
* 状态: 1-正常, 0-已移除
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
106
src/main/java/cn/yinlihupo/domain/entity/ProjectMilestone.java
Normal file
106
src/main/java/cn/yinlihupo/domain/entity/ProjectMilestone.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目里程碑实体类
|
||||
* 对应数据库表: project_milestone
|
||||
*/
|
||||
@Data
|
||||
@TableName("project_milestone")
|
||||
public class ProjectMilestone {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 里程碑名称
|
||||
*/
|
||||
private String milestoneName;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 计划日期
|
||||
*/
|
||||
private LocalDate planDate;
|
||||
|
||||
/**
|
||||
* 实际日期
|
||||
*/
|
||||
private LocalDate actualDate;
|
||||
|
||||
/**
|
||||
* 状态: pending-待开始, in_progress-进行中, completed-已完成, delayed-延期
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 完成进度
|
||||
*/
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 是否关键里程碑: 1-是, 0-否
|
||||
*/
|
||||
private Integer isKey;
|
||||
|
||||
/**
|
||||
* 交付物列表
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<String> deliverables;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object extraData;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 项目时间节点实体类
|
||||
* 对应数据库表: project_timeline
|
||||
*/
|
||||
@Data
|
||||
@TableName("project_timeline")
|
||||
public class ProjectTimeline {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 节点类型: phase-阶段, milestone-里程碑, event-事件, checkpoint-检查点
|
||||
*/
|
||||
private String nodeType;
|
||||
|
||||
/**
|
||||
* 父节点ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 计划日期
|
||||
*/
|
||||
private LocalDate planDate;
|
||||
|
||||
/**
|
||||
* 实际日期
|
||||
*/
|
||||
private LocalDate actualDate;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态: pending-待开始, in_progress-进行中, completed-已完成, delayed-延期
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 知识库范围配置["report","file","risk","ticket"]
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<String> kbScope;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object extraData;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
147
src/main/java/cn/yinlihupo/domain/entity/Resource.java
Normal file
147
src/main/java/cn/yinlihupo/domain/entity/Resource.java
Normal file
@@ -0,0 +1,147 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 资源实体类
|
||||
* 对应数据库表: resource
|
||||
*/
|
||||
@Data
|
||||
@TableName("resource")
|
||||
public class Resource {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 资源编号
|
||||
*/
|
||||
private String resourceCode;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 资源类型: human-人力, material-物料, equipment-设备, software-软件, finance-资金, other-其他
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 状态: planned-计划中, requested-已申请, approved-已批准, procuring-采购中, arrived-已到货, in_use-使用中, completed-已完成
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 计划到位日期
|
||||
*/
|
||||
private LocalDate planArriveDate;
|
||||
|
||||
/**
|
||||
* 实际到位日期
|
||||
*/
|
||||
private LocalDate actualArriveDate;
|
||||
|
||||
/**
|
||||
* 负责人ID
|
||||
*/
|
||||
private Long responsibleId;
|
||||
|
||||
/**
|
||||
* 存放位置
|
||||
*/
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object extraData;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
169
src/main/java/cn/yinlihupo/domain/entity/Risk.java
Normal file
169
src/main/java/cn/yinlihupo/domain/entity/Risk.java
Normal file
@@ -0,0 +1,169 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 风险实体类
|
||||
* 对应数据库表: risk
|
||||
*/
|
||||
@Data
|
||||
@TableName("risk")
|
||||
public class Risk {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 风险编号
|
||||
*/
|
||||
private String riskCode;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 风险分类: technical-技术风险, schedule-进度风险, cost-成本风险, quality-质量风险, resource-资源风险, external-外部风险, other-其他
|
||||
*/
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* 风险名称
|
||||
*/
|
||||
private String riskName;
|
||||
|
||||
/**
|
||||
* 风险描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 风险来源: internal-内部, external-外部, ai_detection-AI检测
|
||||
*/
|
||||
private String riskSource;
|
||||
|
||||
/**
|
||||
* 风险类型
|
||||
*/
|
||||
private String riskType;
|
||||
|
||||
/**
|
||||
* 发生概率(0-100%)
|
||||
*/
|
||||
private BigDecimal probability;
|
||||
|
||||
/**
|
||||
* 影响程度(1-5)
|
||||
*/
|
||||
private BigDecimal impact;
|
||||
|
||||
/**
|
||||
* 风险得分(概率*影响)
|
||||
*/
|
||||
private BigDecimal riskScore;
|
||||
|
||||
/**
|
||||
* 风险等级: critical-严重, high-高, medium-中, low-低
|
||||
*/
|
||||
private String riskLevel;
|
||||
|
||||
/**
|
||||
* 状态: identified-已识别, assigned-已分派工单, mitigating-缓解中, resolved-已解决, closed-已关闭
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 负责人ID
|
||||
*/
|
||||
private Long ownerId;
|
||||
|
||||
/**
|
||||
* 关联的工单ID数组
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<Long> workOrderIds;
|
||||
|
||||
/**
|
||||
* 缓解措施
|
||||
*/
|
||||
private String mitigationPlan;
|
||||
|
||||
/**
|
||||
* 应急计划
|
||||
*/
|
||||
private String contingencyPlan;
|
||||
|
||||
/**
|
||||
* 触发条件
|
||||
*/
|
||||
private String triggerCondition;
|
||||
|
||||
/**
|
||||
* 发现时间
|
||||
*/
|
||||
private LocalDateTime discoverTime;
|
||||
|
||||
/**
|
||||
* 预期解决日期
|
||||
*/
|
||||
private LocalDate dueDate;
|
||||
|
||||
/**
|
||||
* 解决时间
|
||||
*/
|
||||
private LocalDateTime resolvedTime;
|
||||
|
||||
/**
|
||||
* AI分析结果
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object aiAnalysis;
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object extraData;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
158
src/main/java/cn/yinlihupo/domain/entity/Task.java
Normal file
158
src/main/java/cn/yinlihupo/domain/entity/Task.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package cn.yinlihupo.domain.entity;
|
||||
|
||||
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务实体类
|
||||
* 对应数据库表: task
|
||||
*/
|
||||
@Data
|
||||
@TableName("task")
|
||||
public class Task {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 任务编号
|
||||
*/
|
||||
private String taskCode;
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 所属里程碑ID
|
||||
*/
|
||||
private Long milestoneId;
|
||||
|
||||
/**
|
||||
* 父任务ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
private String taskName;
|
||||
|
||||
/**
|
||||
* 任务描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 任务类型
|
||||
*/
|
||||
private String taskType;
|
||||
|
||||
/**
|
||||
* 执行人ID
|
||||
*/
|
||||
private Long assigneeId;
|
||||
|
||||
/**
|
||||
* 计划开始日期
|
||||
*/
|
||||
private LocalDate planStartDate;
|
||||
|
||||
/**
|
||||
* 计划结束日期
|
||||
*/
|
||||
private LocalDate planEndDate;
|
||||
|
||||
/**
|
||||
* 实际开始日期
|
||||
*/
|
||||
private LocalDate actualStartDate;
|
||||
|
||||
/**
|
||||
* 实际结束日期
|
||||
*/
|
||||
private LocalDate actualEndDate;
|
||||
|
||||
/**
|
||||
* 计划工时(小时)
|
||||
*/
|
||||
private BigDecimal planHours;
|
||||
|
||||
/**
|
||||
* 实际工时(小时)
|
||||
*/
|
||||
private BigDecimal actualHours;
|
||||
|
||||
/**
|
||||
* 进度百分比
|
||||
*/
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 优先级: critical-关键, high-高, medium-中, low-低
|
||||
*/
|
||||
private String priority;
|
||||
|
||||
/**
|
||||
* 状态: pending-待开始, in_progress-进行中, completed-已完成, cancelled-已取消
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* 附件列表
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private List<Object> attachments;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||
private Object extraData;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.yinlihupo.domain.dto;
|
||||
package cn.yinlihupo.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.ProjectInitRecord;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 项目初始化记录Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectInitRecordMapper extends BaseMapper<ProjectInitRecord> {
|
||||
}
|
||||
12
src/main/java/cn/yinlihupo/mapper/ProjectMapper.java
Normal file
12
src/main/java/cn/yinlihupo/mapper/ProjectMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.Project;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 项目Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectMapper extends BaseMapper<Project> {
|
||||
}
|
||||
12
src/main/java/cn/yinlihupo/mapper/ProjectMemberMapper.java
Normal file
12
src/main/java/cn/yinlihupo/mapper/ProjectMemberMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.ProjectMember;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 项目成员Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectMemberMapper extends BaseMapper<ProjectMember> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.ProjectMilestone;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 项目里程碑Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectMilestoneMapper extends BaseMapper<ProjectMilestone> {
|
||||
}
|
||||
12
src/main/java/cn/yinlihupo/mapper/ProjectTimelineMapper.java
Normal file
12
src/main/java/cn/yinlihupo/mapper/ProjectTimelineMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.ProjectTimeline;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 项目时间节点Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProjectTimelineMapper extends BaseMapper<ProjectTimeline> {
|
||||
}
|
||||
12
src/main/java/cn/yinlihupo/mapper/ResourceMapper.java
Normal file
12
src/main/java/cn/yinlihupo/mapper/ResourceMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.Resource;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 资源Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface ResourceMapper extends BaseMapper<Resource> {
|
||||
}
|
||||
12
src/main/java/cn/yinlihupo/mapper/RiskMapper.java
Normal file
12
src/main/java/cn/yinlihupo/mapper/RiskMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.Risk;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 风险Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface RiskMapper extends BaseMapper<Risk> {
|
||||
}
|
||||
12
src/main/java/cn/yinlihupo/mapper/TaskMapper.java
Normal file
12
src/main/java/cn/yinlihupo/mapper/TaskMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cn.yinlihupo.mapper;
|
||||
|
||||
import cn.yinlihupo.domain.entity.Task;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 任务Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface TaskMapper extends BaseMapper<Task> {
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package cn.yinlihupo.service.project;
|
||||
|
||||
import cn.yinlihupo.domain.dto.ProjectInitResult;
|
||||
import cn.yinlihupo.domain.vo.ProjectInitResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI项目初始化服务接口
|
||||
@@ -24,4 +27,21 @@ public interface ProjectService {
|
||||
* @return 项目初始化结果
|
||||
*/
|
||||
ProjectInitResult generateProjectFromFile(String fileUrl, String fileType);
|
||||
|
||||
/**
|
||||
* 上传文件并生成项目初始化数据,同时保存到数据库
|
||||
*
|
||||
* @param file 项目资料文件
|
||||
* @return 项目初始化结果
|
||||
*/
|
||||
ProjectInitResult generateAndSaveProject(MultipartFile file);
|
||||
|
||||
/**
|
||||
* 根据项目资料内容生成并保存项目初始化数据
|
||||
*
|
||||
* @param content 项目资料文本内容
|
||||
* @param inputFiles 输入文件列表信息
|
||||
* @return 项目初始化结果
|
||||
*/
|
||||
ProjectInitResult generateAndSaveProjectFromContent(String content, List<cn.yinlihupo.domain.entity.ProjectInitRecord.InputFile> inputFiles);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
package cn.yinlihupo.service.project.impl;
|
||||
|
||||
import cn.yinlihupo.domain.dto.ProjectInitResult;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.yinlihupo.domain.entity.*;
|
||||
import cn.yinlihupo.domain.vo.ProjectInitResult;
|
||||
import cn.yinlihupo.mapper.*;
|
||||
import cn.yinlihupo.service.oss.OssService;
|
||||
import cn.yinlihupo.service.project.ProjectService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.prompt.PromptTemplate;
|
||||
import org.springframework.ai.chat.metadata.Usage;
|
||||
import org.springframework.ai.converter.BeanOutputConverter;
|
||||
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.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI项目初始化服务实现类
|
||||
@@ -21,6 +36,16 @@ 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 ProjectMemberMapper projectMemberMapper;
|
||||
private final ResourceMapper resourceMapper;
|
||||
private final RiskMapper riskMapper;
|
||||
private final ProjectTimelineMapper projectTimelineMapper;
|
||||
private final ProjectInitRecordMapper projectInitRecordMapper;
|
||||
|
||||
/**
|
||||
* 项目初始化系统提示词模板
|
||||
*/
|
||||
@@ -172,4 +197,384 @@ public class ProjectServiceImpl implements ProjectService {
|
||||
|
||||
return generateProjectFromContent(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ProjectInitResult generateAndSaveProject(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. 构建输入文件信息
|
||||
List<ProjectInitRecord.InputFile> inputFiles = new ArrayList<>();
|
||||
ProjectInitRecord.InputFile inputFile = new ProjectInitRecord.InputFile();
|
||||
inputFile.setName(file.getOriginalFilename());
|
||||
inputFile.setPath(fileUrl);
|
||||
inputFile.setType(file.getContentType());
|
||||
inputFile.setSize(file.getSize());
|
||||
inputFiles.add(inputFile);
|
||||
|
||||
// 4. 生成并保存项目数据
|
||||
return generateAndSaveProjectFromContent(content, inputFiles);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("项目初始化失败: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("项目初始化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ProjectInitResult generateAndSaveProjectFromContent(String content,
|
||||
List<ProjectInitRecord.InputFile> inputFiles) {
|
||||
log.info("开始生成并保存项目初始化数据");
|
||||
|
||||
// 1. 先创建初始化记录(状态为处理中)
|
||||
ProjectInitRecord initRecord = new ProjectInitRecord();
|
||||
initRecord.setInputFiles(inputFiles);
|
||||
initRecord.setInputText(content.length() > 5000 ? content.substring(0, 5000) + "..." : content);
|
||||
initRecord.setParseStatus("processing");
|
||||
projectInitRecordMapper.insert(initRecord);
|
||||
|
||||
ProjectInitResult result = null;
|
||||
Usage usage = null;
|
||||
|
||||
try {
|
||||
// 2. 调用AI生成项目数据
|
||||
String userPrompt = "请根据以下项目资料,生成完整的项目初始化结构化数据:\n\n" +
|
||||
content + "\n\n" +
|
||||
"请严格按照系统提示词中的JSON格式输出,确保所有字段都包含合理的值。";
|
||||
|
||||
// 创建 BeanOutputConverter 用于转换响应
|
||||
BeanOutputConverter<ProjectInitResult> outputConverter = new BeanOutputConverter<>(ProjectInitResult.class);
|
||||
|
||||
var chatResponse = chatClient.prompt()
|
||||
.system(PROJECT_INIT_SYSTEM_PROMPT)
|
||||
.user(userPrompt)
|
||||
.call()
|
||||
.chatResponse();
|
||||
|
||||
// 使用 BeanOutputConverter 手动转换响应内容
|
||||
String responseContent = chatResponse.getResult().getOutput().getText();
|
||||
result = outputConverter.convert(responseContent);
|
||||
usage = chatResponse.getMetadata().getUsage();
|
||||
|
||||
// 3. 保存项目数据到数据库
|
||||
Long projectId = saveProjectData(result);
|
||||
|
||||
// 4. 更新初始化记录为成功状态
|
||||
initRecord.setProjectId(projectId);
|
||||
initRecord.setParseStatus("completed");
|
||||
initRecord.setParseResult(result);
|
||||
initRecord.setGeneratedMilestones(result.getMilestones() != null ? result.getMilestones().size() : 0);
|
||||
initRecord.setGeneratedTasks(result.getTasks() != null ? result.getTasks().size() : 0);
|
||||
initRecord.setGeneratedMembers(result.getMembers() != null ? result.getMembers().size() : 0);
|
||||
initRecord.setGeneratedResources(result.getResources() != null ? result.getResources().size() : 0);
|
||||
if (usage != null) {
|
||||
initRecord.setTokensUsed(usage.getTotalTokens());
|
||||
}
|
||||
projectInitRecordMapper.updateById(initRecord);
|
||||
|
||||
log.info("项目初始化数据保存成功, projectId: {}", projectId);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("项目初始化失败: {}", e.getMessage(), e);
|
||||
|
||||
// 更新初始化记录为失败状态
|
||||
initRecord.setParseStatus("failed");
|
||||
initRecord.setErrorMessage(e.getMessage());
|
||||
if (usage != null) {
|
||||
initRecord.setTokensUsed(usage.getTotalTokens());
|
||||
}
|
||||
projectInitRecordMapper.updateById(initRecord);
|
||||
|
||||
throw new RuntimeException("项目初始化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存项目数据到数据库
|
||||
*
|
||||
* @param result AI生成的项目初始化结果
|
||||
* @return 项目ID
|
||||
*/
|
||||
private Long saveProjectData(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);
|
||||
|
||||
// 使用任务名称作为临时ID进行映射
|
||||
String tempId = taskInfo.getTaskName();
|
||||
taskTempIdToId.put(tempId, task.getId());
|
||||
}
|
||||
|
||||
// 第二轮:更新任务依赖关系
|
||||
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 parentId = taskTempIdToId.get(taskInfo.getParentTaskId());
|
||||
if (parentId != null) {
|
||||
task.setParentId(parentId);
|
||||
taskMapper.updateById(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("保存了 {} 个任务", result.getTasks().size());
|
||||
}
|
||||
|
||||
// 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);
|
||||
// TODO: 需要根据name查找或创建用户,暂时留空
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ spring:
|
||||
|
||||
# PostgreSQL 数据库配置
|
||||
datasource:
|
||||
url: jdbc:postgresql://10.200.8.25:5432/ylhp_ai_project_manager
|
||||
url: jdbc:postgresql://10.200.8.25:5432/aiprojectmanager
|
||||
username: postgres
|
||||
password: postgres
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
Reference in New Issue
Block a user