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