package com.ruoyi.web.controller.tool;
|
|
import cn.hutool.core.io.FileUtil;
|
import freemarker.cache.ClassTemplateLoader;
|
import freemarker.template.Configuration;
|
import freemarker.template.Template;
|
import freemarker.template.TemplateException;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.poi.util.Units;
|
import org.apache.poi.xwpf.usermodel.*;
|
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.stereotype.Component;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import javax.annotation.Resource;
|
import java.io.*;
|
import java.util.*;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
@Slf4j
|
@Component
|
public class WordUtil {
|
|
/**
|
* 基于模板生成 Word 文档
|
*
|
* @param basePackagePath resources 目录下模板所在包路径
|
* @param templateFileName 模板文件名
|
* @param templateParam 模板参数
|
* @param fileName 文件名
|
*/
|
// public void generate(HttpServletResponse response, String basePackagePath, String templateFileName, Object templateParam, String fileName) {
|
// try {
|
// // 设置 HTTP 响应的内容类型为 Microsoft Word 文档
|
// response.setContentType("application/msword");
|
// // 设置响应字符编码为 UTF-8
|
// response.setCharacterEncoding("utf-8");
|
// // 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题
|
// String filename = URLEncoder.encode(fileName + "_" + System.currentTimeMillis(), "utf-8");
|
// // 设置响应头,指定文档将以附件的形式下载,并定义文件名
|
// response.setHeader("Content-disposition", "attachment;filename=" + filename + ".doc");
|
// // 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项
|
// Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
|
// configuration.setDefaultEncoding("utf-8");
|
// // 设置模板加载器,加载模板文件
|
// configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), basePackagePath));
|
// Template t = configuration.getTemplate(templateFileName, "utf-8");
|
// // 创建 Writer 对象,用于将生成的文档写到输出流中,缓存区大小设为 10KB
|
// Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8), 10240);
|
// // 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中
|
// t.process(templateParam, out);
|
// out.close();
|
// } catch (Exception e) {
|
// log.info("基于模板{}生成Word文档异常,异常原因:{}", templateFileName, e.getMessage(), e);
|
// throw new RuntimeException("生成Word文档异常,异常原因:" + e.getMessage());
|
// }
|
// }
|
@Resource
|
private TencentCosUtil tencentCosUtil;
|
@Resource
|
private PdfUtils pdfUtils;
|
public static void fillTemplate(String templatePath, String outputPath,Map<String, Object> dataMap,String url) {
|
try (FileInputStream fis = new FileInputStream(templatePath)) {
|
// 设置默认编码为UTF-8
|
System.setProperty("file.encoding", "UTF-8");
|
|
XWPFDocument document = new XWPFDocument(fis);
|
XWPFParagraph pic = document.createParagraph();
|
Base64.Decoder decoder = Base64.getDecoder();
|
byte[] imageByte = decoder.decode(url);
|
InputStream stream = new ByteArrayInputStream(imageByte);
|
File tempFile = FileUtil.createTempFile("/usr/local/project/file/temp", ".jpg", true);
|
// File tempFile = File.createTempFile("/usr/local/project/file/temp", ".jpg");
|
// tempFile.deleteOnExit(); // 程序结束时删除文件
|
|
try (OutputStream out = new FileOutputStream(tempFile);
|
InputStream in = stream) {
|
byte[] buffer = new byte[1024];
|
int length;
|
// 从原始流读取数据并写入临时文件
|
while ((length = in.read(buffer)) > 0) {
|
out.write(buffer, 0, length);
|
}
|
}
|
|
//处理图片
|
for (XWPFParagraph paragraph : document.getParagraphs()) {
|
List<XWPFRun> runs = paragraph.getRuns();
|
for (XWPFRun run : runs) {
|
String text = run.getText(0);
|
if (text != null && text.contains("picture")) {
|
run.setText("", 0); // 清除占位符文本
|
run.addPicture(
|
new FileInputStream(new File("/usr/local/project/file/sign.jpg")), XWPFDocument.PICTURE_TYPE_JPEG,
|
"/usr/local/project/file/sign.jpg",
|
Units.toEMU(60),
|
Units.toEMU(30)); // 插入图片
|
}
|
}
|
}
|
|
// 处理段落
|
for (XWPFParagraph paragraph : document.getParagraphs()) {
|
replaceParagraph(paragraph, dataMap);
|
}
|
|
// 处理表格
|
for (XWPFTable table : document.getTables()) {
|
for (XWPFTableRow row : table.getRows()) {
|
for (XWPFTableCell cell : row.getTableCells()) {
|
for (XWPFParagraph paragraph : cell.getParagraphs()) {
|
replaceParagraph(paragraph, dataMap);
|
}
|
}
|
}
|
}
|
|
// 使用UTF-8编码保存文件
|
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
|
document.write(fos);
|
}
|
|
System.out.println("模板填充完成!文件保存在: " + outputPath);
|
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
}
|
|
private static void replaceParagraph(XWPFParagraph paragraph, Map<String, Object> dataMap) {
|
// 获取段落中所有runs
|
List<XWPFRun> runs = paragraph.getRuns();
|
if (runs == null || runs.isEmpty()) return;
|
|
// 首先合并所有runs的文本,以便正确识别占位符
|
StringBuilder fullText = new StringBuilder();
|
for (XWPFRun run : runs) {
|
String text = run.getText(0);
|
if (text != null) {
|
fullText.append(text);
|
}
|
}
|
|
String paragraphText = fullText.toString();
|
|
// 使用正则表达式查找所有占位符,包括括号内的
|
Pattern pattern = Pattern.compile("\\$\\{[^}]+\\}|\\([^)]*\\$\\{[^}]+\\}[^)]*\\)");
|
Matcher matcher = pattern.matcher(paragraphText);
|
|
List<ReplacementInfo> replacements = new ArrayList<>();
|
|
// 收集所有需要替换的信息
|
while (matcher.find()) {
|
String matched = matcher.group();
|
int start = matcher.start();
|
int end = matcher.end();
|
|
// 找出涉及到的runs
|
int startRun = -1;
|
int endRun = -1;
|
int currentPos = 0;
|
|
for (int i = 0; i < runs.size(); i++) {
|
XWPFRun run = runs.get(i);
|
String runText = run.getText(0);
|
if (runText == null) continue;
|
|
int runLength = runText.length();
|
if (startRun == -1 && currentPos + runLength > start) {
|
startRun = i;
|
}
|
if (currentPos + runLength >= end) {
|
endRun = i;
|
break;
|
}
|
currentPos += runLength;
|
}
|
|
if (startRun != -1 && endRun != -1) {
|
// 处理括号内的占位符
|
String replacement = processPlaceholder(matched, dataMap);
|
replacements.add(new ReplacementInfo(startRun, endRun, matched, replacement));
|
}
|
}
|
|
// 从后向前替换,避免位置变化影响
|
Collections.sort(replacements, (a, b) -> b.startRun - a.startRun);
|
|
for (ReplacementInfo info : replacements) {
|
replaceRunRange(paragraph, info);
|
}
|
}
|
|
private static String processPlaceholder(String text, Map<String, Object> dataMap) {
|
// 处理括号内的占位符
|
Pattern placeholderPattern = Pattern.compile("\\$\\{([^}]+)\\}");
|
Matcher matcher = placeholderPattern.matcher(text);
|
|
StringBuffer result = new StringBuffer();
|
while (matcher.find()) {
|
String placeholder = matcher.group(0); // 完整的占位符
|
String key = matcher.group(1); // 占位符中的键
|
String replacement = Objects.nonNull(dataMap.get("${" + key + "}"))?String.valueOf(dataMap.get("${" + key + "}")):"";
|
|
if (replacement != null) {
|
// 如果在括号内,保留括号
|
if (text.startsWith("(") && text.endsWith(")")) {
|
matcher.appendReplacement(result, replacement);
|
} else {
|
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
}
|
}
|
}
|
matcher.appendTail(result);
|
|
return result.toString();
|
}
|
|
private static void replaceRunRange(XWPFParagraph paragraph, ReplacementInfo info) {
|
List<XWPFRun> runs = paragraph.getRuns();
|
|
// 保存第一个run的样式
|
XWPFRun styleRun = runs.get(info.startRun);
|
RunStyle style = new RunStyle(styleRun);
|
|
// 删除范围内的所有runs
|
for (int i = info.endRun; i >= info.startRun; i--) {
|
paragraph.removeRun(i);
|
}
|
|
// 创建新的run并设置文本
|
XWPFRun newRun = paragraph.insertNewRun(info.startRun);
|
newRun.setText(info.replacementText);
|
|
// 应用样式
|
style.applyStyle(newRun);
|
}
|
|
public String generate(String basePackagePath, String templateFileName, Object templateParam, String fileName, String saveDirectory) {
|
try {
|
// 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项
|
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
|
configuration.setDefaultEncoding("utf-8");
|
// 设置模板加载器,加载模板文件
|
configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), basePackagePath));
|
Template t = configuration.getTemplate(templateFileName, "utf-8");
|
|
// 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题
|
// String encodedFileName = URLEncoder.encode(fileName + "_" + System.currentTimeMillis(), "utf-8");
|
String encodedFileName =fileName ;
|
|
// 定义保存文件的路径
|
File saveDir = new File(saveDirectory);
|
if (!saveDir.exists()) {
|
saveDir.mkdirs();
|
}
|
|
// 定义文件名
|
String filePath = saveDir.getAbsolutePath() + File.separator + encodedFileName + ".doc";
|
|
// 创建 Writer 对象,用于将生成的文档写到文件中,缓存区大小设为 10KB
|
Writer out = new BufferedWriter(new FileWriter(filePath), 10240);
|
|
// 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中
|
t.process(templateParam, out);
|
out.close();
|
|
File file = new File(filePath);
|
|
// 检查文件是否存在
|
if (!file.exists()) {
|
throw new FileNotFoundException("文件不存在: " + filePath);
|
}
|
|
// 读取文件内容
|
byte[] fileContent = new byte[(int) file.length()];
|
try (FileInputStream fis = new FileInputStream(file)) {
|
fis.read(fileContent);
|
}
|
MultipartFile mockMultipartFile = new MockMultipartFile(encodedFileName+".doc", fileContent);
|
String s = tencentCosUtil.upLoadFile(mockMultipartFile,"/wordGenerate");
|
return s;
|
} catch (IOException | TemplateException e) {
|
log.error("生成Word文档异常,异常原因:{}", e.getMessage(), e);
|
throw new RuntimeException("生成Word文档异常,异常原因:" + e.getMessage());
|
}
|
}
|
|
public String generatePdf(String basePackagePath, String templateFileName, Map<String,Object> templateParam, String fileName, String saveDirectory,String url) {
|
try {
|
|
fillTemplate(basePackagePath+templateFileName, saveDirectory+fileName+".docx", templateParam,url);
|
|
// 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项
|
// Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
|
// configuration.setDefaultEncoding("utf-8");
|
// // 设置模板加载器,加载模板文件
|
// configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), basePackagePath));
|
// Template t = configuration.getTemplate(templateFileName, "utf-8");
|
//
|
// // 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题
|
//// String encodedFileName = URLEncoder.encode(fileName + "_" + System.currentTimeMillis(), "utf-8");
|
// String encodedFileName =fileName ;
|
//
|
// // 定义保存文件的路径
|
// File saveDir = new File(saveDirectory);
|
// if (!saveDir.exists()) {
|
// saveDir.mkdirs();
|
// }
|
//
|
// // 定义文件名
|
// String filePath = saveDir.getAbsolutePath() + File.separator + encodedFileName + ".doc";
|
//
|
// // 创建 Writer 对象,用于将生成的文档写到文件中,缓存区大小设为 10KB
|
// Writer out = new BufferedWriter(new FileWriter(filePath), 10240);
|
//
|
// // 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中
|
// t.process(templateParam, out);
|
// out.close();
|
String filePath = saveDirectory + File.separator + fileName + ".docx";
|
File file = new File(filePath);
|
|
// 检查文件是否存在
|
if (!file.exists()) {
|
throw new FileNotFoundException("文件不存在: " + filePath);
|
}
|
|
// 读取文件内容
|
byte[] fileContent = new byte[(int) file.length()];
|
try (FileInputStream fis = new FileInputStream(file)) {
|
fis.read(fileContent);
|
}
|
|
String test = pdfUtils.test(fileName + ".docx");
|
// MultipartFile mockMultipartFile = new MockMultipartFile(encodedFileName+".doc", fileContent);
|
// String s = ObsUploadUtil.obsUpload(mockMultipartFile);
|
return test;
|
} catch (IOException e) {
|
log.error("生成pdf异常,异常原因:{}", e.getMessage(), e);
|
throw new RuntimeException("生成pdf异常,异常原因:" + e.getMessage());
|
}
|
}
|
|
private static class ReplacementInfo {
|
int startRun;
|
int endRun;
|
String originalText;
|
String replacementText;
|
|
ReplacementInfo(int startRun, int endRun, String originalText, String replacementText) {
|
this.startRun = startRun;
|
this.endRun = endRun;
|
this.originalText = originalText;
|
this.replacementText = replacementText;
|
}
|
}
|
|
// 用于保存和恢复运行样式的辅助类
|
private static class RunStyle {
|
String fontFamily;
|
int fontSize;
|
boolean bold;
|
boolean italic;
|
String color;
|
UnderlinePatterns underline;
|
|
RunStyle(XWPFRun run) {
|
this.fontFamily = run.getFontFamily();
|
this.fontSize = run.getFontSize();
|
this.bold = run.isBold();
|
this.italic = run.isItalic();
|
this.color = run.getColor();
|
this.underline = run.getUnderline();
|
}
|
|
void applyStyle(XWPFRun run) {
|
if (fontFamily != null) {
|
run.setFontFamily(fontFamily);
|
run.setFontFamily(fontFamily, XWPFRun.FontCharRange.eastAsia);
|
}
|
if (fontSize != -1) {
|
run.setFontSize(fontSize);
|
}
|
run.setBold(bold);
|
run.setItalic(italic);
|
if (color != null) {
|
run.setColor(color);
|
}
|
if (underline != null) {
|
run.setUnderline(underline);
|
}
|
}
|
}
|
|
// public String generatePdf(String basePackagePath, String templateFileName, Object templateParam, String fileName, String saveDirectory) {
|
// try {
|
// // 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项
|
// Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
|
// configuration.setDefaultEncoding("utf-8");
|
// // 设置模板加载器,加载模板文件
|
// configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), basePackagePath));
|
// Template t = configuration.getTemplate(templateFileName, "utf-8");
|
//
|
// // 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题
|
//// String encodedFileName = URLEncoder.encode(fileName + "_" + System.currentTimeMillis(), "utf-8");
|
// String encodedFileName =fileName ;
|
//
|
// // 定义保存文件的路径
|
// File saveDir = new File(saveDirectory);
|
// if (!saveDir.exists()) {
|
// saveDir.mkdirs();
|
// }
|
//
|
// // 定义文件名
|
// String filePath = saveDir.getAbsolutePath() + File.separator + encodedFileName + ".doc";
|
//
|
// // 创建 Writer 对象,用于将生成的文档写到文件中,缓存区大小设为 10KB
|
// Writer out = new BufferedWriter(new FileWriter(filePath), 10240);
|
//
|
// // 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中
|
// t.process(templateParam, out);
|
// out.close();
|
//
|
// File file = new File(filePath);
|
//
|
// // 检查文件是否存在
|
// if (!file.exists()) {
|
// throw new FileNotFoundException("文件不存在: " + filePath);
|
// }
|
//
|
// // 读取文件内容
|
// byte[] fileContent = new byte[(int) file.length()];
|
// try (FileInputStream fis = new FileInputStream(file)) {
|
// fis.read(fileContent);
|
// }
|
//
|
// String test = pdfUtils.test(encodedFileName + ".doc");
|
//// MultipartFile mockMultipartFile = new MockMultipartFile(encodedFileName+".doc", fileContent);
|
//// String s = ObsUploadUtil.obsUpload(mockMultipartFile);
|
// return test;
|
// } catch (IOException | TemplateException e) {
|
// log.error("生成pdf异常,异常原因:{}", e.getMessage(), e);
|
// throw new RuntimeException("生成pdf异常,异常原因:" + e.getMessage());
|
// }
|
// }
|
|
|
|
|
}
|