|
|
@@ -0,0 +1,372 @@
|
|
|
+package com.storlead.framework.common.util;
|
|
|
+
|
|
|
+import freemarker.template.Configuration;
|
|
|
+import freemarker.template.Template;
|
|
|
+import freemarker.template.TemplateException;
|
|
|
+import org.apache.commons.io.FileUtils;
|
|
|
+import org.apache.commons.lang3.ObjectUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.core.io.ClassPathResource;
|
|
|
+import org.springframework.core.io.FileSystemResource;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.*;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
+import java.net.URL;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Paths;
|
|
|
+import java.util.Base64;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @program: storlead-saas-platform
|
|
|
+ * @description:
|
|
|
+ * @author: chenkq
|
|
|
+ * @create: 2026-05-18 17:50
|
|
|
+ */
|
|
|
+public class WordUtils {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(WordUtils.class);
|
|
|
+ private static WordUtils service = null;
|
|
|
+
|
|
|
+ public WordUtils() {
|
|
|
+ super();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static WordUtils getInstance() {
|
|
|
+ if(service == null) {
|
|
|
+ service = new WordUtils();
|
|
|
+ }
|
|
|
+ return service;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出至word
|
|
|
+ * @param ftl ftl模板地址
|
|
|
+ * @param fileName 文件名
|
|
|
+ * @param dataMap 导出数据
|
|
|
+ * @param response response
|
|
|
+ */
|
|
|
+ public void exportDocFile(String ftl, String fileName, Map<String, Object> dataMap, HttpServletResponse response){
|
|
|
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
|
|
|
+ configuration.setDefaultEncoding("UTF-8");
|
|
|
+
|
|
|
+ OutputStreamWriter oWriter = null;
|
|
|
+ Writer out = null;
|
|
|
+ try {
|
|
|
+ ClassPathResource resource = new ClassPathResource(ftl);
|
|
|
+ File file = File.createTempFile("StatementTemp",".ftl");
|
|
|
+ FileUtils.copyURLToFile(resource.getURL(), file);
|
|
|
+ String templateFile = file.getAbsolutePath();
|
|
|
+ templateFile = pathReplace(templateFile);
|
|
|
+ if(null == templateFile){
|
|
|
+ log.error("导出失败,模板不存在");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String ftlPath = templateFile.substring(0, templateFile.lastIndexOf("/"));
|
|
|
+ log.info("模板路径:{}", ftlPath);
|
|
|
+ // FTL文件所存在的位置--绝对路径
|
|
|
+ configuration.setDirectoryForTemplateLoading(new File(ftlPath));
|
|
|
+ //configuration.setClassForTemplateLoading(this.getClass(), ftlPath);
|
|
|
+ String ftlFile = templateFile.substring(templateFile.lastIndexOf("/")+1);
|
|
|
+ log.info("模板临时文件名称:{}", ftlFile);
|
|
|
+ // 模板文件名
|
|
|
+ Template template = configuration.getTemplate(ftlFile);
|
|
|
+
|
|
|
+ response.reset();
|
|
|
+ response.setContentType("application/msword; charset=utf-8");
|
|
|
+ String filename = fileName + ".doc";
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename="+ urlEncode(filename));
|
|
|
+ oWriter = new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8);
|
|
|
+ out = new BufferedWriter(oWriter);
|
|
|
+ template.process(dataMap, out);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("导出word失败:", e);
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ if(null != out){
|
|
|
+ out.close();
|
|
|
+ }
|
|
|
+ if(null != oWriter){
|
|
|
+ oWriter.close();
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("Writer or OutputStreamWriter close failed:{}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出至word
|
|
|
+ * @param ftl ftl模板地址
|
|
|
+ * @param fileName 文件名
|
|
|
+ * @param dataMap 导出数据
|
|
|
+ * @param response response
|
|
|
+ */
|
|
|
+ public ResponseEntity<FileSystemResource> repExportDocFile(String ftl, String fileName, Map<String, Object> dataMap) {
|
|
|
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
|
|
|
+ configuration.setDefaultEncoding("UTF-8");
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 获取模板文件
|
|
|
+ ClassPathResource resource = new ClassPathResource(ftl);
|
|
|
+ File tempTemplate = File.createTempFile("StatementTemp", ".ftl");
|
|
|
+ FileUtils.copyURLToFile(resource.getURL(), tempTemplate);
|
|
|
+
|
|
|
+ String templatePath = pathReplace(tempTemplate.getAbsolutePath());
|
|
|
+ if (templatePath == null) {
|
|
|
+ log.error("导出失败,模板不存在");
|
|
|
+ return ResponseEntity.notFound().build();
|
|
|
+ }
|
|
|
+
|
|
|
+ String ftlDir = templatePath.substring(0, templatePath.lastIndexOf("/"));
|
|
|
+ configuration.setDirectoryForTemplateLoading(new File(ftlDir));
|
|
|
+ String ftlFileName = templatePath.substring(templatePath.lastIndexOf("/") + 1);
|
|
|
+ Template template = configuration.getTemplate(ftlFileName);
|
|
|
+
|
|
|
+ // 2. 生成 Word 临时文件
|
|
|
+ File tempWordFile = File.createTempFile("Export-", ".doc");
|
|
|
+ try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempWordFile), StandardCharsets.UTF_8))) {
|
|
|
+ template.process(dataMap, out);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 返回 Word 文件
|
|
|
+ FileSystemResource resourceBody = new FileSystemResource(tempWordFile);
|
|
|
+ String downloadFileName = fileName + ".doc";
|
|
|
+
|
|
|
+// response.setContentType("application/msword; charset=utf-8");
|
|
|
+// String filename = fileName + ".doc";
|
|
|
+// response.setHeader("Content-Disposition", "attachment; filename="+ urlEncode(filename));
|
|
|
+
|
|
|
+ return ResponseEntity.ok()
|
|
|
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + urlEncode(downloadFileName))
|
|
|
+// .header("application/octet-stream", "attachment; filename=" + urlEncode(downloadFileName))
|
|
|
+ .contentType(MediaType.parseMediaType("application/msword"))
|
|
|
+ .contentLength(resourceBody.contentLength())
|
|
|
+ .body(resourceBody);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("导出word失败:", e);
|
|
|
+ return ResponseEntity.status(500).build();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String exportDocToUrl(String ftl, String fileName, Map<String, Object> dataMap) {
|
|
|
+ try {
|
|
|
+ // 生成 Word 文件
|
|
|
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
|
|
|
+ configuration.setDefaultEncoding("UTF-8");
|
|
|
+
|
|
|
+ ClassPathResource resource = new ClassPathResource(ftl);
|
|
|
+ File tempTemplate = File.createTempFile("Template-", ".ftl");
|
|
|
+ FileUtils.copyURLToFile(resource.getURL(), tempTemplate);
|
|
|
+
|
|
|
+ String templatePath = pathReplace(tempTemplate.getAbsolutePath());
|
|
|
+ String ftlDir = templatePath.substring(0, templatePath.lastIndexOf("/"));
|
|
|
+ configuration.setDirectoryForTemplateLoading(new File(ftlDir));
|
|
|
+ String ftlFileName = templatePath.substring(templatePath.lastIndexOf("/") + 1);
|
|
|
+
|
|
|
+ Template template = configuration.getTemplate(ftlFileName);
|
|
|
+
|
|
|
+ // 1. 导出目录(容器内路径)
|
|
|
+ File exportDir = new File("/app/tempfiles");
|
|
|
+ if (!exportDir.exists()) {
|
|
|
+ exportDir.mkdirs();
|
|
|
+ }
|
|
|
+
|
|
|
+ File targetFile = new File(exportDir, fileName + ".doc");
|
|
|
+
|
|
|
+ try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), StandardCharsets.UTF_8))) {
|
|
|
+ template.process(dataMap, out);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 拼接 URL(这里假设映射后访问路径是 /export/)
|
|
|
+ return "/static/file/" + fileName + ".doc";
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("生成word失败:", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public void exportDocFileTest(String ftl, String fileName, Map<String, Object> dataMap, HttpServletResponse response){
|
|
|
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
|
|
|
+ configuration.setDefaultEncoding("UTF-8");
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 直接从类路径加载FTL资源
|
|
|
+ ClassPathResource resource = new ClassPathResource(ftl);
|
|
|
+ URL templateUrl = resource.getURL();
|
|
|
+
|
|
|
+ // 注意:这里需要确保Freemarker可以直接从URL加载模板,或者您需要将其内容复制到字符串或ByteArrayInputStream
|
|
|
+ // 但对于大多数情况,如果模板在类路径中,您可以直接使用setClassLoaderForTemplateLoading
|
|
|
+ configuration.setClassForTemplateLoading(this.getClass().getClassLoader().getClass(), "");
|
|
|
+
|
|
|
+ // 获取模板
|
|
|
+ Template template = configuration.getTemplate(ftl);
|
|
|
+
|
|
|
+ // 设置响应头
|
|
|
+ response.reset();
|
|
|
+ response.setContentType("application/octet-stream; charset=utf-8");
|
|
|
+ String encodedFileName = java.net.URLEncoder.encode(fileName + ".doc", StandardCharsets.UTF_8.toString());
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
|
|
|
+
|
|
|
+ // 使用BufferedWriter写入响应
|
|
|
+ try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8))) {
|
|
|
+ template.process(dataMap, writer);
|
|
|
+ writer.flush(); // 确保所有数据都被写入响应
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (IOException | TemplateException e) {
|
|
|
+ // 处理异常,例如记录日志或返回错误响应
|
|
|
+ // 注意:在实际应用中,您可能想要将异常信息返回给客户端或进行其他错误处理
|
|
|
+ System.err.println("导出word失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建doc文件
|
|
|
+ * @param ftl src/main/resources/ftl/xxx.ftl
|
|
|
+ * @param dataMap 导出数据
|
|
|
+ * @param exportPath eg: /tmp/test/test123.doc
|
|
|
+ * @param loadType 设置路径加载方式。1-绝对路径,2-项目相对路径
|
|
|
+ * @return {@link File}
|
|
|
+ * @ver v1.0.0
|
|
|
+ */
|
|
|
+ public File createDocFile(String ftl, Map<String, Object> dataMap, String exportPath, int loadType) {
|
|
|
+ File file = new File(ftl);
|
|
|
+ String templateFile = file.getAbsolutePath();
|
|
|
+ Template t = null;
|
|
|
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
|
|
|
+ configuration.setDefaultEncoding("UTF-8");
|
|
|
+ try {
|
|
|
+ templateFile = pathReplace(templateFile);
|
|
|
+ if(null == templateFile){
|
|
|
+ log.error("模板不存在!");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String ftlPath = templateFile.substring(0, templateFile.lastIndexOf("/"));
|
|
|
+ if(loadType == 1) {
|
|
|
+ // FTL文件所存在的位置
|
|
|
+ configuration.setDirectoryForTemplateLoading(new File(ftlPath));
|
|
|
+ }else {
|
|
|
+ //以类加载的方式查找模版文件路径
|
|
|
+ configuration.setClassForTemplateLoading(this.getClass(), ftlPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ String ftlFile = templateFile.substring(templateFile.lastIndexOf("/")+1);
|
|
|
+ // 模板文件名
|
|
|
+ t = configuration.getTemplate(ftlFile);
|
|
|
+
|
|
|
+ File outFile = new File(exportPath);
|
|
|
+ Writer out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(outFile.toPath())));
|
|
|
+ t.process(dataMap, out);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("导出word文档出错:{}", e.getMessage());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 把路径的\替换成/
|
|
|
+ * @param path 路径
|
|
|
+ * @return {@link String}
|
|
|
+ * @ver v1.0.0
|
|
|
+ */
|
|
|
+ private static String pathReplace(String path) {
|
|
|
+ if(!ObjectUtils.isEmpty(path)) {
|
|
|
+ path = path.replace("\\", "/");
|
|
|
+// path = path.replace("\"", "/");
|
|
|
+ }
|
|
|
+ return path;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 图片转base64
|
|
|
+ * @param imgStr 图片 http 地址
|
|
|
+ * @return {@link String}
|
|
|
+ * @ver v1.0.0
|
|
|
+ */
|
|
|
+ public static String getImgStrToBase64(String imgStr) {
|
|
|
+ if(StringUtils.isEmpty(imgStr)){
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ log.info("imgStr:===>{}",imgStr);
|
|
|
+ InputStream inputStream = null;
|
|
|
+ ByteArrayOutputStream outputStream = null;
|
|
|
+ byte[] buffer = null;
|
|
|
+ try {
|
|
|
+ //判断网络链接图片文件/本地目录图片文件
|
|
|
+ if (imgStr.startsWith("http://") || imgStr.startsWith("https://")) {
|
|
|
+ // 创建URL
|
|
|
+ URL url = new URL(imgStr);
|
|
|
+ // 创建链接
|
|
|
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
|
+ conn.setRequestMethod("GET");
|
|
|
+ conn.setConnectTimeout(5000);
|
|
|
+ inputStream = conn.getInputStream();
|
|
|
+ outputStream = new ByteArrayOutputStream();
|
|
|
+ // 将内容读取内存中
|
|
|
+ buffer = new byte[1024];
|
|
|
+ int len = -1;
|
|
|
+ while ((len = inputStream.read(buffer)) != -1) {
|
|
|
+ outputStream.write(buffer, 0, len);
|
|
|
+ }
|
|
|
+ buffer = outputStream.toByteArray();
|
|
|
+ } else {
|
|
|
+ inputStream = Files.newInputStream(Paths.get(imgStr));
|
|
|
+ int count = 0;
|
|
|
+ while (count == 0) {
|
|
|
+ count = inputStream.available();
|
|
|
+ }
|
|
|
+ buffer = new byte[count];
|
|
|
+ inputStream.read(buffer);
|
|
|
+ }
|
|
|
+ // 对字节数组Base64编码
|
|
|
+ return new String(Base64.getDecoder().decode(buffer));
|
|
|
+ }catch (Exception e) {
|
|
|
+ log.error("图片转换失败:{}", e.getMessage());
|
|
|
+ return "";
|
|
|
+ } finally {
|
|
|
+ if (inputStream != null) {
|
|
|
+ try {
|
|
|
+ // 关闭inputStream流
|
|
|
+ inputStream.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("InputStream 关闭失败:{}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (outputStream != null) {
|
|
|
+ try {
|
|
|
+ // 关闭outputStream流
|
|
|
+ outputStream.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("ByteArrayOutputStream 关闭失败:{}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * URL 编码, Encode默认为UTF-8.
|
|
|
+ */
|
|
|
+ public static String urlEncode(String part) {
|
|
|
+ try {
|
|
|
+ return URLEncoder.encode(part, "UTF-8");
|
|
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
+ log.error("编码失败:{}", e.getMessage());
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|