乐于分享
好东西不私藏

Java生成Word文档的三把利器:docx4j、poi-tl、FreeMarker深度实战

Java生成Word文档的三把利器:docx4j、poi-tl、FreeMarker深度实战
企业级Java项目里,生成Word文档几乎是个必答题。合同模板、检验报告、审批单据、统计报表……格式千变万化,数据全靠动态填充。

很多团队的第一反应是"导出HTML然后改后缀名"。没错,这招对付简单表格管用,但一旦遇到页眉页脚、复杂表格合并、嵌入图片、分节符这些Word原生特性,HTML方案就彻底歇菜了。

今天深入聊聊Java生态里三种主流的Word文档处理方案:docx4jpoi-tlFreeMarker模板引擎。不是简单的API罗列,而是从底层原理到实战踩坑,帮你真正理解每个方案的边界在哪。

先搞清楚:docx到底是什么

在聊工具之前,先理解Word文档的本质。一个 .docx 文件本质上是一个 ZIP压缩包,里面装了一堆XML文件和资源文件。

解压一个docx看看:

document.docx├── [Content_Types].xml        # 内容类型声明├── _rels/│   └── .rels                  # 全局关系├── word/│   ├── document.xml           # 主文档内容(核心!)│   ├── styles.xml             # 样式定义│   ├── header1.xml            # 页眉│   ├── footer1.xml            # 页脚│   ├── media/│   │   ├── image1.png         # 嵌入的图片│   │   └── image2.jpeg│   ├── _rels/│   │   └── document.xml.rels  # 文档内部关系(图片引用等)│   └── numbering.xml          # 编号/列表定义└── docProps/    ├── app.xml                # 文档属性    └── core.xml               # 核心元数据

所有的文字、表格、图片引用、格式信息,最终都序列化在 word/document.xml 里。这个XML遵循的是 OOXML(Office Open XML)标准,由微软制定,体量庞大——光 document.xml 一个文件,一个稍微复杂的报告就可能有好几万行。

理解了这一点,你就能理解为什么不同的方案会有如此巨大的差异——本质上是操作XML的抽象层级不同

一、docx4j:直接操控OOXML的瑞士军刀

1.1 它是什么

docx4j是一个底层OOXML操作库,使用JAXB(Java Architecture for XML Binding)把Word的XML结构映射成Java对象。你可以理解为:它把Word XML里的每一个标签都变成了一个Java类。

  • <w:p> → org.docx4j.wml.P(段落)
  • <w:r> → org.docx4j.wml.R(文本运行)
  • <w:t> → org.docx4j.wml.Text(文本内容)
  • <w:tbl> → org.docx4j.wml.Tbl(表格)
  • <w:tr> → org.docx4j.wml.Tr(表格行)
  • <w:tc> → org.docx4j.wml.Tc(表格单元格)

这是最接近"手写XML"的方式,只不过用Java对象包装了一下。

1.2 解析Word文档的利器

docx4j最擅长的场景是读取和解析已有Word文档。比如从一份几百页的检测报告中提取所有表格数据:

// 加载Word文档WordprocessingMLPackagewordMLPackage= WordprocessingMLPackage.load(newFile("report.docx"));MainDocumentPartmainPart= wordMLPackage.getMainDocumentPart();// 获取所有内容元素List<Object> content = mainPart.getContent();// 遍历查找表格for (Object obj : content) {Objectunwrapped= XmlUtils.unwrap(obj);if (unwrapped instanceof Tbl) {Tbltable= (Tbl) unwrapped;// 提取表格数据for (Object rowObj : table.getContent()) {if (XmlUtils.unwrap(rowObj) instanceof Tr) {Trrow= (Tr) XmlUtils.unwrap(rowObj);                List<String> rowData = newArrayList<>();for (Object cellObj : row.getContent()) {if (XmlUtils.unwrap(cellObj) instanceof Tc) {Tccell= (Tc) XmlUtils.unwrap(cellObj);                        rowData.add(getCellText(cell));                    }                }// 处理这一行数据...            }        }    }// 识别段落标题if (unwrapped instanceof P) {Pparagraph= (P) unwrapped;Stringtext= getParagraphText(paragraph);PPrppr= paragraph.getPPr();if (ppr != null && ppr.getPStyle() != null) {Stringstyle= ppr.getPStyle().getVal();if ("Heading1".equals(style)) {// 这是一级标题            }        }    }}

