难点:图片是对象存储,图片URL是存在有效期的,所以系统做了定时任务更新图片,数据库存储该字段内容时只存储对象存储的KEY(不存在URL),当定时任务来执行更新的时候,要查找到符文本中的图片KEY就相当的困难,甚至容易更新出错的可能
解决思路:富文本中存在的图片KEY,再单独创建一个字段存储(多个图片可以使用中间表或者用特殊符号分隔这些KEY),在查询时(或更新时)通过该字段中KEY先通过缓存拿到URL,然后去富文本内容中通过KEY替换成URL,达到前端显示是图片URL,存储在表中是KEY,定时任务只需要更新存储KEY的字段到缓存即可
- 安装quill
npm i react-quill --save
npm i quill --save
- 封装成组件的方式使用
import React, {useEffect} from 'react';
import {message} from 'antd';
import Quill from 'quill';
import 'react-quill/dist/quill.snow.css';
import {uploadInfoImage} from '../apis/index';
/**
* quill富文本编辑器
* @param setQuillMaps 存储ecs对象
* @param setDataLoading
* @desc
* 内容回显: document.getElementById('id').children[0].innerHTML = '';
* 内容获取: document.getElementById('id').children[0].innerHTML;
* @returns {*}
* @constructor
*/
const QuillEditor = ({setQuillMaps, setDataLoading}) => {
//只能使用这种方式,useState()的方式不行
let quill = {};
//创建一个map存储富文本上传的图片,ecsKEY作为map的key,ecsURL作为map的value
const quillImages = [];
useEffect(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
quill = new Quill('#quill-editor', {
// 设置主题
theme: 'snow',
modules: {
toolbar: {
container: [
[{'header': [1, 2, false]}],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{'align': []}],
[{'color': []}],
[{'indent': '-1'}, {'indent': '+1'}],
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'direction': 'rtl'}],
['link', 'image', 'video'],
['clean']
],
handlers: {
image() {
imageHandler();
}
}
}
}
});
}, []);
const imageHandler = () => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
const formData = new FormData();
formData.append('infoImage', input.files[0]);
try {
setDataLoading(true);
const {key, url} = await uploadInfoImage(formData);
setDataLoading(false);
if (!key || !url) {
message.error('图片上传失败');
} else {
quillImages.push({key, url});
setQuillMaps(quillImages);
//获取当前光标位置
const selection = quill.getSelection();
//插入图片
quill.insertEmbed(selection.index, 'image', url);
//光标位置加1
quill.setSelection(selection.index + 1);
}
} catch (e) {
setDataLoading(false);
message.error('图片上传出错');
}
};
};
return (
<div id="quill-editor" />
);
};
export default QuillEditor;
- 引入使用
//setQuillMaps:存储富文本中上传的图片,单独用字段存起来;setDataLoading是上传图片的加载
<QuillEditor setQuillMaps={setQuillMaps} setDataLoading={setDataLoading} />
- 富文本回显内容
document.getElementById('quill-editor').children[0].innerHTML = data['info'];
- 获取富文本的内容
const infoTxt = document.getElementById('quill-editor').children[0].innerHTML;
- 处理符文本的图片(特别重要,如果存在修改的情况,需要区分历史上传的图片和本次上传的图片)
infoImages 是单独作为字段传到后台存到单独的字段中
const infoImages = [];
if (!isEmpty(oldQuillMaps)) {
infoImages.push(...oldQuillMaps);
}
if (!isEmpty(quillMaps)) {
infoImages.push(...quillMaps);
}
- 后端存库处理
//处理内容富文本的html内容
List<String> newKeys = new ArrayList<>();
final String[] info = {shop.getInfo()};
logger.info("info[0]========>" + info[0]);
List<ECSBean> quillMaps = shop.getQuillMaps();
if (StringUtils.isNotEmpty(info[0]) && CollectionUtil.isNotEmpty(quillMaps)) {
//说明内容有值,并且传了图片,那么用quillMaps里的key替换掉info里的URL,才能存到数据库
Map<String, String> map = quillMaps.stream().collect(Collectors.toMap(ECSBean::getKey, ECSBean::getUrl, (key1, key2) -> key2));
//keys = quillMaps.stream().map(ECSBean::getKey).distinct().collect(Collectors.toList());
List<String> keys = new ArrayList<>(map.keySet());
keys.stream().forEach(key -> {
String url = map.get(key).replaceAll(" ", "");
logger.info("url=============>" + url);
if (info[0].contains(url)) {
info[0] = info[0].replace(url, String.valueOf(key));
//把url缓存到数据库
asyncService.updateShopInfoImages(key, url);
//处理key(因为存在删除图片)
newKeys.add(key);
}
});
}
shop.setInfo(info[0]);
String infoImages = newKeys.toString()
.replaceAll(",", "~")
.replaceAll("\\[", "")
.replaceAll("]", "")
.replaceAll(" ", "");
shop.setInfoImages(infoImages);
- 查询时需要处理
//处理内容富文本的html内容
final String[] info = {shop.getInfo()};
List<ECSBean> quillMaps = new ArrayList<>();
String infoImages = shop.getInfoImages();
if (StringUtils.isNotEmpty(info[0]) && StringUtils.isNotEmpty(infoImages)) {
List<String> ecsKeys = new ArrayList<>();
if (infoImages.contains("~")) {
ecsKeys.addAll(Arrays.asList(infoImages.split("~")));
} else {
ecsKeys.add(infoImages);
}
ecsKeys.stream().forEach(ecsKey -> {
if (info[0].contains(ecsKey)) {
String url = redisHelper.getStr(Consts.appCode + "-shop-info-images-" + ecsKey);
if (StringUtils.isEmpty(url)) {
url = storeService.getUrl(ecsKey);
//再更新到缓存
asyncService.updateShopInfoImages(ecsKey, url);
}
info[0] = info[0].replace(ecsKey, url);
quillMaps.add(new ECSBean(ecsKey, url));
}
});
shop.setInfo(info[0]);
shop.setQuillMaps(quillMaps);
}