公司用的是tinymce富文本编辑器,并且集成了importword插件,目前发现一个问题,word中所有的内容都是图片,大概有几十张,由于默认mportword插件会将图片内容转换成base64,这样在提交表单时富文本的那个字段数值就会非常大,导致报表单内容超出范围的错误,而且这么大的内容,存到数据库中也会对后面的查询产生影响。于是决定在读取word中的图片后顺序的将其转换成本地服务器的地址,公司用的是fastdfs文件上传,再将转换后的图片路径保存到富文本字段,这样就不会造成表单内容过大的问题了。上代码
importword_filter函数负责处理导入word之后的逻辑,并通过调用insert函数,将转换后的图片路径回插到富文本编辑器中,我在例子中写了两种写法,目前用的是第一种顺序上传Promise链的方式
,即将word中的图片从第一张往后依次上传,这样保证word中的图片顺序不会乱。
第二种方式也曾经试验过,不会等待上一个Promise完成后才开始下一个Promise,而是会同时开始所有的Promise。因此,图片是并发上传的,但是会导致后台报
ava.io.IOException: recv cmd: 48 is not correct, expect cmd: 100错误
tinymce.init({
selector: '#nvcContent',
language: 'zh_CN',
menubar: false,
plugins: 'code print preview searchreplace autolink directionality visualblocks visualchars image link media template codesample table charmap lineheight pagebreak nonbreaking anchor insertdatetime advlist lists wordcount textpattern help emoticons autosave bdmap formatpainter importword kityformula-editor importword paste ',
toolbar: toolbar,
paste_data_images: true,
width: 950,
height: 650, //编辑器高度
min_height: 400,
fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',
content_style:"body{font-size:16px}",
font_formats: '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats;知乎配置=BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, sans-serif;小米配置=Helvetica Neue,Helvetica,Arial,Microsoft Yahei,Hiragino Sans GB,Heiti SC,WenQuanYi Micro Hei,sans-serif',
//content_security_policy: "script-src *;",
extended_valid_elements: 'script[src]',
//
template_cdate_format: '[CDATE: %m/%d/%Y : %H:%M:%S]',
template_mdate_format: '[MDATE: %m/%d/%Y : %H:%M:%S]',
autosave_ask_before_unload: false,
toolbar_mode: 'wrap',
images_upload_url: '/Home/upload?dir=image',
importword_handler: function(editor, files, next) {
var file_name = files[0].name
if (file_name.substr(file_name.lastIndexOf(".") + 1) == 'docx') {
editor.notificationManager.open({
text: '正在转换中...',
type: 'info',
closeButton: false,
});
next(files);
} else {
editor.notificationManager.open({
text: '目前仅支持docx文件格式,若为doc111,请将扩展名改为docx',
type: 'warning',
});
}
// next(files);
},
importword_filter: function(result,insert,message){
// 使用 DOMParser 解析 HTML 字符串
const parser = new DOMParser();
const doc = parser.parseFromString(result, 'text/html');
// 获取所有 img 元素并移除它们
const images = doc.querySelectorAll('img');
const imageArray = Array.from(images);
// 分批上传图片
function uploadImagesSequentially(images) {
let promiseChain = Promise.resolve(); // 在这个函数中,promiseChain 是一个Promise链。每次循环时,都会在这个链上添加一个新的Promise,这个新的Promise代表了一张图片的上传任务。由于Promise链的特性,后一个Promise(即后一张图片的上传任务)会等待前一个Promise(即前一张图片的上传任务)完成后才开始执行。因此,图片是顺序上传的,符合读取word文档中的图片的逻辑
images.forEach(img => {
promiseChain = promiseChain.then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const src = img.getAttribute('src');
if (src && src.startsWith('data:image')) {
uploadImageToFastDFS(src).then(uploadedUrl => {
if (uploadedUrl != '') {
img.setAttribute('src', uploadedUrl);
}
resolve();
}).catch(error => {
console.error('Error uploading image:', error);
reject(error);
});
} else {
resolve();
}
}, 1); // 设置延迟时间为1秒
});
});
});
return promiseChain.then(() => {
const modifiedResult = doc.documentElement.innerHTML;
insert(modifiedResult); // 回插函数
});
}
uploadImagesSequentially(imageArray);
},
/* 已弃用,并且会导致java.io.IOException: recv cmd: 48 is not correct, expect cmd: 100错误:在这个函数中,uploadPromises 是一个包含所有上传任务的Promise数组。images.map 会遍历所有的图片,并为每张图片创建一个新的Promise,这个Promise代表该图片的上传任务。然后,Promise.all 会接受这个数组,并返回一个新的Promise,这个新的Promise会在所有上传任务都完成时解决。由于Promise.all的特性,它不会等待上一个Promise完成后才开始下一个Promise,而是会同时开始所有的Promise。因此,图片是并发上传的
importword_filter: function(result, insert, message) {
// 使用 DOMParser 解析 HTML 字符串
const parser = new DOMParser();
const doc = parser.parseFromString(result, 'text/html');
// 获取所有 img 元素
const images = doc.querySelectorAll('img');
const imageArray = Array.from(images);
// 并发上传图片
function uploadImagesConcurrently(images) {
// 创建一个包含所有上传任务的Promise数组
const uploadPromises = images.map(img => {
return new Promise((resolve, reject) => {
const src = img.getAttribute('src');
if (src && src.startsWith('data:image')) {
uploadImageToFastDFS(src).then(uploadedUrl => {
if (uploadedUrl != '') {
img.setAttribute('src', uploadedUrl);
}
resolve();
}).catch(error => {
console.error('Error uploading image:', error);
reject(error);
});
} else {
resolve();
}
});
});
// 使用Promise.all来等待所有上传任务完成
return Promise.all(uploadPromises).then(() => {
const modifiedResult = doc.documentElement.innerHTML;
insert(modifiedResult); // 回插函数
});
}
uploadImagesConcurrently(imageArray);
},*/
media_live_embeds: 'true',
// 想要哪一个图标提供本地文件选择功能,参数可为media(媒体)、image(图片)、file(文件),多个参数用空格分隔
file_picker_types: 'file media',
// init_instance_callback显示富文本框输入内容,2019-10-15新增
init_instance_callback: function(editor) {
// console.log('lalalalall------------',editor)
// 视频等上传成功后点击保存时的回调
editor.on('input change undo redo', () => {
console.log('-------保存', editor.getContent())
})
},
// 对嵌入编辑器的代码进行设置,以解决此问题
video_template_callback: function(data) {
return `
<span
class="mce-preview-object mce-object-video"
contenteditable="false"
data-mce-object="video"
data-mce-p-allowfullscreen="allowfullscreen"
data-mce-p-frameborder="no"
data-mce-p-scrolling="no"
data-mce-p-src="${data.source}"
data-mce-p-width="${data.width}"
data-mce-p-height="${data.height}"
data-mce-p-controls="controls"
data-mce-html="%20"
>
<video width="${data.width}" height="${data.height}" controls="controls">
<source src="${data.source}" type="${data.sourcemime}" ></source>
</video>
</span>
`
}
});
该方法读取图片的base64数据,并将其上传到后台,后台返回保存后的图片地址。
function uploadImageToFastDFS(base64Image) {
// 返回一个Promise,解析为上传后的URL
return new Promise((resolve, reject) => {
// 使用fetch函数来调用/upload接口
fetch('/Image/upload', {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: base64Image // 将base64图片数据作为请求体
})
.then(response => {
if (!response.ok) {
reject(new Error('Upload failed with status: ' + response.status));
return;
}
return response.text();
})
.then(data => {
// 检查data是否为null,如果不是null则解析为上传后的URL
if (data !== null && data !='null' && data!='') {
resolve(data);
} else {
reject(new Error('Received null as uploaded URL'));
}
})
.catch(error => {
// 处理请求错误
reject(error);
});
});
}
后台方法如下
@PostMapping("/Image/upload")
@ResponseBody
public String uploadImage(@RequestBody String base64Image) {
logger.info("word图片开始上传");
// 将纯base64编码转换为字节数组
String base64Data = base64Image.split(",")[1];
byte[] byteArray = Base64.getDecoder().decode(base64Data);
FastDFS fastDFS = FastDFS.getInstance();
try{
String uploadedPath = fastDFS.uploadFileFastDFS(byteArray,"png");
logger.info("word图片上传完成");
return uploadedPath;
}catch (Exception e){
logger.error("word图片上传失败");
e.printStackTrace();
return null;
}
}