Browse Source

完成合同审核的优化和调整

ai_300
zhouhaibin 2 weeks ago
parent
commit
547eb54a5b
  1. 4
      ruoyi-admin/src/main/resources/application-test.yml
  2. 12
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualRegulationNamesController.java
  3. 12
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualTaskResultsController.java
  4. 20
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/ContractualTasks.java
  5. 17
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/StartContractReviewRequest.java
  6. 41
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/bo/ContractualTasksBo.java
  7. 10
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/ContractualTasksVo.java
  8. 13
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/DocumentTaskResultVO.java
  9. 8
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/IContractualTaskResultsService.java
  10. 828
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTaskResultsServiceImpl.java
  11. 19
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java
  12. 2
      zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTaskResultsServiceImpl.java
  13. 2
      zaojiaManagement/zaojia-productManagement/src/main/resources/mapper/productManagement/DocumentTaskResultsMapper.xml

4
ruoyi-admin/src/main/resources/application-test.yml

@ -282,6 +282,6 @@ justauth:
# maxConnectNumPerRoute: 100 # maxConnectNumPerRoute: 100
chat: chat:
# 聊天机器人配置 # 聊天机器人配置
filePath: /guoYanXinXi/data/software/minio/data/ruoyi/ filePath: /guoYanXinXi/data/software/minio/data/ruoyi
tempfilePath: /guoYanXinXi/data/software/app/tempfile/ tempfilePath: /guoYanXinXi/data/software/app/tempfile
chatUrl: http://127.0.0.1:8081 chatUrl: http://127.0.0.1:8081

12
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualRegulationNamesController.java

@ -135,4 +135,16 @@ public class ContractualRegulationNamesController extends BaseController {
List<ContractualRegulationArticlesVo> articles = contractualRegulationNamesService.getRegulationArticles(id); List<ContractualRegulationArticlesVo> articles = contractualRegulationNamesService.getRegulationArticles(id);
return R.ok(articles); return R.ok(articles);
} }
/**
* 获取有效法规名称列表用于合规性审查
*/
@SaCheckPermission("productManagement:ContractualRegulationNames:list")
@GetMapping("/effective/list")
public R<List<ContractualRegulationNamesVo>> getEffectiveRegulationsList() {
ContractualRegulationNamesBo bo = new ContractualRegulationNamesBo();
bo.setIsEffective("Y"); // 只查询有效的法规
List<ContractualRegulationNamesVo> list = contractualRegulationNamesService.queryList(bo);
return R.ok(list);
}
} }

12
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/controller/ContractualTaskResultsController.java

@ -65,6 +65,18 @@ public class ContractualTaskResultsController extends BaseController {
contractualTaskResultsService.getPdfStream(taskId, response); contractualTaskResultsService.getPdfStream(taskId, response);
} }
/**
* 获取招投标文件PDF文件流
*
* @param taskId 任务ID
*/
@GetMapping("/getBidPdfStream/{taskId}")
public void getBidPdfStream(@NotNull(message = "任务ID不能为空")
@PathVariable String taskId,
HttpServletResponse response) {
contractualTaskResultsService.getBidPdfStream(taskId, response);
}
/** /**
* 更新合同任务结果项的状态已读/采纳 * 更新合同任务结果项的状态已读/采纳
* *

20
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/ContractualTasks.java

@ -29,16 +29,6 @@ public class ContractualTasks extends TenantEntity {
@TableId(value = "id") @TableId(value = "id")
private Long id; private Long id;
/**
* 模型所属行业
*/
private String taskIndustry;
/**
* 模型所属区域
*/
private String taskRegion;
/** /**
* 合同任务名称 * 合同任务名称
*/ */
@ -60,6 +50,11 @@ public class ContractualTasks extends TenantEntity {
*/ */
private String progressStatus; private String progressStatus;
/**
* 审查类型实质性审查合规性审查一致性审查
*/
private String reviewTypes;
/** /**
* *
*/ */
@ -114,4 +109,9 @@ public class ContractualTasks extends TenantEntity {
*/ */
private String reviewData; private String reviewData;
private String pdfPath; private String pdfPath;
/**
* 招投标文件路径
*/
private String bidPath;
} }

17
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/StartContractReviewRequest.java

@ -85,24 +85,19 @@ public class StartContractReviewRequest {
@Data @Data
public static class ComplianceData { public static class ComplianceData {
/** /**
* 关注要点 * 法规检查方式ai: AI自动选择, manual: 人工选择法规
*/ */
private List<String> focusPoints; private String regulationMethod;
/** /**
* 行业类型 * 选择的法规名称ID列表当regulationMethod为manual时使用
*/ */
private String industry; private List<String> regulationIds;
/** /**
* 合规级别 * 特别说明
*/
private String level;
/**
* 适用法规列表
*/ */
private List<String> regulations; private String specialNote;
/** /**
* 审查类型 * 审查类型

41
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/bo/ContractualTasksBo.java

@ -60,36 +60,15 @@ public class ContractualTasksBo extends BaseTaskBo {
*/ */
private String reviewData; private String reviewData;
private String pdfPath; private String pdfPath;
// /**
// * 模型所属行业 /**
// */ * 招投标文件路径
// @NotBlank(message = "模型所属行业不能为空", groups = { AddGroup.class, EditGroup.class }) */
// private String taskIndustry; private String bidPath;
//
// /** /**
// * 模型所属区域 * 审查类型实质性审查合规性审查一致性审查
// */ */
// @NotBlank(message = "模型所属区域不能为空", groups = { AddGroup.class, EditGroup.class }) private String reviewTypes;
// private String taskRegion;
//
//
// @NotEmpty(message = "任务名称列表不能为空", groups = { AddGroup.class, EditGroup.class })
// private List<@NotBlank(message = "任务名称不能为空") String> taskNameList;
// /**
// *
// */
// private String documentName;
//
// /**
// *
// */
// @NotNull(message = "不能为空", groups = { AddGroup.class, EditGroup.class })
// private Long ossId;
//
// /**
// *
// */
// private String progressStatus;
} }

10
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/ContractualTasksVo.java

@ -75,4 +75,14 @@ public class ContractualTasksVo extends BaseTaskVo implements Serializable {
private String reviewData; private String reviewData;
private Long groupId; private Long groupId;
private String pdfPath; private String pdfPath;
/**
* 招投标文件路径
*/
private String bidPath;
/**
* 审查类型实质性审查合规性审查一致性审查
*/
private String reviewTypes;
} }

