乐于分享
好东西不私藏

TypeScript 纯前端 Office 渲染实践

TypeScript 纯前端 Office 渲染实践

从零用 TypeScript 实现 Office 纯前端渲染:一次把 DOC、DOCX、PPT、PPTX、XLS、XLSX 拆开的技术实践

如果你做过企业系统,大概率遇到过这样的需求:

用户上传一个 Word、Excel、PPT,希望在浏览器里直接预览。不能强制下载,不能跳第三方页面,最好不要把文件传到外部服务。表格、图片、页眉页脚、字体、批注、图表、合并单元格还得尽量还原。

听起来像一个普通的“文件预览”功能,真正做下去才会发现:这不是一个组件,这是一个小型 Office 渲染引擎。🙂

这篇文章不讲空泛概念。我会尽量把我们从零开始用 TypeScript 做纯前端 Office 渲染的过程拆开:怎么选型,为什么不能只套一个开源库,DOC/DOCX/PPT/PPTX/XLS/XLSX 六类格式如何分层,核心调用链怎么设计,性能瓶颈怎么处理,以及最后怎么把它变成一个可以在线体验的前端 Demo。

先看最终效果。

本次线上实测数据如下,数据来自同一个 Demo 页面里的诊断面板:

格式
样例大小
模型摘要
渲染状态
耗时
WASM 校验
DOCX
4.6 MB
流式 / 1 节
完成
317 ms
正常
PPTX
20.8 MB
35 页
完成
1638 ms
正常
XLSX
387 KB
16 个工作表
完成
1984 ms
正常

这不是“截图转图片”的路线,也不是把文件上传到某个 Office 服务后嵌一个 iframe。核心目标很朴素:

文件字节进入浏览器,解析、建模、布局、渲染都在前端完成;业务侧只拿到一个可以挂载的 DOM 容器。


一、为什么 Office 预览这么难?

PDF 预览相对明确:页面就是页面。浏览器里用 canvas 或 SVG 把每页画出来,问题虽然多,但边界很清楚。

Office 不一样。它的真实结构更像这样:

Office 文件  -> 压缩包或复合二进制容器  -> 文档关系、样式、主题、字体、媒体资源  -> 段落、表格、图形、批注、域、页眉页脚  -> 布局规则、分页规则、溢出规则、锚定规则  -> 最后才是浏览器里能看到的页面

尤其是旧格式 .doc.ppt.xls,它们不是简单 XML,而是 Compound File Binary,也就是复合二进制文件。你要先读 FAT、目录流、Table Stream、WordDocument Stream,再根据 FIB、CLX、FKP、SPRM 等结构恢复正文、样式、表格和图片。

新格式 .docx.pptx.xlsx 也没有想象中轻松。它们是 Open Packaging Convention 的 ZIP 包,里面有大量 XML part、relationship、theme、media、drawing、chart、styles。一个图片为什么出现在这个位置,一个文本框为什么跟随某个段落,一个单元格为什么允许文字溢出到右侧,这些都藏在不同的 XML 和样式规则里。

所以 Office 纯前端渲染的第一件事,不是写 UI,而是承认它本质上有四层:

层级
任务
难点
容器层
识别 ZIP / CFB / 加密 / 媒体资源
二进制结构、偏移、压缩、关系索引
解析层
把 XML/二进制记录转成中间模型
Office 规范庞大,兼容历史版本
布局层
计算页、表格、文本、图片、锚点
Word/Excel/PPT 的布局规则完全不同
渲染层
输出 DOM / SVG / CSS / Canvas
浏览器模型和 Office 模型并不一致

这也是为什么“找一个库就完事”的方案,经常在真实客户文档面前崩掉。


二、开源选型:我们到底参考了什么,又放弃了什么?

做这种项目,第一步一定不是闭门造车。我们先把市面上常见方案过了一遍。

方案
优点
问题
最终判断
Office Online / Google Docs iframe
接入快,效果通常不错
文件要给第三方服务,权限、内网、审计都麻烦
不适合私有化和纯前端
ONLYOFFICE / Collabora
兼容性强,接近完整 Office
要服务端,部署重,授权和资源成本高
适合协同编辑,不适合轻量纯前端
LibreOffice WASM
理论上还原度高
体积和启动成本大,工程集成复杂
可以关注,但不适合作为轻量 viewer 核心
Mammoth.js
DOCX 转语义 HTML 很干净
它主动忽略很多视觉细节,不追求像 Word
适合内容抽取,不适合高还原预览
docx-preview
浏览器渲染 DOCX 的好起点
对分页、复杂表格、图片锚定仍要深改
DOCX 路线参考和改造基础
SheetJS / ExcelJS
读写数据能力成熟
重点在数据,不负责完整 Excel 视觉渲染
可以参考数据结构,渲染仍要自建
Luckysheet / x-spreadsheet
表格 UI 体验成熟
更像在线表格编辑器,不是 Office 文件还原引擎
可参考交互,不适合通用预览内核
PPTXjs
可把 PPTX 转 HTML
jQuery 时代实现,复杂形状/动画/旧 PPT 不够
可参考思路,不适合作为最终底座
PptxGenJS
生成 PPTX 很强
方向是“写文件”,不是“还原预览”
生成工具,不是 viewer

