package com.ruoyi.web.controller.tool; 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.xwpf.usermodel.*; import org.springframework.mock.web.MockMultipartFile; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; 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 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 templateParam, String fileName, String saveDirectory) { try { fillTemplate(basePackagePath+templateFileName, saveDirectory+fileName+".docx", templateParam); // 创建 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()); } } public static void fillTemplate(String templatePath, String outputPath,Map dataMap) { try (FileInputStream fis = new FileInputStream(templatePath)) { // 设置默认编码为UTF-8 System.setProperty("file.encoding", "UTF-8"); XWPFDocument document = new XWPFDocument(fis); // 处理段落 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 dataMap) { // 获取段落中所有runs List 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 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 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 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 void replaceRunRange(XWPFParagraph paragraph, ReplacementInfo info) { List 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); } // 用于保存和恢复运行样式的辅助类 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()); // } // } }