feat(project): 实现AI项目初始化及相关实体管理

- 新增通用返回类BaseResponse用于统一接口响应格式
- 新增业务异常BusinessException及全局异常处理GlobalExceptionHandler
- 新增OSS文件上传控制器支持文件上传与删除接口
- 添加项目核心实体类Project、ProjectMember、ProjectMilestone、ProjectTimeline和Resource
- 实现ProjectService接口及其实现类,使用AI能力从项目文档生成结构化项目数据
- 在ProjectServiceImpl中实现项目数据解析、保存及业务逻辑,包括项目、里程碑、任务、成员、资源、风险等
- 项目初始化控制器ProjectController提供文件上传触发项目初始化功能
- 设计了详细的系统提示词和用户提示词,用于AI模型指导生成严格格式的结构化项目数据
- 设计项目数据持久化流程,确保生成的数据正确保存至数据库,支持事务回滚
- 增强日志记录,便于追踪项目初始化全过程及错误调试
This commit is contained in:
2026-03-27 10:25:13 +08:00
parent 729af44585
commit 294ef21d50
19 changed files with 237 additions and 287 deletions

View File

@@ -44,7 +44,6 @@ public class ProjectServiceImpl implements ProjectService {
private final ResourceMapper resourceMapper;
private final RiskMapper riskMapper;
private final ProjectTimelineMapper projectTimelineMapper;
private final ProjectInitRecordMapper projectInitRecordMapper;
/**
* 项目初始化系统提示词模板
@@ -214,17 +213,8 @@ public class ProjectServiceImpl implements ProjectService {
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);
// 3. 生成并保存项目数据
return generateAndSaveProjectFromContent(content);
} catch (Exception e) {
log.error("项目初始化失败: {}", e.getMessage(), e);
@@ -234,22 +224,13 @@ public class ProjectServiceImpl implements ProjectService {
@Override
@Transactional(rollbackFor = Exception.class)
public ProjectInitResult generateAndSaveProjectFromContent(String content,
List<ProjectInitRecord.InputFile> inputFiles) {
public ProjectInitResult generateAndSaveProjectFromContent(String content) {
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;
ProjectInitResult result;
try {
// 2. 调用AI生成项目数据
// 1. 调用AI生成项目数据
String userPrompt = "请根据以下项目资料,生成完整的项目初始化结构化数据:\n\n" +
content + "\n\n" +
"请严格按照系统提示词中的JSON格式输出确保所有字段都包含合理的值。";
@@ -266,38 +247,15 @@ public class ProjectServiceImpl implements ProjectService {
// 使用 BeanOutputConverter 手动转换响应内容
String responseContent = chatResponse.getResult().getOutput().getText();
result = outputConverter.convert(responseContent);
usage = chatResponse.getMetadata().getUsage();
// 3. 保存项目数据到数据库
// 2. 保存项目数据到数据库
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);
}
}