这里有一个很重要的判断:

解析库和渲染器不是一回事。数据能读出来,不代表页面能还原出来。

比如 Excel,很多库能读出单元格值、公式、sheet 名称。但真正预览时,用户在意的是列宽、行高、边框、合并单元格、颜色、冻结窗格、图表、文字溢出、图片锚点。一个单元格里的文字能不能溢出到右侧空白格,溢出时是否遮住边框线,这些都不是“读取 JSON”能解决的。

再比如 Word,语义 HTML 很好,但客户看的是“这份制度文件和 WPS/Word 打开是否接近”。字体、表格、页眉页脚、目录、图注、分页,都会被拿来对比。

所以我们的最终路线是:

不依赖 React/Vue/jQuery 等 UI 框架不依赖在线 Office iframe不把文件交给第三方服务用 TypeScript 分格式实现 viewer:DOC   -> msdoc-viewerDOCX  -> docx-viewerPPT   -> ppt-viewerPPTX  -> pptx-viewerXLS/XLSX -> excel-viewer

每个 viewer 是独立包,Demo 只通过 file: 依赖挂载它们。这样可以让格式能力独立演进,不把所有复杂度揉进一个巨大仓库。


三、总架构:先把“文件预览”拆成流水线

整个前端预览流程可以抽象成一条流水线:

ArrayBuffer / Blob  -> 格式识别  -> WASM 来源与安全校验  -> 动态加载对应 viewer  -> parser 解析为文档模型  -> layout 生成页面/表格/幻灯片模型  -> renderer 输出 DOM / SVG / CSS  -> mount 到预览容器  -> 诊断面板输出耗时、页数、资源数量

Demo 的模块加载非常克制:用户打开页面时,不会一次性加载所有解析器。只有选择某种格式时,才按需 import 对应 viewer。

asyncfunctionloadViewerModule(format) {if (!state.modules.has(format)) {const promise = format === 'doc'      ? import('msdoc-viewer')      : format === 'docx'        ? import('docx-preview')        : format === 'ppt'          ? import('ppt-viewer')          : format === 'pptx'            ? import('pptx-viewer')            : import('excel-viewer');    state.modules.set(format, promise);  }return state.modules.get(format);}

这个函数很小,但它决定了整个体验的基本盘:

  1. 首屏轻,不被 Office 全家桶拖慢。
  2. 每种格式独立打包,问题定位更清楚。
  3. viewer 可以分别优化,不会互相污染。
  4. 后续要把某个格式替换成 WASM 或 worker,也只影响局部。

在入口处,我们只保留一个 renderByFormat,把格式分发到各自的解析和渲染管线:

asyncfunctionrenderByFormat(format, bytes, token) {if (format === 'doc') {const { parseMsDoc, renderMsDoc, mountMsDoc } = awaitloadViewerModule('doc');const parsed = parseMsDoc(bytes, {});const rendered = renderMsDoc(parsed);mountMsDoc(elements.preview, rendered);return { summary`${elements.preview.querySelectorAll('.msdoc-page').length} 页` };  }if (format === 'docx') {const { renderAsync } = awaitloadViewerModule('docx');awaitrenderAsync(newBlob([bytes]), elements.preview, elements.preview, {breakPages: state.docxViewMode === 'paged',strictWordCompatibilitytrue,awaitLayouttrue    });return { summary'Word 文档已渲染' };  }if (format === 'pptx') {const { parsePptx, renderPresentationToElement } = awaitloadViewerModule('pptx');const parsed = awaitparsePptx(bytes);returnrenderPresentationToElement(parsed, elements.preview, {uiMode'bare',virtualizetrue    });  }}

真实代码更完整,还包含下载进度、WASM 校验、错误处理、视图切换、诊断信息。但核心关系就是这样:入口统一,内部独立。


四、DOCX:不是“把 XML 变 HTML”,而是重建 Word 的语境

DOCX 的文件结构看起来友好,因为它是 ZIP + XML。但真正难的是:Word 的视觉结果不是单个 XML 决定的。

一个段落最终长什么样,可能来自:

document.xml  + styles.xml  + numbering.xml  + theme/theme1.xml  + fontTable.xml  + settings.xml  + document relationships  + header/footer parts  + drawing/media parts

所以 DOCX viewer 的入口分成三步:

