ueditor算是后端cms里富文本做的比较全面的项目,奈何比较古早,年久失修,文档也不全面。
现在往富文本编辑器里输入东西,比较喜欢直接粘贴,不喜欢自己写,尤其粘贴别的地方的文本,不过u1s1,秀米那些编辑器的样式确实挺好看的。粘别的地方的东西,总是会粘到一些意想不到的东西,比如别的域名的图片,别的地方要是删了图片,那我们这边的图片就全失效了,可不能这样。
我们需要:
1、把粘过来的文本里的图片url全部抓取出来
2、用服务器根据这些url爬取网络图片,传到本地或者oss
3、返回已经存好的图片路径,并替换
思路就是这三步走,现在我们来具体看代码吧。
在 ueditor或者neditor里的配置文件,(ufig.js里,有这么一个配置项
//抓取远程图片配置
catchRemoteImageEnable:true,
我们先把它开启起来,这个主要作用是告诉编辑器,在我编辑之后别忘了做检查,看看有需要抓的远程图片就去抓,这只是个开关而已。
如果是在vue里面的封装好的话,直接改data里的自定义配置字段开启也是可以的哦。
myConfig: {//增加字段即可catchRemoteImageEnable:true,},
当然,只开这个是远远不够的,我们要解决以下问题:
1、哪些图片是要抓的!总不能循环抓,把自己插入的图片都给抓了吧。
2、抓到哪里去
我们来看看当我们开启远程图片抓取的开关之后,u(n)editor干了些什么吧
代码在(u)neditor.all.js里,搜索catchRemoteImageEnable
哈哈,找到了,原来这个功能是作为插件插到plugins属性下面的catchremoteimage的。我们来逐行查看代码,随着注释来看看吧!
UE.plugins["catchremoteimage"] = function() {//me就是编辑器自己啊,也就是UEvar me = this,ajax = UE.ajax;/* 设置默认值 *///如果我们设置false,下面的代码都不会运行了,会直接返回if (me.options.catchRemoteImageEnable === false) return;me.setOpt({catchRemoteImageEnable: true});//给编辑器加上事件,当有粘贴事件发生之后,才会触发远程图片抓取。//哈哈,果然只有粘贴才会发生这种事情me.addListener("afterpaste", function() {me.fireEvent("catchRemoteImage");});//我们的目标:(白名单)内的不抓取,其他的抓取到对应的(接口),监听远程抓取事件me.addListener("catchRemoteImage", function() {//获取白名单,这个可以在配置里配哦,键就是catcherLocalDomain,值是一个数组//举例值为:catcherLocalDomain: ['127.0.0.1', 'localhost', 'img.baidu']var catcherLocalDomain = me.getOpt("catcherLocalDomain"),catcherActionUrl = me.Opt("catcherActionName")),catcherUrlPrefix = me.getOpt("catcherUrlPrefix"),catcherFieldName = me.getOpt("catcherFieldName");//储存符合抓取要求的元素var remoteImages = [],loadingIMG = me.options.themePath + me.options.theme + '/images/spacer.gif',//获取富文本里style里带url的元素,以及img元素imgs = me.document.querySelectorAll('[style*="url"],img'),//判断图片链接是否在白名单内的方法test = function(src, urls) {if (src.indexOf(location.host) != -1 || /(^.)|(^/)/.test(src)) {return true;}if (urls) {for (var j = 0, url; (url = urls[j++]); ) {if (src.indexOf(url) !== -1) {return true;}}}return false;};//对带有外链的元素逐个排查for (var i = 0, ci; (ci = imgs[i++]); ) {//ci为current item,当前元素(这就是我们从上面搜出来的元素里的一个)//如果有word_img这个属性的话,就略过该元素if (ci.getAttribute("word_img")) {continue;}//如果该元素是一img元素deName == "IMG"){//获取图片元素的src的值,“”空字符串兜底,因为有的图片确实是没有写src链接的var src = ci.getAttribute("_src") || ci.src || "";//判断头部是否含有https、http或者ftp字样,并且不在白名单内if (/^(https?|ftp):/i.test(src) && !test(src, catcherLocalDomain)) {//将路径丢到一个数组里,暂时存起来,先不处理remoteImages.push(src);// 将该元素的路径换成咱的loading路径,class也换成loading的样式,然后新建一个属性_src,带_的,把真正的路径暂存进去domUtils.setAttributes(ci, {class: "loadingclass",_src: src,src: loadingIMG})}} else {// 获取背景图片urlvar backgroundImageurl = ci.place(/.*s?url(['"]?/, '').replace(/['"]?).*/, '');//跟上面的img差不多的判断if (/^(https?|ftp):/i.test(backgroundImageurl) && !test(backgroundImageurl, catcherLocalDomain)) {//同样暂时不处理,存在数组里remoteImages.push(backgroundImageurl);ci.style.cssText = ci.place(backgroundImageurl, loadingIMG);//将真正的url存在自构造的属性 data-background里,有点jquery那味了domUtils.setAttributes(ci, {"data-background": backgroundImageurl})}}}//前面只是洗菜,择菜,下面要开始做菜了,目前我们有原料:一个存了真正url的数组。//我们需要把这些路径传到接口去,服务器端需要将这些路径的网络图片爬取下来,存到我们想存的地方去//然后返回给前端处理后的路径,后端代码(后端代码与ueditor也是有约定的)会附在后面//现在我们假装已经成功获取到新的路径了
我们这边对ajax进行了一个封装,遵循ajax的写法,将数组参数转成以“,”分隔的字符串,然后去请求接口。我们其实可以对catcherActionUrl做判断,不同的配置去调用不同的接口路径。接下来是对回调的处理,回调失败没啥好处理的,主要是对回调成功的处理,服务器端返回的数据格式为以下。当然这个数据约定不是固定的,想要取什么键就取什么键,只要能让前端认出来这个原来是谁,现在又是谁就行
{
list:[{"source":原路径,“url”:新路径"state": "SUCCESS"或"FAIL" }
],
"state": "SUCCESS"或"FAIL"
}
if (remoteImages.length) {catchremoteimage(remoteImages, {//成功抓取后才会触发的回调success: function (r) {try {var info = r.state !== undefined? r: eval("(" + r.responseText + ")");} catch (e) {return;}/* 获取源路径和新路径 */var i,j,ci,cj,oldSrc,newSrc,list = info.list;/* 抓取失败统计 */var catchFailList = [];/* 抓取成功统计 */var catchSuccessList = [];/* 抓取失败时显示的图片 */var failIMG = me.options.themePath + me.options.theme + '/images/img-cracked.png';//之前我们把那些要抓取的元素的显示全换成了Loading,我们要把这些元素的src换成现在的for (i = 0; ci = imgs[i++];) {oldSrc = ci.getAttribute("_src") || ci.src || "";oldBgIMG = ci.getAttribute("data-background") || "";//简单的双重循环匹配两个数组内的相同元素,这里分别写了4重判断,想改进可以自行改进for (j = 0; cj = list[j++];) {if (oldSrc == cj.source && cj.state == "SUCCESS") {newSrc = catcherUrlPrefix + cj.url;// 上传成功时删除uploading动画veClasses(ci, "loadingclass");domUtils.setAttributes(ci, {"src": newSrc,"_src": newSrc,"data-catchResult": "img_catchSuccess" // 添加catch成功标记});catchSuccessList.push(ci);break;} else if (oldSrc == cj.source && cj.state == "FAIL") {// 替换成统一的失败图片veClasses(ci, "loadingclass");domUtils.setAttributes(ci, {"src": failIMG,"_src": failIMG,"data-catchResult": "img_catchFail" // 添加catch失败标记});catchFailList.push(ci);break;} else if (oldBgIMG == cj.source && cj.state == "SUCCESS") {newBgIMG = catcherUrlPrefix + cj.url;ci.style.cssText = ci.place(loadingIMG, newBgIMG);veAttributes(ci, "data-background");domUtils.setAttributes(ci, {"data-catchResult": "img_catchSuccess" // 添加catch成功标记});catchSuccessList.push(ci);break;} else if (oldBgIMG == cj.source && cj.state == "FAIL") {ci.style.cssText = ci.place(loadingIMG, failIMG);veAttributes(ci, "data-background");domUtils.setAttributes(ci, {"data-catchResult": "img_catchFail" // 添加catch失败标记});catchFailList.push(ci);break;}}}// 监听事件添加成功抓取和抓取失败的dom列表参数me.fireEvent('catchremotesuccess', catchSuccessList, catchFailList);},//失败的回调,本次请求超时error: function () {me.fireEvent("catchremoteerror");}});}});
};
这边附上前后端分离的后端接收代码部分,走的都是接口,这部分也可以自行修改,自行修改的部分,跟前端部分匹配就行,其他没什么大问题。你也可以在前端传的时候,多加一个参数,后端根据这个参数传到不同的位置,都是可以的。还有什么问题的话,欢迎留言。
/*** 抓取远程图片*/@PostMapping("/catchRemoteFile")public Map<String, Object> catchRemoteFile(HttpServletRequest request, @RequestParam(required = false) String source,@RequestParam(required = false) String goal) {Map<String, Object> map = new HashMap<>();if (StringUtils.isBlank(source)) {map.put("state", "FAIL");map.put("message", "上传文件不能为空");return map;}String[] sources = source.split(",");if (sources.length == 0) {map.put("state", "FAIL");map.put("message", "上传文件不能为空");return map;}List<Map<String, Object>> list = uploadRemoteFileToAliyun(sources);map.put("list", list);map.put("state", "SUCCESS");return map;}/*** uploadRemoteFileToAliyun:(上传远程图片到阿里云). <br>** @param sources 远程图片地址* @return** @since 1.0*/private List<Map<String, Object>> uploadRemoteFileToAliyun(String[] sources) {List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();String aliyunDomain = Config().getAliyunDomain();for (int i = 0; i < sources.length; i++) {try {String source = sources[i];if (StringUtils.isNotBlank(source)) {URL url = new URL(source);URLConnection con = url.openConnection();try (InputStream is = InputStream();) {// 取出后缀名String suffix = source.substring(source.lastIndexOf("."), source.length());String objectKey = Config().getAliyunPrefix() + "/neditor/caych/" + DateTimeUtil.format(new Date(), DateTimeUtil.YYYYMMDD) + "/" + UUID.randomUUID() + suffix;ossService.uploadFileToAliyun(is, objectKey);//上传文件Map<String, Object> map = new HashMap<String, Object>();map.put("source", source);map.put("url", aliyunDomain + objectKey);map.put("state", "SUCCESS");result.add(map);} catch (Exception ex) {(ex.getMessage(), ex);}}} catch (Exception ex) {(ex.getMessage(), ex);}}return result;}
还有一个问题,如果是配置秀米插件插入内容的话,就不是复制黏贴触发,需要在xiumi-ue-dialog-v5.html里加上对抓取远程事件的触发
配置完之后,如果是vue封装的,需要重新重启项目
本文发布于:2024-02-05 03:29:18,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170723006462665.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |