医院里的HIS系统还在用百度编辑器(UEditor)的,基本都能懂这个痛:医生从Word里复制一段病史、一份查房记录过来,文字倒是好好的,可一碰到Word里的插图,要么干脆不显示,要么变成一长串又臭又乱的编码,严重的时候整篇病历格式全崩。最麻烦的是,HIS系统不像普通官网那样想换编辑器就换,病历模板、历史数据、科室的录入习惯全都绑在上面,动一刀都得掉层皮。这篇文章就从我实际处理过的某医院HIS系统集成场景出发,把Word图片粘贴这件事从原理到代码、再到内网部署环境里的坑,完整拆开讲一遍。
1. 为什么医院HIS系统里的Word图片粘贴处理比普通网站复杂得多
先泼盆冷水:别指望给UEditor加个一两行的配置就能解决,医院环境里的问题往往不是技术本身难,而是约束条件太多。我做过的这个模拟场景里,HIS系统部署在院内网,前端还在用jQuery时代的老代码,浏览器被限定在某种兼容模式,数据库那头还牵着十几年的老表结构。这种情况下,处理一张Word图片粘贴,实际上是在处理一整条链路:浏览器剪贴板拦截、图片二进制获取、接口上传、URL回写、HTML还原、最后再跟编辑器自带的功能做兼容。
普通企业网站遇到同样的需求,最简单粗暴的做法是直接把UEditor后台的上传接口改成接收base64字符串,前端配置一下UEDITOR_CONFIG里的catchRemoteImageEnable,或者干脆让UEditor的pasteImage开着就行。但医院HIS系统里这条路通常走不通,原因有三。
第一,很多医院的病史录入终端是老机器、老浏览器,甚至还有科室里开着IE内核的定制浏览器。UEditor官方早就停止维护了,它对现代剪贴板API(比如clipboardData.files)的支持本身就很不完整,更不要说那些非标准浏览器环境。你要求医生统一换Chrome 90以上版本,现实吗?不现实。第二,病历数据是医疗文书的一部分,图片最终不能以base64的形态塞进数据库字段里,否则一个月下来那几张表膨胀到几个GB都是小事,检索、备份、迁移的时候会痛不欲生。第三,医院网络环境特殊,前后端接口经常要经过网闸、网关转发,跨域配置、相对路径、图片回显这套东西,在公网开发环境里可能随便配配就好,在内网环境里常常要跟网络科的同事来回折腾好几天。
所以,正确的处理思路应该是:在前端拦截粘贴行为,把Word里的图片变成真正的图片文件,走自己的上传接口落到服务器存储里,再把返回的URL以标准HTML的形式插入编辑器内容区。整个过程对医生来说应该是无感的——他复制、粘贴,图片自己就上传了、显示了,就完事了。这篇文章的方案,都是围绕这个思路展开的。
提示:如果你的项目只是普通的PC网站、不需要考虑老浏览器和数据库膨胀,那直接把UEditor的
pasteImage开启、后端接收base64就够了。但既然你的背景里有“医院HIS”,那就老老实实按下面的链路走,少踩很多坑。
2. Word复制过来的图片,在剪贴板里到底是什么形态
要解决问题,先得知道病根在哪。很多人疑惑:“我在Word里复制一张图,到编辑器里粘贴,为什么UEditor拿到的是那么长一串东西?”原因在于,浏览器从Word里读取剪贴板内容时,能拿到的不是一张干净的图片文件,而是一堆HTML片段,里面用不同的方式“描述”了这张图。常见的有四种形态。
第一种,本地文件路径。Word复制出来的内容里,img标签的src可能长这样:
<img src="file:///C:/Users/doctor/AppData/Local/Temp/xxx/Word/Image.png" width="200" height="150">这是最坑的。这个路径只在医生自己的电脑上有意义,换一台电脑、换一个浏览器,路径完全失效。就算在同一台电脑上,浏览器出于安全策略也基本不会加载本地文件的file://路径,所以粘贴之后显示出一张“裂图”。
第二种,VML格式的图形。在IE时代,Word往剪贴板里塞的是VML(Vector Markup Language)对象,会看到类似下面的东西:
<v:shape id="图片_x0020_1" o:spid="_x0000_s1026" type="#_x0000_t75" style="width:150pt;height:100pt"> <v:imagedata src="file:///C:/.../Image.png" o:title=""/> </v:shape>现在的浏览器基本都不认VML,直接粘贴出来就是一段看不见的乱码标签。这也是为什么网上很多老帖子里,医生反馈“粘贴出来啥都没有”的原因之一。
第三种,base64编码的data URL。部分浏览器会把图片编码成base64直接放在src里,看起来是一长串以data:image/png;base64,开头的内容:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." />这种形态至少图片还能显示,但缺点是一张两三百KB的图片,转成base64之后体积要膨胀约33%,如果真的以HTML文本形式存进数据库,后续读写压力都很大。
第四种,独立的图片文件。这种情况一般不是从Word里复制图片,而是从文件管理器或微信、截图工具里复制。这时clipboardData.files里能取到真正的File对象,处理起来最干净。
所以,你会发现一个麻烦的现实:医生在Word里的操作各有各的花样,有的图片是“嵌入式”,有的是“浮于文字上方”,有的是一张截图粘贴到Word里又被再复制,剪贴板里到底能给浏览器什么,既取决于Word版本,也取决于浏览器,还跟操作系统的剪贴板机制有关。
关键认知:不要试图写一套代码去兼容上面所有形态的HTML解析。最稳妥的策略是“建立在
paste事件上统一拦截,能拿到File就走File,拿不到File就从HTML里抠base64,两条腿走路”,后面的章节就是按这个逻辑展开的。
3. 前端核心方案:从拦截粘贴事件到图片URL回写
这个方案的主干分四步:拦截paste事件、提取图片数据、调用上传接口、把返回的图片URL插回编辑器。我用jQuery配合原生事件来写,因为医院老项目里普遍还在用jQuery,直接把代码嵌进页面里就能跑。
3.1 第一步:给UEditor的粘贴动作装上拦截
UEditor初始化完成之后,它的DOM容器其实是一个设置了contenteditable属性的iframe body区域。我们可以在这个容器上监听paste事件:
var ue = UE.getEditor('editorContainer'); ue.ready(function() { var body = ue.document.body; // UEditor内部的document if (body.addEventListener) { body.addEventListener('paste', handlePaste, false); } else { body.attachEvent('onpaste', handlePaste); // 老IE兜底 } });这里有个容易踩的小坑:很多人直接给$("#editorContainer")绑定paste事件,但UEditor的内容区在iframe里,外层div根本接收不到粘贴事件,必须拿到ue.document.body来挂。其次,现在的代码里不要用ue.body这种老写法,UEditor不同版本对这个API支持不一致,先用ue.ready拿到实例,再通过ue.document取body最稳。
3.2 第二步:从粘贴事件里提取图片文件
来看handlePaste的核心逻辑:
function handlePaste(e) { // 拿到剪贴板数据 var clipboardData = e.clipboardData || window.clipboardData; if (!clipboardData) return; // 优先走files通道 var files = clipboardData.files; if (files && files.length > 0) { var imageFiles = []; for (var i = 0; i < files.length; i++) { var file = files[i]; if (file.type && file.type.indexOf('image/') === 0) { imageFiles.push(file); } } if (imageFiles.length > 0) { // 说明剪贴板里直接有图片文件,直接上传 e.preventDefault(); uploadImageFiles(imageFiles); return; } } // 如果没有文件,尝试从HTML里提取base64图片 var html = clipboardData.getData('text/html'); if (html) { var base64Images = extractBase64Images(html); if (base64Images.length > 0) { e.preventDefault(); uploadBase64Images(base64Images); } } }这段代码里值得展开讲的是文件类型的判断。file.type.indexOf('image/')能过滤掉非图片文件,比如医生从Word里复制一段带表格的内容时,剪贴板里可能混着好几种格式。注意这里用indexOf('image/')而不是=== 'image/png',是为了把jpg、png、gif、bmp、webp一次性都放进来。另外,别忘记调用e.preventDefault(),否则拦截之后浏览器还会执行默认粘贴动作,图片URL会被UEditor自带的逻辑再处理一次,容易产生双重插入。
3.3 第三步:把图片File对象上传到自己的接口
接下来是上传函数:
function uploadImageFiles(fileList) { for (var i = 0; i < fileList.length; i++) { (function(file) { var formData = new FormData(); formData.append('upfile', file); // 如果你的后端还希望接收一些额外参数,比如病案号、操作人ID,可以加在这里 // formData.append('bizType', 'medical_record'); // formData.append('operatorId', getCurrentUserId()); $.ajax({ url: '/his-upload/uploadImage', type: 'POST', data: formData, processData: false, contentType: false, xhr: function() { var xhr = new XMLHttpRequest(); // 这里可以加一个上传进度条,给医生一个反馈 return xhr; }, success: function(res) { if (res && res.code === 0) { var imageUrl = res.data.url; insertImageToEditor(imageUrl); } else { // 提示失败 alert('图片上传失败:' + (res.msg || '未知错误')); } }, error: function(err) { alert('图片上传接口异常'); } }); })(fileList[i]); } }这里我特意把formData的字段名设置成upfile,这是UEditor官方后端接口的默认接收字段名,好处是如果医院那边已经有现成的UEditor上传接口,你可以先直接打那个接口试试,省得后端还要为这个改代码。当然,如果接口路径和参数都已经按项目自己约定好了,用你自己的字段名就行。
注意:老项目的后端如果还在用
request.getParameter这种方式取文件,那就必须保证contentType为false,否则浏览器会把上传数据序列化成字符串,后端收到的文件就是坏的。
3.4 第四步:处理base64形态的图片
如果剪贴板里没有直接的File对象,那就得从HTML里把base64图片抠出来。这一步是兼容Word老格式的关键。
function extractBase64Images(html) { var images = []; // 匹配base64图片的img标签 var regex = /<img[^>]+src=["'](data:image\/[^"']+)["'][^>]*>/gi; var match; while ((match = regex.exec(html)) !== null) { images.push(match[1]); } return images; } function uploadBase64Images(base64List) { for (var i = 0; i < base64List.length; i++) { (function(base64Data) { // 把base64转成Blob文件对象 var blob = dataURLtoBlob(base64Data); // 构造一个File对象,方便走FormData上传 var file = new File([blob], 'paste_' + Date.now() + '.png', { type: blob.type }); uploadImageFiles([file]); })(base64List[i]); } } function dataURLtoBlob(dataURL) { var arr = dataURL.split(','); var mime = arr[0].match(/:(.*?);/)[1]; var bstr = atob(arr[1]); var n = bstr.length; var u8arr = new Uint8Array(n); for (var i = 0; i < n; i++) { u8arr[i] = bstr.charCodeAt(i); } return new Blob([u8arr], { type: mime }); }这里有三个细节值得注意。
第一个细节,正则里我用["']同时兼容了单引号和双引号,因为Word复制出来的HTML标签属性顺序并不规范,有的浏览器生成的img标签用的是单引号。第二个细节,dataURLtoBlob里没有对大base64做分批处理,如果一张图片好几MB,atob的时候页面可能会卡一下。建议在转换前判断一下base64字符串长度,如果超过3MB就提示医生“图片太大,请压缩后重试”,别硬扛。第三个细节,上传之后要在回调里做类型兜底。因为从dataURL转出来的blob.type在部分老浏览器里可能是空字符串,上传到后端之后后端会校验MIME类型,可能直接拒收。所以前端要在uploadImageFiles里再补一句判断,如果file.type为空,按image/png处理。
3.5 第五步:把上传成功后返回的URL插入编辑器
这一步是整个方案的临门一脚:
function insertImageToEditor(imageUrl) { // 这里要拼一个完整的HTML片段,不要只插一个img标签 var imgHtml = '<img src="' + imageUrl + '" style="max-width:100%;" />'; // 插入光标位置 ue.execCommand('insertHtml', imgHtml); }这里有个经验之谈:插入的img标签里,style="max-width:100%"一定要加上。医院里很多医生会直接把拍下来的照片或CT影像截图贴进去,原图随便就是三四千像素宽,如果不限制最大宽度,图片会把编辑区撑爆,页面直接横着滚动,医生和护士看到这种界面会直接骂人。另外,如果UEditor版本很旧,ue.execCommand('insertHtml')在某些情况下会闪一下再显示内容,你可以在调用前用ue.focus()把焦点先拉回来,效果会顺滑很多。
另外一个小的细节:别用
$(editor).append()这类操作往编辑器里塞内容,那会破坏UEditor内部的undo/redo栈,医生保存后又想撤销粘贴操作时,往往就失灵了。用execCommand('insertHtml')才是正统做法。
4. 前端方案跑通后,医院内网环境里最折磨人的三个坑
代码写好后,在本地开发环境怎么跑都顺畅。但一旦部署到医院的内网服务器,各种奇怪的状况就出来了。我把实际排查过程中遇到的三类问题列出来,按出现频率排序。
4.1 坑一:接口跨域和反向代理路径重写
HIS系统的前端页面可能跑在http://192.168.1.10:8080/his/,而后端上传接口在http://192.168.1.20:8090/his-upload,浏览器直接请求跨域。公网项目大家习惯了配CORS或Nginx反代,但医院内网的网络管理员层级多,很多人不敢随便动网关配置。
如果你没有权限改Nginx或网关,有个临时兜底方案:在医院内网系统里,前后端都在同一台网关下,通常可以走“相对路径转发”的办法。比如前端页面统一通过网关的/his-api/前缀转发到后端服务,上传接口就用/his-api/upload/uploadImage,这样对浏览器来说始终是同源的,不会触发跨域。同时让网络科的同事在网关层加一条规则:location /his-api/ { proxy_pass http://后端真实地址; }。这类操作在纯内网环境里并不复杂,只要沟通顺畅,基本一两天能搞定。
如果实在动不了网关,那就只能在后端接口里加CORS响应头了。需要注意的是,如果HIS项目还涉及登录态(通常是一个科室电脑共用账号),Access-Control-Allow-Credentials必须设为true,并且Access-Control-Allow-Origin不能写*,要写具体的页面域名。这就不展开了。
4.2 坑二:上传接口返回的URL是相对路径还是绝对路径
这是一个非常容易被忽略的细节。上传成功后,后端返回的URL如果写成绝对路径,比如http://192.168.1.20:8090/his-upload/20240612/xxx.png,浏览器在编辑器里能正常显示,但一旦医生保存病历后,其他科室的电脑通过HIS系统打开这条病历时,图片域名可能还是旧的IP端口,就会出现图片打不开的尴尬。反过来,如果后端只返回相对路径如/his-upload/20240612/xxx.png,前端插入编辑器时又可能因为当前页面的路径层级不同导致拼接错误。
我的建议是:前端拿到返回的图片URL后,统一做一次“标准化处理”再插入,规则是——如果返回的是相对路径,前面补上网关的根路径;如果是绝对路径,只保留路径部分。为了省事,通常让后端接口直接返回相对路径,前端在插入前拼上window.location.origin:
function normalizeImageUrl(url) { if (url.indexOf('http') === 0) { return url; } return window.location.origin + url; }当然,如果你们的HIS系统图片访问还需要额外的token鉴权(有些医院对病历图片做了访问控制),这一步会更复杂,后面第6章再讲。
4.3 坑三:UEditor的自动抓取远程图片功能在捣乱
UEditor里有一个很有名的配置项叫catchRemoteImageEnable,默认值是true。它的作用是,当粘贴的内容里出现非本站域名图片时,后台会自动抓取远程图片并转存到自己的服务器上。听起来很贴心,但在医院环境下它经常帮倒忙。
比如,医生从院内科研系统的网页复制一段带图片的内容过来,图片URL是另一个内网系统的地址。UEditor觉得这是远程图片,就去抓取。如果那个内网系统有独立的登录鉴权,你的后端抓取请求肯定拿不到图片,然后UEditor会在图片位置显示一个红叉或者一个过渡中的placeholder,整个编辑区看起来就像出了大问题。
如果你自己已经实现了图片上传处理,强烈建议在UEditor初始化配置里把catchRemoteImageEnable: false关掉,否则两个上传链路互相打架,排查起来非常费劲。
5. 浏览器兼容性收尾:不能让老终端成为拦路虎
医院环境的浏览器兼容问题,远比普通项目严重。我处理的那台终端,系统还是Windows 7,浏览器还停留在IE 11兼容模式,平时打开HIS系统用的是科室内部的定制外壳浏览器。你在Chrome上写的e.clipboardData.files,在IE里很可能是undefined。
针对老浏览器,前端代码必须做双通道兜底。具体来说,判断逻辑是这样的:
function getClipboardFileList(e) { var clipboardData = e.clipboardData || window.clipboardData; if (!clipboardData) return null; // 标准浏览器优先 if (clipboardData.files && clipboardData.files.length > 0) { return clipboardData.files; } // 老IE/老Edge的兜底方案 if (clipboardData.getData && clipboardData.items) { var items = clipboardData.items; var filesResult = []; for (var i = 0; i < items.length; i++) { if (items[i].kind === 'file') { var file = items[i].getAsFile(); if (file) { filesResult.push(file); } } } return filesResult; } return null; }另外还有一个老浏览器特有的问题:复制Word内容时,clipboardData.getData('text/html')可能会抛异常,因为部分浏览器对HTML格式的支持不完整。建议在调用前包一层try...catch,拿不到HTML就放弃base64提取,直接走纯文本粘贴:
var html = ''; try { html = clipboardData.getData('text/html'); } catch (ex) { html = ''; }如果你们的HIS系统里真的还有大量IE 8或以下环境,那前面写的attachEvent('onpaste')只能帮你把事件接住,但clipboardData.files和clipboardData.items基本不可用。这种情况下,连从剪贴板取图片文件都做不到,唯一可行的方案是给UEditor加一个“本地图片上传”按钮,让医生把Word里的图片先另存到本地,再通过上传按钮传进编辑器。这个方案虽然笨一点,但绝对可靠,也符合医院信息科“稳定压倒一切”的办事风格。
经验之谈:在做医院类项目时,前端代码宁可多写几个
if分支,也尽量不要依赖单一API。把兼容代码写在一个公共函数里,后面接其他医院系统的时候直接复用,会很省心。
6. 后端接口与存储策略:图片不该往数据库里硬塞
前端把图片文件传上来了,后端怎么接、怎么存,同样有一堆讲究。很多HIS系统的老后端是Java或PHP写的,文件存储还在用最原始的本地磁盘目录。这里给一个比较稳妥的做法。
6.1 接口设计的基本要求
上传接口至少要做三件事:校验文件类型、校验文件大小、存储文件并返回URL。
文件类型校验不要只信前端的Content-Type,这个字段可以被伪造。建议后端通过读文件头几个字节(magic number)来判断真实类型。比如JPEG开头是FF D8 FF,PNG开头是89 50 4E 47,GIF是47 49 46 38。医院场景下主要收JPG、PNG、GIF、BMP、WebP这几种就够了,其他类型直接返回错误。
大小限制一般设置为10MB以内。病历图片不是影像科的DICOM原图,只是给医生看个病灶截图、手术照片、化验单翻拍,10MB完全够用。如果医生贴的是病理报告原图,可能一张十几MB,那就得单独考虑分片上传或后台压缩,不在这个方案里展开。
存储路径建议按日期分目录,例如/upload/2024/06/12/,文件名用UUID或时间戳+随机数生成,不要用医生本地的文件名,防止中文名、特殊字符引发路径问题。下面是一段Java后端的简化示例:
String saveDir = "/data/his-upload/" + datePath; File dir = new File(saveDir); if (!dir.exists()) { dir.mkdirs(); } String ext = getExtensionByMagicNumber(fileBytes); // 按文件头识别扩展名 String filename = UUID.randomUUID().toString().replace("-", "") + "." + ext; File targetFile = new File(saveDir, filename); file.transferTo(targetFile); // 返回相对路径 return "/his-upload/" + datePath + "/" + filename;这段代码里的getExtensionByMagicNumber是关键,不要用originalFilename来截取扩展名,万一医生复制的文件名是“图 片 1. jpg”,中间的空格、特殊符号很容易搞出问题。
6.2 数据库里存什么
数据库的字段设计,建议只存相对路径或URL字符串,不要把图片二进制写进CLOB/BLOB字段,除非医院那边确实有硬性的归档要求。
这样做有几个实际好处:图片文件在文件服务器上好迁移、好备份;数据库查询时不背大字段,病历列表页打开速度快;历史病历归档时图片可以按日期目录整体拷走,不用跟数据库一起导出再挨个解包。代价是要额外处理孤儿文件清理,这个后面会说。
6.3 图片回显的权限控制
医院病历里的图片有一定的敏感属性,如果HIS系统本身对图片访问没有任何鉴权,任何只要拿到URL的人都能直接打开,那是不行的。有些医院会要求图片URL必须带一个临时的token参数,比如/his-upload/2024/06/12/xxx.png?token=xxxx。
这个token由后端在上传接口返回时生成,同时写入一个短时效缓存(比如Redis或内存Map),图片请求网关在转发前校验token是否有效和是否过期。如果你们的HIS系统后端还没有这套机制,至少要把图片目录设置在Web应用目录之外,例如不要放在webapps/ROOT/upload/下面,而是放在/data/his-upload/,由后端接口通过流的方式输出图片,并在输出时校验会话登录状态。这样虽然多写一个getImage接口,但安全性和可控性都会好很多。
7. 消毒清理:别让孤儿图片把服务器磁盘塞爆
图片上传方案上线跑几周后,你一定会面临一个问题:医生粘贴了图片,用编辑器里的功能删掉了,或者粘贴后觉得不对又撤销了,但后端文件已经真实存储下来了,下次保存病历并不会自动删除这张图。日积月累,服务器磁盘会爆掉。这就是“孤儿图片”问题。
处理思路分两种:被动清理和主动标记。
被动清理最简单,写一个定时任务,扫描上传目录,找出创建时间超过30天、且没有被数据库里任何记录引用的图片,定期删除。但这里有个难点,怎么判断“没有被引用”?常规做法是:上传图片时往数据库的一张image_ref表里插一条记录,字段包括图片路径、业务类型、关联业务ID(比如病历号)。如果保存病历成功后,把关联业务ID回填进这条记录。定时清理时,只要找出关联业务ID为空的记录,再比对创建时间,超过N天就删文件并把记录也删掉。这种方案在UEditor应用场景里很成熟。
主动标记则是在前端做联动。医生在编辑器里删除图片时,通过ue.execCommand无法直接拦截到具体删了哪个节点,得用ue.ready里面注册contentChange事件来对比编辑器内当前所有图片URL与之前是否减少,减少的部分就可以调接口通知后端标记删除。这个方案虽然能做到实时清理,但实现复杂度高,而且医生反复撤销粘贴时容易误判。我建议第一次上线先用定时任务兜底,等稳定了再考虑实时标记。
8. 上线前必须做一轮“医生视角”验收测试
这类功能在办公室里用开发数据怎么测都测不出真问题,一定要拿真实医生的工作习惯来测。
我整理了一份验收清单,你可以照着逐项打勾:
| 操作场景 | 测试方式 | 通过标准 |
|---|---|---|
| Word内嵌图片复制 | 在Word文档里插入一张图片,选中复制后粘贴到编辑器 | 图片正常显示,图片文件出现在服务器目录,病历保存后重新打开图片存在 |
| Word表格内复制的图片 | 做一个带图片的表格,全选复制后粘贴 | 表格结构保留,图片正常上传,不出现乱码或VML标签 |
| 截图工具粘贴 | 用截图工具截屏后直接粘贴 | 图片正常上传,不被拦截 |
| 多张图片连续粘贴 | 一次复制多张图片,分两次粘贴 | 图片都上传成功,顺序不乱,编辑区不卡死 |
| 大图片粘贴 | 用一张5MB以上的大图测试 | 有上传过程提示,画面不假死,能成功或给出友好错误提示 |
| 老浏览器打开 | 用科室实际使用的浏览器打开 | 图片粘贴功能可用,至少不会报脚本错误 |
| 保存后二次打开 | 粘贴图片后保存病历,关闭页面重新进入编辑 | 图片地址正确,图片正常回显,URL不缺失 |
测试的时候,特别注意“从Word里复制的第一张图片”和“从网页里复制的图片”的区别,很多开发环境里只测了截图粘贴,上线之后医生用Word一贴就翻车,这种教训我见过不止一次。
最后再分享一个我在实际项目里换来的教训:千万别急着去改UEditor源码。网上有一种做法是直接修改baidu/ueditor里的_getImgUrl之类的内部方法,或者给UE.commands['insertimage']打补丁。这类方案对特定版本有效,但UEditor的版本在集成商手里五花八门,一旦以后有人升级编辑器版本或换了前端框架,补丁就失效了,排查成本极高。老老实实把处理逻辑挂在外围事件上,通过标准API操作,才经得起时间考验。另外,如果你们的HIS系统本身就有一个统一的文件上传公共接口,优先复用它,而不是单独给UEditor开一个专用上传通道,省得后面做权限控制、磁盘清理时要同时维护两套逻辑。