JAVA生成PDF(itextpdf)_java 生成pdf
java生成PDF有多种方式,比如itextpdf、 Apache PDFBox、Flying Saucer (XHTMLRenderer)、 OpenPDF等。今天要介绍的是itextpdf,及在开发过程中处理的问题。
1. 引入POM
com.itextpdf itextpdf 5.5.9 com.itextpdf.tool xmlworker 5.5.9 com.itextpdf itext-asian 5.2.0 org.xhtmlrenderer flying-saucer-pdf 9.0.3 org.freemarker freemarker 2.3.28
2. 导出工具类
import cn.hutool.core.io.FileUtil;import com.jxdj.framework.common.domain.exception.CustomException;import com.lowagie.text.pdf.BaseFont;import freemarker.template.Configuration;import freemarker.template.Template;import lombok.extern.slf4j.Slf4j;import org.xhtmlrenderer.pdf.ITextRenderer;import java.io.File;import java.io.FileOutputStream;import java.io.OutputStream;import java.io.StringWriter;import java.nio.file.Paths;import java.util.Locale;/** * 生成pdf文件工具类 * @author ******* * @create 2025/7/09 */@Slf4jpublic class PdfUtils { public static String PDF_BASE_PATH = \"com/file/export/\"; public static String TEMPLATE_VERIFY_APPLY = \"verifyApply.ftl\"; // PDF模板名称 private final static String TEMPLATE_BASE_PATH = \"template/\"; private final static String FONT_BASE_PATH = \"fonts/\"; private final static String DEFAULT_FONT = \"simkai.ttf\"; // 字体资源文件 private final static String ENCODING = \"UTF-8\"; /** * 生成pdf * @param templateName 模板名称 * @param data 数据 * @param filePath 生成pdf文件路径 * @return 生成的文件数量 */ public static void createPDF(String templateName, Object data, String filePath) throws Exception { // 如果不存在,则创建目录 File file = new File(filePath); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } OutputStream out = null; boolean isSuccess = false; try { out = new FileOutputStream(file); // 资源文件存放的根路径 String resourcePath = Paths.get(PdfUtils.class.getResource(\"/\").toURI()).toString(); Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); cfg.setDirectoryForTemplateLoading(Paths.get(resourcePath , TEMPLATE_BASE_PATH).toFile()); ITextRenderer renderer = new ITextRenderer(); // 设置 css中 的字体样式 renderer.getFontResolver().addFont(Paths.get(resourcePath, FONT_BASE_PATH , DEFAULT_FONT).toString(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); cfg.setEncoding(Locale.CHINA, ENCODING); // 获取模板文件 Template template = cfg.getTemplate(templateName, ENCODING); StringWriter writer = new StringWriter(); template.process(data, writer); writer.flush(); String html = writer.toString(); renderer.setDocumentFromString(html); renderer.layout(); renderer.createPDF(out, false); renderer.finishPDF(); out.flush(); isSuccess = true; } catch (Exception e) { throw e; } finally { if (out != null) { try { out.close(); if (!isSuccess) { deleteFile(filePath); } } catch (Exception e) { throw new CustomException(\"文件导出PDF异常,资源未正确释放\", e); } } } } private static void deleteFile(String path) { try { File file = new File(path); if (file.exists()) { boolean isDel = FileUtil.del(file); if (!isDel) { log.error(\"删除本地磁盘文件失败,请检查路径是否被占用或权限问题:{}\", path); } } } catch (Exception e) { log.error(\"删除本地磁盘文件异常\", e); } }}
3. 资源文件的存放路径,与代码相匹配。(你放在其他位置也行,java代码的写法要相对应)
字体文件可以根据实际需求引入其他不同的字体。
模板文件根据实际进行修改
4. 模板文件(部分代码)
这实际就是一个html文件
费用报销单 编号 ${applyNo!\"\"} 公司 ${companyName!\"\"} 部门 ${deptProject!\"\"} 申请日期 ${(createTime?string(\"yyyy-MM-dd HH:mm:ss\"))!\"\"} 金额 ${amountUpper!\"\"} 元( ¥${amount!\"\"} ) 申请人 ${creator!\"\"}
5.PDF生成效果
6. 问题
以上代码在本地开发环境(Windows系统)上运行时没有问题的,但是打包成 jar 在linux服务器上运行就会找不到文件
原因:因为将资源文件打包到jar里面后,文件不是以真实的物理文件形式存在,而无法获取文件路径(但可以获取文件流InputStream)。
解决方案1:把资源文件单独拿出来,放在与Jar同一目录下
缺点就是:部署项目时,要在jar存放目录下新建对应的目录,将文件放进去;如果是多实例部署,每个实例下都要手动操作一次。
/** * 获取文件路径 * 资源文件的存放目录与 jar 的存放目录一致 * @param relativePath 资源文件的相对路径 * */ public static String getExternalResourceFilePath(String relativePath) { // 获取 JAR 所在目录 String jarDir = new File(PdfUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParentFile().getAbsolutePath(); String path = null; // 拼接资源文件路径(JAR 目录 + 相对路径) if (!relativePath.startsWith(\"/\")) { path = jarDir + \"/\" + relativePath; } else { path = jarDir + relativePath; } log.info(\"获取外部资源文件路径:{}\", path); return path; } /** * 获取文件资源 * 资源文件的存放目录与 jar 的存放目录一致 * @param relativePath 资源文件的相对路径 * */ public static File getExternalResourceFile(String relativePath) { File resourceFile = new File(getExternalResourceFilePath(relativePath)); if (!resourceFile.exists()) { throw new RuntimeException(\"资源文件未找到: \" + resourceFile.getAbsolutePath()); } return resourceFile; } public static void main(String[] args) { String path = getExternalResourceFilePath(\"exportConfig/template/verifyApply.ftl\"); System.out.println(path); File file = getExternalResourceFile(\"exportConfig/template/verifyApply.ftl\"); if (file.exists()) { System.out.println(\"存在\"); } else { System.out.println(\"不存在\"); } }
解决方案2:资源文件依旧打包到 Jar里面,从Jar里读取文件流,然后创建临时目录生成文件,相当于通过java代码把文件复制到jar外面的目录下。
/** * 将 JAR 内部的资源文件提取到指定临时目录,方便资源文件的操作 * @param resourcePath 资源路径(相对于 resources 目录,如 \"exportConfig/template/verifyApply.ftl\") * @param targetDir 目标目录(如 \"/jxdj/file/***\") * @return Path对象 */ public static Path extractResourceToFile(String resourcePath, String targetDir) throws Exception { try { // 2. 确保目标目录存在 Path targetPath = Paths.get(targetDir); if (!Files.exists(targetPath)) { Files.createDirectories(targetPath); // 递归创建目录 } // 3. 从资源路径提取文件名 String fileName = Paths.get(resourcePath).getFileName().toString(); Path targetFile = targetPath.resolve(fileName); if (Files.exists(targetFile)) { return targetFile; } // 1. 从 JAR 中读取资源流 InputStream is = PdfUtils.class.getClassLoader().getResourceAsStream(resourcePath); if (is == null) { throw new RuntimeException(\"资源文件未找到: \" + resourcePath); } // 4. 将 InputStream 写入目标文件 try (FileOutputStream fos = new FileOutputStream(targetFile.toFile())) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } } return targetFile; } catch (Exception e) { throw new Exception(\"从JAR中提取资源文件失败:\" + resourcePath, e); } }