ARTICLE DETAIL

资讯详情

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

老CMS集成多格式文档导入:KindEditor扩展的预处理流水线设计

老CMS集成多格式文档导入:KindEditor扩展的预处理流水线设计 前两年接了一个政务网站内容管理系统的二次开发项目技术栈比较老后台编辑器还是KindEditor 4.1.x。需求方提了一个特别具体的诉求编辑们平时写通知、发政策解读、整理会议纪要经常要把Word、PDF、Excel里的内容搬进编辑器。以前的操作路径是先另存为纯文本再粘贴进去重新排版结果格式全乱、图片丢失、表格挤破。他们希望做一个“多格式文档智能填充”选中一个文件系统自动识别类型把内容按公文的编辑习惯整理好直接落到编辑器里。做完之后我的感受是这个需求本质上不是在让KindEditor变聪明而是在它的能力边界外面补一条“预处理流水线”。整条流水线由三层组成服务端格式转换、安全清洗、结构化排版。这篇文章就把整体设计和踩坑过程完整记录下来给还在老CMS上维护项目的同行做个参考。1. 先把问题盘清楚KindEditor在政务CMS里的“多格式文档”到底难在哪1.1 政务场景为什么不能直接换编辑器很多人第一反应是都2024年了KindEditor这玩意早就不更新了直接换成CKEditor 5或者TinyMCE不就行了这个想法在普通项目里成立但在政务CMS里基本行不通。我看过的这类老系统KindEditor往往不只是“一个编辑器”那么简单。它跟一堆自定义组件绑在一起玉兰图片上传接口、附件管理插件、内容审核流程、发布接口。有些页面里甚至写了依赖KindEditor私有API的脚本换掉编辑器意味着所有发布通道、工具栏配置、内容模板都要跟着动一遍。这个改造量的风险和成本不是业务方愿意承担的。另外政务类的网站有个特点存量数据特别重要。旧文章里大量内容是用KindEditor生成的HTML结构比如内联style、特定的换行方式、表格嵌套写法。换个编辑器之后这些历史内容在编辑状态下能不能正常回显、能不能兼容旧模板是个巨大的不确定项。所以这个项目的选型方向注定是不换引擎扩展能力。把“多格式文档智能填充”做成一个附加功能模块通过标准接口对接KindEditor原有编辑流程不动。1.2 “智能填充”不是AI而是三层流水线需求方口中的“智能”其实是非常具体的办公场景诉求。我把它拆成三层第一层是格式适配把doc、docx、xls、xlsx、pdf这些不同格式统一转成一种可编辑的中间HTML。这一层解决“打不开”“复制出来是乱码”的问题。第二层是安全适配把转换出来的HTML做严格的白名单清洗。政务系统对安全性要求很高服务端不能存储带脚本、带事件属性、带危险标签的富文本内容。这一层解决“能用但不敢用”的问题。第三层是排版适配把清洗后的HTML按照公文习惯重新编排标题识别、字体映射、首行缩进、序号段落层级调整。这一层解决“内容进来了但没法看”的问题。三层解耦的好处很明显每一层都可以独立替换、独立测试。比如后来我们发现LibreOffice转表格不够理想只需要调整格式适配层的策略完全不用动前端的编辑器集成逻辑。2. 整体方案设计为什么选“服务端转换为主、前端清洗兜底”的架构2.1 服务端转换为主的原因这个架构决定是在项目第一周就定下来的。当时对比过纯前端方案比如用mammoth.js在浏览器里直接解析docx或者用SheetJS解析Excel好处是不占服务器资源坏处也很明显。首先是浏览器兼容性。这类政务CMS还在用IE内核的不少前端方案在老浏览器上未必跑得稳。mammoth.js虽然能解析docx但遇到老版本IE就直接卡住。其次是安全审计问题政务系统做等保测评时要求服务端存储的富文本必须是经过净化处理的。如果只在前端转换服务端接收的是“不可信”的HTML代码这个口径过不了审计。服务端转换为主的架构还有一个好处处理能力不受浏览器限制。政务文档经常几十页甚至上百页带大量表格和图片的章排版前端解析这种文件会直接把编辑页卡死。但在服务端跑转换编辑器界面始终流畅用户体验是好的。2.2 统一中间结构一份“干净的基础HTML”多格式转换最怕的是“各转各的”Word转出一套结构Excel又转出另一套结构后面的清洗和排版逻辑得为每种格式各自维护一套规则项目直接失控。我们在服务端定了一个统一中间结构所有格式的文档转换完成后都落成这个结构。这个结构本质上是一份“受限HTML”只有这些标签允许出现p、h2、h3、table、thead、tbody、tr、th、td、ul、ol、li、img、strong、em、span、div。同时定了几条硬性限制不允许script、iframe、object、embed等危险标签不允许onclick之类的事件属性style属性里只允许字体、颜色、缩进等展示类样式img标签必须有对应的附件标识方便做图片转存。这样设计之后后面的安全清洗和排版优化的逻辑只需要处理这一种结构。每个环节之间传的参数是确定的出问题也好定位。3. 核心实现把Word/PDF/Excel装进KindEditor的完整实操3.1 服务端转换层LibreOfficeTika的务实组合服务端技术栈是Java这是政务CMS里最常见的形态。转换层的核心组件选了Apache Tika加LibreOffice。Tika负责内容识别LibreOffice负责把各种Office格式统一转换成中间HTML。Tika在这里的角色很关键。政务系统上传的文件经常存在扩展名和真实格式不一致的情况有人把docx改成doc有人把xlsx改成pdf。如果只信扩展名转换层会直接崩掉。Tika通过读取文件头部的magic bytes识别真实格式这一步稳得很。转换逻辑的核心代码大致是这样// 1. 用Tika识别真实格式 Tika tika new Tika(); String mediaType tika.detect(inputStream, fileName); // 2. 根据真实格式分派转换策略 if (mediaType.contains(word) || mediaType.contains(excel)) { // LibreOffice headless转换 String cmd soffice --headless --convert-to html:\HTML (StarWriter)\ --outdir outputDir inputFile; Process p Runtime.getRuntime().exec(cmd); p.waitFor(120, TimeUnit.SECONDS); } else if (mediaType.equals(application/pdf)) { // PDF走PDFBox文本提取 PDDocument doc PDDocument.load(inputFile); PDFTextStripper stripper new PDFTextStripper(); String text stripper.getText(doc); doc.close(); }这里有几个要注意的细节。第一个是soffice进程管理LibreOffice第一次启动比较慢进程不会自动退出如果不处理连续转换多次后会攒一堆进程。解决办法是启动命令加上临时用户目录参数-env:UserInstallationfile:///tmp/lo_profile_xxx每个转换任务用独立配置目录转换完直接结束。第二个细节是超时控制。政务文档大的有几十上百页LibreOffice转得慢但一般也就在十几秒内完成。设120秒超时足够了超过就认为是文档损坏或者格式特殊直接返回错误提示不无限等待。保留格式方面实测下来LibreOffice转docx的效果综合最好标题样式、段落缩进、表格边框基本都能保留下来。转excel会偶尔出现合并单元格丢失的情况但好在政务场景里Excel导入后通常还要再调样式影响可控。3.2 政务排版再造从“转换HTML”到“结构化智能填充”LibreOffice转出来的HTML直接填进编辑器是没法看的。字体五花八门段落没有缩进标题样式是Word里的Heading 1并不符合公文的排版习惯。政务网站的公文排版是有明确规范的大标题用二号小标宋体正文用三号仿宋_GB2312段落首行缩进两个字符一级二级标题各有对应的字体和层级。这里说的“智能填充”核心就是做了一个排版优化器把转换后的HTML按规范重新编排。排版优化器的主要逻辑分为四步。第一步是标题识别。docx转换后的HTML里标题段落通常会带Heading标记或者有对应的style属性。把这些段落统一映射成h2和h3去掉Word里多余的背景色和字体定义交给编辑器端CSS控制展示效果。第二步是正文字体映射。清洗掉所有直接的font-family和font-size然后统一给正文段落加上仿宋_GB2312、三号的样式。这一步用正则匹配段落的style属性做到简单直接。第三步是序号层级识别。公文里的序号层级通常是“一、”“一”“1.”这个结构。通过正则识别段首序号决定该段的缩进层级。一级标题加粗二级标题缩进两字符正文段落统一首行缩进两个字符。第四步是表格处理。对转换出来的table标签做统一修正设置宽度为百分比或auto避免超出编辑器可视区域清理掉单元格内部的嵌套div合并连续相同的相邻单元格减少转换过程中的结构冗余。这套排版规则跑完之后导入的内容在编辑器里已经非常接近正式公文的形态了。编辑们再手动微调工作量下降了百分之七十以上这也是整个项目里他们评价最高的部分。我贴一段排版优化器的核心逻辑方便理解正则映射是怎么做的// 打方形阵段落的小集团处理 public String restructure(String cleanedHtml) { // 标题映射 html html.replaceAll(h1([^]*), h2$1) .replaceAll(h4([^]*), h3$1); // 去掉Word多余字体 html html.replaceAll(font-family:[^;\];?, ) .replaceAll(font-size:[^;\];?, ); // 序号段落缩进 html html.replaceAll((p[^]*)(\\s*[一二三四五六七八九十]), $1 style\text-indent:2em;font-weight:bold;\$2); return html; }处理完的HTML入库前还要再过一次白名单过滤器防止附件里有恶意代码被带进来。这一步用的是白名单模式而不是黑名单模式黑名单永远追不上攻击方式的更新白名单固定放行安全标签其余全部拿掉。3.3 前端扩展给KindEditor加一个“导入文档”按钮并正确落库服务端的转换和排版逻辑完成之后前端的工作就是把转换结果送回KindEditor并展示出来。KindEditor的工具栏是支持自定义项的。初始化时在items数组里加上一个自定义按钮然后用afterCreate回调去绑定点击事件。点击按钮触发一个隐藏的file input上传文件后拿着返回的干净HTML调用KindEditor的insertHtml或者html方法写入编辑区。这里有一个需要根据需求区分的细节如果用户选中了“按模板替换全文”走的是editor.html(cleanHtml)如果用户只是想在当前光标处插入Excel表格或者PDF内容走的是editor.insertHtml(cleanHtml)。这两者的逻辑不一样产品层需要给用户一个明确的选项。KindEditor.create(#content, { items: [source, undo, redo, docimport, |], afterCreate: function() { var editor this; var btn editor.toolbar.docimport; // 绑定file input btn.click(function() { document.getElementById(docImportInput).click(); }); } }); // 文件上传回调 function handleImportResult(data) { if (data.code 0) { var editor KindEditor.instances[0]; if (data.action replace) { editor.html(data.content); } else { editor.insertHtml(data.content); } } else { alert(data.message); } }文件上传接口走的是单独的路由不经过CMS原有的附件上传模块。原因在于这个接口需要返回“转换后的HTML”跟普通图片上传返回URL的逻辑完全不一样混在一起会互相干扰。还有一点很关键KindEditor在收到通过html方法注入的内容后自己内部会再过一遍HTML过滤器把非白名单标签去掉。利用这个特性前端相当于多了一层兜底清洗即使服务端清洗逻辑有遗漏敌人想通过编辑器注入攻击代码也很难成功。4. 常见问题与排查技巧实录4.1 高发问题速查表整理一下这个功能上线后遇到的问题Top级清单对照排查效率会更高现象可能原因处理办法导入docx后字体全变宋体转换层没有映射仿宋_GB2312字体在排版优化器里增加字体映射规则把font-family统一替换图片在发布后不显示转换产生的图片文件没有转存到CMS附件库检查附件转存逻辑必须把图片从临时目录复制到正式存储目录表格超出编辑器宽度Word里表格列宽固定转HTML后保持固定像素值清洗table样式把width统一改成百分比或auto导入PDF内容为空扫描版PDF没有文本层提取不到文字识别为扫描件提示用户以附件形式上传不做文本填充转换接口超时文档过大或LibreOffice进程卡死设置120秒超时用独立临时用户目录避免进程冲突页面乱码老系统页面用GBK编码转换产物是UTF-8统一全链路UTF-8数据库连接串加characterEncoding参数工具栏按钮不出现KindEditor版本差异或items配置错误检查afterCreate回调里的按钮引用优先用全局插件方式注册4.2 三个典型踩坑复盘第一个坑是字符集。这系统部署给一家下属单位时他们的服务器数据库字符集是GBK。LibreOffice转换出的HTML是UTF-8编码直接存库之后所有中文内容在页面上全部变成乱码。排查了半天发现是JDBC连接串里没有指定characterEncoding。解决办法是统一全链路UTF-8数据库连接串加上useUnicodetruecharacterEncodingUTF-8同时把数据库表和字段的字符集手工改成utf8mb4。这个坑在政务项目里特别常见因为老库建设时间早很多都是GBK。第二个坑是图片转存。Word里插的图片LibreOffice转HTML时会把图片导出成独立文件放到转换目录里。一开始只把HTML文本入库了图片目录还在临时路径上发布之后图片全部显示不出来。这个问题上线第一天就被编辑们发现了。解决办法是在转换层写一个附件转存组件把临时目录里的图片文件转存到CMS的统一附件表或者存储目录同时把HTML里img标签的src指向替换成新的存储地址。注意这里替换src时要处理好相对路径和绝对路径的坑数据库里存的必须是能访问的完整URL。第三个坑是表格失控。政务文档里的表格经常是复杂合并单元格结构的LibreOffice转出来的HTML会生成大量嵌套table结构。KindEditor插入这种嵌套表格之后编辑器里的显示还算正常但发布出去的前端页面会乱成一团。解决思路是在HTML清洗阶段加一个表格拆分逻辑把嵌套的子表格从父表格中拆出来合并相邻相同单元格把固定px宽度改成百分比。这一步当时花了大概两天时间反复调试最后的效果是任何表格进来至少不会撑破页面布局。上线之后我还发现一个经验一定不能用开发自造的简单样例文档做测试。政务系统这批人会用到极端格式比如红头文件套红、盖章扫描件、一堆嵌套表格的台账。我们用真实文档回归了几十轮每次都能发现新的转换问题。这套“真实文档库回归测试”的方式建议做同类项目的同学都保留下来。5. 最后再分享一点个人体会做完这个功能最大的心得是扩展KindEditor这种已经停止迭代的编辑器最稳的方式不是往它内部打补丁而是把复杂度全部拆到服务端把编辑器当成一个展示和交互的“哑终端”。因为KindEditor本身并不擅长解析文档解析是个重活放浏览器里受兼容性和内存限制放服务端却能随意拿性能换能力。另外真实的政务内容场景里“智能”往往不是那种AI自动生成内容的智能而是让旧系统里的人少做几次复制粘贴、少修几次排版。一个文档导入按钮背后是一整套格式适配、安全清洗、排版再造的流水线用户看到的是“一下就填好了”看不到的是服务端防了脚本注入、防了表格溢出、防了图片丢失。这个功能后续如果要继续扩展可以做的地方还很多。比如接入文档预览的在线预览服务替代现在的转换方式或者对扫描件引入OCR识别能力把“文字进不来”的PDF也纳入填充范围。但不管怎么扩三层流水线的框架基本不用动这大概就是先把架构想清楚的价值所在。
返回列表