exportfunctionparseAsync(data, userOptions) {returnWordDocument.load(data, newDocumentParser(ops), ops);}exportasyncfunctionrenderDocument(document, userOptions) {const renderer = newHtmlRenderer();returnawait renderer.render(document, ops);}exportasyncfunctionrenderAsync(data, bodyContainer, styleContainer, userOptions) {const doc = awaitparseAsync(data, ops);const nodes = awaitrenderDocument(doc, ops);// append style + body nodes// wait fonts/images// measure layout}

这个设计的好处是:解析和渲染解耦。你可以只解析得到模型,也可以拿模型做二次检查,还可以在渲染后做 layout snapshot。

最近比较重要的几个增强点包括:

问题
处理方式
图片锚定错位
解析 drawing anchor,按 Word 坐标体系放置
表格图片不跟随单元格
保留 table / drawing 的相对位置关系
标题编号样式异常
渲染后同步 numbering marker 样式
字体图片未加载完就测量
awaitLayout
 等待字体、图片和分页测量
分页容易截断
默认流式,遇到显式分页再分节展示

这里有一个非常务实的取舍:Word 官方分页算法很复杂,浏览器排版模型也不是 Word。与其用一套半吊子的“自动分页”把文档切碎,不如先保证流式视图忠实展示内容,再逐步增强显式分页、节属性、页眉页脚和页面尺寸。

这也是我们现在默认 DOCX 使用流式视图的原因。客户第一眼要看到内容完整、表格不丢、图片不乱,分页可以继续深挖,但不能为了分页把正文搞坏。


五、DOC:旧二进制格式才是真正的硬菜

.doc 是另一类问题。它不是 ZIP,也不是 XML,而是历史悠久的二进制结构。

解析 DOC 大致要经历:

Compound File  -> WordDocument stream  -> 0Table / 1Table stream  -> FIB  -> CLX / Piece Table  -> CHPX / PAPX  -> SPRM 属性解码  -> 段落、表格、图片、页眉页脚、节属性

包根入口保持得很小:

export { parseMsDoc } from'./msdoc/parser.js';export { renderMsDoc, defaultMsDocCss } from'./render/html.js';export { createMsDocViewer, mountMsDoc, parseMsDocToHtml } from'./viewer.js';export { convertMetafileToSvg } from'./msdoc/vector.js';export { MsDocWorkerClient } from'./worker-client.js';

这类旧格式渲染,最容易出问题的是表格、页眉页脚和浮动对象。比如一个跨页大表格,Word 会根据页面剩余空间、行高、表格属性、段前段后、节尺寸来决定是否换页。浏览器的 table 布局并不知道这些 Word 规则。

所以我们给 DOC viewer 加了一个挂载层,提供分页和文档流两种体验:

exportfunctionmountMsDoc(container, rendered) {  container.innerHTML = `    <style data-msdoc>${rendered.css}</style>    <style data-msdoc-viewer>${runtimeViewerCss()}</style>    <div class="msdoc-viewer-shell">      <div class="msdoc-viewer-toolbar">        <button data-view="paged">分页</button>        <button data-view="flow">文档流</button>      </div>      <div class="msdoc-root" data-msdoc-view="paged">        <div class="msdoc-flow-view">${rendered.html}</div>        <div class="msdoc-paged-view"></div>      </div>    </div>`;  container.__msdocCleanup = installViewerRuntime(container, rendered);return container;}

这里很像做一个小型排版运行时:静态 HTML 只是第一步,挂载后还要根据真实 DOM 测量结果处理分页、浮动对象、页眉页脚和页面标记。

说得更直白一点:Office 渲染不是“字符串拼 HTML”,而是“解析模型 + 布局运行时”。


六、PPT/PPTX:幻灯片的核心是坐标系、形状和资源

PPT 和 Word 的思路完全不同。Word 是流式排版,PPT 是绝对坐标舞台。

每一页幻灯片可以理解成:

slide size  -> background  -> shape tree  -> text body  -> image / media  -> chart / table  -> group transform  -> animation / notes / comments

PPTX viewer 的入口是:

export { parsePptx } from'./parser/presentation.js';export { renderPresentationToHtml, renderSlideToHtml } from'./render/html.js';export { renderPresentationToElement, mountPptxViewer } from'./viewer.js';export { PptxWorkerClient } from'./worker/client.js';export { applyTextLayout, scheduleTextLayout } from'./render/text-layout.js';

渲染到页面时,不是一次性把所有幻灯片无脑塞进 DOM,而是支持按需渲染:

renderPresentationToElement(parsed, container, {uiMode'bare',virtualizetrue,showNotesfalse,rootMargin'1200px 0px'});

这几个参数背后有很实际的考虑:

参数
意义
uiMode: 'bare'
Demo 自己提供外层 UI,viewer 只负责内容
virtualize: true
长 PPT 不一次性渲染所有页面
rootMargin
提前渲染视口附近 slide,滚动更顺
scheduleTextLayout
文字进入 DOM 后再做排版修正

旧 .ppt 更难,因为它同样是二进制格式,还会遇到 EMF、WMF、DIB、OLE 等历史资源。我们的处理方式是:先解析 PowerPoint 记录和 persist directory,再把图片、形状、文本块转成统一模型,最后逐页渐进渲染。

for (let index = 0; index < slideCount; index += 1) {const slide = presentation.slides[index];const html = awaitrenderSlideToHtmlAsync(slide, presentation);  slide.html = html;  root.insertAdjacentHTML('beforeend', html);awaityieldToBrowserPaint();}

这段代码看起来普通,但它解决的是一个很关键的体验问题:大 PPT 不应该让浏览器主线程一直卡着。每渲染一页就让出一次浏览器绘制机会,用户能看到进度,页面也不会假死。


七、Excel:还原度的细节藏在“格子行为”里

Excel 最容易被低估。

很多人以为 Excel 预览就是 <table>。但真实 Excel 不是普通表格,它有一整套自己的视觉规则:

Excel 行为
浏览器默认 table 是否支持
列宽按字符宽度换算
不支持,需要转换
行高按 pt 换算
不支持,需要转换
合并单元格
部分支持,但样式边界要额外处理
文字可溢出到右侧空白单元格
不支持,需要模拟
溢出文字遮住经过的边框线
不支持,需要单独处理层级
图片按单元格锚点定位
不支持,需要 overlay
大表格虚拟滚动
不支持,需要窗口化渲染

Excel viewer 暴露的 API 很明确:

export { parseExcelWorkbook, streamExcelRows } from'./data.js';export { defaultExcelCss, renderExcelHtml } from'./view.js';export { mountExcel, syncExcelOverlays } from'./controller.js';export { createExcelWorkerClient, loadExcelWorkbookInWorker } from'./worker-client.js';

大文件解析放进 Worker:

const workerClient = createExcelWorkerClient({ workerawaitcreateExcelWorker() });const workbook = await workerClient.load(bytes, { format, onProgress });const rendered = mountExcel(elements.preview, workbook, {  workerClient,  onProgress,terminateWorkerOnDestroytrue});

大表格渲染则靠虚拟窗口。我们不是把几十万格一次性渲染出来,而是根据滚动位置计算当前可视范围:

const range = getVirtualViewportRange(sheet, metrics, scrollState, viewport, options);if (windowed) ensureVirtualWindow(range);layer.innerHTML = renderVirtualViewport(  sheet,  workbook,  metrics,  scrollState,  viewport,  options,  overlayHtml,  range);

Excel 还原度里最有意思的一个细节,是“文字溢出”。

在 Excel 里,如果一个单元格文字很长,右边单元格为空,文字可以视觉上延伸过去。但背景填充不会跟着延伸,边界仍然清楚;同时被文字压住的边框线不能穿过文字。

这就不能简单给文字套一个白色背景,因为那会把后面单元格的填充色也盖掉。正确思路是:

  1. 判断这个单元格是否允许溢出。
  2. 内容层可以超出当前格宽度。
  3. 背景层仍然只属于原单元格。
  4. 边框层要避让文字覆盖区域。

代码里先识别“可溢出文本单元格”:

exportfunctionisOverflowTextCell(sheet, row, col, workbook) {const cell = getCell(sheet, row, col);const value = getCellRawValue(sheet, row, col);if (!hasRenderableContent(sheet, row, col, workbook)) returnfalse;if (cell?.style?.alignment?.wrapTextreturnfalse;if (typeof value === 'number' && !cell?.richText?.lengthreturnfalse;if (cell?.checkboxreturnfalse;returntrue;}

这类细节非常“小”,但它决定了用户看到的是“像 Excel”,还是“像一个被 CSS 勉强拼出来的表格”。


八、性能:纯前端不是把所有工作都塞给主线程

Office 文件一大,性能问题立刻出现。我们总结下来,真正有效的优化不是某一个奇技淫巧,而是三件事:

1. 按需加载

用户看 Word,就不加载 Excel 和 PPT。用户看 PPTX,就不加载 DOC 解析器。

选择格式 -> 动态 import 对应 viewer -> 缓存 promise -> 后续复用

这能显著降低首屏压力。

2. Worker 解析

Excel 这种大量单元格、样式和 sheet 的格式,非常适合放到 Worker。DOCX 也支持 worker parsing,避免 ZIP/XML 解析阻塞主线程。

主线程:负责 UI、进度、挂载Worker:负责解析、窗口数据加载

3. 分批渲染与虚拟滚动

PPT 按页渐进渲染,Excel 按可视窗口渲染,DOCX 在渲染节点时主动让出浏览器绘制机会。

exportfunctionyieldToBrowser(): Promise<void> {returnnewPromise(resolve =>requestAnimationFrame(() =>resolve()));}

用户不怕等 1 秒,怕的是页面像卡死了一样没有反馈。所以我们在 Demo 里加入了下载进度、解析阶段、渲染阶段、模型摘要和 WASM 状态。


九、工程结构:不要把所有格式塞进一个巨型 viewer

当前项目结构大致如下:

office-render-demo  -> 统一 Demo、上传、样例、WASM 校验、诊断面板docx-viewer  -> DOCX parser / renderer / worker / layout snapshotmsdoc-viewer  -> DOC CFB parser / Word binary model / HTML renderer / paged runtimeppt-viewer  -> PPT binary parser / EMF/WMF 转换 / progressive rendererpptx-viewer  -> PPTX OPC parser / shape renderer / text layout / virtual slidesexcel-viewer  -> XLS/XLSX parser / workbook model / worker / virtual grid

这种拆法有几个好处:

设计
收益
每种格式独立仓库
方便单独修 bug、打包、回归
统一 Demo 只做调度
UI 不绑死具体格式实现
入口 API 保持 parse/render/mount
学习成本低,便于替换
worker client 单独暴露
大文件性能优化不污染业务代码
诊断信息统一
客户问题可以快速定位到格式和阶段

我个人很喜欢这个结构,因为它没有试图用一个抽象吃掉所有复杂度。Office 六种格式的历史包袱完全不同,强行统一只会把问题藏起来。好的统一,应该发生在“调用边界”和“诊断边界”,不是发生在所有内部模型上。


十、从零实现时,可以按这个顺序推进

如果你也想自己做一个纯前端 Office viewer,不建议一上来就做六种格式。更可落地的路线是:

第一步:先选一个格式

推荐从 DOCX 或 XLSX 开始,因为它们是 ZIP + XML,调试成本比旧二进制低。

目标不要定成“100% 还原 Word”。第一阶段只要做到:

能解包能读取 document.xml / styles.xml / relationships能渲染段落、文本、图片、表格能输出基础 CSS

第二步:定义中间模型

不要边解析边拼 HTML。建议先生成模型:

interfaceParagraphModel {runsTextRun[];  styleId?: string;  spacing?: Spacing;}interfaceTableModel {rowsTableRow[];  borders?: BorderSet;}interfaceImageModel {srcstring;widthnumber;heightnumber;  anchor?: AnchorInfo;}

这样后续你要修表格、分页、图片锚定时,有地方下手。

第三步:渲染器只关心模型

渲染器不要再去读 ZIP,不要再理解 relationship。它应该只做一件事:把模型变成 DOM/SVG/CSS。

parser 负责“读懂文件”renderer 负责“画出来”viewer 负责“挂载和交互”

这三者分开以后,debug 会舒服很多。

第四步:建立测试样例库

真实 Office 兼容性不是靠想象做出来的。你需要持续积累样例:

类型
必备样例
Word
大表格、跨页表格、页眉页脚、目录、图片锚定、图注
PPT
背景、母版、组合形状、箭头、EMF/WMF、表格、渐变
Excel
合并单元格、长文本溢出、边框、行高列宽、图片、图表、多 sheet

每修一个 bug,都应该沉淀成样例。Office 预览项目最怕的是“修好 A,改坏 B”。样例库就是项目的记忆。

第五步:最后再做分页和极限性能

分页、虚拟滚动、worker、WASM 都很重要,但不要在第一天就全部做。先保证内容完整,再保证视觉接近,再保证性能,顺序不要反。


十一、这件事带给我的几个经验

1. 不要相信“能解析”就等于“能预览”

解析出文本是一回事,还原页面是另一回事。Office 用户真正关心的是视觉语义:这个表格有没有断,这个图有没有丢,这个标题是不是在正确位置。

2. 浏览器不是 Word,需要尊重差异

很多 Word 行为无法直接映射到 CSS。比如分页、浮动图片、文字环绕、表格跨页。遇到这些问题时,要么做运行时测量,要么接受阶段性取舍,不要用一堆脆弱 CSS 伪装成完整算法。

3. 旧格式比新格式更需要耐心

.doc.ppt.xls 里有太多历史包袱。你会遇到二进制结构、压缩文本、属性修饰、老式图片格式和各种兼容规则。写这类 parser,最重要的是小步推进,每一个结构都留诊断信息。

4. 性能优化要和用户感知绑定

Worker、虚拟滚动、渐进渲染都不是为了炫技,而是为了让用户知道系统还活着。一个能显示进度、先出首屏的 2 秒,比一个卡住 1 秒的页面更让人安心。

5. 纯前端路线的价值在“边界清楚”

文件不出浏览器,不依赖第三方 Office 服务,部署成静态站点即可体验。这对内网、私有化、审计、低代码平台、知识库、档案系统都很有吸引力。


十二、怎么读 Microsoft Office Specs:从“看不懂”到“能落代码”

做 Office viewer,最容易走弯路的地方,是只看开源实现,不看规格文档。

开源实现能告诉你“别人怎么写”,但规格文档能告诉你“文件为什么长这样”。当你遇到一个真实客户文档:表格断了、箭头没了、图片错位、Excel 边框穿过文字,如果只靠猜,很容易越改越乱;如果能沿着 Microsoft Office Specs 往下追,问题会慢慢从“玄学”变成“路径清晰的工程问题”。🧭

我建议把 specs 当成地图,而不是教材。不要从第一页读到最后一页,那样很快会被术语淹没。正确姿势是:拿着一个具体 bug,带着文件、offset、XML path、record id 去查。

1. 先判断文件家族:CFB 还是 OOXML

Office 文件第一步先分家族。

文件
容器
核心入口
主要规格
.doc
Compound File Binary
WordDocument
0Table/1Table
[MS-DOC]、[MS-CFB]、[MS-ODRAW]
.ppt
Compound File Binary
PowerPoint Document
Current User
[MS-PPT]、[MS-CFB]、[MS-ODRAW]
.xls
Compound File Binary
Workbook
 stream
[MS-XLS]、[MS-CFB]、[MS-ODRAW]
.docx
ZIP / OPC
word/document.xml
ISO/IEC 29500、[MS-DOCX]、[MS-OI29500]
.pptx
ZIP / OPC
ppt/presentation.xml
ISO/IEC 29500、[MS-PPTX]、[MS-ODRAWXML]
.xlsx
ZIP / OPC
xl/workbook.xml
ISO/IEC 29500、[MS-XLSX]、[MS-ODRAWXML]

判断容器以后,调试路线就完全不同。

旧二进制文件要先读 [MS-CFB]。它把一个 Office 文件描述成“文件里的小文件系统”:有 header、sector、FAT、directory entry、storage、stream。Microsoft 的 [MS-CFB] 文档明确说,CFB 是一个 general-purpose file format,用来在单个文件里存储层级化的 stream data。

OOXML 文件则先按 ZIP 解包,读 [Content_Types].xml 和 _rels/.rels,再沿 relationship 找主文档 part。此时 ISO/IEC 29500 是基础,Microsoft 的 [MS-DOCX]、[MS-PPTX]、[MS-XLSX] 负责说明 Office 在标准之外的扩展。Microsoft 还提供 [MS-OI29500],记录 Word、Excel、PowerPoint 对 ISO/IEC 29500 的实现信息,这对处理兼容差异非常关键。

2. 旧格式调试:从 stream 和 record 开始追

以 .doc 为例,调试路径不是“打开文档找 HTML”,而是:

CFB header  -> Directory entries  -> WordDocument stream  -> FIB  -> Table stream  -> CLX / Piece Table  -> Paragraph ranges  -> CHPX / PAPX  -> SPRM  -> paragraph/table/image model

[MS-DOC] 的 Introduction 里有一句很重要的话:Word Binary File Format begins with a master record named the File Information Block。也就是说,FIB 是你在 .doc 里的导航仪。它会告诉你其它数据在哪里,正文怎么分片,样式和属性怎么找。

一个可落地的调试方式是:给二进制 reader 加 trace。

classBinaryReader {constructor(privatebytesUint8Array) {}u16(offsetnumberlabelstring): number {const value = this.bytes[offset] | (this.bytes[offset + 1] << 8);    trace.push({ offset, size2, label, value });return value;  }u32(offsetnumberlabelstring): number {const value =this.bytes[offset] |      (this.bytes[offset + 1] << 8) |      (this.bytes[offset + 2] << 16) |      (this.bytes[offset + 3] << 24);    trace.push({ offset, size4, label, value: value >>> 0 });return value >>> 0;  }}

为什么要记 offset?因为 specs 里大量结构都是“字段 + 长度 + 偏移”。没有 offset trace,你只能看最终结果错了;有 trace,你可以回答:

这个表格属性来自哪个 SPRM?这个图片资源来自哪个 BLIP?这个段落的 PAPX 是不是没套上?这个 shape anchor 是不是解析成了错误坐标?

.ppt 的路线类似,但入口换成 Current User 和 UserEditAtom。你要沿着 Persist Directory 找 slide、master、notes、pictures,再根据 record header 解析每个 atom/container。

.xls 则进入 Workbook stream 后读 BIFF records。行、列、单元格、合并区域、XF 样式、调色板、图表、图片锚点都在不同 record 里。Excel 的难点不是把值读出来,而是把“值 + 样式 + 布局 + grid 行为”合起来。

3. 图形和图片:不要只看主格式文档

很多渲染问题不在 [MS-DOC]、[MS-PPT]、[MS-XLS] 主文档里,而在图形规格里。

旧格式里的 shapes、connectors、arrows、BLIP 图片,经常要查 [MS-ODRAW]。Microsoft 对 [MS-ODRAW] 的描述是 OfficeArt binary file format,用于表达 Office 应用里的 drawing elements 和它们的格式。它里面有 OfficeArt record header、container、property table、BLIP store、shape hierarchy。

这也是为什么很多 PPT 文件里“流程图箭头丢失”特别常见。箭头不是普通图片,它可能是 OfficeArt shape;你如果只解析 raster image,自然看不到它。

OOXML 里的 DrawingML 扩展则要看 [MS-ODRAWXML]。它会解释 DrawingML 在 Office 中的扩展元素、AlternateContent、Ignorable attribute 等兼容机制。遇到 PPTX 里的复杂图形、smart art fallback、嵌入对象预览图时,这份文档非常有用。

4. OOXML 调试:从 relationship 走,不要全局搜索

解开 .docx/.pptx/.xlsx 后,目录很多。新手常见错误是全局搜索某个 id,然后靠运气改解析逻辑。

更可靠的方式是按 relationship 追:

_rels/.rels  -> officeDocument relationship  -> main document part  -> part-local .rels  -> styles/theme/media/charts/drawings  -> parse target part

以 PPTX 为例:

ppt/presentation.xml  -> ppt/_rels/presentation.xml.rels  -> slide master / slide layout / slide  -> ppt/slides/_rels/slide1.xml.rels  -> image / chart / embedded object

以 XLSX 为例:

xl/workbook.xml  -> xl/_rels/workbook.xml.rels  -> worksheets / styles / sharedStrings / theme  -> worksheet rels  -> drawings / charts / images

当你遇到一个单元格颜色不对,应该先查:

cell.s  -> styles.xml cellXfs  -> fill/font/border/numFmt  -> theme color  -> tint transform  -> CSS color

当你遇到一个 PPTX 图片丢失,应该先查:

slide xml blip r:embed  -> slide rels 找 target  -> media part 是否存在  -> content type 是否支持  -> renderer 是否处理该 mime

这套路径比“猜一个 selector”慢一点,但一旦定位到规格字段,改动就会很有底气。

5. 把 specs 变成代码时,要维护一张“字段翻译表”

Office specs 里大量单位和字段不能直接丢给 CSS。

Office 概念
常见单位/表达
前端渲染转换
Word page / margin
twips
px = twips / 20 * 96 / 72
DrawingML 坐标
EMU
px = emu / 914400 * 96
Excel row height
point
px = pt * 96 / 72
Excel column width
character width
需要按默认字体近似换算
PPT slide size
EMU / master size
转成固定 stage 宽高
theme color + tint
theme slot + transform
解析 theme 后计算最终色
OfficeArt shape
record + property table
转成 SVG path / positioned DOM

这张表很重要。没有它,项目里会散落一堆魔法数字;半年后没人知道 12700914400twipsEMU_PER_PIXEL 到底从哪来。把转换写成独立函数,并在注释里标明对应 specs,可以大幅降低维护成本。

6. 一个真实问题应该这样闭环

假设客户反馈:“PPT 流程图箭头丢失。”

不要直接去 renderer 里补一个箭头。建议按这个流程走:

1. 用 PowerPoint/WPS 打开,确认原始效果2. 判断文件是 .ppt 还是 .pptx3. 如果是 .ppt,进入 CFB,定位 PowerPoint Document stream4. 沿 UserEditAtom / PersistDirectory 找到对应 slide record5. 查 [MS-PPT] 确认 record 类型6. 如果是 OfficeArt shape,跳到 [MS-ODRAW]7. 解析 OfficeArt record header、shape container、property table8. 把 shape 转成内部 model9. renderer 把 model 转成 SVG 或 DOM10. 加最小样例和回归截图

假设客户反馈:“Excel 长文本溢出后边框穿过文字。”

路径应该是:

1. 查 XLS/XLSX 单元格是否 wrapText2. 查右侧单元格是否为空、是否有内容阻挡3. 查 fill 是否只属于原单元格4. 查 border 层是否需要避让文字视觉区域5. 把内容层、背景层、边框层拆开6. 用截图回归验证同一行多个单元格的重叠关系

注意这里不是“字符串匹配某个客户文件”,而是把问题还原成 Office 行为规则。只有这样,修完一个文件,才不会改坏另一批文件。

7. specs 研发路径建议

最后给一条很实用的学习路线:

第 1 周:只读容器  CFB / ZIP / OPC / relationship / content type第 2 周:只读文本  DOC piece table、DOCX runs、PPT text records、sharedStrings第 3 周:只做样式  font、color、border、fill、paragraph spacing、alignment第 4 周:只做图片和图形  relationship、media、DrawingML、OfficeArt、EMF/WMF fallback第 5 周:只做布局  Word section、PPT stage、Excel row/column metrics、anchor第 6 周:只做性能  worker、progressive render、virtual viewport、diagnostics

这条路线不会让你一天做出完整 Office,但它能让你每一周都有一个稳定的增量。更重要的是,它会逼你用 specs 来解释代码,而不是用代码去猜格式。


十三、最后:真正的技术干货,来自愿意和格式细节死磕

Office 纯前端渲染不是一个轻松项目。

你会在凌晨盯着一个跨页表格想:为什么 Word 能放下,浏览器放不下?你会被一个 EMF 箭头折磨很久,因为它在客户 PPT 里恰好代表流程方向。你会发现 Excel 的一个长文本溢出规则,比很多组件库的表格都要讲究。你也会慢慢意识到:所谓高还原度,不是某个神奇库给你的,而是你愿意把一个个“看起来很小”的细节认真补上。

这件事难,但也很有意思。因为它把前端从“调接口、画页面”的舒适区里拽出来,重新面对二进制、文件格式、排版系统、图形模型、性能调度这些更底层的问题。

如果你正在做文档预览、低代码平台、知识库、网盘、OA、档案系统,希望这篇文章能给你一个清晰的起点:

先拆格式,再建模型;先保内容,再做还原;先让用户看见,再让体验变快;最后,用样例和诊断把每一次进步留下来。🚀


参考资料

  • Mammoth.js: https://github.com/mwilliamson/mammoth.js[1]
  • docx-preview / docxjs: https://github.com/VolodymyrBaydalka/docxjs[2]
  • SheetJS Community Edition: https://docs.sheetjs.com/[3]
  • ExcelJS: https://github.com/exceljs/exceljs[4]
  • Luckysheet: https://github.com/adminium/luckysheet[5]
  • PPTXjs: https://github.com/meshesha/PPTXjs[6]
  • PptxGenJS: https://github.com/gitbrent/PptxGenJS[7]
  • ONLYOFFICE Docs Viewing API: https://api.onlyoffice.com/docs/docs-api/get-started/how-it-works/viewing/[8]
  • Microsoft [MS-CFB] Compound File Binary File Format: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b[9]
  • Microsoft [MS-DOC] Word Binary File Format: https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-doc/ccd7b486-7881-484c-a137-51170af7cc22[10]
  • Microsoft [MS-PPT] PowerPoint Binary File Format: https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/6be79dde-33c1-4c1b-8ccc-4b2301c08662[11]
  • Microsoft [MS-XLS] Excel Binary File Format: https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/cd03cb5f-ca02-4934-a391-bb674cb8aa06[12]
  • Microsoft [MS-DOCX] Word Extensions to Office Open XML: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/728a7abc-7f55-40dc-90a7-1276ff53c8b2[13]
  • Microsoft [MS-PPTX] PowerPoint Extensions to Office Open XML: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-pptx/efd8bb2d-d888-4e2e-af25-cad476730c9f[14]
  • Microsoft [MS-XLSX] Excel Extensions to Office Open XML: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/2c5dee00-eff2-4b22-92b6-0738acd4475e[15]
  • Microsoft [MS-OI29500] Office implementation notes for ISO/IEC 29500: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oi29500/bd9e8289-844a-42e2-9809-66c7005bd9e2[16]
  • Microsoft [MS-ODRAW] Office Drawing Binary File Format: https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/8560795e-7759-4745-838f-f7f2ef2f1872[17]
  • Microsoft [MS-ODRAWXML] DrawingML Extensions: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-odrawxml/a807ad3a-1f35-4540-9237-353ed61c93ea[18]

引用链接

[1]https://github.com/mwilliamson/mammoth.js

[2]https://github.com/VolodymyrBaydalka/docxjs

[3]https://docs.sheetjs.com/

[4]https://github.com/exceljs/exceljs

[5]https://github.com/adminium/luckysheet

[6]https://github.com/meshesha/PPTXjs

[7]https://github.com/gitbrent/PptxGenJS

[8]https://api.onlyoffice.com/docs/docs-api/get-started/how-it-works/viewing/

[9]https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b

[10]https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-doc/ccd7b486-7881-484c-a137-51170af7cc22

[11]https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/6be79dde-33c1-4c1b-8ccc-4b2301c08662

[12]https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/cd03cb5f-ca02-4934-a391-bb674cb8aa06

[13]https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/728a7abc-7f55-40dc-90a7-1276ff53c8b2

[14]https://learn.microsoft.com/en-us/openspecs/office_standards/ms-pptx/efd8bb2d-d888-4e2e-af25-cad476730c9f

[15]https://learn.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/2c5dee00-eff2-4b22-92b6-0738acd4475e

[16]https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oi29500/bd9e8289-844a-42e2-9809-66c7005bd9e2

[17]https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/8560795e-7759-4745-838f-f7f2ef2f1872

[18]https://learn.microsoft.com/en-us/openspecs/office_standards/ms-odrawxml/a807ad3a-1f35-4540-9237-353ed61c93ea

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-06-04 15:34:17 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/710224.html
  2. 运行时间 : 0.107859s [ 吞吐率:9.27req/s ] 内存消耗:4,829.05kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=641cfd7dfa92e2b9d2ab3984f498a96e
  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.000520s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000778s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000305s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000305s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000490s ]
  6. SELECT * FROM `set` [ RunTime:0.014009s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000901s ]
  8. SELECT * FROM `article` WHERE `id` = 710224 LIMIT 1 [ RunTime:0.004465s ]
  9. UPDATE `article` SET `lasttime` = 1780558457 WHERE `id` = 710224 [ RunTime:0.006203s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000262s ]
  11. SELECT * FROM `article` WHERE `id` < 710224 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000516s ]
  12. SELECT * FROM `article` WHERE `id` > 710224 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000396s ]
  13. SELECT * FROM `article` WHERE `id` < 710224 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000747s ]
  14. SELECT * FROM `article` WHERE `id` < 710224 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001038s ]
  15. SELECT * FROM `article` WHERE `id` < 710224 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.002920s ]
0.109529s