13
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/domain/vo/DocumentTaskResultVO.java

@ -13,9 +13,9 @@ public class DocumentTaskResultVO {
@Data @Data
public static class ResultItem { public static class ResultItem {
private String id; // 唯一标识ID private String id; // 唯一标识ID
private Integer serialNumber; // 序号 private Integer serialNumber; // 序号
private String issueName; // 问题点名称 private String issueName; // 问题点名称
@ -30,18 +30,11 @@ public class DocumentTaskResultVO {
private String existingIssues; // 存在的问题 private String existingIssues; // 存在的问题
private ReviewBasis reviewBasis; // 审查依据 private String reviewBasis; // 审查依据
private String isRead; // 是否已读 private String isRead; // 是否已读
private String isAdopted; // 是否采纳 private String isAdopted; // 是否采纳
@Data
public static class ReviewBasis {
private String reviewContent; // 审查内容
private List<String> reviewPoints; // 审查点
}
} }
} }

8
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/IContractualTaskResultsService.java

@ -37,6 +37,14 @@ public interface IContractualTaskResultsService {
*/ */
void getPdfStream(String taskId, HttpServletResponse response); void getPdfStream(String taskId, HttpServletResponse response);
/**
* 获取招投标文件PDF文件流
*
* @param taskId 任务ID
* @param response HTTP响应对象
*/
void getBidPdfStream(String taskId, HttpServletResponse response);
/** /**
* 更新合同任务结果项的状态已读/采纳 * 更新合同任务结果项的状态已读/采纳
* *

828
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTaskResultsServiceImpl.java

@ -4,15 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.data.MutableDataSet;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.*;
import org.dromara.common.core.service.DictService; import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.productManagement.domain.ContractualTaskResultDetail; import org.dromara.productManagement.domain.ContractualTaskResultDetail;
@ -24,13 +19,9 @@ import org.dromara.productManagement.service.IContractualTaskResultsService;
import org.dromara.productManagement.service.IContractualTasksService; import org.dromara.productManagement.service.IContractualTasksService;
import org.dromara.system.domain.vo.SysOssVo; import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.service.ISysOssService; import org.dromara.system.service.ISysOssService;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Safelist;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.io.*; import java.io.*;
@ -39,6 +30,7 @@ import java.nio.charset.StandardCharsets;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -60,10 +52,10 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
private final IContractualTasksService contractualTasksService; private final IContractualTasksService contractualTasksService;
private final DictService dictTypeService; private final DictService dictTypeService;
protected final ISysOssService ossService; protected final ISysOssService ossService;
@Value("${chat.tempfilePath}") @Value("${chat.tempfilePath}")
protected String tempfilePath; protected String tempfilePath;
@Value("${chat.filePath}") @Value("${chat.filePath}")
protected String fileRootPath; protected String fileRootPath;
@ -161,7 +153,6 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
@Override @Override
public void downloadResult(Long[] ids, HttpServletResponse response) { public void downloadResult(Long[] ids, HttpServletResponse response) {
try { try {
// 使用新的导出逻辑,基于getDetailResultsByTaskId获取结果
if (ids.length > 0) { if (ids.length > 0) {
if (ids.length > 1) { if (ids.length > 1) {
// 多文件压缩下载 // 多文件压缩下载
@ -181,43 +172,22 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
// 获取详细的任务结果 // 获取详细的任务结果
List<ContractualTaskResultVO> resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); List<ContractualTaskResultVO> resultDetails = getDetailResultsByTaskId(String.valueOf(taskId));
if (resultDetails != null && !resultDetails.isEmpty()) { if (resultDetails != null && resultDetails.size() > 1) {
// 第一个是"全部"分类 // 跳过第一个"全部"分类,从第二个开始处理
ContractualTaskResultVO allCategory = resultDetails.get(0); List<ContractualTaskResultVO> categorizedResults = resultDetails.subList(1, resultDetails.size());
if (allCategory.getResults() != null && !allCategory.getResults().isEmpty()) {
// 获取任务类型对应的字段配置 // 为每个结果创建docx文件
List<FieldConfig> fieldConfigs = getFieldConfigsByTaskType(taskName); String docxFileName = label + "_" + documentName + "_" + ".docx";
String docxPath = tempDir + File.separator + docxFileName;
// 将结果转换为Markdown
StringBuilder markdown = new StringBuilder(); // 生成docx文档
for (ContractualTaskResultVO.ResultItem item : allCategory.getResults()) { generateDocxDocument(categorizedResults, docxPath);
markdown.append("## ").append(item.getSerialNumber()).append(". ").append(item.getExistingIssues()).append("\n\n");
// 根据配置添加各字段内容
for (FieldConfig config : fieldConfigs) {
String fieldValue = getFieldValue(item, config.getField());
if (StringUtils.isNotBlank(fieldValue)) {
markdown.append("### ").append(config.getTitle()).append("\n\n").append(fieldValue).append("\n\n");
}
}
// 添加分隔线
markdown.append("---\n\n");
}
// 为每个结果创建PDF文件
String pdfFileName = label + "_" + documentName + "_" + ".pdf";
String pdfPath = tempDir + File.separator + pdfFileName;
// 转换Markdown为PDF
convertMarkdownToPdf(markdown.toString(), pdfPath);
// 添加到ZIP // 添加到ZIP
addToZip(pdfPath, pdfFileName, zos); addToZip(docxPath, docxFileName, zos);
// 删除临时PDF文件 // 删除临时docx文件
new File(pdfPath).delete(); new File(docxPath).delete();
}
} }
} }
} }
@ -242,46 +212,25 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
// 获取详细的任务结果 // 获取详细的任务结果
List<ContractualTaskResultVO> resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); List<ContractualTaskResultVO> resultDetails = getDetailResultsByTaskId(String.valueOf(taskId));
if (resultDetails == null || resultDetails.isEmpty() || if (resultDetails == null || resultDetails.size() <= 1) {
resultDetails.get(0).getResults() == null || resultDetails.get(0).getResults().isEmpty()) {
throw new RuntimeException("未找到相关结果"); throw new RuntimeException("未找到相关结果");
} }
// 第一个"全部"分类 // 跳过第一个"全部"分类,从第二个开始处理
ContractualTaskResultVO allCategory = resultDetails.get(0); List<ContractualTaskResultVO> categorizedResults = resultDetails.subList(1, resultDetails.size());
// 获取任务类型对应的字段配置 String docxFileName = label + "_" + documentName + "_" + ".docx";
List<FieldConfig> fieldConfigs = getFieldConfigsByTaskType(taskName);
// 将结果转换为Markdown
StringBuilder markdown = new StringBuilder();
for (ContractualTaskResultVO.ResultItem item : allCategory.getResults()) {
markdown.append("## ").append(item.getSerialNumber()).append(". ").append(item.getExistingIssues()).append("\n\n");
// 根据配置添加各字段内容
for (FieldConfig config : fieldConfigs) {
String fieldValue = getFieldValue(item, config.getField());
if (StringUtils.isNotBlank(fieldValue)) {
markdown.append("### ").append(config.getTitle()).append("\n\n").append(fieldValue).append("\n\n");
}
}
// 添加分隔线
markdown.append("---\n\n");
}
String pdfFileName = label + "_" + documentName + "_" + ".pdf";
String tempDir = System.getProperty("java.io.tmpdir"); String tempDir = System.getProperty("java.io.tmpdir");
String pdfPath = tempDir + File.separator + pdfFileName; String docxPath = tempDir + File.separator + docxFileName;
// 转换Markdown为PDF // 生成docx文档
convertMarkdownToPdf(markdown.toString(), pdfPath); generateDocxDocument(categorizedResults, docxPath);
// 下载PDF文件 // 下载docx文件
downloadFile(response, pdfPath, pdfFileName); downloadFile(response, docxPath, docxFileName);
// 删除临时PDF文件 // 删除临时docx文件
new File(pdfPath).delete(); new File(docxPath).delete();
} }
} else { } else {
throw new RuntimeException("未找到相关文件"); throw new RuntimeException("未找到相关文件");
@ -329,7 +278,7 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
previewDir.mkdirs(); previewDir.mkdirs();
} }
filerotPath = fileRootPath + fileInfo.getFileName(); filerotPath = fileRootPath +File.separator+ fileInfo.getFileName();
// 如果不是pdf文件,需要转换 // 如果不是pdf文件,需要转换
if (!fileName.toLowerCase().endsWith(".pdf")) { if (!fileName.toLowerCase().endsWith(".pdf")) {
@ -392,86 +341,133 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
} }
} }
// Markdown转PDF方法 @Override
private void convertMarkdownToPdf(String markdown, String outputPath) throws Exception { public void getBidPdfStream(String taskId, HttpServletResponse response) {
MutableDataSet options = new MutableDataSet(); try {
// 启用表格解析 // 查询任务信息
options.set(Parser.EXTENSIONS, Arrays.asList( ContractualTasksVo task = contractualTasksService.queryById(Long.valueOf(taskId));
TablesExtension.create(), if (task == null) {
StrikethroughExtension.create() throw new RuntimeException("任务不存在");
)); }
Parser parser = Parser.builder(options).build(); String outputPath = tempfilePath + File.separator + "pdf_preview" + File.separator;
HtmlRenderer renderer = HtmlRenderer.builder(options) String bidPath = task.getBidPath();
.extensions(Arrays.asList( String filePath = "";
TablesExtension.create(), String filerotPath = "";
StrikethroughExtension.create()
)) if (StringUtils.isNotEmpty(bidPath)) {
.build(); // 如果bidPath存在,直接使用该路径
filePath = bidPath;
Node document = parser.parse(markdown); } else {
String htmlContent = renderer.render(document); // 如果bidPath不存在,从reviewData中解析bidDocumentPath
// 使用 JSoup 清理 String reviewData = task.getReviewData();
Document.OutputSettings settings = new Document.OutputSettings(); if (StringUtils.isEmpty(reviewData)) {
settings.prettyPrint(false); throw new RuntimeException("未找到审查数据");
}
// 配置允许的标签
Safelist safelist = Safelist.relaxed() try {
.addTags("em", "strong", "div", "span", "br", "table", "thead", "tbody", "tr", "th", "td", "img") ObjectMapper objectMapper = new ObjectMapper();
.addAttributes(":all", "style", "class") JsonNode rootNode = objectMapper.readTree(reviewData);
.addAttributes("table", "border", "cellpadding", "cellspacing") JsonNode consistencyNode = rootNode.get("consistency");
.addAttributes("img", "src", "alt", "width", "height"); // 允许img标签及其属性
htmlContent = Jsoup.clean(htmlContent, "", safelist, settings); if (consistencyNode == null || consistencyNode.isNull()) {
throw new RuntimeException("未找到一致性审查配置");
// 创建完整的HTML文档,使用微软雅黑 }
String html = String.format("""
<!DOCTYPE html> JsonNode bidDocumentPathNode = consistencyNode.get("bidDocumentPath");
<html> if (bidDocumentPathNode == null || bidDocumentPathNode.isNull()) {
<head> throw new RuntimeException("未找到招投标文件路径");
<meta charset="UTF-8"/> }
<style type="text/css">
body { String bidDocumentPath = bidDocumentPathNode.asText();
font-family: Microsoft YaHei; if (StringUtils.isEmpty(bidDocumentPath)) {
line-height: 1.6; throw new RuntimeException("招投标文件路径为空");
margin: 20px; }
font-size: 14px;
} log.info("从reviewData中获取到招投标文件路径: " + bidDocumentPath);
p { margin: 10px 0; }
pre { // 创建pdf预览目录
background-color: #f5f5f5; File previewDir = new File(outputPath);
padding: 10px; if (!previewDir.exists()) {
border-radius: 4px; previewDir.mkdirs();
} }
/* 增强表格样式 */
table { filerotPath = bidDocumentPath;
border-collapse: collapse;
width: 100%%; // 检查文件是否为PDF格式
margin: 10px 0; if (!bidDocumentPath.toLowerCase().endsWith(".pdf")) {
table-layout: fixed; // 需要转换为PDF
} String fileName = new File(bidDocumentPath).getName();
th, td { String fileNameWithoutExt = fileName.substring(0, fileName.lastIndexOf("."));
border: 1px solid #ddd; filePath = outputPath + fileNameWithoutExt + ".pdf";
padding: 8px; log.info("需要转换招投标文件: " + bidDocumentPath + " -> " + filePath);
text-align: left; } else {
word-wrap: break-word; // 已经是PDF格式,直接使用
} filePath = bidDocumentPath;
th { }
background-color: #f2f2f2;
font-weight: bold; } catch (Exception e) {
} log.error("解析reviewData失败", e);
</style> throw new RuntimeException("解析审查数据失败: " + e.getMessage());
</head> }
<body> }
%s
</body> log.info("开始输出招投标文件: " + filePath);
</html>
""", htmlContent); // 读取文件并输出到response
File file = new File(filePath);
try (OutputStream outputStream = new FileOutputStream(outputPath)) { if (!file.exists()) {
ITextRenderer renderer2 = new ITextRenderer(); log.info("招投标文件不存在,开始转换: " + filePath);
renderer2.setDocumentFromString(html);
renderer2.layout(); // 使用libreoffice转换为pdf
renderer2.createPDF(outputStream); ProcessBuilder builder = new ProcessBuilder("libreoffice", "--headless", "--convert-to", "pdf", filerotPath, "--outdir", outputPath);
Process process = builder.start();
int completed = process.waitFor();
if (completed != 0) {
InputStream errorStream = process.getErrorStream();
String errorMessage = new String(errorStream.readAllBytes());
process.destroyForcibly();
throw new RuntimeException("招投标文件转换失败: " + errorMessage);
}
process.destroyForcibly();
// 重新检查转换后的文件
if (!file.exists()) {
throw new RuntimeException("招投标文件转换后仍不存在: " + filePath);
}
}
log.info("招投标文件存在,开始输出: " + filePath);
// 使用JDBC更新数据库中的bid_path
try (Connection conn = dataSource.getConnection()) {
String sql = "UPDATE contractual_tasks SET bid_path = ? WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, filePath);
pstmt.setLong(2, Long.parseLong(taskId));
pstmt.executeUpdate();
}
} catch (SQLException e) {
log.error("更新招投标PDF路径失败", e);
throw new RuntimeException("更新招投标PDF路径失败: " + e.getMessage());
}
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(file.getName(), StandardCharsets.UTF_8.name()));
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis, 8192);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
}
} catch (Exception e) {
log.error("获取招投标PDF文件流失败", e);
throw new RuntimeException("获取招投标PDF文件流失败: " + e.getMessage());
} }
} }
@ -519,40 +515,251 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
} }
} }
private static class FieldConfig { /**
private String field; * 生成docx文档
private String title; *
* @param categorizedResults 分类结果列表
* @param outputPath 输出路径
* @throws Exception 异常
*/
private void generateDocxDocument(List<ContractualTaskResultVO> categorizedResults, String outputPath) throws Exception {
try (XWPFDocument document = new XWPFDocument()) {
int categoryIndex = 1; // 一级标题序号
for (ContractualTaskResultVO category : categorizedResults) {
if (category.getResults() == null || category.getResults().isEmpty()) {
continue;
}
// 添加一级标题(带序号)
XWPFParagraph title = document.createParagraph();
XWPFRun titleRun = title.createRun();
titleRun.setText(categoryIndex + ". " + category.getName());
titleRun.setBold(true);
titleRun.setFontSize(16);
titleRun.setFontFamily("Microsoft YaHei");
// 根据分类名称获取表格配置
TableConfiguration tableConfig = getTableConfiguration(category.getName());
int issueIndex = 1; // 二级标题序号
for (ContractualTaskResultVO.ResultItem item : category.getResults()) {
// 添加二级标题 - 问题点(带序号)
XWPFParagraph issueTitle = document.createParagraph();
XWPFRun issueTitleRun = issueTitle.createRun();
issueTitleRun.setText(categoryIndex + "." + issueIndex + ". 问题点:" + item.getExistingIssues());
issueTitleRun.setBold(true);
issueTitleRun.setFontSize(12);
issueTitleRun.setFontFamily("Microsoft YaHei");
// 创建表格
createTableForItem(document, tableConfig, item, category.getName());
// 添加段落间距
XWPFParagraph spacer = document.createParagraph();
spacer.createRun().addBreak();
issueIndex++;
}
categoryIndex++;
}
// 写入文件
try (FileOutputStream out = new FileOutputStream(outputPath)) {
document.write(out);
}
}
}
public FieldConfig(String field, String title) { /**
this.field = field; * 为特定项目创建表格 - 使用新的坐标配置系统
this.title = title; */
private void createTableForItem(XWPFDocument document, TableConfiguration config, ContractualTaskResultVO.ResultItem item, String categoryName) {
XWPFTable table = document.createTable();
setTableStyle(table);
// 创建表格的所有行
for (int i = 1; i < config.getTotalRows(); i++) {
table.createRow();
} }
public String getField() { // 确保表格有足够的列
return field; for (int i = 0; i < config.getTotalRows(); i++) {
XWPFTableRow row = table.getRow(i);
while (row.getTableCells().size() < config.getTotalColumns()) {
row.addNewTableCell();
}
} }
public String getTitle() { // 根据配置填充单元格
return title; for (CellConfiguration cellConfig : config.getCellConfigs()) {
XWPFTableRow row = table.getRow(cellConfig.getRow());
XWPFTableCell cell = row.getCell(cellConfig.getColumn());
String content = "";
if (cellConfig.isHeader()) {
content = cellConfig.getHeaderText();
} else {
content = getFieldValue(item, cellConfig.getFieldName());
}
setupTableCell(cell, content, cellConfig.isHeader());
// 处理合并
if (cellConfig.getMergeType() != MergeType.NONE) {
applyCellMerge(row, cellConfig);
}
} }
} }
/** /**
* 根据任务类型获取字段配置 * 应用单元格合并
*
* @param taskName 任务名称
* @return 字段配置列表
*/ */
private List<FieldConfig> getFieldConfigsByTaskType(String taskName) { private void applyCellMerge(XWPFTableRow row, CellConfiguration cellConfig) {
// 这里可以根据不同的任务类型返回不同的字段配置 switch (cellConfig.getMergeType()) {
// 目前返回通用的配置 case HORIZONTAL:
return Arrays.asList( int endCol = cellConfig.getColumn() + cellConfig.getMergeSpanColumns() - 1;
new FieldConfig("originalText", "原文"), mergeCellsHorizontally(row, cellConfig.getColumn(), endCol);
new FieldConfig("comparedText", "比对原文"), break;
new FieldConfig("modifiedContent", "修改后的内容"), case VERTICAL:
new FieldConfig("modificationDisplay", "展示修改情况"), // 垂直合并的实现(如果需要的话)
new FieldConfig("reviewBasis", "审查依据") // mergeVertically(table, cellConfig.getRow(), cellConfig.getColumn(), cellConfig.getMergeSpanRows());
); break;
case BOTH:
// 双向合并的实现(如果需要的话)
break;
default:
break;
}
}
/**
* 格式化审查依据字段
*/
private String formatReviewBasisField(String reviewBasis, String fieldName) {
if (StringUtils.isBlank(reviewBasis)) {
return "";
}
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(reviewBasis);
if (fieldName.equals("review_points[0]")) {
if (jsonNode.has("review_points") && jsonNode.get("review_points").isArray()) {
JsonNode arrayNode = jsonNode.get("review_points");
if (arrayNode.size() > 0) {
return arrayNode.get(0).asText();
}
}
} else if (fieldName.equals("review_content")) {
if (jsonNode.has("review_content") && !jsonNode.get("review_content").isNull()) {
return jsonNode.get("review_content").asText();
}
}
} catch (Exception e) {
log.warn("解析审查依据字段失败: " + fieldName, e);
}
return "";
}
/**
* 设置表格单元格 - 简化版
*/
private void setupTableCell(XWPFTableCell cell, String text, boolean isHeader) {
// 清除默认内容
if (cell.getParagraphs().size() > 0) {
cell.removeParagraph(0);
}
XWPFParagraph paragraph = cell.addParagraph();
XWPFRun run = paragraph.createRun();
run.setText(text != null ? text : "");
run.setFontFamily("Microsoft YaHei");
run.setFontSize(12);
if (isHeader) {
run.setBold(true);
// 设置表头背景色
cell.setColor("E6E6E6");
}
}
/**
* 设置表格样式 - 简化版
*/
private void setTableStyle(XWPFTable table) {
// 设置表格宽度为100%
table.setWidth("100%");
}
/**
* 根据分类名称获取表格配置
*/
private TableConfiguration getTableConfiguration(String categoryName) {
TableConfiguration config;
switch (categoryName) {
case "实质性审查":
// 4行2列:表头行 + 数据行 + 审查依据标签行 + 审查依据内容行
config = new TableConfiguration(categoryName, 4, 2);
// 表头行
config.addCellConfig(new CellConfiguration(0, 0).header("原文"));
config.addCellConfig(new CellConfiguration(0, 1).header("修改建议"));
// 数据行
config.addCellConfig(new CellConfiguration(1, 0).field("originalText"));
config.addCellConfig(new CellConfiguration(1, 1).field("modifiedContent"));
// 审查依据标签行(跨两列合并)
config.addCellConfig(new CellConfiguration(2, 0).header("审查依据").mergeHorizontal(2));
// 审查依据内容行(跨两列合并)
config.addCellConfig(new CellConfiguration(3, 0).field("review_basis_combined").mergeHorizontal(2));
break;
case "合规性审查":
// 4行2列:表头行 + 数据行 + 法规依据标签行 + 法规依据内容行
config = new TableConfiguration(categoryName, 4, 1);
// 表头行
config.addCellConfig(new CellConfiguration(0, 0).header("原文"));
// config.addCellConfig(new CellConfiguration(0, 1).header("修改后的内容"));
// 数据行
config.addCellConfig(new CellConfiguration(1, 0).field("originalText"));
// config.addCellConfig(new CellConfiguration(1, 1).field("modifiedContent"));
// 法规依据标签行(跨两列合并)
config.addCellConfig(new CellConfiguration(2, 0).header("法规依据").mergeHorizontal(2));
// 法规依据内容行(跨两列合并)
config.addCellConfig(new CellConfiguration(3, 0).field("review_points[0]").mergeHorizontal(2));
break;
case "一致性审查":
// 4行3列:表头行 + 数据行 + 修改后内容标签行 + 修改后内容数据行
config = new TableConfiguration(categoryName, 4, 2);
// 表头行
config.addCellConfig(new CellConfiguration(0, 0).header("合同原文"));
config.addCellConfig(new CellConfiguration(0, 1).header("招标文件原文"));
// 数据行
config.addCellConfig(new CellConfiguration(1, 0).field("originalText"));
config.addCellConfig(new CellConfiguration(1, 1).field("comparedText"));
// 修改后内容标签行(跨三列合并)
config.addCellConfig(new CellConfiguration(2, 0).header("问题描述").mergeHorizontal(2));
// 修改后内容数据行(跨三列合并)
config.addCellConfig(new CellConfiguration(3, 0).field("existingIssues").mergeHorizontal(2));
break;
default:
// 默认配置:4行2列
config = new TableConfiguration(categoryName, 4, 2);
config.addCellConfig(new CellConfiguration(0, 0).header("原文"));
config.addCellConfig(new CellConfiguration(0, 1).header("修改建议"));
config.addCellConfig(new CellConfiguration(1, 0).field("originalText"));
config.addCellConfig(new CellConfiguration(1, 1).field("modifiedContent"));
config.addCellConfig(new CellConfiguration(2, 0).header("审查依据").mergeHorizontal(2));
config.addCellConfig(new CellConfiguration(3, 0).field("review_basis_combined").mergeHorizontal(2));
break;
}
return config;
} }
/** /**
@ -563,6 +770,10 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
* @return 字段值 * @return 字段值
*/ */
private String getFieldValue(ContractualTaskResultVO.ResultItem item, String fieldName) { private String getFieldValue(ContractualTaskResultVO.ResultItem item, String fieldName) {
if (fieldName == null || fieldName.isEmpty()) {
return "";
}
switch (fieldName) { switch (fieldName) {
case "originalText": case "originalText":
return item.getOriginalText(); return item.getOriginalText();
@ -573,36 +784,235 @@ public class ContractualTaskResultsServiceImpl implements IContractualTaskResult
case "modificationDisplay": case "modificationDisplay":
return item.getModificationDisplay(); return item.getModificationDisplay();
case "reviewBasis": case "reviewBasis":
if (item.getReviewBasis() != null && StringUtils.isNotBlank(item.getReviewBasis())) { return item.getReviewBasis();
// reviewBasis现在是JSON字符串,可以直接返回 case "existingIssues":
// 或者解析后格式化显示 return item.getExistingIssues();
case "review_basis_combined":
// 组合审查依据:review_points[0] + review_content
return formatReviewBasisCombined(item.getReviewBasis());
case "review_points[0]":
return formatReviewBasisField(item.getReviewBasis(), "review_points[0]");
case "review_content":
return formatReviewBasisField(item.getReviewBasis(), "review_content");
case "":
return "";
default:
return "";
}
}
/**
* 格式化组合的审查依据
*/
private String formatReviewBasisCombined(String reviewBasis) {
if (StringUtils.isBlank(reviewBasis)) {
return "";
}
try { try {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(item.getReviewBasis()); JsonNode jsonNode = objectMapper.readTree(reviewBasis);
StringBuilder sb = new StringBuilder(); StringBuilder result = new StringBuilder();
if (jsonNode.has("reviewContent") && !jsonNode.get("reviewContent").isNull()) {
sb.append("审查内容: ").append(jsonNode.get("reviewContent").asText()).append("\n"); // 添加review_points[0]
} if (jsonNode.has("review_points") && jsonNode.get("review_points").isArray()) {
if (jsonNode.has("reviewPoints") && jsonNode.get("reviewPoints").isArray()) { JsonNode arrayNode = jsonNode.get("review_points");
sb.append("审查点: "); if (arrayNode.size() > 0) {
for (JsonNode point : jsonNode.get("reviewPoints")) { result.append(arrayNode.get(0).asText());
sb.append(point.asText()).append(", "); }
} }
// 移除最后的逗号和空格
if (sb.length() > 4) { // 添加review_content
sb.setLength(sb.length() - 2); if (jsonNode.has("review_content") && !jsonNode.get("review_content").isNull()) {
} String content = jsonNode.get("review_content").asText();
} if (StringUtils.isNotBlank(content)) {
return sb.toString(); if (result.length() > 0) {
result.append(" ");
}
result.append(content);
}
}
return result.toString();
} catch (Exception e) { } catch (Exception e) {
// 如果JSON解析失败,直接返回原始字符串 log.warn("解析组合审查依据失败", e);
return item.getReviewBasis(); return reviewBasis;
}
}
/**
* 水平合并单元格 - 使用POI真正合并功能
* @param row 表格行
* @param fromCol 起始列
* @param toCol 结束列
*/
private void mergeCellsHorizontally(XWPFTableRow row, int fromCol, int toCol) {
if (fromCol >= toCol) return;
try {
// 使用POI的真正合并功能
for (int i = fromCol; i <= toCol; i++) {
XWPFTableCell cell = row.getCell(i);
if (cell != null) {
if (i == fromCol) {
// 第一个单元格设置为合并开始
cell.getCTTc().addNewTcPr().addNewHMerge().setVal(
org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART);
} else {
// 其他单元格设置为合并继续
cell.getCTTc().addNewTcPr().addNewHMerge().setVal(
org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE);
// 清空被合并单元格的内容
while (cell.getParagraphs().size() > 0) {
cell.removeParagraph(0);
}
cell.addParagraph();
} }
} }
return null; }
default: } catch (Exception e) {
return null; log.warn("合并单元格失败: fromCol=" + fromCol + ", toCol=" + toCol, e);
}
}
private boolean isEmpty(String str) {
return StringUtils.isBlank(str);
}
/**
* 表格配置类 - 基于坐标的配置系统
*/
private static class TableConfiguration {
private String categoryName;
private int totalRows;
private int totalColumns;
private List<CellConfiguration> cellConfigs;
public TableConfiguration(String categoryName, int totalRows, int totalColumns) {
this.categoryName = categoryName;
this.totalRows = totalRows;
this.totalColumns = totalColumns;
this.cellConfigs = new ArrayList<>();
}
public void addCellConfig(CellConfiguration cellConfig) {
this.cellConfigs.add(cellConfig);
}
// Getters
public String getCategoryName() { return categoryName; }
public int getTotalRows() { return totalRows; }
public int getTotalColumns() { return totalColumns; }
public List<CellConfiguration> getCellConfigs() { return cellConfigs; }
}
/**
* 单元格配置类
*/
private static class CellConfiguration {
private int row;
private int column;
private String fieldName; // 对应ResultItem的字段名
private String headerText; // 表头文本
private boolean isHeader; // 是否为表头
private MergeType mergeType; // 合并类型
private int mergeSpanRows; // 合并的行数
private int mergeSpanColumns; // 合并的列数
public CellConfiguration(int row, int column) {
this.row = row;
this.column = column;
this.mergeType = MergeType.NONE;
this.mergeSpanRows = 1;
this.mergeSpanColumns = 1;
}
// Builder模式的方法
public CellConfiguration field(String fieldName) {
this.fieldName = fieldName;
return this;
}
public CellConfiguration header(String headerText) {
this.headerText = headerText;
this.isHeader = true;
return this;
}
public CellConfiguration mergeHorizontal(int spanColumns) {
this.mergeType = MergeType.HORIZONTAL;
this.mergeSpanColumns = spanColumns;
return this;
}
public CellConfiguration mergeVertical(int spanRows) {
this.mergeType = MergeType.VERTICAL;
this.mergeSpanRows = spanRows;
return this;
}
public CellConfiguration mergeBoth(int spanRows, int spanColumns) {
this.mergeType = MergeType.BOTH;
this.mergeSpanRows = spanRows;
this.mergeSpanColumns = spanColumns;
return this;
}
// Getters
public int getRow() { return row; }
public int getColumn() { return column; }
public String getFieldName() { return fieldName; }
public String getHeaderText() { return headerText; }
public boolean isHeader() { return isHeader; }
public MergeType getMergeType() { return mergeType; }
public int getMergeSpanRows() { return mergeSpanRows; }
public int getMergeSpanColumns() { return mergeSpanColumns; }
}
/**
* 合并类型枚举
*/
private enum MergeType {
NONE, // 不合并
HORIZONTAL, // 水平合并
VERTICAL, // 垂直合并
BOTH // 双向合并
}
/**
* 审查依据配置类 - 简化版
*/
private static class BasisConfiguration {
private String label;
private List<FieldCombination> fieldCombinations;
public BasisConfiguration(String label, List<FieldCombination> fieldCombinations) {
this.label = label;
this.fieldCombinations = fieldCombinations;
}
public String getLabel() { return label; }
public List<FieldCombination> getFieldCombinations() { return fieldCombinations; }
}
/**
* 字段组合配置类
*/
private static class FieldCombination {
private String fieldPath;
private String separator;
public FieldCombination(String fieldPath, String separator) {
this.fieldPath = fieldPath;
this.separator = separator;
} }
public FieldCombination(String fieldPath) {
this(fieldPath, "");
}
public String getFieldPath() { return fieldPath; }
public String getSeparator() { return separator; }
} }
} }

19
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/ContractualTasksServiceImpl.java

@ -124,6 +124,9 @@ public class ContractualTasksServiceImpl extends AbstractTaskProcessor<Contractu
protected LambdaQueryWrapper<ContractualTasks> buildQueryWrapper(ContractualTasksBo bo) { protected LambdaQueryWrapper<ContractualTasks> buildQueryWrapper(ContractualTasksBo bo) {
LambdaQueryWrapper<ContractualTasks> lqw = Wrappers.lambdaQuery(); LambdaQueryWrapper<ContractualTasks> lqw = Wrappers.lambdaQuery();
lqw.eq(ContractualTasks::getTaskType, TaskEnum.TaskType.CONTRACT_REVIEW.getValue()); lqw.eq(ContractualTasks::getTaskType, TaskEnum.TaskType.CONTRACT_REVIEW.getValue());
lqw.like(bo.getDocumentName() != null, ContractualTasks::getDocumentName, bo.getDocumentName());
lqw.like(bo.getReviewTypes() != null, ContractualTasks::getReviewTypes, bo.getReviewTypes());
lqw.eq(bo.getProgressStatus() != null, ContractualTasks::getProgressStatus, bo.getProgressStatus());
return lqw; return lqw;
} }
@ -265,7 +268,13 @@ public class ContractualTasksServiceImpl extends AbstractTaskProcessor<Contractu
contractTask.setTaskType(TaskEnum.TaskType.CONTRACT_REVIEW.getValue()); contractTask.setTaskType(TaskEnum.TaskType.CONTRACT_REVIEW.getValue());
contractTask.setProgressStatus("PENDING"); contractTask.setProgressStatus("PENDING");
// 4. 处理合同方角色 // 4. 保存审查类型
if (request.getReviewTypes() != null && !request.getReviewTypes().isEmpty()) {
String reviewTypesStr = String.join(",", request.getReviewTypes());
contractTask.setReviewTypes(reviewTypesStr);
}
// 5. 处理合同方角色
String contractPartyRole = "中立"; // 默认值 String contractPartyRole = "中立"; // 默认值
if (request.getReviewData() != null && request.getReviewData().getSubstantive() != null) { if (request.getReviewData() != null && request.getReviewData().getSubstantive() != null) {
String position = request.getReviewData().getSubstantive().getPosition(); String position = request.getReviewData().getSubstantive().getPosition();
@ -287,17 +296,17 @@ public class ContractualTasksServiceImpl extends AbstractTaskProcessor<Contractu
} }
contractTask.setContractPartyRole(contractPartyRole); contractTask.setContractPartyRole(contractPartyRole);
// 5. 将reviewData序列化并保存到review_data字段 // 6. 将reviewData序列化并保存到review_data字段
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
String reviewDataJson = objectMapper.writeValueAsString(request.getReviewData()); String reviewDataJson = objectMapper.writeValueAsString(request.getReviewData());
contractTask.setReviewData(reviewDataJson); contractTask.setReviewData(reviewDataJson);
// 6. 保存到数据库 // 7. 保存到数据库
if (!saveTaskEntity(contractTask)) { if (!saveTaskEntity(contractTask)) {
throw new RuntimeException("保存合同任务失败"); throw new RuntimeException("保存合同任务失败");
} }
// 7. 调用Python接口启动任务 // 8. 调用Python接口启动任务
String pythonUrl = chatUrl + "/back/taskStart"; String pythonUrl = chatUrl + "/back/taskStart";
MyHttpUtils.sendTaskStartMessage( MyHttpUtils.sendTaskStartMessage(
pythonUrl, pythonUrl,
@ -307,7 +316,7 @@ public class ContractualTasksServiceImpl extends AbstractTaskProcessor<Contractu
1L 1L
); );
// 8. 立即返回任务创建成功信息 // 9. 立即返回任务创建成功信息
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("success", true); result.put("success", true);
result.put("message", "合同审查任务已创建,正在后台处理"); result.put("message", "合同审查任务已创建,正在后台处理");

2
zaojiaManagement/zaojia-productManagement/src/main/java/org/dromara/productManagement/service/impl/DocumentTaskResultsServiceImpl.java

@ -721,7 +721,7 @@ public class DocumentTaskResultsServiceImpl implements IDocumentTaskResultsServi
previewDir.mkdirs(); previewDir.mkdirs();
} }
filerotPath = fileRootPath + fileInfo.getFileName(); filerotPath = fileRootPath +File.separator+ fileInfo.getFileName();
// 如果不是pdf文件,需要转换 // 如果不是pdf文件,需要转换
if (!fileName.toLowerCase().endsWith(".pdf")) { if (!fileName.toLowerCase().endsWith(".pdf")) {

2
zaojiaManagement/zaojia-productManagement/src/main/resources/mapper/productManagement/DocumentTaskResultsMapper.xml

@ -12,6 +12,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="modifiedContent" column="modified_content"/> <result property="modifiedContent" column="modified_content"/>
<result property="modificationDisplay" column="modification_display"/> <result property="modificationDisplay" column="modification_display"/>
<result property="existingIssues" column="existing_issues"/> <result property="existingIssues" column="existing_issues"/>
<result property="reviewBasis" column="review_basis"/>
<result property="isRead" column="is_read"/> <result property="isRead" column="is_read"/>
<result property="isAdopted" column="is_adopted"/> <result property="isAdopted" column="is_adopted"/>
<!-- 审查依据需要在代码中解析JSON --> <!-- 审查依据需要在代码中解析JSON -->

Loading…
Cancel
Save