这是一个在富文本编辑器中实现自定义图片上传到阿里云OSS、并防止Base64图片粘贴的完整方案。
实现富文本请参考https://blog.csdn.net/qq_70172010/article/details/156090593?spm=1001.2014.3001.5501
一、自定义图片上传处理程序 (handlers.image)
主要流程:
handlers: {
image: async function() {
// 1. 创建文件选择input
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click(); // 触发文件选择对话框
input.onchange = async () => {
// 2. 获取选中的文件
const file = input.files?.[0];
// 3. 从后端获取阿里云上传签名(按实际接口修改)
const signData = await request.get("/common/getSign", {
params: { dir: "images" }
});
// 4. 构建上传参数
const timestamp = new Date().getTime();
const fileExt = file.name.substring(file.name.lastIndexOf('.'));
const timestampFileName = `${timestamp}${fileExt}`; // 生成唯一文件名
// 5. 创建FormData并添加必要参数
const formData = new FormData();
formData.append('OSSAccessKeyId', signData.data.accessId);
formData.append('policy', signData.data.policy);
formData.append('signature', signData.data.signature);
formData.append('key', `images/${timestampFileName}`); // 存储路径
formData.append('file', file);
// 6. 使用XMLHttpRequest上传到阿里云OSS
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://fs-voss.oss-cn-beijing.aliyuncs.com', true);
xhr.send(formData);
// 7. 上传成功后获取图片URL并插入编辑器
const url = `https://fs-voss.oss-cn-beijing.aliyuncs.com/images/${timestampFileName}`;
// 8. 将图片URL插入到Quill编辑器当前光标位置
const quill = this.quill; // 获取Quill实例
const range = quill.getSelection(); // 获取当前光标位置
quill.insertEmbed(range.index, 'image', url); // 插入图片
quill.setSelection(range.index + 1); // 光标移动到图片后面
}
}
}
关键点:
-
安全性:通过后端获取签名,避免在前端暴露AccessKey
-
文件唯一性:使用时间戳作为文件名,防止覆盖
-
非阻塞上传:使用XMLHttpRequest支持进度监控
-
直接插入:上传成功后直接将图片插入编辑器,无需二次操作
二、监听内容变化清理Base64图片
检测逻辑:
useEffect(() => {
if (value && (
// 检测各种Base64格式
value.includes('data:image/png;base64') ||
value.includes('data:image/jpeg;base64') ||
value.includes('data:image/jpg;base64') ||
value.includes('data:image/gif;base64') ||
value.includes('data:image/webp;base64') ||
value.match(/src=["']data:image\/[^"']*["']/) ||
value.match(/data:image\/[^;]*;base64,[^"'\s]*/)
)) {
message.warning('请使用图片上传功能上传图片,不支持直接粘贴 base64 图片');
// 延迟执行确保DOM更新完成
setTimeout(() => {
const quillEditor = document.querySelector('.ql-editor');
// 1. 找到所有图片元素
const images = quillEditor.querySelectorAll('img');
// 2. 遍历删除Base64图片
images.forEach((img) => {
const src = img.getAttribute('src');
if (src && src.startsWith('data:image')) {
img.remove(); // 从DOM中移除
}
});
// 3. 清理HTML内容中的Base64图片标签
const editorContent = quillEditor.innerHTML;
const cleanContent = editorContent.replace(
/<img[^>]*src=["']data:image\/[^"']*["'][^>]*>/g,
''
);
// 4. 更新编辑器内容
quillEditor.innerHTML = cleanContent;
// 5. 触发变更事件让ReactQuill感知变化
const event = new Event('input', { bubbles: true });
quillEditor.dispatchEvent(event);
}, 50);
}
}, [value]);
三、粘贴事件处理阻止Base64图片
双重检测机制:
// 处理粘贴事件,阻止 base64 图片
const handlePaste = (e: Event) => {
const clipboardEvent = e as ClipboardEvent;
const clipboardData = clipboardEvent.clipboardData;
if (!clipboardData) return;
// 检查是否有图片文件(如截图)
const items = Array.from(clipboardData.items);
const hasImage = items.some(item => item.type.startsWith('image/'));
if (hasImage) {
e.preventDefault();
message.warning('请使用图片上传功能上传图片,不支持直接粘贴图片');
return;
}
// 获取粘贴内容
const htmlContent = clipboardData.getData('text/html');
const textContent = clipboardData.getData('text/plain');
// 检查HTML内容中的base64图片
if (htmlContent && (
htmlContent.includes('data:image/png;base64') ||
htmlContent.includes('data:image/jpeg;base64') ||
htmlContent.includes('data:image/jpg;base64') ||
htmlContent.includes('data:image/gif;base64') ||
htmlContent.includes('data:image/webp;base64') ||
htmlContent.match(/src=["']data:image\/[^"']*["']/) ||
htmlContent.match(/data:image\/[^;]*;base64,[^"'\s]*/)
)) {
e.preventDefault();
message.warning('请使用图片上传功能上传图片,不支持直接粘贴 base64 图片');
return;
}
// 检查纯文本内容中的base64图片
if (textContent && (
textContent.includes('data:image/png;base64') ||
textContent.includes('data:image/jpeg;base64') ||
textContent.includes('data:image/jpg;base64') ||
textContent.includes('data:image/gif;base64') ||
textContent.includes('data:image/webp;base64') ||
textContent.match(/data:image\/[^;]*;base64,[^\s]*/)
)) {
e.preventDefault();
message.warning('请使用图片上传功能上传图片,不支持直接粘贴 base64 图片');
return;
}
};
// 组件挂载时添加粘贴事件监听
useEffect(() => {
const quillElement = document.querySelector('.ql-editor');
if (quillElement) {
quillElement.addEventListener('paste', handlePaste);
return () => {
quillElement.removeEventListener('paste', handlePaste);
};
}
}, []);
四、为何这么实现?
1. 性能考虑:
-
Base64图片会大幅增加HTML体积
-
直接存储在数据库会导致数据库膨胀
-
影响页面加载速度
2. 成本考虑:
-
OSS存储成本远低于数据库存储
-
CDN加速提高图片访问速度
-
便于图片管理和清理
3. 用户体验:
-
统一的图片管理
-
支持图片压缩、格式转换等处理
-
防止因粘贴大图导致编辑器卡顿
4. 安全性:
-
防止恶意上传超大Base64图片
-
控制图片格式和大小
-
通过签名机制保证上传安全
五、整体流程图
用户点击图片按钮 → 弹出文件选择 → 选择图片 →
获取OSS签名 → 上传到阿里云 → 返回URL →
插入编辑器 → 保存时存储URL
用户粘贴内容 → 检测Base64 → 阻止粘贴 →
显示提示 → 清理已粘贴内容 → 引导使用上传功能
正常上传流程:


粘贴复制流程(清空复制的图片内容):

该方案实现了既提供便捷的上传功能,又防止Base64图片污染内容的双重目标。
注意⚠:其他验证及交互逻辑可自行修改!!!