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图片污染内容的双重目标。

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

相关推荐
人工智能训练9 小时前
【极速部署】Ubuntu24.04+CUDA13.0 玩转 VLLM 0.15.0:预编译 Wheel 包 GPU 版安装全攻略
运维·前端·人工智能·python·ai编程·cuda·vllm
会跑的葫芦怪9 小时前
若依Vue 项目多子路径配置
前端·javascript·vue.js
2601_9495936510 小时前
基础入门 React Native 鸿蒙跨平台开发:模拟智能音响
react native·react.js·harmonyos
xiaoqi92210 小时前
React Native鸿蒙跨平台如何进行狗狗领养中心,实现基于唯一标识的事件透传方式是移动端列表开发的通用规范
javascript·react native·react.js·ecmascript·harmonyos
jin12332211 小时前
React Native鸿蒙跨平台剧本杀组队消息与快捷入口组件,包含消息列表展示、快捷入口管理、快捷操作触发和消息详情预览四大核心功能
javascript·react native·react.js·ecmascript·harmonyos
烬头882112 小时前
React Native鸿蒙跨平台实现二维码联系人APP(QRCodeContactApp)
javascript·react native·react.js·ecmascript·harmonyos
pas13612 小时前
40-mini-vue 实现三种联合类型
前端·javascript·vue.js
摇滚侠12 小时前
2 小时快速入门 ES6 基础视频教程
前端·ecmascript·es6
珑墨13 小时前
【Turbo】使用介绍
前端
军军君0114 小时前
Three.js基础功能学习十三:太阳系实例上
前端·javascript·vue.js·学习·3d·前端框架·three