feat(ai-report): 新增委托单位管理和报告审批功能

- 新增委托单位管理模块,支持增删改查、导入导出功能
- 实现委托单位 Excel 模板下载和数据导入功能
- 添加报告审批抽象处理器,支持审批流程日志记录
- 统一文件存储路径配置,将各模块独立路径改为统一根目录
- 新增 PQDIF 文件名存储字段,完善文件管理功能
- 修复代码中的乱码问题和错误提示信息
- 优化系统常量定义和字典配置管理
This commit is contained in:
2026-07-01 13:15:57 +08:00
parent 0ea686d3cb
commit c1ad7feec2
99 changed files with 6511 additions and 24 deletions

58
comservice/pom.xml Normal file
View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://www.w3.org/2001/XMLSchema-instance http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn.gather</groupId>
<artifactId>CN_Tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>comservice</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>spingboot2.3.12</artifactId>
<version>2.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.36</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,116 @@
package com.njcn.gather.comservice.filepreview;
import cn.hutool.core.util.StrUtil;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.util.HtmlUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* 公共文件预览服务,统一处理报告模板相关预览内容转换。
*/
@Component
public class FilePreviewService {
private static final String PDF_EXTENSION = ".pdf";
private static final String DOC_EXTENSION = ".doc";
private static final String DOCX_EXTENSION = ".docx";
private static final String HTML_CONTENT_TYPE = MediaType.TEXT_HTML_VALUE + ";charset=UTF-8";
public PreviewContent preview(Path filePath, String fileName) {
String extension = resolveExtension(fileName);
try {
if (PDF_EXTENSION.equals(extension)) {
return new PreviewContent(MediaType.APPLICATION_PDF_VALUE, Files.readAllBytes(filePath));
}
if (DOCX_EXTENSION.equals(extension)) {
return buildHtmlPreview(extractDocxText(filePath));
}
if (DOC_EXTENSION.equals(extension)) {
return buildHtmlPreview(extractDocText(filePath));
}
throw new IllegalArgumentException("仅支持预览 .docx、.doc、.pdf 文件");
} catch (IOException exception) {
throw new IllegalArgumentException("读取文件预览内容失败", exception);
}
}
private PreviewContent buildHtmlPreview(String text) {
String[] lines = StrUtil.nullToEmpty(text).replace("\r\n", "\n").replace('\r', '\n').split("\n");
StringBuilder htmlBuilder = new StringBuilder(256);
htmlBuilder.append("<!DOCTYPE html><html><head><meta charset=\"UTF-8\">")
.append("<title>文件预览</title>")
.append("<style>")
.append("body{margin:0;padding:24px;background:#f5f7fa;color:#1f2329;font:14px/1.7 'Microsoft YaHei',sans-serif;}")
.append(".preview{max-width:960px;margin:0 auto;padding:24px;background:#fff;border-radius:8px;box-shadow:0 4px 16px rgba(31,35,41,.08);}")
.append("p{margin:0 0 12px;white-space:pre-wrap;word-break:break-word;}")
.append("</style></head><body><div class=\"preview\">");
if (lines.length == 1 && StrUtil.isBlank(lines[0])) {
htmlBuilder.append("<p>文件内容为空</p>");
} else {
for (String line : lines) {
if (StrUtil.isBlank(line)) {
htmlBuilder.append("<p>&nbsp;</p>");
} else {
htmlBuilder.append("<p>").append(HtmlUtils.htmlEscape(line)).append("</p>");
}
}
}
htmlBuilder.append("</div></body></html>");
return new PreviewContent(HTML_CONTENT_TYPE, htmlBuilder.toString().getBytes(StandardCharsets.UTF_8));
}
private String extractDocxText(Path filePath) throws IOException {
try (XWPFDocument document = new XWPFDocument(Files.newInputStream(filePath));
XWPFWordExtractor extractor = new XWPFWordExtractor(document)) {
return extractor.getText();
}
}
private String extractDocText(Path filePath) throws IOException {
try (HWPFDocument document = new HWPFDocument(Files.newInputStream(filePath));
WordExtractor extractor = new WordExtractor(document)) {
return extractor.getText();
}
}
private String resolveExtension(String fileName) {
String normalized = StrUtil.nullToEmpty(fileName).trim().toLowerCase();
if (normalized.endsWith(DOCX_EXTENSION)) {
return DOCX_EXTENSION;
}
if (normalized.endsWith(DOC_EXTENSION)) {
return DOC_EXTENSION;
}
if (normalized.endsWith(PDF_EXTENSION)) {
return PDF_EXTENSION;
}
return "";
}
public static class PreviewContent {
private final String contentType;
private final byte[] content;
public PreviewContent(String contentType, byte[] content) {
this.contentType = contentType;
this.content = content;
}
public String getContentType() {
return contentType;
}
public byte[] getContent() {
return content;
}
}
}

View File

@@ -0,0 +1,77 @@
package com.njcn.gather.comservice.filepreview;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.http.MediaType;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class FilePreviewServiceTest {
@Test
public void previewPdfReturnsInlinePdfContent() throws Exception {
Path file = Files.createTempFile("common-preview", ".pdf");
byte[] content = "%PDF-1.4".getBytes(StandardCharsets.UTF_8);
Files.write(file, content);
FilePreviewService service = new FilePreviewService();
FilePreviewService.PreviewContent previewContent = service.preview(file, "sample.pdf");
Assert.assertEquals(MediaType.APPLICATION_PDF_VALUE, previewContent.getContentType());
Assert.assertArrayEquals(content, previewContent.getContent());
}
@Test
public void previewDocxReturnsHtmlContent() throws Exception {
Path file = Files.createTempFile("common-preview", ".docx");
try (XWPFDocument document = new XWPFDocument();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
document.createParagraph().createRun().setText("第一行");
document.createParagraph().createRun().setText("第二行<tag>");
document.write(outputStream);
Files.write(file, outputStream.toByteArray());
}
FilePreviewService service = new FilePreviewService();
FilePreviewService.PreviewContent previewContent = service.preview(file, "sample.docx");
String html = new String(previewContent.getContent(), StandardCharsets.UTF_8);
Assert.assertEquals(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8", previewContent.getContentType());
Assert.assertTrue(html.contains("第一行"));
Assert.assertTrue(html.contains("第二行&lt;tag&gt;"));
}
@Test
public void previewDocReturnsHtmlContent() throws Exception {
Path file = Files.createTempFile("common-preview", ".doc");
try (HWPFDocument document = new HWPFDocument();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Range range = document.getRange();
range.insertAfter("DOC 正文");
document.write(outputStream);
Files.write(file, outputStream.toByteArray());
}
FilePreviewService service = new FilePreviewService();
FilePreviewService.PreviewContent previewContent = service.preview(file, "sample.doc");
String html = new String(previewContent.getContent(), StandardCharsets.UTF_8);
Assert.assertEquals(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8", previewContent.getContentType());
Assert.assertTrue(html.contains("DOC 正文"));
}
@Test(expected = IllegalArgumentException.class)
public void previewRejectsUnsupportedExtension() throws Exception {
Path file = Files.createTempFile("common-preview", ".txt");
Files.write(file, "text".getBytes(StandardCharsets.UTF_8));
FilePreviewService service = new FilePreviewService();
service.preview(file, "sample.txt");
}
}