18 changed files with 1034 additions and 250 deletions
@ -0,0 +1,84 @@ |
|||||
|
package org.dromara.productManagement.controller; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import jakarta.servlet.http.HttpServletResponse; |
||||
|
import jakarta.validation.constraints.*; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
import org.springframework.validation.annotation.Validated; |
||||
|
import org.dromara.common.log.annotation.Log; |
||||
|
import org.dromara.common.web.core.BaseController; |
||||
|
import org.dromara.common.core.domain.R; |
||||
|
import org.dromara.common.log.enums.BusinessType; |
||||
|
import org.dromara.productManagement.domain.vo.ContractualTaskResultVO; |
||||
|
import org.dromara.productManagement.service.IContractualTaskResultsService; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 合同任务结果 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
* @date 2024-12-11 |
||||
|
*/ |
||||
|
@Validated |
||||
|
@RequiredArgsConstructor |
||||
|
@RestController |
||||
|
@RequestMapping("/productManagement/ContractualTaskResults") |
||||
|
public class ContractualTaskResultsController extends BaseController { |
||||
|
|
||||
|
private final IContractualTaskResultsService contractualTaskResultsService; |
||||
|
|
||||
|
/** |
||||
|
* 根据任务ID获取详细的合同任务结果 |
||||
|
* |
||||
|
* @param taskId 任务ID |
||||
|
*/ |
||||
|
// @SaCheckPermission("productManagement:ContractualTaskResults:query")
|
||||
|
@GetMapping("/taskDetail/{taskId}") |
||||
|
public R<List<ContractualTaskResultVO>> getDetailResultsByTaskId(@NotNull(message = "任务ID不能为空") |
||||
|
@PathVariable String taskId) { |
||||
|
return R.ok(contractualTaskResultsService.getDetailResultsByTaskId(taskId)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下载合同任务结果 |
||||
|
* |
||||
|
* @param ids 主键串 |
||||
|
*/ |
||||
|
// @SaCheckPermission("productManagement:ContractualTaskResults:download")
|
||||
|
@Log(title = "合同任务结果", businessType = BusinessType.EXPORT) |
||||
|
@GetMapping("/downloadResult/{ids}") |
||||
|
public void download(@NotEmpty(message = "主键不能为空") |
||||
|
@PathVariable Long[] ids,HttpServletResponse response) { |
||||
|
contractualTaskResultsService.downloadResult(ids,response); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取合同任务PDF文件流 |
||||
|
* |
||||
|
* @param taskId 任务ID |
||||
|
*/ |
||||
|
@GetMapping("/getPdfStream/{taskId}") |
||||
|
public void getPdfStream(@NotNull(message = "任务ID不能为空") |
||||
|
@PathVariable String taskId, |
||||
|
HttpServletResponse response) { |
||||
|
contractualTaskResultsService.getPdfStream(taskId, response); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新合同任务结果项的状态(已读/采纳) |
||||
|
* |
||||
|
* @param id 结果项ID |
||||
|
* @param field 字段名(isRead/isAdopted) |
||||
|
* @param value 值(0/1) |
||||
|
*/ |
||||
|
// @SaCheckPermission("productManagement:ContractualTaskResults:edit")
|
||||
|
@Log(title = "更新合同任务结果项状态", businessType = BusinessType.UPDATE) |
||||
|
@PutMapping("/updateResultItemStatus/{id}/{field}/{value}") |
||||
|
public R<Void> updateResultItemStatus( |
||||
|
@NotEmpty(message = "ID不能为空") @PathVariable("id") String id, |
||||
|
@NotEmpty(message = "字段名不能为空") @PathVariable("field") String field, |
||||
|
@NotEmpty(message = "值不能为空") @PathVariable("value") String value) { |
||||
|
return toAjax(contractualTaskResultsService.updateResultItemStatus(id, field, value)); |
||||
|
} |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package org.dromara.productManagement.domain; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableLogic; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.dromara.common.tenant.core.TenantEntity; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@TableName("contractual_task_categories") |
||||
|
public class ContractualTaskCategory extends TenantEntity { |
||||
|
|
||||
|
@TableId(value = "id") |
||||
|
private String id; |
||||
|
|
||||
|
private String contractualTaskId; |
||||
|
|
||||
|
private String categoryName; |
||||
|
|
||||
|
/** |
||||
|
* 删除标志 |
||||
|
*/ |
||||
|
@TableLogic |
||||
|
private String delFlag; |
||||
|
|
||||
|
} |
@ -0,0 +1,46 @@ |
|||||
|
package org.dromara.productManagement.domain; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableLogic; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.dromara.common.tenant.core.TenantEntity; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@TableName("contractual_task_result_details") |
||||
|
public class ContractualTaskResultDetail extends TenantEntity { |
||||
|
|
||||
|
@TableId(value = "id") |
||||
|
private String id; |
||||
|
|
||||
|
/** |
||||
|
* 分类id |
||||
|
*/ |
||||
|
private String categoryId; |
||||
|
private String issueName; // 问题点名称
|
||||
|
|
||||
|
private String originalText; // 原文
|
||||
|
|
||||
|
private String comparedText; // 比对原文
|
||||
|
|
||||
|
private String modifiedContent; // 修改后的内容
|
||||
|
|
||||
|
private String modificationDisplay; // 展示修改情况
|
||||
|
|
||||
|
private String existingIssues; // 存在的问题
|
||||
|
|
||||
|
private String reviewBasis; // 审查依据
|
||||
|
|
||||
|
private String isRead; // 是否已读
|
||||
|
|
||||
|
private String isAdopted; // 是否采纳
|
||||
|
|
||||
|
/** |
||||
|
* 删除标志 |
||||
|
*/ |
||||
|
@TableLogic |
||||
|
private String delFlag; |
||||
|
|
||||
|
} |
@ -1,42 +0,0 @@ |
|||||
package org.dromara.productManagement.domain; |
|
||||
|
|
||||
import com.baomidou.mybatisplus.annotation.*; |
|
||||
import lombok.Data; |
|
||||
import lombok.EqualsAndHashCode; |
|
||||
import org.dromara.common.tenant.core.TenantEntity; |
|
||||
|
|
||||
import java.io.Serial; |
|
||||
|
|
||||
/** |
|
||||
* 合同任务额外字段 |
|
||||
对象 contractual_task_supplement |
|
||||
* |
|
||||
* @author Lion Li |
|
||||
* @date 2025-01-21 |
|
||||
*/ |
|
||||
@Data |
|
||||
@EqualsAndHashCode(callSuper = true) |
|
||||
@TableName("contractual_task_supplement") |
|
||||
public class ContractualTaskSupplement extends TenantEntity { |
|
||||
|
|
||||
@Serial |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
/** |
|
||||
* |
|
||||
*/ |
|
||||
@TableId(value = "id") |
|
||||
private Long id; |
|
||||
|
|
||||
/** |
|
||||
* 任务id |
|
||||
*/ |
|
||||
private Long documentTasksId; |
|
||||
|
|
||||
/** |
|
||||
* 合同角色 |
|
||||
*/ |
|
||||
private String contractPartyRole; |
|
||||
|
|
||||
|
|
||||
} |
|
@ -0,0 +1,39 @@ |
|||||
|
package org.dromara.productManagement.domain.vo; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class ContractualTaskResultVO { |
||||
|
|
||||
|
private String name; // 分类名称
|
||||
|
|
||||
|
private List<ResultItem> results; |
||||
|
|
||||
|
@Data |
||||
|
public static class ResultItem { |
||||
|
|
||||
|
private String id; // 唯一标识ID
|
||||
|
|
||||
|
private Integer serialNumber; // 序号
|
||||
|
|
||||
|
private String issueName; // 问题点名称
|
||||
|
|
||||
|
private String originalText; // 原文
|
||||
|
|
||||
|
private String comparedText; // 比对原文
|
||||
|
|
||||
|
private String modifiedContent; // 修改后的内容
|
||||
|
|
||||
|
private String modificationDisplay; // 展示修改情况
|
||||
|
|
||||
|
private String existingIssues; // 存在的问题
|
||||
|
|
||||
|
private String reviewBasis; // 审查依据(JSON字符串)
|
||||
|
|
||||
|
private String isRead; // 是否已读
|
||||
|
|
||||
|
private String isAdopted; // 是否采纳
|
||||
|
} |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
package org.dromara.productManagement.mapper; |
||||
|
|
||||
|
import org.dromara.productManagement.domain.ContractualTaskResultDetail; |
||||
|
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; |
||||
|
|
||||
|
/** |
||||
|
* 合同任务结果详情Mapper接口 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
* @date 2024-12-11 |
||||
|
*/ |
||||
|
public interface ContractualTaskResultDetailMapper extends BaseMapperPlus<ContractualTaskResultDetail, ContractualTaskResultDetail> { |
||||
|
|
||||
|
} |
@ -0,0 +1,50 @@ |
|||||
|
package org.dromara.productManagement.service; |
||||
|
|
||||
|
import jakarta.servlet.http.HttpServletResponse; |
||||
|
import org.dromara.productManagement.domain.vo.ContractualTaskResultVO; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 合同任务结果Service接口 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
* @date 2024-12-11 |
||||
|
*/ |
||||
|
public interface IContractualTaskResultsService { |
||||
|
|
||||
|
/** |
||||
|
* 根据任务ID查询详细的合同任务结果 |
||||
|
* |
||||
|
* @param taskId 任务ID |
||||
|
* @return 详细的合同任务结果列表 |
||||
|
*/ |
||||
|
List<ContractualTaskResultVO> getDetailResultsByTaskId(String taskId); |
||||
|
|
||||
|
/** |
||||
|
* 下载合同任务结果 |
||||
|
* |
||||
|
* @param ids 主键数组 |
||||
|
* @param response HTTP响应对象 |
||||
|
*/ |
||||
|
void downloadResult(Long[] ids, HttpServletResponse response); |
||||
|
|
||||
|
/** |
||||
|
* 获取合同任务PDF文件流 |
||||
|
* |
||||
|
* @param taskId 任务ID |
||||
|
* @param response HTTP响应对象 |
||||
|
*/ |
||||
|
void getPdfStream(String taskId, HttpServletResponse response); |
||||
|
|
||||
|
/** |
||||
|
* 更新合同任务结果项的状态(已读/采纳) |
||||
|
* |
||||
|
* @param id 结果项ID |
||||
|
* @param field 字段名(isRead/isAdopted) |
||||
|
* @param value 值(0/1) |
||||
|
* @return 是否更新成功 |
||||
|
*/ |
||||
|
Boolean updateResultItemStatus(String id, String field, String value); |
||||
|
|
||||
|
} |
@ -0,0 +1,608 @@ |
|||||
|
package org.dromara.productManagement.service.impl; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
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 lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.dromara.common.core.service.DictService; |
||||
|
import org.dromara.common.core.utils.StringUtils; |
||||
|
import org.dromara.productManagement.domain.ContractualTaskResultDetail; |
||||
|
import org.dromara.productManagement.domain.vo.ContractualTaskResultVO; |
||||
|
import org.dromara.productManagement.domain.vo.ContractualTasksVo; |
||||
|
import org.dromara.productManagement.mapper.ContractualTaskResultDetailMapper; |
||||
|
import org.dromara.productManagement.mapper.ContractualTasksMapper; |
||||
|
import org.dromara.productManagement.service.IContractualTaskResultsService; |
||||
|
import org.dromara.productManagement.service.IContractualTasksService; |
||||
|
import org.dromara.system.domain.vo.SysOssVo; |
||||
|
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.Value; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.xhtmlrenderer.pdf.ITextRenderer; |
||||
|
|
||||
|
import javax.sql.DataSource; |
||||
|
import java.io.*; |
||||
|
import java.net.URLEncoder; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.sql.Connection; |
||||
|
import java.sql.PreparedStatement; |
||||
|
import java.sql.SQLException; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.List; |
||||
|
import java.util.zip.ZipEntry; |
||||
|
import java.util.zip.ZipOutputStream; |
||||
|
|
||||
|
/** |
||||
|
* 合同任务结果Service业务层处理 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
* @date 2024-12-11 |
||||
|
*/ |
||||
|
@RequiredArgsConstructor |
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class ContractualTaskResultsServiceImpl implements IContractualTaskResultsService { |
||||
|
|
||||
|
private final ContractualTasksMapper contractualTasksMapper; |
||||
|
private final ContractualTaskResultDetailMapper contractualTaskResultDetailMapper; |
||||
|
private final IContractualTasksService contractualTasksService; |
||||
|
private final DictService dictTypeService; |
||||
|
protected final ISysOssService ossService; |
||||
|
|
||||
|
@Value("${chat.tempfilePath}") |
||||
|
protected String tempfilePath; |
||||
|
|
||||
|
@Value("${chat.filePath}") |
||||
|
protected String fileRootPath; |
||||
|
|
||||
|
@Autowired |
||||
|
private DataSource dataSource; |
||||
|
|
||||
|
/** |
||||
|
* 根据任务ID查询详细的合同任务结果 |
||||
|
* |
||||
|
* @param taskId 任务ID |
||||
|
* @return 详细的合同任务结果列表 |
||||
|
*/ |
||||
|
@Override |
||||
|
public List<ContractualTaskResultVO> getDetailResultsByTaskId(String taskId) { |
||||
|
// 获取原始分类结果
|
||||
|
List<ContractualTaskResultVO> originalResults = contractualTasksMapper.getResultsByTaskId(taskId); |
||||
|
|
||||
|
// 如果没有结果,直接返回
|
||||
|
if (originalResults == null || originalResults.isEmpty()) { |
||||
|
return originalResults; |
||||
|
} |
||||
|
|
||||
|
// 创建"全部"分类
|
||||
|
ContractualTaskResultVO allCategory = new ContractualTaskResultVO(); |
||||
|
allCategory.setName("全部"); |
||||
|
|
||||
|
// 合并所有结果并设置全局序号
|
||||
|
List<ContractualTaskResultVO.ResultItem> allResults = new java.util.ArrayList<>(); |
||||
|
int globalSerialNumber = 1; |
||||
|
|
||||
|
for (ContractualTaskResultVO category : originalResults) { |
||||
|
if (category.getResults() != null) { |
||||
|
for (ContractualTaskResultVO.ResultItem item : category.getResults()) { |
||||
|
// 创建新的item以避免引用相同的对象
|
||||
|
ContractualTaskResultVO.ResultItem newItem = new ContractualTaskResultVO.ResultItem(); |
||||
|
// 复制所有属性
|
||||
|
org.springframework.beans.BeanUtils.copyProperties(item, newItem); |
||||
|
// 设置全局序号
|
||||
|
newItem.setSerialNumber(globalSerialNumber++); |
||||
|
// 添加到全部分类
|
||||
|
allResults.add(newItem); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 为每个原始分类中的结果设置序号
|
||||
|
for (ContractualTaskResultVO category : originalResults) { |
||||
|
if (category.getResults() != null) { |
||||
|
for (int i = 0; i < category.getResults().size(); i++) { |
||||
|
ContractualTaskResultVO.ResultItem item = category.getResults().get(i); |
||||
|
item.setSerialNumber(i + 1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 设置"全部"分类的结果
|
||||
|
allCategory.setResults(allResults); |
||||
|
|
||||
|
// 将"全部"分类添加到结果列表的开头
|
||||
|
List<ContractualTaskResultVO> finalResults = new java.util.ArrayList<>(); |
||||
|
finalResults.add(allCategory); |
||||
|
finalResults.addAll(originalResults); |
||||
|
|
||||
|
return finalResults; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Boolean updateResultItemStatus(String id, String field, String value) { |
||||
|
// 验证字段名是否合法
|
||||
|
if (!"isRead".equals(field) && !"isAdopted".equals(field)) { |
||||
|
throw new RuntimeException("不支持更新的字段: " + field); |
||||
|
} |
||||
|
|
||||
|
// 验证值是否合法
|
||||
|
if (!"0".equals(value) && !"1".equals(value)) { |
||||
|
throw new RuntimeException("无效的值: " + value); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
LambdaUpdateWrapper<ContractualTaskResultDetail> updateWrapper = Wrappers.lambdaUpdate(ContractualTaskResultDetail.class); |
||||
|
updateWrapper.eq(ContractualTaskResultDetail::getId, id); |
||||
|
|
||||
|
if ("isRead".equals(field)) { |
||||
|
updateWrapper.set(ContractualTaskResultDetail::getIsRead, value); |
||||
|
} else { |
||||
|
updateWrapper.set(ContractualTaskResultDetail::getIsAdopted, value); |
||||
|
} |
||||
|
|
||||
|
return contractualTaskResultDetailMapper.update(null, updateWrapper) > 0; |
||||
|
} catch (Exception e) { |
||||
|
throw new RuntimeException("更新状态失败: " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void downloadResult(Long[] ids, HttpServletResponse response) { |
||||
|
try { |
||||
|
// 使用新的导出逻辑,基于getDetailResultsByTaskId获取结果
|
||||
|
if (ids.length > 0) { |
||||
|
if (ids.length > 1) { |
||||
|
// 多文件压缩下载
|
||||
|
String tempDir = System.getProperty("java.io.tmpdir"); |
||||
|
String zipFileName = "contractual_results_export.zip"; |
||||
|
String zipFilePath = tempDir + File.separator + zipFileName; |
||||
|
|
||||
|
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFilePath))) { |
||||
|
for (Long taskId : ids) { |
||||
|
ContractualTasksVo contractualTasksVo = contractualTasksService.queryById(taskId); |
||||
|
if (contractualTasksVo == null) continue; |
||||
|
|
||||
|
String documentName = contractualTasksVo.getDocumentName(); |
||||
|
String taskName = contractualTasksVo.getTaskName(); |
||||
|
String taskType = contractualTasksVo.getTaskType(); |
||||
|
String label = dictTypeService.getDictLabel(taskType, taskName); |
||||
|
|
||||
|
// 获取详细的任务结果
|
||||
|
List<ContractualTaskResultVO> resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); |
||||
|
if (resultDetails != null && !resultDetails.isEmpty()) { |
||||
|
// 第一个是"全部"分类
|
||||
|
ContractualTaskResultVO allCategory = resultDetails.get(0); |
||||
|
if (allCategory.getResults() != null && !allCategory.getResults().isEmpty()) { |
||||
|
// 获取任务类型对应的字段配置
|
||||
|
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"); |
||||
|
} |
||||
|
|
||||
|
// 为每个结果创建PDF文件
|
||||
|
String pdfFileName = label + "_" + documentName + "_" + ".pdf"; |
||||
|
String pdfPath = tempDir + File.separator + pdfFileName; |
||||
|
|
||||
|
// 转换Markdown为PDF
|
||||
|
convertMarkdownToPdf(markdown.toString(), pdfPath); |
||||
|
|
||||
|
// 添加到ZIP
|
||||
|
addToZip(pdfPath, pdfFileName, zos); |
||||
|
|
||||
|
// 删除临时PDF文件
|
||||
|
new File(pdfPath).delete(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 下载ZIP文件
|
||||
|
downloadFile(response, zipFilePath, zipFileName); |
||||
|
|
||||
|
// 删除临时ZIP文件
|
||||
|
new File(zipFilePath).delete(); |
||||
|
} else { |
||||
|
// 单文件下载
|
||||
|
Long taskId = ids[0]; |
||||
|
ContractualTasksVo contractualTasksVo = contractualTasksService.queryById(taskId); |
||||
|
if (contractualTasksVo == null) { |
||||
|
throw new RuntimeException("未找到相关任务"); |
||||
|
} |
||||
|
|
||||
|
String documentName = contractualTasksVo.getDocumentName(); |
||||
|
String taskName = contractualTasksVo.getTaskName(); |
||||
|
String taskType = contractualTasksVo.getTaskType(); |
||||
|
String label = dictTypeService.getDictLabel(taskType, taskName); |
||||
|
|
||||
|
// 获取详细的任务结果
|
||||
|
List<ContractualTaskResultVO> resultDetails = getDetailResultsByTaskId(String.valueOf(taskId)); |
||||
|
if (resultDetails == null || resultDetails.isEmpty() || |
||||
|
resultDetails.get(0).getResults() == null || resultDetails.get(0).getResults().isEmpty()) { |
||||
|
throw new RuntimeException("未找到相关结果"); |
||||
|
} |
||||
|
|
||||
|
// 第一个是"全部"分类
|
||||
|
ContractualTaskResultVO allCategory = resultDetails.get(0); |
||||
|
|
||||
|
// 获取任务类型对应的字段配置
|
||||
|
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 pdfPath = tempDir + File.separator + pdfFileName; |
||||
|
|
||||
|
// 转换Markdown为PDF
|
||||
|
convertMarkdownToPdf(markdown.toString(), pdfPath); |
||||
|
|
||||
|
// 下载PDF文件
|
||||
|
downloadFile(response, pdfPath, pdfFileName); |
||||
|
|
||||
|
// 删除临时PDF文件
|
||||
|
new File(pdfPath).delete(); |
||||
|
} |
||||
|
} else { |
||||
|
throw new RuntimeException("未找到相关文件"); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
throw new RuntimeException("文件下载失败: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void getPdfStream(String taskId, HttpServletResponse response) { |
||||
|
try { |
||||
|
// 查询任务信息
|
||||
|
ContractualTasksVo task = contractualTasksService.queryById(Long.valueOf(taskId)); |
||||
|
String outputPath = tempfilePath + File.separator + "pdf_preview" + File.separator; |
||||
|
String filerotPath =""; |
||||
|
if (task == null) { |
||||
|
throw new RuntimeException("任务不存在"); |
||||
|
} |
||||
|
|
||||
|
String pdfPath = task.getPdfPath(); |
||||
|
String filePath=""; |
||||
|
|
||||
|
if (StringUtils.isNotEmpty(pdfPath)) { |
||||
|
// 如果pdfPath存在,直接使用该路径
|
||||
|
filePath = pdfPath; |
||||
|
} else { |
||||
|
// 如果pdfPath不存在,从oss获取文件
|
||||
|
Long ossId = task.getOssId(); |
||||
|
if (ossId == null) { |
||||
|
throw new RuntimeException("文件不存在"); |
||||
|
} |
||||
|
|
||||
|
SysOssVo fileInfo = ossService.getById(ossId); |
||||
|
if (fileInfo == null) { |
||||
|
throw new RuntimeException("文件信息不存在"); |
||||
|
} |
||||
|
|
||||
|
String fileName = fileInfo.getOriginalName(); |
||||
|
|
||||
|
// 创建pdf预览目录
|
||||
|
File previewDir = new File(outputPath); |
||||
|
if (!previewDir.exists()) { |
||||
|
previewDir.mkdirs(); |
||||
|
} |
||||
|
|
||||
|
filerotPath = fileRootPath + fileInfo.getFileName(); |
||||
|
|
||||
|
// 如果不是pdf文件,需要转换
|
||||
|
if (!fileName.toLowerCase().endsWith(".pdf")) { |
||||
|
// 更新文件路径为转换后的pdf路径
|
||||
|
String originalFileName = fileInfo.getFileName(); |
||||
|
// 获取不带路径的文件名
|
||||
|
String fileNameOnly = originalFileName.substring(originalFileName.lastIndexOf("/") + 1); |
||||
|
// 获取不带扩展名的文件名
|
||||
|
String fileNameWithoutExt = fileNameOnly.substring(0, fileNameOnly.lastIndexOf(".")); |
||||
|
filePath = outputPath + fileNameWithoutExt + ".pdf"; |
||||
|
log.info("开始转换文件: " + fileName + " -> " + filePath); |
||||
|
} |
||||
|
} |
||||
|
log.info("开始输出文件: " + filePath); |
||||
|
// 读取文件并输出到response
|
||||
|
File file = new File(filePath); |
||||
|
if (!file.exists()) { |
||||
|
log.info("开始转换文件: 文件不存在" + filePath); |
||||
|
// 使用libreoffice转换为pdf
|
||||
|
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(); |
||||
|
} |
||||
|
log.info("开始输出文件: 文件存在" + filePath); |
||||
|
// 使用JDBC更新数据库
|
||||
|
try (Connection conn = dataSource.getConnection()) { |
||||
|
String sql = "UPDATE contractual_tasks SET pdf_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) { |
||||
|
throw new RuntimeException("获取PDF文件流失败: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Markdown转PDF方法
|
||||
|
private void convertMarkdownToPdf(String markdown, String outputPath) throws Exception { |
||||
|
MutableDataSet options = new MutableDataSet(); |
||||
|
// 启用表格解析
|
||||
|
options.set(Parser.EXTENSIONS, Arrays.asList( |
||||
|
TablesExtension.create(), |
||||
|
StrikethroughExtension.create() |
||||
|
)); |
||||
|
|
||||
|
Parser parser = Parser.builder(options).build(); |
||||
|
HtmlRenderer renderer = HtmlRenderer.builder(options) |
||||
|
.extensions(Arrays.asList( |
||||
|
TablesExtension.create(), |
||||
|
StrikethroughExtension.create() |
||||
|
)) |
||||
|
.build(); |
||||
|
|
||||
|
Node document = parser.parse(markdown); |
||||
|
String htmlContent = renderer.render(document); |
||||
|
// 使用 JSoup 清理
|
||||
|
Document.OutputSettings settings = new Document.OutputSettings(); |
||||
|
settings.prettyPrint(false); |
||||
|
|
||||
|
// 配置允许的标签
|
||||
|
Safelist safelist = Safelist.relaxed() |
||||
|
.addTags("em", "strong", "div", "span", "br", "table", "thead", "tbody", "tr", "th", "td", "img") |
||||
|
.addAttributes(":all", "style", "class") |
||||
|
.addAttributes("table", "border", "cellpadding", "cellspacing") |
||||
|
.addAttributes("img", "src", "alt", "width", "height"); // 允许img标签及其属性
|
||||
|
htmlContent = Jsoup.clean(htmlContent, "", safelist, settings); |
||||
|
|
||||
|
// 创建完整的HTML文档,使用微软雅黑
|
||||
|
String html = String.format(""" |
||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"/> |
||||
|
<style type="text/css"> |
||||
|
body { |
||||
|
font-family: Microsoft YaHei; |
||||
|
line-height: 1.6; |
||||
|
margin: 20px; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
p { margin: 10px 0; } |
||||
|
pre { |
||||
|
background-color: #f5f5f5; |
||||
|
padding: 10px; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
/* 增强表格样式 */ |
||||
|
table { |
||||
|
border-collapse: collapse; |
||||
|
width: 100%%; |
||||
|
margin: 10px 0; |
||||
|
table-layout: fixed; |
||||
|
} |
||||
|
th, td { |
||||
|
border: 1px solid #ddd; |
||||
|
padding: 8px; |
||||
|
text-align: left; |
||||
|
word-wrap: break-word; |
||||
|
} |
||||
|
th { |
||||
|
background-color: #f2f2f2; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
%s |
||||
|
</body> |
||||
|
</html> |
||||
|
""", htmlContent); |
||||
|
|
||||
|
try (OutputStream outputStream = new FileOutputStream(outputPath)) { |
||||
|
ITextRenderer renderer2 = new ITextRenderer(); |
||||
|
renderer2.setDocumentFromString(html); |
||||
|
renderer2.layout(); |
||||
|
renderer2.createPDF(outputStream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void addToZip(String filePath, String fileName, ZipOutputStream zos) throws IOException { |
||||
|
File file = new File(filePath); |
||||
|
if (!file.exists()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try (FileInputStream fis = new FileInputStream(file)) { |
||||
|
ZipEntry zipEntry = new ZipEntry(fileName); |
||||
|
zos.putNextEntry(zipEntry); |
||||
|
|
||||
|
byte[] buffer = new byte[1024]; |
||||
|
int length; |
||||
|
while ((length = fis.read(buffer)) >= 0) { |
||||
|
zos.write(buffer, 0, length); |
||||
|
} |
||||
|
zos.closeEntry(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void downloadFile(HttpServletResponse response, String filePath, String fileName) throws IOException { |
||||
|
File file = new File(filePath); |
||||
|
if (!file.exists()) { |
||||
|
throw new FileNotFoundException("文件不存在: " + filePath); |
||||
|
} |
||||
|
|
||||
|
response.setContentType("application/octet-stream"); |
||||
|
response.setCharacterEncoding("UTF-8"); |
||||
|
|
||||
|
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8); |
||||
|
response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName); |
||||
|
response.setContentLength((int) file.length()); |
||||
|
|
||||
|
try (FileInputStream fis = new FileInputStream(file); |
||||
|
OutputStream os = response.getOutputStream()) { |
||||
|
|
||||
|
byte[] buffer = new byte[1024]; |
||||
|
int length; |
||||
|
while ((length = fis.read(buffer)) != -1) { |
||||
|
os.write(buffer, 0, length); |
||||
|
} |
||||
|
os.flush(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class FieldConfig { |
||||
|
private String field; |
||||
|
private String title; |
||||
|
|
||||
|
public FieldConfig(String field, String title) { |
||||
|
this.field = field; |
||||
|
this.title = title; |
||||
|
} |
||||
|
|
||||
|
public String getField() { |
||||
|
return field; |
||||
|
} |
||||
|
|
||||
|
public String getTitle() { |
||||
|
return title; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据任务类型获取字段配置 |
||||
|
* |
||||
|
* @param taskName 任务名称 |
||||
|
* @return 字段配置列表 |
||||
|
*/ |
||||
|
private List<FieldConfig> getFieldConfigsByTaskType(String taskName) { |
||||
|
// 这里可以根据不同的任务类型返回不同的字段配置
|
||||
|
// 目前返回通用的配置
|
||||
|
return Arrays.asList( |
||||
|
new FieldConfig("originalText", "原文"), |
||||
|
new FieldConfig("comparedText", "比对原文"), |
||||
|
new FieldConfig("modifiedContent", "修改后的内容"), |
||||
|
new FieldConfig("modificationDisplay", "展示修改情况"), |
||||
|
new FieldConfig("reviewBasis", "审查依据") |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取结果项的字段值 |
||||
|
* |
||||
|
* @param item 结果项 |
||||
|
* @param fieldName 字段名 |
||||
|
* @return 字段值 |
||||
|
*/ |
||||
|
private String getFieldValue(ContractualTaskResultVO.ResultItem item, String fieldName) { |
||||
|
switch (fieldName) { |
||||
|
case "originalText": |
||||
|
return item.getOriginalText(); |
||||
|
case "comparedText": |
||||
|
return item.getComparedText(); |
||||
|
case "modifiedContent": |
||||
|
return item.getModifiedContent(); |
||||
|
case "modificationDisplay": |
||||
|
return item.getModificationDisplay(); |
||||
|
case "reviewBasis": |
||||
|
if (item.getReviewBasis() != null && StringUtils.isNotBlank(item.getReviewBasis())) { |
||||
|
// reviewBasis现在是JSON字符串,可以直接返回
|
||||
|
// 或者解析后格式化显示
|
||||
|
try { |
||||
|
ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
JsonNode jsonNode = objectMapper.readTree(item.getReviewBasis()); |
||||
|
|
||||
|
StringBuilder sb = new StringBuilder(); |
||||
|
if (jsonNode.has("reviewContent") && !jsonNode.get("reviewContent").isNull()) { |
||||
|
sb.append("审查内容: ").append(jsonNode.get("reviewContent").asText()).append("\n"); |
||||
|
} |
||||
|
if (jsonNode.has("reviewPoints") && jsonNode.get("reviewPoints").isArray()) { |
||||
|
sb.append("审查点: "); |
||||
|
for (JsonNode point : jsonNode.get("reviewPoints")) { |
||||
|
sb.append(point.asText()).append(", "); |
||||
|
} |
||||
|
// 移除最后的逗号和空格
|
||||
|
if (sb.length() > 4) { |
||||
|
sb.setLength(sb.length() - 2); |
||||
|
} |
||||
|
} |
||||
|
return sb.toString(); |
||||
|
} catch (Exception e) { |
||||
|
// 如果JSON解析失败,直接返回原始字符串
|
||||
|
return item.getReviewBasis(); |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
default: |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue