ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java中docx4j实现HTML转Word的完整实践指南

Java中docx4j实现HTML转Word的完整实践指南 简介这是一套面向Java开发者的HTML转Word文档转换方案基于docx4j库及其导入XHTML的扩展模块实现专门解决将超文本内容转换为可编辑Word文档的常见需求适用于办公自动化、在线文档生成及格式迁移等场景。资源以完整Spring Boot工程形式打包压缩包共一百七十个文件、大小约十六点七兆其中包含一百三十二个XML配置文件、十个Java源文件、十个class字节码以及八个HTML模板另外还有少量字体文件、YAML配置和项目元数据目录结构清晰便于快速定位核心转换逻辑。已有七百六十一人浏览学习。借助此方案可掌握完整的转换流程包括创建带占位符的HTML模板、利用扩展模块解析内容并映射为Word可识别的XML结构、动态替换业务数据等环节同时涉及字体、段落、列表、表格等多种元素的转换规则。代码中还演示了PDF导出、日志切面、操作记录等扩展功能并兼顾页眉页脚等专业文档特性对需要实现文档自动生成或二次开发的Java工程师具有较高参考价值。1. 直接说docx4j转word这条路为什么值得走如果你的Java后端接到一个需求把一段HTML——可能是富文本编辑器保存的文章、可能是运营配置的活动页——转换成一份可编辑的Word文档其中一个成熟做法就是docx4j配合docx4j-ImportXHTML。docx4j直接操作docx的OpenXML结构ImportXHTML负责把XHTML里的标签和CSS翻译成段落、表格和图片。它不是你用模板渲染那种填变量也不是把HTML打印成PDF而是真的一行行生成Word对象用户在Office里能继续改。适合报表导出、合同生成、CMS内容导出这类场景。下面这些步骤是我在实际项目里整理出来的最小可运行链路也会把三次踩坑写清楚。2. docx4j与ImportXHTML的分工从HTML到docx的转换链路2.1 docx4j是什么WordprocessingMLPackage与主文档部件docx4j本质上是一个把Word文档当Java对象来操作的类库。一个docx文件其实是一个zip包里面有word/document.xml、word/styles.xml、word/media/这些部件。docx4j把这些部件映射成WordprocessingMLPackage、MainDocumentPart、StylesPart等类。我们写代码时不直接和XML字符串打交道而是操作这些Java对象最后由docx4j负责把对象序列化成规范的docx包。其中最关键的是WordprocessingMLPackage它代表一个完整的Word文档。创建空白文档用WordprocessingMLPackage.createPackage()这个包里面默认包含一个空的MainDocumentPart对应的是word/document.xml也就是正文内容。我们后续转换得到的段落、表格、图片最终都是往MainDocumentPart.getContent()列表里添加对象。如果这一步没有做哪怕转换器生成了内容也只会停留在内存里不会进到最终文件很多人在这里翻车。docx4j还提供了对样式、节属性、页眉页脚、目录等部分的封装。HTML转换通常只涉及正文但如果你想控制页面大小、页边距、字体就需要同时接触Section部分。比如设置A4纸其实就是往wordMLPackage.getDocumentModel().getSections()里的Section对象设置PgSz和PgMar不过用ImportXHTML时导入器自己也有一层页面参数二选一即可不用两边都设。2.2 docx4j-ImportXHTML的转换原理为什么输入必须是XHTMLdocx4j-ImportXHTML是docx4j的一个扩展模块它接收的是XHTML加CSS而不是浏览器里那种松散HTML。它的核心工作是把XHTML的DOM树和CSS规则映射到docx的OpenXML对象上。比如h1会被映射成一个Word段落P并且尝试套用内置的Heading1样式table映射成Tbl对象img映射成图片引用最终落到word/media/下的二进制资源。为什么必须强调XHTML因为XHTML是XML语法要求标签闭合、属性带引号、布尔属性写成disableddisabled这种完整形式。解析器不需要处理省略结束标签、属性不带引号这类浏览器容错逻辑映射关系才能稳定。换句话说如果你直接把一段从编辑器里拷出来的HTML丢给ImportXHTML它大概率会解析出错或者样式识别得歪歪扭扭。常见做法是先用Jsoup把HTML清洗成XHTML这也是下面代码里我多走一步的原因。ImportXHTML对CSS的支持不是万能的它能处理的通常是内联样式和简单类选择器比如text-align、font-weight、color、border。对于CSS3的弹性布局、浮动、伪元素基本是忽略的。这一点要在项目启动前就和产品对齐否则后面验收阶段天天解释“为什么样式不一样”会非常疲惫。2.3 选型决策什么时候用这条链路什么时候别用做一个HTML转Word的功能业界不止一条路。常见有三条用POI手写Word对象适合内容结构完全由你控制的情况。你有数据按规则创建Paragraph和Table可编辑性最好但对复杂HTML无能为力因为你要自己写一个HTML解析器成本太高。用Freemarker或docx4j的模板变量替换适合固定格式的合同、证书。在Word里埋好占位符运行时填值。优点是不破坏原模板样式缺点是一旦HTML内容本身需要动态排版模板就没法覆盖了。用docx4j加ImportXHTML适合输入是富文本HTML、需要保留段落、表格、图片和基本排版信息的情况。它能把这三种对象完整翻译成docx对象用户拿到Word后还能继续编辑这是截图方案给不了的价值。如果最终目的是要一份不可编辑的打印件那我建议你直接考虑PDF方案比如wkhtmltopdf或Chromium无头模式。HTML在PDF渲染器里的还原度远高于docx转换器因为PDF是按照视觉页面来渲染的而docx是流式文档。选错方向再补救比一开始选对代价大得多。为了让你快速判断我列一个简单的对比表。方案输入输出可编辑性样式还原度开发成本POI手写结构化数据高高但要手工高模板替换固定模板高高低docx4jImportXHTMLHTML/XHTML中高中上中浏览器转PDFHTML低高低选择的核心标准只有一条用户拿到文件后还要不要在Word里继续改如果答案是“要”那docx4j这条链路就是非常值得投入的。如果答案只是“看一眼”那PDF更省心。我带过的项目里合同和招投标文件通常需要docx给客户看的报告用PDF就是这么分工的。3. 搭建最小工程Maven依赖与第一个转换程序3.1 依赖清单引入docx4j与ImportXHTML在Java项目里引入依赖是最容易踩坑的一步因为docx4j-ImportXHTML和docx4j核心包必须保持同版本进退。我一般只在pom里显式引入docx4j-ImportXHTML让它通过依赖传递带入docx4j-core。这样能减少一个需要对齐的版本点。properties !-- 这里的版本号请替换成你项目实际能拉取到的release版本 -- docx4j.versionYOUR_DOCX4J_VERSION/docx4j.version jsoup.versionYOUR_JSOUP_VERSION/jsoup.version /properties dependencies dependency groupIdorg.docx4j/groupId artifactIddocx4j-ImportXHTML/artifactId version${docx4j.version}/version /dependency dependency groupIdorg.jsoup/groupId artifactIdjsoup/artifactId version${jsoup.version}/version /dependency /dependencies这里没有写死版本号是因为docx4j在6.x之后对Java最低版本有要求且ImportXHTML的坐标在不同阶段有调整。你在中央仓库搜索docx4j-ImportXHTML时会看到很多历史版本我建议选跟你JDK主版本兼容的最新release。比如JDK8项目就不要硬上依赖Java11的版本否则运行时NoSuchMethodError会来得莫名其妙。另外如果你的项目里已经用了POI、FOP这些工具要注意类库之间的XML解析器冲突。docx4j自带JAXB依赖老项目如果用了别的JAXB实现需要做排除。最常见的现象是报JAXBException或者ClassNotFoundException: com.sun.xml.bind.v2.ContextFactory这个放在后面的避坑章节详细说。3.2 用Jsoup把脏HTML洗成XHTML拿到html字符串之后不要直接传给转换器先让Jsoup做一次标准化。目标有三个闭合所有标签、补齐属性引号、把布尔属性转成XHTML合法形式。顺便还能把script、style这些Word里用不到的东西去掉减少转换噪音。import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Entities.EscapeMode; import org.jsoup.parser.Parser; import java.io.IOException; public class HtmlCleaner { public static String toXhtml(String rawHtml) { Document doc Jsoup.parse(rawHtml, UTF-8); // 删掉脚本和样式避免导入器试图把它们当正文 doc.select(script, style, link, meta).remove(); // 输出成XML语法 doc.outputSettings() .syntax(Document.OutputSettings.Syntax.xml) .escapeMode(EscapeMode.xhtml) .charset(UTF-8); // 强制body内标签闭合 return doc.body().html(); } }这段代码里最关键的是syntax(xml)和escapeMode(xhtml)。前者让Jsoup输出自闭合的空元素比如br/而不是br后者让特殊字符用XHTML实体表示。doc.body().html()只取body内部内容避免把html、head这些外层结构再传给导入器因为ImportXHTML期望的是一个XHTML片段而不是完整文档。我习惯在这里同时做一件额外的事检查HTML里有没有langzh-cn这种语言属性如果有就保留docx4j能把它映射到Word的语言设置。没有也无所谓后面转换代码里会设置字体中文显示主要靠字体不靠语言标签。3.3 核心转换代码一段能跑通的Java类转换本身只做四件事创建空白docx包、创建XHTML导入器、执行转换、把转换结果加到正文。下面是完整的最小代码。import org.docx4j.convert.xhtml.XHTMLImporterImpl; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import java.io.File; import java.util.List; public class HtmlToWordConverter { public static void convert(String html, File outputFile) throws Exception { // 1. 创建空白Word包 WordprocessingMLPackage wordMLPackage WordprocessingMLPackage.createPackage(); // 2. 创建导入器 XHTMLImporterImpl importer new XHTMLImporterImpl(wordMLPackage); // 3. 执行转换得到内容对象列表 String xhtml HtmlCleaner.toXhtml(html); ListObject content importer.convert(xhtml, null); // 4. 把内容对象添加到正文 wordMLPackage.getMainDocumentPart().getContent().addAll(content); // 5. 保存 wordMLPackage.save(outputFile); } public static void main(String[] args) throws Exception { String html htmlbody h1标题/h1 p style\color:#333\这是一段b加粗/b文字/p table border\1\trtd单元格1/tdtd单元格2/td/tr/table /body/html; convert(html, new File(output.docx)); } }这里的importer.convert(xhtml, null)第二个参数是导入器的内容创建策略传null表示使用默认行为返回一个ListObject。这个list里的对象类型不固定可能是Paragraph、Table、Image等统一addAll到getContent()列表里即可。有些历史版本的导入器没有返回list的convert方法而是要求你传入一个容器对象比如convert(xhtml, wordMLPackage.getMainDocumentPart(), null)。遇到编译报错不用慌看IDE提示改成对应签名就行思路是一样的。设置页面大小可以在创建包之后加一行importer.setPageSize(A4)但这一步不是必须的默认值通常也能用。我在交付项目里的做法是先不设等实际样张出来再调整避免一上来就过度配置。4. 让转换结果可交付样式、图片与表格的收口4.1 控制标题与正文样式内联CSS映射到Word样式转换出来的Word文档默认会带一套docx4j的内置样式标题有Heading1到Heading9正文有Normal。如果你的HTML里标题只是h1、h2导入器会自动映射到Word的标题样式。但如果你想控制中文显示需要先设置导入器的字体参数否则生成出来的文档在英文系统OpenXML里可能默认使用Calibri中文会回落成宋体以外的字体看起来怪怪。XHTMLImporterImpl importer new XHTMLImporterImpl(wordMLPackage); // 页面与字体 importer.setPageSize(A4); importer.setFontFamily(宋体); importer.setAsciiFont(Calibri); importer.setHighAnsiFont(宋体);setFontFamily设置的是高优先级字体也就是中文文本使用的字体setAsciiFont负责英文和数字setHighAnsiFont处理扩展字符集。这个组合我基本每个项目都会写。如果你的公司有统一字体要求比如标题用黑体可以再额外在HTML的h1 stylefont-family:黑体上设置内联样式的优先级高于导入器默认字体这也是我更喜欢用内联CSS而不是依赖类选择器的原因。如果发现转换后的标题颜色、大小和预期不一样先不要去找docx4j的样式接口回头检查HTML里的内联样式。ImportXHTML对stylecolor:red; font-size:16px;这种写法识别得最稳对style块里的class定义经常丢。因此我在代码层面对HTML做规范凡是需要进Word的标签都带内联style少依赖class。还有一个容易被忽略的点Word文档的“正文”样式不一定叫Normal如果你的模板里自定义了正文样式名导入器不会知道。这时候要在转换前先给包设置一个默认样式或者干脆不依赖模板直接让docx4j创建包它的Normal样式是内置的不会出问题。我建议从docx4j空白包开始做减少样式名冲突。4.2 图片处理相对路径、网络图片与base64三种策略图片是HTML转Word里最折腾的部分。首先明确一点docx4j-ImportXHTML本身并不负责下载图片它只是把img的src指向的文件读取出来再嵌入Word。如果src是绝对URL且服务器出网没问题直接能用如果src是相对路径必须给导入器设置一个base URL否则它不知道到哪里找图。importer.setBaseURL(new File(/tmp/html-assets/).toURI().toURL());这句代码的含义是告诉导入器当遇到srcimages/logo.png时去/tmp/html-assets/images/logo.png找文件。如果你的HTML是后端动态拼接的我一般会先把所有图片下载到本地临时目录再把HTML里的src改成相对路径名最后设置baseURL指向那个临时目录。这样比直接传远程URL稳定因为远程URL可能带签名、会过期还可能被外网访问策略挡掉。对于base64格式的图片也就是data:image/png;base64,xxxx这种不同版本的ImportXHTML支持情况不一样。我碰上过旧版根本不认的case。遇到这种情况先自己解码再写临时文件把src替换成本地文件路径。byte[] imageBytes Base64.getDecoder().decode(base64Data); File tempImage File.createTempFile(embedded, .png); Files.write(tempImage.toPath(), imageBytes); img.attr(src, tempImage.toURI().toURL().toString());这样做的缺点是临时文件生命周期需要管理记得在转换完成后删除。我一般用一个try-finally包住整个转换过程在finally里清理临时目录避免服务器磁盘被导出任务堆满。关于图片还有一个坑docx4j在嵌入图片时会自动读取图片尺寸如果你的图片本身没有宽高信息它默认按1:1放到Word里可能会撑满整页。稳妥做法是在img标签里显式写width400 height300导入器会尝试映射成Word的Extent对象。4.3 表格宽度与换页容易翻车的两个细节HTML里的table转换为Word的Tbl对象后默认行为是自动适应窗口。也就是说如果你的HTML里写了width800这种固定像素值导入器会把这800px按近似比例转成Word单位但Word打开后仍然会根据页面宽度拉伸最终结果和你预期可能差很多。我常用的做法是在HTML源头就控制表格CSS为百分比宽度并且尽可能简化table border1 stylewidth:100%; border-collapse:collapse; tr td stylewidth:50%; padding:4px;列1/td td stylewidth:50%; padding:4px;列2/td /tr /tableborder-collapse:collapse这个属性ImportXHTML支持得还算好它会把Word表格边框设置成细线避免出现大粗框。如果你发现转换后的表格没有边框检查一下是不是忘了给table加border属性或者CSS里没写border。所以我在清洗HTML时会主动给所有table补上border1这是最不容易丢样式的做法。列宽控制上docx4j的表格对象用的是TblW和TcW单位是dxa1英寸约等于1440dxa。如果你用POI设置过Word表格单元格宽度会知道这层映射很痛苦在docx4j这里靠HTML的width30%这种百分比写法反而最省心。百分比由导入器计算成dxa我们不需要手动干预。如果最终用户反馈“Word表格列宽无法拖动”通常是因为单元格宽度被设置成了固定值这时把stylewidth:auto加回表格Word会恢复到可拖动状态。换页问题也常在这里爆发。HTML里一个很长的表格转换后可能在Word里被分页截断更糟糕的是表头不会重复。docx4j对重复表头是有支持的通过设置tr的tblHeader属性但ImportXHTML从HTML转过来时不会自动生成。所以我在后期都会写一个“表格后处理”方法遍历docx对象里的Tbl把第一行标记为tblHeader。这个后处理放在添加content之后、save之前。for (Object obj : wordMLPackage.getMainDocumentPart().getContent()) { if (obj instanceof org.docx4j.wml.Tbl) { org.docx4j.wml.Tbl table (org.docx4j.wml.Tbl) obj; if (!table.getContent().isEmpty()) { // 将第一行设置为表头行 org.docx4j.wml.Tr firstRow (org.docx4j.wml.Tr) table.getContent().get(0); firstRow.setTblHeader(org.docx4j.wml.Tr.TblHeader.TRUE); } } }这段代码有个前提第一行确实是表头。如果你的表格第一行是标题而且不希望跨页重复就不要加这段后处理。我给客户交付时通常做配置开关默认开启遇到特殊表格再关。5. 避坑/常见问题转换翻车现场与排查清单5.1 现象中文变成乱码或者Word里搜索不到中文原因HTML被读取时使用了错误编码。最常见的是从HTTP响应拿字节流后直接new String(bytes)没有指定UTF-8。还有一种是Jsoup解析时没指定charset导致字符串本身已经乱码后面转换再正确也没用。解决所有读取HTML的入口统一指定UTF-8。如果是InputStream用Jsoup.parse(inputStream, UTF-8, )如果是字符串确保字符串在生成时就走UTF-8。生成docx时不用操心docx4j内部用XML序列化UTF-8是标准。InputStream htmlStream new FileInputStream(htmlFile); Document doc Jsoup.parse(htmlStream, UTF-8, );这个方法比先读String再parse更稳妥因为Jsoup会按meta里的charset自动处理但指定参数优先。我的习惯是两边都指定html标签里的meta也保留charsetutf-8。5.2 现象图片位置是个红叉Word里无法显示原因导入器找不到图片文件。常见原因是src是相对路径但你没设置baseURL或者网络图片URL需要认证导入器拿不到字节流。解决转换前统一处理图片。要么在调用convert前设置importer.setBaseURL(...)指向图片所在根目录要么把所有图片下载到本地后替换src。不要指望docx4j帮你走HTTP协议它在这块没有容错拿不到就是红叉。我见过一个项目HTML是用户粘贴的富文本图片src都是站内相对地址专职接口导出时报红叉。我们的修复策略是先用图片下载服务把src替换成临时绝对路径再设baseURL。后来为了避免临时文件泄漏我们把图片下载和转换放在同一个目录里finally里删除整个临时目录。5.3 现象表格全部挤在页面左侧列宽乱掉原因HTML里的table使用了固定像素宽度比如width600或者单元格里写了特别多的连续英文文本不换行。导入器把这些像素转换成docx长度单位时Word按流式布局重新计算结果和你看到的浏览器效果完全不像。解决把HTML里所有宽属性改成百分比或移除。给table加width:100%给td用min-width可能没用直接删掉width。如果必须精确控制列宽就在转换后手动操作TcW但这是最后的手段成本高于收益。我对这类问题的排查顺序是先用浏览器打开原HTML截图再打开生成的docx对比找出差异最大的两个属性再回头改HTML。不要试图在docx4j里找“神级设置”能一步到位还原浏览器排版它做不到。5.4 现象生成的docx文件损坏Word打开时提示内容不可读原因转换出来的内容对象没有正确挂到包关系里。最常见的是你直接对wordMLPackage.getMainDocumentPart().getContent()进行了赋值而不是复用原有列表或者对图片引用只添加了二进制但没添加相关类型。解决使用标准流程始终getContent().addAll(...)。如果是自己添加图片一定要用docx4j的BinaryPart和关系机制不要手动改zip。如果还是损坏用unzip -t output.docx验证压缩包完整性再逐步注释代码定位。这块我要多说一句docx4j的API对调用顺序有要求。先创建包再创建导入器再convert再addAll再save。如果你在convert之前往正文里塞了空段落可能会干扰段落ID生成导致文档结构异常。我遇到过的一次损坏是反复转换同一个包对象第二次addAll后内容对象引用了旧的包关系文件就打不开了。解决方案是转换前新建包不要复用。5.5 现象CSS样式大量丢失加粗、颜色、对齐都没有原因ImportXHTML对CSS的识别有子集限制。它支持内联style但对style标签里的class规则支持不稳定尤其是选择器嵌套、伪类、flex这类东西基本直接忽略。解决清洗HTML时把内联style保留并精简把class里的关键样式转换成内联style。比如p { text-align:center }可以变成p styletext-align:center。如果你不想手工做可以用Jsoup在清洗阶段扫描所有元素把匹配的class规则合并到style属性里但这是一个比较复杂的工程我只在项目初期图省事时做过。另外font-size:14px这种单位在docx里会被转成半磅值如果你看到字号偏大或偏小调整HTML里的px即可不需要动docx4j。像素和Word字号的关系不是1:1是导入器按96DPI折算的14px约等于10.5磅这在Word里看起来偏小我一般建议HTML里直接写font-size:14pt这样导入器能按磅来几乎无损。6. 进阶把这套转换封装成生产级工具6.1 一个带超时、编码和图片策略的工具方法前面的代码是框架真正到生产环境我会把转换逻辑封装成一个工具类把编码、图片目录、页面设置、字体设置都变成参数。这样不同业务模块只要传HTML进来就能得到干净的docx文件不用关心内部细节。public class DocumentConverter { private String fontFamily 宋体; private String asciiFont Calibri; private String highAnsiFont 宋体; private URL baseUrl; public DocumentConverter baseUrl(URL url) { this.baseUrl url; return this; } public void convert(String html, File output) throws Exception { // 超时无法直接控制导入器但可以在上层用线程池限制整体执行时间 ExecutorService executor Executors.newSingleThreadExecutor(); Future? future executor.submit(() - doConvert(html, output)); try { future.get(30, TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true); throw new IOException(HTML转Word超时请检查图片和HTML大小); } finally { executor.shutdownNow(); } } private void doConvert(String html, File output) throws Exception { WordprocessingMLPackage wordMLPackage WordprocessingMLPackage.createPackage(); XHTMLImporterImpl importer new XHTMLImporterImpl(wordMLPackage); importer.setPageSize(A4); importer.setFontFamily(fontFamily); importer.setAsciiFont(asciiFont); importer.setHighAnsiFont(highAnsiFont); if (baseUrl ! null) { importer.setBaseURL(baseUrl); } String xhtml HtmlCleaner.toXhtml(html); ListObject content importer.convert(xhtml, null); wordMLPackage.getMainDocumentPart().getContent().addAll(content); markFirstRowAsHeader(wordMLPackage); wordMLPackage.save(output); } }这里用CompletableFuture或ExecutorService做超时控制是我自己的习惯因为一个用户在浏览器里等一个接口30秒是极限。超过这个时间多半是图片下载卡住或者HTML太大与其让用户一直转圈不如直接返回失败并记录日志。超时时间根据HTML的复杂度调整纯文字页面5秒内完成带十几张网络图片可能要10-20秒。如果项目里有很大的表格比如上百行我还会单独调大超时并优化表格生成逻辑避免客户端报超时。6.2 批量转换与回归验证用文本抽取检查输出工具方法封装好后我会准备三个回归样本一个纯中文长文一个带表格和图片的混合HTML一个带特殊符号如公式图片转word的片段。每次改完依赖版本或转换逻辑都跑一遍这三个样本而不是只测当前需求。这能帮我快速发现docx4j版本升级带来的行为变化。验证生成结果我用的方法是先解压docx读word/document.xml检查关键文本是否存在。这比完全靠眼睛看Word要快得多。unzip -p output.docx word/document.xml | grep -o 关键内容 | head -n 1如果grep能匹配到说明文本确实进了文档。如果匹配不到再打开Word看多半是脚本、隐藏文本或者图片替代文本混在里面。还有一种情况是文本确实在但被拆分成了多个w:r节点grep不到所以我的脚本会先做一次XML转义再匹配必要时用Java解析document.xml来验证。图片验证我用的是检查word/media/目录下的文件数量如果原HTML有5张图转换后理论上media里也有至少5个文件。unzip -l output.docx | grep word/media | wc -l这个方法只能验证图片嵌入数量不能验证位置是否正确。位置问题就得用Word打开看。我在交付前一定会人工过一遍样张因为docx4j的图片定位偶尔会把两个图片叠在一起这种问题是自动化脚本发现不了的。最后一个我自己养成的习惯是版本锁定。每次项目升级docx4j依赖我一定会把回归样本提前跑一遍因为ImportXHTML的API在不同版本间有细微变化有些方法被标记过期有些方法签名调整编译能过不代表行为一致。处理线上问题的时候我也要求团队在故障描述里带上docx4j版本号不然很多问题是无法复现的。如果你准备把这个方案落地到正式项目建议按这个顺序推进先用最小代码跑通一个样例再扩展图片和表格处理最后封装成带超时和验证的工具方法。不要一上来就追求复杂docx4j的黑匣子区域比想象中多但每一步踩坑都值得记下来时间长了它就是一套很稳定的导出服务。希望帮到你。本文还有配套的精品资源点击获取
返回列表