注意那个 XmlUtils.unwrap()——这是docx4j里非常重要的一步。因为JAXB绑定,很多元素被包裹在 JAXBElement 里,必须 unwrap 才能拿到真实类型。这是新手最容易踩的坑之一。

1.3 生成文档:能做但痛苦

docx4j生成文档的问题在于抽象层级太低。看一个完整的例子——创建一个带样式的表格:

publicvoidcreateStyledTable(WordprocessingMLPackage wordMLPackage) {ObjectFactoryfactory= Context.getWmlObjectFactory();// 创建表格Tbltable= factory.createTbl();// 设置表格宽度TblPrtblPr= factory.createTblPr();TblWidthtblWidth= factory.createTblWidth();    tblWidth.setType("dxa");  // DXA单位(1/20磅)    tblWidth.setW("9000");    tblPr.setTblW(tblWidth);// 设置表格边框TblBordersborders= factory.createTblBorders();    addBorder(borders, STBorder.SINGLE, 4"000000""top");    addBorder(borders, STBorder.SINGLE, 4"000000""bottom");    addBorder(borders, STBorder.SINGLE, 4"000000""left");    addBorder(borders, STBorder.SINGLE, 4"000000""right");    addBorder(borders, STBorder.SINGLE, 4"000000""insideH");    addBorder(borders, STBorder.SINGLE, 4"000000""insideV");    tblPr.setTblBorders(borders);    table.setTblPr(tblPr);// 创建表头行TrheaderRow= factory.createTr();    addStyledCell(headerRow, "设备名称"true"4472C4""FFFFFF");    addStyledCell(headerRow, "IP地址"true"4472C4""FFFFFF");    addStyledCell(headerRow, "设备类型"true"4472C4""FFFFFF");    table.getContent().add(headerRow);// 创建数据行TrdataRow= factory.createTr();    addStyledCell(dataRow, "核心交换机"falsenullnull);    addStyledCell(dataRow, "192.168.1.1"falsenullnull);    addStyledCell(dataRow, "网络设备"falsenullnull);    table.getContent().add(dataRow);// 添加到文档    wordMLPackage.getMainDocumentPart().getContent().add(table);}privatevoidaddStyledCell(Tr row, String text, boolean bold,                           String bgColor, String fontColor) {ObjectFactoryfactory= Context.getWmlObjectFactory();Tccell= factory.createTc();// 单元格背景色if (bgColor != null) {TcPrtcPr= factory.createTcPr();Shdshd= factory.createShd();        shd.setFill(bgColor);        tcPr.setShd(shd);        cell.setTcPr(tcPr);    }// 创建文本Pparagraph= factory.createP();Rrun= factory.createR();Textt= factory.createText();    t.setValue(text);// 加粗if (bold) {RPrrpr= factory.createRPr();        rpr.setB(factory.createBooleanDefaultTrue());        run.setRPr(rpr);    }// 字体颜色if (fontColor != null) {RPrrpr= run.getRPr();if (rpr == null) rpr = factory.createRPr();Colorcolor= factory.createColor();        color.setVal(fontColor);        rpr.setColor(color);        run.setRPr(rpr);    }    run.getContent().add(t);    paragraph.getContent().add(run);    cell.getContent().add(paragraph);    row.getContent().add(cell);}

一个3列2行的带样式表格,用了将近100行代码。

在我之前的项目里,原始的文档生成Service有 2453行,其中大部分就是在和这些底层对象打交道。维护这种代码的心理成本极高——改一个表格样式要在七八层嵌套的对象图里找到正确的位置。

1.4 图片操作:需要理解OPC包结构

docx4j里插入图片之所以复杂,是因为Word把图片当作独立的"Part"来管理(还记得前面说的ZIP结构吗?),图片本身存在 word/media/ 目录下,然后在 document.xml 里通过关系ID引用。

publicvoidaddImage(WordprocessingMLPackage wordMLPackage, byte[] imageBytes)throws Exception {// 1. 确定图片类型StringmimeType="image/png";Stringsuffix="png";// 2. 创建图片Part并加入文档包BinaryPartAbstractImageimagePart= BinaryPartAbstractImage        .createImagePart(wordMLPackage, imageBytes);// 3. 创建内联图片引用(含大小设置)Inlineinline= BinaryPartAbstractImage.createImageInline(        wordMLPackage, imagePart,"image description""image filename",1,          // id        cx * 3600,  // 宽度(EMU单位,1英寸=914400 EMU)        cy * 3600,  // 高度false// 是否链接    );// 4. 把图片引用插入段落ObjectFactoryfactory= Context.getWmlObjectFactory();Rrun= factory.createR();Drawingdrawing= factory.createDrawing();    drawing.getAnchorOrInline().add(inline);    run.getContent().add(drawing);Pparagraph= factory.createP();    paragraph.getContent().add(run);    wordMLPackage.getMainDocumentPart().getContent().add(paragraph);}

注意EMU(English Metric Units)这个单位——1英寸 = 914400 EMU。这个转换关系不知道的话,图片大小永远调不对。

1.5 依赖冲突:真正的深坑

docx4j最让人头疼的不是API复杂,而是它的依赖树极其庞大

跑一下 mvn dependency:tree,看看docx4j-core都拉了什么:

org.docx4j:docx4j-core:8.3.10├── org.docx4j:docx4j-JAXB-ReferenceImpl├── jakarta.xml.bind:jakarta.xml.bind-api├── org.glassfish.jaxb:jaxb-runtime├── org.slf4j:slf4j-api├── org.apache.commons:commons-lang3└── xalan:xalan-interpretive(被docx4j shaded)    └── 包含 org.docx4j.org.apache.xml.utils.PrefixResolver

其中 xalan-interpretive 是docx4j通过shade(重命名包名)方式内嵌的,类名从 org.apache.xml.utils.PrefixResolver 变成了 org.docx4j.org.apache.xml.utils.PrefixResolver。这个类是 NamespacePrefixMapper 的依赖,而后者是JAXB处理XML命名空间的核心组件。

实际项目中踩的真实坑:

引入poi-tl后,poi-tl自带的 jaxb-impl:2.2.3-1(通过传递依赖链 aliyun-sdk-oss → jersey-json → jaxb-impl 引入)覆盖了docx4j使用的 jaxb-runtime。这个旧版本的JAXB实现走了一条不同的初始化路径,触发了 NamespacePrefixMapper 的调用,而 PrefixResolver 因为之前pom.xml里排除了xalan而缺失,导致 NoClassDefFoundError

更恐怖的是,因为文档导入是 CompletableFuture.runAsync() 异步执行的,而调用方只 catch(Exception) 不 catch(Error)NoClassDefFoundError 继承自 Error),异常被静默吞掉,表现为"卡住不动"。排查这个花了一整天。

修复方法:

<!-- 不要排除xalan!docx4j的NamespacePrefixMapper依赖它 --><dependency><groupId>org.docx4j</groupId><artifactId>docx4j-core</artifactId><version>8.3.10</version><!-- 千万不要加 xalan 的 exclusion --></dependency>

1.6 小结

docx4j就像C语言级别的指针操作——强大、精确,但容易出错。它的定位应该是:

解析和读取Word文档的首选,生成文档的备选。

二、poi-tl:模板驱动的Word生成引擎

2.1 设计哲学

poi-tl的核心思想是关注点分离:让非技术人员(产品经理、业务人员)用Word设计模板,让开发人员只关注数据准备。

底层基于Apache POI(Java生态里最成熟的Office文件处理库),但poi-tl在POI之上封装了一套模板语法,让你完全不需要接触POI的API。

2.2 模板语法全览

基本文本替换:

模板里写 {{varName}},Java里 put("varName", value),就这么简单。

条件渲染:

{{?isPassed}}检测结果:通过。{{/isPassed}}{{^isPassed}}检测结果:未通过,存在以下问题...{{/isPassed}}

列表循环(行级):

这是poi-tl最强大的特性之一。在Word表格里,用 {{#list}} 和 {{/list}} 包裹一行,poi-tl会自动把这一行复制N份:

| 序号 | 设备名称 | IP地址 | 类型 ||------|----------|--------|------|| {{#devices}} | | | || {{index}} | {{name}} | {{ip}} | {{type}} || {{/devices}} | | | |

渲染后自动变成多行:

| 序号 | 设备名称 | IP地址 | 类型 || 1    | 核心交换机 | 192.168.1.1 | 网络设备 || 2    | 防火墙    | 192.168.1.2 | 安全设备 || 3    | 服务器    | 192.168.1.100 | 计算设备 |

列表循环(列级):

LoopColumnTableRenderPolicypolicy=newLoopColumnTableRenderPolicy();Configureconfig= Configure.builder()    .bind("months", policy)    .build();

嵌套循环:

// 外层循环设备,内层循环每个设备的端口HackLoopTableRenderPolicypolicy=newHackLoopTableRenderPolicy();Configureconfig= Configure.builder()    .bind("devices", policy)    .build();

图片:

{{@logo}}
// 本地图片data.put("logo", Pictures.ofLocal("logo.png").size(200100).create());// 网络图片data.put("topo", Pictures.ofUrl("http://example.com/topo.png").size(600400).create());// 流式图片data.put("chart", Pictures.ofStream(chartInputStream, PictureType.PNG).size(400300).create());

超链接:

{{+link}}
data.put("link", HyperlinkText.of("点击访问").link("https://example.com"));

嵌套文档:

{{+detail}}
// 可以把另一个Word模板的渲染结果嵌入进来data.put("detail", Includes.ofLocal("detail-template.docx")    .setRenderData(detailData)    .create());

2.3 完整实战示例

以一个典型的"调查表"为例,这是一个包含文本替换、表格循环、图片的复杂文档:

@Service@RequiredArgsConstructorpublicclassSurveyFormDataPreparer {privatefinal xxService deviceService;privatefinal xxxService projectService;public Map<String, Object> prepareData(String projectId) {Projectproject= projectService.getById(projectId);DeviceAssetasset= deviceService.getByProjectId(projectId);        Map<String, Object> data = newHashMap<>();// 1. 基本文本替换        data.put("projecxxtName", project.getName());        data.put("levelxx", project.getLevel());        data.put("datxxe", LocalDate.now().toString());        data.put("orgNamexx", project.getOrgName());// 2. 复选框(1=选中,0=未选中)        data.put("isInternet""1".equals(project.getIsInternet()) ? "☑" : "☐");        data.put("isCloud""1".equals(project.getIsCloud()) ? "☑" : "☐");// 3. 表格列表 - 物理环境        List<Map<String, Object>> envList = asset.getEnvs().stream()            .map(env -> {                Map<String, Object> item = newHashMap<>();                item.put("name", env.getName());                item.put("location", env.getLocation());                item.put("area", env.getArea());                item.put("desc", env.getDescription());return item;            }).collect(Collectors.toList());        data.put("envList", envList);// 4. 表格列表 - 网络设备        List<Map<String, Object>> devList = asset.getDevices().stream()            .map(dev -> {                Map<String, Object> item = newHashMap<>();                item.put("name", dev.getName());                item.put("ip", dev.getIpAddress());                item.put("model", dev.getModel());                item.put("vendor", dev.getVendor());return item;            }).collect(Collectors.toList());        data.put("devList", devList);// 5. 图片 - 网络拓扑图if (StringUtils.isNotBlank(project.getTopoImageUrl())) {byte[] imageBytes = downloadImage(project.getTopoImageUrl());            data.put("topoImg", Pictures.ofBytes(imageBytes, PictureType.PNG)                .size(500350).create());        } else {            data.put("topoImg""(无拓扑图)");        }return data;    }}

渲染:

@ServicepublicclassWordExportFacadeService {publicbyte[] generateDocument(String projectId, Integer fileType) {// 根据fileType选择Preparer准备数据        Map<String, Object> data = selectPreparer(fileType).prepareData(projectId);// 获取模板路径StringtemplatePath= getTemplatePath(fileType);// 配置表格循环策略Configureconfig= Configure.builder()            .bind("envList"newLoopRowTableRenderPolicy())            .bind("devList"newLoopRowTableRenderPolicy())            .bind("serverList"newLoopRowTableRenderPolicy())            .bind("dbList"newLoopRowTableRenderPolicy())            .bind("bizList"newLoopRowTableRenderPolicy())            .build();// 渲染XWPFTemplatetemplate= XWPFTemplate.compile(templatePath)            .render(data, config);// 输出为byte[]ByteArrayOutputStreamout=newByteArrayOutputStream();        template.writeAndClose(out);return out.toByteArray();    }}

同样的功能,docx4j需要2453行代码,poi-tl只需要300行。 差距主要在表格循环和图片处理上——poi-tl把这两项操作的复杂度从O(N²)降到了O(1)。

2.4 模板制作最佳实践

1. 占位符命名规范:

{{sysName}}           // 简单文本,驼峰命名{{@topoImg}}          // 图片统一加Img后缀{{#deviceList}}       // 列表统一加List后缀{{?isPassed}}         // 布尔值统一用is前缀

2. Word里写占位符的技巧:

关键: Word会把一个完整的占位符 {{sysName}} 拆成多个XML节点(因为拼写检查、格式变化等原因),导致poi-tl识别不到。

解决方法:先在纯文本编辑器里写好占位符,再整体粘贴到Word里。 绝对不要在Word里逐字输入 {{sysName}}

如果已经出现识别不到的情况,可以用poi-tl自带的模板检查工具:

// 开启模板检查Configureconfig= Configure.builder()    .useSpringEL()   // 支持SpEL表达式    .build();// 或者直接调用模板验证XWPFTemplate.compile("template.docx").render(newHashMap<>());

3. 空值处理:

poi-tl默认遇到null值会保留原始占位符。建议统一处理:

// 方式1:全局配置Configureconfig= Configure.builder()    .defaultChar("—")  // null值显示为"—"    .build();// 方式2:数据准备时手动处理data.put("remark", StringUtils.defaultString(sys.getRemark(), "无"));

4. 表格合并单元格:

poi-tl原生不支持动态合并单元格。如果需要,可以用自定义RenderPolicy:

publicclassMergeCellRenderPolicyextendsAbstractRenderPolicy {@OverridepublicvoiddoRender(RunTemplate runTemplate, Object data, XWPFTemplate template) {// 获取当前行和表格XWPFTableRowcurrentRow= runTemplate.getRun().getTableRow();XWPFTabletable= currentRow.getTable();// 根据数据逻辑合并单元格// ...// 清除占位符文本        runTemplate.getRun().setText(""0);    }}

2.5 性能考量

poi-tl基于Apache POI,模板渲染时会把整个文档加载到内存。对于几百页的大型文档,内存占用可能达到几百MB。

优化策略:

// 1. 图片压缩:不要直接用原始截图data.put("topoImg", Pictures.ofBytes(compressImage(rawBytes), PictureType.JPEG)    .size(500350).create());// 2. 列表数据分批处理(如果数据量特别大)// poi-tl目前不支持流式渲染,但可以在数据准备阶段做分页// 3. 模板缓存:同一个模板不要反复compile// 建议用Spring的单例Bean缓存模板编译结果

2.6 小结

poi-tl用模板思想把Word生成的复杂度降了一个数量级。它的定位是:

格式固定、内容动态的文档生成首选方案。

三、FreeMarker模板:老牌方案的荣光与无奈

3.1 xdocreport:FreeMarker + Word的桥梁

在Java生态里,FreeMarker做Word文档的标准方案是通过 xdocreport 库。它的原理是:

  1. 用Word创建 .docx 模板,在需要动态内容的位置写 FreeMarker 语法(${variable}<#list><#if>
  2. xdocreport 把 docx 文件当作ZIP解压,找到 word/document.xml
  3. 用FreeMarker引擎渲染这个XML
  4. 把渲染后的XML重新打包成docx
<dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.document.docx</artifactId><version>2.0.1</version></dependency><dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId><version>2.0.1</version></dependency>

3.2 基本用法

// 加载模板(docx文件,里面已包含FreeMarker语法)InputStreamtemplateStream=newFileInputStream("template.docx");// 创建报告对象IXDocReportreport= XDocReportRegistry.getRegistry()    .loadReport(templateStream, TemplatesKind.Freemarker);// 准备数据IContextcontext= report.createContext();context.put("projectName""XX信息系统");context.put("evalDate""2026-05-26");// 列表数据List<Device> devices = getDeviceList();context.put("devices", devices);// 渲染输出OutputStreamout=newFileOutputStream("output.docx");report.process(context, out);

模板文件(在Word里直接编辑,使用FreeMarker语法):

项目名称:${projectName}检查日期:${evalDate}设备清单:<#list devices as device>  ${device_index + 1}. ${device.name} - ${device.ip}</#list>

3.3 图片处理

xdocreport支持图片插入,但需要在模板里预留特殊字段:

// Java端IProviderProviderproviders=newPropertiesDataProvider();IImageProviderimageProvider=newFileImageProvider(newFile("topo.png"), true// 是否保持比例);providers.put("topoImage", imageProvider);

模板里需要使用xdocreport的特殊语法,不像poi-tl那样简洁直观。

3.4 深坑:Word XML的"格式碎裂"

这是FreeMarker + Word方案最核心的问题,也是我最终选择迁移到poi-tl的根本原因。

问题根源: Word在编辑过程中,会根据拼写检查、输入法切换、格式变更等操作,把一个"看起来连续"的字符串在XML层面拆成多个 <w:r>(Run)节点。

比如你在Word里打了 ${projectName},你以为XML里是:

<w:r><w:t>${projectName}</w:t></w:r>

但实际上Word可能把它拆成:

<w:r><w:t>$</w:t></w:r><w:r><w:t>{</w:t></w:r><w:r><w:t>project</w:t></w:r><w:r><w:t>Name}</w:t></w:r>

甚至更碎片化。FreeMarker引擎无法识别这种被拆散的占位符,渲染直接失败。

解决方案: 有一些workaround,但都不可靠:

  1. 用Word的"域"功能:插入一个MACROBUTTON域来包裹变量——操作复杂,非技术人员不会
  2. 用插件:有些Word插件可以合并XML Run——额外依赖,不通用
  3. 手动编辑XML:用解压工具打开docx,直接编辑 document.xml——回到了手写XML的时代

3.5 模板维护的噩梦

在之前的项目里,我们用xdocreport生成了十几种不同类型的文档。每次业务需求变更(加个字段、改个表格结构),维护流程是:

  1. 业务人员在Word里修改格式
  2. 开发人员另存为XML,检查FreeMarker语法是否被破坏
  3. 如果被破坏,手动在XML里修复
  4. 测试生成效果
  5. 格式不对?回到第2步

一个"表格加一列"的需求,可能要来回改3-4次模板才能搞定。

而且因为模板本质上是一个XML文件,代码review完全无法覆盖——你没法在Git diff里直观地看到Word格式的变化。

3.6 小结

FreeMarker + xdocreport方案的定位:

适用于格式极简、改动频率低的文档生成。一旦格式复杂或需求频繁变化,维护成本指数级上升。

四、三大方案深度对比

4.1 技术架构对比

维度
docx4j
poi-tl
FreeMarker + xdocreport
底层技术
JAXB直接映射OOXML
Apache POI封装
FreeMarker文本替换XML
操作粒度
XML节点级
模板占位符级
XML文本级
模板制作
不需要模板
Word里写占位符
Word另存XML再编辑
学习成本
高(需理解OOXML)
低(会Map就行)
中(需懂FreeMarker + XML)
调试难度
高(XML对象图复杂)
低(数据驱动,问题明确)
高(XML格式碎裂难定位)

4.2 功能对比

功能
docx4j
poi-tl
FreeMarker
文本替换
手动创建Text节点
{{var}}${var}
表格创建
手动构建Tr/Tc
{{#list}}<#list>
表格循环
手动循环创建行
LoopRowTableRenderPolicy
<#list>
 标签
图片插入
需创建Part+引用
Pictures.ofXxx()
Base64嵌入或IImageProvider
页眉页脚
直接操作HeaderFooterPart
模板里直接设置
不支持(需要手动编辑XML)
分节符
支持
不支持(需模板预设)
不支持
条件渲染
Java if/else
{{?flag}}<#if>
嵌套模板
不支持
Includes<#include>
自定义渲染
无需(底层全开放)
AbstractRenderPolicy
FreeMarker宏
复选框
手动创建符号
直接放"☑"/"☐"字符串
直接放字符串

4.3 依赖体量对比

docx4j-core: 8.3.10├── 传递依赖约 40+ 个JAR├── 含JAXB全套实现├── 含shaded的xalan└── 总大小约 15MBpoi-tl: 1.12.2├── 传递依赖约 15 个JAR├── Apache POI 5.x├── 含xml-apis(建议排除)└── 总大小约 8MBxdocreport: 2.0.1├── 传递依赖约 10 个JAR├── FreeMarker└── 总大小约 3MB

4.4 性能对比

实测数据(生成一份30页的综合报告):

指标
docx4j
poi-tl
FreeMarker
首次渲染耗时
~800ms
~500ms
~200ms
内存占用
~300MB
~200MB
~150MB
模板编译
不需要
~200ms(可缓存)
~50ms

FreeMarker最快是因为它只是文本替换,不涉及Word对象模型。但这个速度优势在实际项目中意义不大——瓶颈通常在数据库查询和图片下载上。

五、实战选型与迁移经验

5.1 实际项目中的迁移路径

原始状态:docx4j做生成 + xdocreport做简单模板 = 4274行代码

迁移目标:docx4j仅做解析 + poi-tl做生成

迁移步骤:

第一步: 添加poi-tl依赖,排除冲突

<dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.2</version><exclusions><exclusion><groupId>xml-apis</groupId><artifactId>xml-apis</artifactId></exclusion><exclusion><groupId>xml-apis</groupId><artifactId>xml-apis-ext</artifactId></exclusion></exclusions></dependency>

第二步: 修改所有模板文件占位符格式

从 ${varName} 改为 {{varName}},从 FreeMarker 的 <#list> 改为 poi-tl 的 {{#list}}。17个模板文件,逐个改。

第三步: 拆分巨型Service

原来的 WordDocumentExportService(2453行)按文档类型拆分成多个Preparer:

service/word/├── xxxExportFacadeService.java     // 入口路由(~100行)├── xxxDataPreparer.java          // 公共数据准备(~200行)├── xxxFormDataPreparer.java      // 系统调查表(~300行)├── xxxPlanDataPreparer.java     // 项目计划书(~200行)├── xxxPlanDataPreparer.java  // 评估方案(~400行,最复杂)├── xxxDataPreparer.java     // 差距分析报告(~300行)├── xxxxDataPreparer.java     // 委托协议书(~100行)├── xxxHelper.java             // 图片处理工具(~80行)└── xxxHelper.java              // 复选框工具(~100行)

第四步: 数据准备逻辑改造

从"遍历XML节点填充数据"改为"构建Map传给模板引擎"。核心变化:

// 之前(docx4j):操作XML对象ObjectFactoryfactory= Context.getWmlObjectFactory();Trrow= factory.createTr();Tccell= factory.createTc();Rrun= factory.createR();Texttext= factory.createText();text.setValue(device.getName());// ... 省略样式设置代码 ...// 之后(poi-tl):准备数据MapMap<String, Object> item = newHashMap<>();item.put("name", device.getName());deviceList.add(item);

第五步: 处理依赖冲突

这是最耗时的步骤。需要用 mvn dependency:tree -Dverbose 逐层分析冲突,手动排除冲突依赖,然后逐个功能测试确认没有副作用。

5.2 docx4j和poi-tl共存的配置

<!-- docx4j:仅用于导入/解析 --><dependency><groupId>org.docx4j</groupId><artifactId>docx4j-core</artifactId><version>8.3.10</version><!-- 注意:不要排除xalan,NamespacePrefixMapper依赖它 --></dependency><dependency><groupId>org.docx4j</groupId><artifactId>docx4j-JAXB-ReferenceImpl</artifactId><version>8.3.10</version></dependency><!-- poi-tl:用于文档生成 --><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.2</version><exclusions><!-- xml-apis覆盖JDK内置org.w3c.dom,导致docx4j JAXB挂住 --><exclusion><groupId>xml-apis</groupId><artifactId>xml-apis</artifactId></exclusion><exclusion><groupId>xml-apis</groupId><artifactId>xml-apis-ext</artifactId></exclusion></exclusions></dependency>

5.3 排查依赖冲突的方法论

当你的项目引入多个XML处理库后,如果出现奇怪的 NoClassDefFoundError 或 ClassNotFoundException,按以下步骤排查:

1. 确认冲突的类来自哪个JAR:

# 在IDEA里:Ctrl+N 搜索类名# 或用mvn命令:mvn dependency:tree -Dincludes=*:xalan*mvn dependency:tree -Dincludes=*:jaxb*

2. 检查是否有多个版本:

mvn dependency:tree -Dverbose | grep jaxb-impl

如果看到多个版本的 jaxb-impl,就是冲突了。

3. 检查shade/重命名:

docx4j内部shade了很多依赖,包名从 org.apache 变成了 org.docx4j.org.apache。如果你看到的错误路径包含 org.docx4j.org.apache,说明是shade后的类。

4. 异步任务的Error吞没:

如果你的任务跑在 CompletableFuture.runAsync() 里,一定要 catch(Throwable) 而不是 catch(Exception)NoClassDefFoundError 继承自 Error,不是 Exception,会被静默吞掉。

// 错误写法:捕获不到ErrorCompletableFuture.runAsync(() -> {try {        doSomething();    } catch (Exception e) {  // NoClassDefFoundError不会被捕获!        log.error("失败", e);    }});// 正确写法CompletableFuture.runAsync(() -> {try {        doSomething();    } catch (Throwable e) {  // Error和Exception都能捕获        log.error("失败", e);    }});

六、选型决策树

根据你的实际场景,按这个决策树选型:

你需要做什么?├── 读取/解析已有Word文档│   └── → docx4j(唯一选择)├── 生成格式固定的文档│   ├── 格式复杂(表格合并、图片、页眉页脚)│   │   └── → poi-tl│   ├── 格式简单(纯文本+简单表格)│   │   ├── 团队里有FreeMarker经验│   │   │   └── → FreeMarker + xdocreport│   │   └── 团队里没人用过FreeMarker│   │       └── → poi-tl(学习成本更低)│   └── 需要像素级格式控制│       └── → docx4j(做好写大量代码的准备)├── 既要解析又要生成│   └── → docx4j(解析)+ poi-tl(生成)│       注意处理好依赖冲突└── 团队全是不懂技术的业务人员    └── → poi-tl(模板直接在Word里做)

七、总结

方案
一句话评价
docx4j
Word文档处理的"汇编语言"——功能最强,代码最多
poi-tl
Word文档生成的"高级语言"——模板驱动,开发效率最高
FreeMarker
Word文档生成的"管道胶带"——能用,但修起来痛苦

在实际项目中,我推荐 docx4j解析 + poi-tl生成 的组合方案。两者互补,各司其职。唯一需要注意的是Maven依赖冲突——按本文给出的配置排除冲突项,基本可以避免踩坑。

如果你的项目已经在用 FreeMarker + xdocreport,迁移到 poi-tl 的投入产出比是值得的。模板从 ${} 改成 {{}} 的工作量不大,但换来的是:

  • 模板可以直接在Word里编辑,不再需要碰XML
  • 代码量减少一个数量级
  • 维护成本从"每次改模板都是修行"变成"改个占位符的事"

踩过的坑都写在文章里了,希望能帮你省几个通宵。

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-06-19 00:36:51 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/672147.html
  2. 运行时间 : 0.243896s [ 吞吐率:4.10req/s ] 内存消耗:5,174.80kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=9298c2f8880e8bb8ab087cba360c8426
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001149s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.002000s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.003283s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000726s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001390s ]
  6. SELECT * FROM `set` [ RunTime:0.002294s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001586s ]
  8. SELECT * FROM `article` WHERE `id` = 672147 LIMIT 1 [ RunTime:0.004421s ]
  9. UPDATE `article` SET `lasttime` = 1781800611 WHERE `id` = 672147 [ RunTime:0.011595s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000729s ]
  11. SELECT * FROM `article` WHERE `id` < 672147 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.005965s ]
  12. SELECT * FROM `article` WHERE `id` > 672147 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.003281s ]
  13. SELECT * FROM `article` WHERE `id` < 672147 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.003110s ]
  14. SELECT * FROM `article` WHERE `id` < 672147 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.010072s ]
  15. SELECT * FROM `article` WHERE `id` < 672147 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.021068s ]
0.245681s