10 changed files with 14279 additions and 1 deletions
@ -0,0 +1,264 @@ |
|||
package org.dromara.business.utils; |
|||
|
|||
|
|||
import cn.hutool.extra.spring.SpringUtil; |
|||
import com.spire.doc.Document; |
|||
import com.spire.doc.FileFormat; |
|||
import freemarker.template.Template; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import org.springframework.core.io.ByteArrayResource; |
|||
import org.springframework.core.io.InputStreamSource; |
|||
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; |
|||
|
|||
import java.io.*; |
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
import java.net.URLEncoder; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.Base64; |
|||
import java.util.Map; |
|||
|
|||
public class FreemarkerUtil { |
|||
|
|||
|
|||
/** |
|||
* Freemarker的配置类,模板目录默认 在 resources下的 templates |
|||
*/ |
|||
private static FreeMarkerConfigurer freeMarkerConfigurer = SpringUtil.getBean(FreeMarkerConfigurer.class); |
|||
|
|||
|
|||
/** |
|||
* 将模板文件填充数据后转为String |
|||
* |
|||
* @param templateName ftl模板文件名 |
|||
* @param datas 数据集合 |
|||
* @return |
|||
*/ |
|||
public static String renderTplFileToStr(String templateName, Map<String, Object> datas) { |
|||
|
|||
try { |
|||
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(templateName, "UTF-8"); |
|||
|
|||
|
|||
StringWriter out = new StringWriter(); |
|||
|
|||
template.process(datas, out); |
|||
|
|||
out.flush(); |
|||
out.close(); |
|||
return out.getBuffer().toString(); |
|||
|
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 将模板文件填充数据后导出为文件(导出html,word文件都使用此方法) |
|||
* |
|||
* @param templateName ftl模板文件名 |
|||
* @param datas 数据集合 |
|||
* @param outFile 导出的文件路径名称 |
|||
*/ |
|||
public static void renderTplFileToLocalFile(String templateName, Map<String, Object> datas, ByteArrayOutputStream fos) { |
|||
|
|||
Writer out = null; |
|||
|
|||
OutputStreamWriter oWriter = null; |
|||
try { |
|||
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(templateName, "UTF-8"); |
|||
oWriter = new OutputStreamWriter(fos, "UTF-8"); |
|||
// 这个地方对流的编码不可或缺,使用main()单独调用时,应该可以,但是如果是web请求导出时导出后word文档就会打不开,并且包XML文件错误。主要是编码格式不正确,无法解析。
|
|||
out = new BufferedWriter(new OutputStreamWriter(fos)); |
|||
out = new BufferedWriter(oWriter); |
|||
template.process(datas, out); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} finally { |
|||
try { |
|||
if (oWriter != null) { |
|||
oWriter.close(); |
|||
} |
|||
if (fos != null) { |
|||
fos.close(); |
|||
} |
|||
if (out != null) { |
|||
out.close(); |
|||
} |
|||
} catch (IOException ioException) { |
|||
ioException.printStackTrace(); |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
/** |
|||
* 根据word模板获取输入流 |
|||
* |
|||
* @param templateName |
|||
* @param datas |
|||
* @return |
|||
* @throws IOException |
|||
*/ |
|||
public static InputStream renderTplFileToWordInputStream(String templateName, Map<String, Object> datas) throws IOException { |
|||
Document doc = null; |
|||
DataInputStream in = null; |
|||
try { |
|||
in = renderTplFileToIOFile(templateName, datas); |
|||
doc = new Document(); |
|||
doc.loadFromStream(in, FileFormat.Auto); |
|||
|
|||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|||
doc.saveToStream(baos, FileFormat.Docx); |
|||
|
|||
ByteArrayOutputStream byteArrayOutputStream = ZipStreamUtil.copyStream(baos); |
|||
//输出流转为输入流
|
|||
InputStreamSource inputStreamSource = new ByteArrayResource(byteArrayOutputStream.toByteArray()); |
|||
return inputStreamSource.getInputStream(); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} finally { |
|||
if (in != null) { |
|||
in.close(); |
|||
} |
|||
if (doc != null) { |
|||
doc.close(); |
|||
} |
|||
} |
|||
return null; |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/** |
|||
* 将模板文件填充数据后返回文件流 |
|||
* |
|||
* @param templateName ftl模板文件名 |
|||
* @param datas 数据集合 |
|||
*/ |
|||
public static DataInputStream renderTplFileToIOFile(String templateName, Map<String, Object> datas) throws Exception { |
|||
ByteArrayInputStream in = null; |
|||
Writer out = null; |
|||
try { |
|||
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(templateName, "UTF-8"); |
|||
out = new StringWriter(); |
|||
template.process(datas, out); |
|||
String out1 = out.toString().replace("&", "&");//替换非法字符
|
|||
in = new ByteArrayInputStream(out1.getBytes("UTF-8")); |
|||
return new DataInputStream(in); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} finally { |
|||
if (out != null) { |
|||
out.close(); |
|||
} |
|||
if (in != null) { |
|||
in.close(); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 根据word模板获取输入流 |
|||
* |
|||
* @param templateName |
|||
* @param datas |
|||
* @return |
|||
* @throws IOException |
|||
*/ |
|||
public static void renderTplFileToWord(String documentName , String templateName, Map<String, Object> datas, HttpServletResponse response) throws IOException { |
|||
response.setContentType("application/x-msdownload;"); |
|||
response.setHeader("Content-disposition", "attachment; filename=" + documentName); |
|||
Document doc = null; |
|||
DataInputStream in = null; |
|||
try { |
|||
in = renderTplFileToIOFile(templateName, datas); |
|||
doc = new Document(); |
|||
doc.loadFromStream(in, FileFormat.Auto); |
|||
|
|||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|||
doc.saveToStream(baos, FileFormat.Docx); |
|||
|
|||
ByteArrayOutputStream byteArrayOutputStream = ZipStreamUtil.copyStream(baos); |
|||
// 将生成的文档写入到响应的输出流中
|
|||
response.reset(); // 重置响应
|
|||
response.getOutputStream().write(byteArrayOutputStream.toByteArray()); |
|||
response.getOutputStream().flush(); |
|||
|
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} finally { |
|||
if (in != null) { |
|||
in.close(); |
|||
} |
|||
if (doc != null) { |
|||
doc.close(); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
/** |
|||
* 根据word模板获取输入流 |
|||
* |
|||
* @param templateName |
|||
* @param datas |
|||
* @return |
|||
* @throws IOException |
|||
*/ |
|||
public static void renderTplFileToPDF(String fileName ,String templateName, Map<String, Object> datas,HttpServletResponse response) throws IOException { |
|||
response.setCharacterEncoding(StandardCharsets.UTF_8.name()); // 字符集编码
|
|||
response.setContentType("application/pdf;"); |
|||
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8")); |
|||
response.addHeader("Access-Control-Allow-Origin", "*"); // 实现跨域
|
|||
Document doc = null; |
|||
DataInputStream in = null; |
|||
try { |
|||
in = renderTplFileToIOFile(templateName, datas); |
|||
doc = new Document(); |
|||
doc.loadFromStream(in, FileFormat.Auto); |
|||
|
|||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|||
doc.saveToStream(baos, FileFormat.PDF); |
|||
|
|||
// 将生成的文档写入到响应的输出流中
|
|||
response.reset(); // 重置响应
|
|||
response.getOutputStream().write(baos.toByteArray()); |
|||
response.getOutputStream().flush(); |
|||
|
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
|
|||
public static String encodeImageToBase64(String imageUrl) throws Exception { |
|||
URL url = new URL(imageUrl); |
|||
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
|||
connection.setRequestMethod("GET"); |
|||
|
|||
try (InputStream in = connection.getInputStream(); |
|||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) { |
|||
byte[] buffer = new byte[1024]; |
|||
int bytesRead; |
|||
while ((bytesRead = in.read(buffer)) != -1) { |
|||
baos.write(buffer, 0, bytesRead); |
|||
} |
|||
byte[] imageBytes = baos.toByteArray(); |
|||
return Base64.getEncoder().encodeToString(imageBytes); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,283 @@ |
|||
package org.dromara.business.utils; |
|||
|
|||
import java.io.*; |
|||
import java.net.MalformedURLException; |
|||
import java.net.URL; |
|||
import java.nio.file.Files; |
|||
import java.util.function.BiFunction; |
|||
import java.util.zip.ZipEntry; |
|||
import java.util.zip.ZipInputStream; |
|||
import java.util.zip.ZipOutputStream; |
|||
|
|||
import static java.util.zip.ZipEntry.STORED; |
|||
import static java.util.zip.ZipOutputStream.DEFLATED; |
|||
|
|||
public class ZipStreamUtil { |
|||
|
|||
public static void main(String[] args) { |
|||
/* String srcPath = "E:\\360极速浏览器下载\\Linksla生产 (1).docx"; |
|||
String outPath = "E:\\360极速浏览器下载\\Linksla生产2.docx"; |
|||
try (FileInputStream is = new FileInputStream(srcPath)) { |
|||
try (FileOutputStream os = new FileOutputStream(outPath)) { |
|||
ZipStreamUtil.zipEntryCopyStream(os, is, new BiFunction<ZipEntry, ZipInputStream, byte[]>() { |
|||
@Override |
|||
public byte[] apply(ZipEntry se, ZipInputStream zis) { |
|||
byte[] rt = null; |
|||
String name = se.getName(); |
|||
if (null == name) return rt; |
|||
String line = String.format("ZipEntry(%s, isDirectory=%d, size=%d, compressedSize=%d, time=%d, crc=%d, method=%d, comment=%s)", |
|||
se.getName(), (se.isDirectory()) ? 1 : 0, se.getSize(), se.getCompressedSize(), se.getTime(), se.getCrc(), se.getMethod(), se.getComment()); |
|||
System.out.println(line); |
|||
if (name.endsWith("word/document.xml")) { |
|||
try { |
|||
byte[] oldBytes = ZipStreamUtil.toByteArray(zis); |
|||
String str = (new String(oldBytes)); |
|||
String appendString = str.replace("Evaluation Warning: The document was created with Spire.Doc for JAVA.", ""); |
|||
//rt = str.getBytes();
|
|||
// 为了避免多余的编码转换, 改成下面的代码更好.
|
|||
try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { |
|||
//ZipStreamUtil.copyStream(buf, zis);
|
|||
buf.write(appendString.getBytes()); |
|||
rt = buf.toByteArray(); |
|||
} |
|||
} catch (IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
return rt; |
|||
} |
|||
}); |
|||
} |
|||
} catch (FileNotFoundException e) { |
|||
e.printStackTrace(); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
System.out.println("ZipTxtAppendTest done." + outPath);*/ |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 过滤word 水印 |
|||
* |
|||
* @param baos |
|||
* @return |
|||
* @throws IOException |
|||
*/ |
|||
public static ByteArrayOutputStream copyStream(ByteArrayOutputStream baos) throws IOException { |
|||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); |
|||
|
|||
ByteArrayOutputStream baoss = new ByteArrayOutputStream(); |
|||
try { |
|||
|
|||
ZipStreamUtil.zipEntryCopyStream(baoss, bais, new BiFunction<ZipEntry, ZipInputStream, byte[]>() { |
|||
@Override |
|||
public byte[] apply(ZipEntry se, ZipInputStream zis) { |
|||
byte[] rt = null; |
|||
String name = se.getName(); |
|||
if (null == name) return rt; |
|||
String line = String.format("ZipEntry(%s, isDirectory=%d, size=%d, compressedSize=%d, time=%d, crc=%d, method=%d, comment=%s)", |
|||
se.getName(), (se.isDirectory()) ? 1 : 0, se.getSize(), se.getCompressedSize(), se.getTime(), se.getCrc(), se.getMethod(), se.getComment()); |
|||
System.out.println(line); |
|||
if (name.endsWith("word/document.xml")) { |
|||
try { |
|||
byte[] oldBytes = ZipStreamUtil.toByteArray(zis); |
|||
String str = (new String(oldBytes)); |
|||
String appendString = str.replace("Evaluation Warning: The document was created with Spire.Doc for JAVA.", ""); |
|||
try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { |
|||
buf.write(appendString.getBytes()); |
|||
rt = buf.toByteArray(); |
|||
} |
|||
} catch (IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
return rt; |
|||
} |
|||
}); |
|||
} finally { |
|||
bais.close(); |
|||
baoss.close(); |
|||
} |
|||
|
|||
return baoss; |
|||
} |
|||
|
|||
|
|||
public static byte[] toByteArray(InputStream in) throws IOException { |
|||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { |
|||
copyStream(out, in); |
|||
return out.toByteArray(); |
|||
} |
|||
} |
|||
|
|||
public static void copyStream(OutputStream os, InputStream is) throws IOException { |
|||
copyStream(os, is, 0); |
|||
} |
|||
public static void copyStream(OutputStream os, InputStream is, int bufsize) throws IOException { |
|||
if (bufsize <= 0) bufsize = 4096; |
|||
int len; |
|||
byte[] bytes = new byte[bufsize]; |
|||
while ((len = is.read(bytes)) != -1) { |
|||
os.write(bytes, 0, len); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 基于ZIP项目的复制Zip流. |
|||
* |
|||
* @param dst The output stream of the destination zip. |
|||
* @param src Source zip. |
|||
* @param transform 转换处理. 可以为null, 不转换. 该回调函数的原型为`byte[] transform(ZipEntry zipEntry, ZipInputStream zis)`, 当返回值为 null时保留原值, 为非null时用返回值替换当前ZipEntry对应的流数据. |
|||
* @return 返回转换次数. |
|||
* @throws IOException |
|||
*/ |
|||
public static int zipEntryCopyStreamZip(ZipOutputStream zos, ZipInputStream zis, BiFunction<ZipEntry, ZipInputStream, byte[]> transform) throws IOException { |
|||
int rt = 0; |
|||
ZipEntry se; |
|||
while ((se = zis.getNextEntry()) != null) { |
|||
if (null == se) continue; |
|||
//String line = String.format("ZipEntry(%s, isDirectory=%d, size=%d, compressedSize=%d, time=%d, crc=%d, method=%d, comment=%s)",
|
|||
// se.getName(), (se.isDirectory())?1:0, se.getSize(), se.getCompressedSize(), se.getTime(), se.getCrc(), se.getMethod(), se.getComment());
|
|||
//System.out.println(line);
|
|||
byte[] dstBytes = null; |
|||
if (null != transform) { |
|||
dstBytes = transform.apply(se, zis); |
|||
} |
|||
// choose by dstBytes.
|
|||
if (null == dstBytes) { |
|||
ZipEntry de = new ZipEntry(se); |
|||
de.setCompressedSize(-1); // 重新压缩后, csize 可能不一致, 故需要恢复为默认值.
|
|||
zos.putNextEntry(de); |
|||
copyStream(zos, zis); |
|||
zos.closeEntry(); |
|||
} else { |
|||
++rt; |
|||
//ZipEntry de = new ZipEntry(se);
|
|||
//de.setCompressedSize(-1);
|
|||
//de.setCrc(-1);
|
|||
// == fix IllegalArgumentException.
|
|||
ZipEntry de = new ZipEntry(se.getName()); |
|||
//System.out.println(se.getTime());
|
|||
//final long timeNone = 312739200000L;
|
|||
//if (timeNone!=se.getTime() && null!=se.getLastModifiedTime()) { // 发现会被自动改为当前时间.
|
|||
if (null != se.getLastModifiedTime()) { |
|||
de.setLastModifiedTime(se.getLastModifiedTime()); |
|||
} |
|||
if (null != se.getLastAccessTime()) { |
|||
de.setLastAccessTime(se.getLastAccessTime()); |
|||
} |
|||
if (null != se.getCreationTime()) { |
|||
de.setCreationTime(se.getCreationTime()); |
|||
} |
|||
de.setSize(dstBytes.length); |
|||
//de.setCompressedSize(se.getCompressedSize()); // changed.
|
|||
//de.setCrc(se.getCrc()); // changed.
|
|||
int method = se.getMethod(); |
|||
if (method != STORED && method != DEFLATED) { |
|||
// No setMethod .
|
|||
} else { |
|||
de.setMethod(method); |
|||
} |
|||
de.setExtra(se.getExtra()); |
|||
de.setComment(se.getComment()); |
|||
zos.putNextEntry(de); |
|||
zos.write(dstBytes); |
|||
zos.closeEntry(); |
|||
} |
|||
} |
|||
return rt; |
|||
} |
|||
|
|||
/** |
|||
* 基于ZIP项目的复制流. |
|||
* |
|||
* @param dst The output stream of the destination zip. |
|||
* @param src Source zip. |
|||
* @param transform 转换处理. 可以为null, 不转换. 该回调函数的原型为`byte[] transform(ZipEntry zipEntry, ZipInputStream zis)`, 当返回值为 null时保留原值, 为非null时用返回值替换当前ZipEntry对应的流数据. |
|||
* @return 返回转换次数. |
|||
* @throws IOException |
|||
*/ |
|||
public static int zipEntryCopyStream(OutputStream os, InputStream is, BiFunction<ZipEntry, ZipInputStream, byte[]> transform) throws IOException { |
|||
try (ZipInputStream zis = new ZipInputStream(is)) { |
|||
try (ZipOutputStream zos = new ZipOutputStream(os)) { |
|||
return zipEntryCopyStreamZip(zos, zis, transform); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void saveImageToFile(String imageUrl, File destination) throws IOException { |
|||
try (InputStream in = new URL(imageUrl).openStream()) { |
|||
Files.copy(in, destination.toPath()); |
|||
} |
|||
} |
|||
|
|||
public static void addFolderToZip(File sourceFolder, String baseFolderName, ZipOutputStream zos) throws IOException { |
|||
for (File file : sourceFolder.listFiles()) { |
|||
if (file.isDirectory()) { |
|||
addFolderToZip(file, baseFolderName + "/" + file.getName(), zos); |
|||
} else { |
|||
try (InputStream in = Files.newInputStream(file.toPath())) { |
|||
ZipEntry zipEntry = new ZipEntry(baseFolderName + "/" + file.getName()); |
|||
zos.putNextEntry(zipEntry); |
|||
byte[] buffer = new byte[1024]; |
|||
int length; |
|||
while ((length = in.read(buffer)) >= 0) { |
|||
zos.write(buffer, 0, length); |
|||
} |
|||
zos.closeEntry(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static void deleteDirectory(File directory) { |
|||
for (File file : directory.listFiles()) { |
|||
if (file.isDirectory()) { |
|||
deleteDirectory(file); |
|||
} |
|||
file.delete(); |
|||
} |
|||
directory.delete(); |
|||
} |
|||
|
|||
|
|||
|
|||
// Helper method to add image to zip stream
|
|||
public static void addImageToZip(String imageUrl, File targetFile, String folderName, ZipOutputStream zos) throws IOException { |
|||
if (imageUrl == null || imageUrl.isEmpty()) { |
|||
throw new IllegalArgumentException("The image URL cannot be null or empty."); |
|||
} |
|||
|
|||
URL url; |
|||
try { |
|||
url = new URL(imageUrl); |
|||
} catch (MalformedURLException e) { |
|||
throw new IOException("Invalid URL format: " + imageUrl, e); |
|||
} |
|||
|
|||
try (InputStream imageStream = url.openStream()) { |
|||
// Create zip entry for image
|
|||
String zipEntryName = folderName + "/" + targetFile.getName(); |
|||
ZipEntry zipEntry = new ZipEntry(zipEntryName); |
|||
zos.putNextEntry(zipEntry); |
|||
|
|||
// Write image content to zip stream
|
|||
byte[] buffer = new byte[8192]; // 8KB buffer for optimal performance
|
|||
int bytesRead; |
|||
while ((bytesRead = imageStream.read(buffer)) != -1) { |
|||
zos.write(buffer, 0, bytesRead); |
|||
} |
|||
|
|||
zos.closeEntry(); |
|||
} catch (IOException e) { |
|||
// Log the error and rethrow the exception for higher-level handling
|
|||
System.err.println("Error processing image from URL: " + imageUrl); |
|||
e.printStackTrace(); |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
File diff suppressed because it is too large
Loading…
Reference in new issue