教育政府网站信创环境富文本编辑器重构记:从UEditor困境到自主适配方案的突破
一、项目启动:双重挑战下的紧急需求
2024年6月,某省级教育厅下属的继续教育平台发起紧急需求:需在1个月内完成富文本编辑器升级,核心要求包括:
功能需求:
- 完美支持Word文档粘贴(需保留文字样式、表格、图片、公式等)
- 兼容WPS教育版生成的特殊格式
- 支持在线批改功能(高亮、批注等)
信创环境约束:
- 操作系统:银河麒麟V10 SP1教育专版
- 浏览器:360安全浏览器教育版(Chromium 91内核)
- 数据库:人大金仓V8(需兼容PHP的PDO扩展)
- 安全要求:通过等保2.0三级认证
原系统痛点:
- UEditor在信创环境下出现"幽灵字符"问题(粘贴后随机出现乱码)
- 图片上传功能在国产防火墙拦截下频繁失败
- 表格样式在WPS与Office间切换时完全错乱
二、技术选型:在信创生态中寻找平衡点
1. 候选方案深度测试
| 方案 | Word粘贴准确率 | 信创兼容性 | 教育功能扩展 | 开发周期 |
|---|---|---|---|---|
| TinyMCE 6 | 78%(基础样式) | ★★☆☆☆ | 需二次开发 | 4周 |
| WangEditor 5 | 65% | ★★★☆☆ | 有限 | 2周 |
| 改写UEditor | 72% | ★★☆☆☆ | 困难 | 3周 |
| 自主开发核心模块 | 92% | ★★★★☆ | 高度灵活 | 5周 |
关键发现:
教育行业特殊需求:
- 需支持LaTeX公式粘贴(来自MathType)
- 批改功能需与现有阅卷系统API对接
- 需实现"纯净模式"(过滤Word中的宏病毒风险)
信创环境技术壁垒:
- 银河麒麟系统缺少
libpng12库(影响图片处理) - 360教育版浏览器禁用了部分Clipboard API
- 人大金仓数据库对BLOB类型支持有限
- 银河麒麟系统缺少
三、开发实施:分阶段攻克核心难题
阶段一:前端架构重构(Vue3实现)
// EduEditor.vue - 核心组件import{ref,onMounted}from'vue'import{parseWordContent}from'./word-parser'// 自定义解析器import{uploadToJinKing}from'./db-adapter'// 人大金仓适配exportdefault{setup(){consteditorRef=ref(null)constisProcessing=ref(false)constalertMessage=ref('')// 信创环境专用粘贴处理consthandlePaste=async(e)=>{if(!e.clipboardData?.types.includes('Files')&&!e.clipboardData?.types.includes('text/html'))returnisProcessing.value=truealertMessage.value='正在处理文档内容...'try{// 优先处理文件粘贴(支持.docx拖拽)constfiles=Array.from(e.clipboardData.files)if(files.length>0){awaithandleFilePaste(files[0])return}// 处理HTML内容(Word粘贴)consthtml=e.clipboardData.getData('text/html')const{content,images}=awaitparseWordContent(html)// 分批上传图片(信创网络限制)constimageUrls=awaitPromise.all(images.map(img=>uploadToJinKing(img)))// 替换图片占位符letfinalContent=content imageUrls.forEach((url,idx)=>{finalContent=finalContent.replace(`__IMG_PLACEHOLDER_${idx}__`,``)})editorRef.value.setContent(finalContent)}catch(error){console.error('解析失败:',error)alertMessage.value=`处理失败:${error.message}`}finally{isProcessing.value=falsesetTimeout(()=>alertMessage.value='',3000)}}onMounted(()=>{document.addEventListener('paste',handlePaste)})return{editorRef,isProcessing,alertMessage}}}阶段二:Word内容深度解析(关键突破)
// word-parser.js - 信创环境专用解析器exportconstparseWordContent=async(html)=>{// 创建隔离解析环境constparser=document.createElement('div')parser.innerHTML=html// 教育行业特殊处理:MathType公式constmathTypes=parser.querySelectorAll('[class*="MathType"]')mathTypes.forEach(el=>{el.outerHTML=`${el.textContent}`})// 信创环境样式修复conststyleFixes=[{regex:/font-family:[^;"]*(Calibri|Arial)[^;"]*/gi,replace:'font-family: "方正仿宋_GBK"'},{regex:/mso-border-shadow:/gi,replace:'border:1px solid #000;'}]styleFixes.forEach(({regex,replace})=>{parser.innerHTML=parser.innerHTML.replace(regex,replace)})// 图片提取与Base64转换(适配内网)constimages=[]constimgElements=parser.querySelectorAll('img')for(constimgofimgElements){if(img.src.startsWith('file://')){// 处理本地文件(需用户授权)images.push({type:'local',path:img.src.replace('file://','')})img.src='__IMG_PLACEHOLDER_'+(images.length-1)+'__'}elseif(!img.src.startsWith('data:')){// 外网图片下载转Base64try{constresponse=awaitfetch(img.src)constblob=awaitresponse.blob()constreader=newFileReader()reader.readAsDataURL(blob)reader.onloadend=()=>{images.push({type:'base64',data:reader.result})}}catch(e){images.push({type:'error',alt:'[图片无法加载]'})img.src='/static/broken-image.png'}}}return{content:parser.innerHTML,images:images.filter(i=>i.type!=='error')}}阶段三:信创数据库适配(PHP实现)
// db-adapter.php - 人大金仓专用适配器classJinKingDB{private$pdo;publicfunction__construct(){try{$this->pdo=newPDO('kingbase:host=localhost;port=54321;dbname=EDU_SYSTEM','edu_admin','SecurePass@123');$this->pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);}catch(PDOException$e){thrownewException("数据库连接失败: ".$e->getMessage());}}// 信创环境优化的大文件存储publicfunctionstoreAttachment($data,$filename){try{// 分块存储(人大金仓单BLOB限制10MB)$chunkSize=8*1024*1024;// 8MB$totalChunks=ceil(strlen($data)/$chunkSize);// 创建存储记录$stmt=$this->pdo->prepare(" INSERT INTO ATTACHMENTS (file_name, total_chunks, created_at) VALUES (?, ?, NOW()) RETURNING id ");$stmt->execute([$filename,$totalChunks]);$attachmentId=$this->pdo->lastInsertId();// 存储各分块for($i=0;$i<$totalChunks;$i++){$chunk=substr($data,$i*$chunkSize,$chunkSize);$stmt=$this->pdo->prepare(" INSERT INTO ATTACHMENT_CHUNKS (attachment_id, chunk_index, chunk_data) VALUES (?, ?, ?) ");$stmt->execute([$attachmentId,$i,$chunk]);}return"/attachments/merge/$attachmentId";}catch(Exception$e){error_log("存储失败: ".$e->getMessage());returnfalse;}}}四、信创环境专项优化
1. 字体兼容方案
/* 强制使用教育系统预装字体 */.edu-content{font-family:"方正书宋_GBK","汉仪楷体_GBK","思源黑体 CN",sans-serif;}/* 公式特殊处理 */.math-formula{font-family:"Latin Modern Math","Cambria Math";background:#f5f5f5;padding:2px 4px;border-radius:3px;}2. 浏览器兼容补丁
// 修复360教育版浏览器的Clipboard APIif(navigator.userAgent.includes('360SE-Edu')){constnativePaste=HTMLDocument.prototype.paste;HTMLDocument.prototype.paste=function(e){// 延迟处理以绕过浏览器安全限制setTimeout(()=>{constcustomEvent=newCustomEvent('eduPaste',{detail:{html:window.clipboardData.getData('text/html'),text:window.clipboardData.getData('text')}});this.dispatchEvent(customEvent);},100);if(nativePaste)nativePaste.apply(this,arguments);};}五、测试与部署
1. 信创环境测试矩阵
| 测试场景 | 银河麒麟+龙芯 | 统信UOS+飞腾 | 中标麒麟+兆芯 |
|---|---|---|---|
| Word复杂样式保留 | 94% | 91% | 88% |
| 20MB大文件粘贴 | 成功(12s) | 成功(15s) | 成功(18s) |
| 与WPS交互兼容性 | 100% | 98% | 95% |
| 等保2.0安全扫描 | 0高危漏洞 | 0高危漏洞 | 0高危漏洞 |
2. 性能优化措施
图片处理:
// 信创环境专用图片压缩asyncfunctioncompressImage(file){returnnewPromise((resolve)=>{constimg=newImage()img.onload=()=>{constcanvas=document.createElement('canvas')constctx=canvas.getContext('2d')// 信创设备性能适配constquality=navigator.hardwareConcurrency>4?0.8:0.6canvas.width=img.width canvas.height=img.height ctx.drawImage(img,0,0)resolve(canvas.toDataURL('image/jpeg',quality))}img.src=URL.createObjectURL(file)})}PHP内存管理:
; php-fpm.conf 信创专项配置 pm.max_children = 10 pm.start_servers = 4 pm.min_spare_servers = 2 pm.max_spare_servers = 6 request_terminate_timeout = 300
六、项目总结与行业启示
信创开发三大原则:
- 早测试原则:在真实信创环境(非模拟器)验证每个功能
- 渐进增强原则:先实现核心功能,再逐步优化兼容性
- 离线优先原则:所有依赖必须支持本地化部署
教育行业特殊经验:
- 公式编辑需同时支持MathType和LaTeX
- 批改功能需与现有阅卷系统API深度集成
- 必须通过教育装备行业协会的认证测试
独立开发者生存指南:
- 建立自己的信创测试环境(成本约¥18,000)
- 与本地信创厂商建立技术支持通道
- 对政府教育项目预留40%的不可预见成本
最终成果:新系统在客户信创环境中稳定运行3个月,处理Word文档2,300余份,样式保留准确率达到93%,获得客户"信创教育应用优秀案例"表彰。这次经历证明**:在信创领域,没有完美的现成方案,只有通过深度定制实现的可用方案**。目前正在将该解决方案封装为Vue组件库,计划在教育行业内部技术社区开源共享。
复制插件文件
安装jquery
npm install jquery导入组件
importEfrom'wangeditor'const{$,BtnMenu,DropListMenu,PanelMenu,DropList,Panel,Tooltip}=Eimport{WordPaster}from'../../static/WordPaster/js/w'import{zyCapture}from'../../static/zyCapture/z'import{zyOffice}from'../../static/zyOffice/js/o'初始化组件
//zyCapture ButtonclasszyCaptureBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){window.zyCapture.setEditor(this.editor).Capture();}tryChangeActive(){this.active()}}//zyOffice ButtonclassimportWordBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){window.zyOffice.SetEditor(this.editor).api.openDoc();}tryChangeActive(){this.active()}}//zyOffice ButtonclassexportWordBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){window.zyOffice.SetEditor(this.editor).api.exportWord();}tryChangeActive(){this.active()}}//zyOffice ButtonclassimportPdfBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){window.zyOffice.SetEditor(this.editor).api.openPdf();}tryChangeActive(){this.active()}}//WordPaster ButtonclassWordPasterBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){WordPaster.getInstance().SetEditor(this.editor).Paste();}tryChangeActive(){this.active()}}//wordImport ButtonclassWordImportBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){WordPaster.getInstance().SetEditor(this.editor).importWord();}tryChangeActive(){this.active()}}//excelImport ButtonclassExcelImportBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){WordPaster.getInstance().SetEditor(this.editor).importExcel();}tryChangeActive(){this.active()}}//ppt paster ButtonclassPPTImportBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){WordPaster.getInstance().SetEditor(this.editor).importPPT();}tryChangeActive(){this.active()}}//pdf paster ButtonclassPDFImportBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){WordPaster.getInstance().SetEditor(this.editor);WordPaster.getInstance().ImportPDF();}tryChangeActive(){this.active()}}//importWordToImg ButtonclassImportWordToImgBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){WordPaster.getInstance().SetEditor(this.editor).importWordToImg();}tryChangeActive(){this.active()}}//network paster ButtonclassNetImportBtnextendsBtnMenu{constructor(editor){const$elem=E.$(`<div class="w-e-menu">`)super($elem,editor)}clickHandler(){WordPaster.getInstance().SetEditor(this.editor);WordPaster.getInstance().UploadNetImg();}tryChangeActive(){this.active()}}exportdefault{name:'HelloWorld',data(){return{msg:'Welcome to Your Vue.js App'}},mounted(){vareditor=newE('#editor');WordPaster.getInstance({//上传接口:http://www.ncmem.com/doc/view.aspx?id=d88b60a2b0204af1ba62fa66288203edPostUrl:"http://localhost:8891/upload.aspx",License2:"",//为图片地址增加域名:http://www.ncmem.com/doc/view.aspx?id=704cd302ebd346b486adf39cf4553936ImageUrl:"http://localhost:8891{url}",//设置文件字段名称:http://www.ncmem.com/doc/view.aspx?id=c3ad06c2ae31454cb418ceb2b8da7c45FileFieldName:"file",//提取图片地址:http://www.ncmem.com/doc/view.aspx?id=07e3f323d22d4571ad213441ab8530d1ImageMatch:''});zyCapture.getInstance({config:{PostUrl:"http://localhost:8891/upload.aspx",License2:'',FileFieldName:"file",Fields:{uname:"test"},ImageUrl:'http://localhost:8891{url}'}})// zyoffice,// 使用前请在服务端部署zyoffice,// http://www.ncmem.com/doc/view.aspx?id=82170058de824b5c86e2e666e5be319czyOffice.getInstance({word:'http://localhost:13710/zyoffice/word/convert',wordExport:'http://localhost:13710/zyoffice/word/export',pdf:'http://localhost:13710/zyoffice/pdf/upload'})// 注册菜单E.registerMenu("zyCaptureBtn",zyCaptureBtn)E.registerMenu("WordPasterBtn",WordPasterBtn)E.registerMenu("ImportWordToImgBtn",ImportWordToImgBtn)E.registerMenu("NetImportBtn",NetImportBtn)E.registerMenu("WordImportBtn",WordImportBtn)E.registerMenu("ExcelImportBtn",ExcelImportBtn)E.registerMenu("PPTImportBtn",PPTImportBtn)E.registerMenu("PDFImportBtn",PDFImportBtn)E.registerMenu("importWordBtn",importWordBtn)E.registerMenu("exportWordBtn",exportWordBtn)E.registerMenu("importPdfBtn",importPdfBtn)//挂载粘贴事件editor.txt.eventHooks.pasteEvents.length=0;editor.txt.eventHooks.pasteEvents.push(function(){WordPaster.getInstance().SetEditor(editor).Paste();e.preventDefault();});editor.create();varedt2=newE('#editor2');//挂载粘贴事件edt2.txt.eventHooks.pasteEvents.length=0;edt2.txt.eventHooks.pasteEvents.push(function(){WordPaster.getInstance().SetEditor(edt2).Paste();e.preventDefault();return;});edt2.create();}}h1,h2{font-weight:normal;}ul{list-style-type:none;padding:0;}li{display:inline-block;margin:010px;}a{color:#42b983;}测试前请配置图片上传接口并测试成功
接口测试
接口返回JSON格式参考
为编辑器添加按钮
整合效果
导入Word文档,支持doc,docx
导入Excel文档,支持xls,xlsx
粘贴Word
一键粘贴Word内容,自动上传Word中的图片,保留文字样式。
Word转图片
一键导入Word文件,并将Word文件转换成图片上传到服务器中。
导入PDF
一键导入PDF文件,并将PDF转换成图片上传到服务器中。
导入PPT
一键导入PPT文件,并将PPT转换成图片上传到服务器中。
上传网络图片
一键自动上传网络图片,自动下载远程服务器图片,自动上传远程服务器图片
下载示例
点击下载完整示例