React 富文本图片上传 OSS 并防止 Base64 图片粘贴

这是一个在富文本编辑器中实现自定义图片上传到阿里云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图片污染内容的双重目标。

注意⚠:其他验证及交互逻辑可自行修改!!!

相关推荐
咬人喵喵5 小时前
告别无脑 <div>:HTML 语义化标签入门
前端·css·编辑器·html·svg
404NotFound3056 小时前
基于 Vue 3 和 Guacamole 搭建远程桌面(利用RDP去实现,去除vnc繁琐配置)
前端
咚咚咚ddd6 小时前
AI 应用开发:Agent @在线文档功能 - 前端交互与设计
前端·aigc·agent
旧梦吟6 小时前
脚本工具 批量md转html
前端·python·html5
ohyeah6 小时前
React 中兄弟组件通信的最佳实践:以 Todo 应用为例
前端
岁月宁静6 小时前
一个 AI 聊天功能,背后至少 8 个你没想到的工程细节
前端·vue.js·aigc
白兰地空瓶6 小时前
从 Todo 项目看 React 组件通信:核心逻辑与优化技巧
react.js
一字白首6 小时前
Vue3 入门,从项目创建到组合式 API 全解析(含 provide/inject)
前端·javascript·vue.js