Skip to content

阿里云oss与@wangeditor-next富文本

约 1931 字大约 6 分钟

阿里云osswangeditor-next前端直传分片上传

2025-05-24

官网:wangEditor-next | 开源 Web 富文本编辑器,开箱即用,配置简单

富文本的内容是HTML 字符串

  • 安装使用

    安装 editor

    shell

    yarn add @wangeditor-next/editor
    # 或者 npm install @wangeditor-next/editor --save

    安装 React 组件(可选)

    shell

    yarn add @wangeditor-next/editor-for-react
    # 或者 npm install @wangeditor-next/editor-for-react --save

    安装 Vue2 组件(可选)

    shell

    yarn add @wangeditor-next/editor-for-vue2
    # 或者 npm install @wangeditor-next/editor-for-vue2 --save

    安装 Vue3 组件(可选)

    shell

    yarn add @wangeditor-next/editor-for-vue@next
    # 或者 npm install @wangeditor-next/editor-for-vue@next --save

    2. 在react 中使用

  • 安装

    yarn add @wangeditor-next/editor
    # 或者 npm install @wangeditor-next/editor --save
    
    yarn add @wangeditor-next/editor-for-react
    # 或者 npm install @wangeditor-next/editor-for-react --save
  • 使用

    const toolbarConfig = { } 主要是工具栏的配置

    // const editorConfig 编辑器的配置

    import '@wangeditor-next/editor/dist/css/style.css' // 引入 css
    
    import React, { useState, useEffect } from 'react'
    import { Editor, Toolbar } from '@wangeditor-next/editor-for-react'
    import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor-next/editor'
    
    function MyEditor() {
        // editor 实例
        const [editor, setEditor] = useState<IDomEditor | null>(null)   // TS 语法
        // const [editor, setEditor] = useState(null)                   // JS 语法
    
        // 编辑器内容
        const [html, setHtml] = useState('<p>hello</p>')
    
        // 模拟 ajax 请求,异步设置 html
        useEffect(() => {
            setTimeout(() => {
                setHtml('<p>hello world</p>')
            }, 1500)
        }, [])
    
        // 工具栏配置
        const toolbarConfig: Partial<IToolbarConfig> = { }  // TS 语法
        // const toolbarConfig = { }                        // JS 语法
    
        // 编辑器配置
        const editorConfig: Partial<IEditorConfig> = {    // TS 语法
        // const editorConfig = {                         // JS 语法
            placeholder: '请输入内容...',
        }
    
        // 及时销毁 editor ,重要!
        useEffect(() => {
            return () => {
                if (editor == null) return
                editor.destroy()
                setEditor(null)
            }
        }, [editor])
    
        return (
            <>
                <div style={{ border: '1px solid #ccc', zIndex: 100}}>
                    <Toolbar
                        editor={editor}
                        defaultConfig={toolbarConfig}
                        mode="default"
                        style={{ borderBottom: '1px solid #ccc' }}
                    />
                    <Editor
                        defaultConfig={editorConfig}
                        value={html}
                        onCreated={setEditor}
                        onChange={editor => setHtml(editor.getHtml())}
                        mode="default"
                        style={{ height: '500px', overflowY: 'hidden' }}
                    />
                </div>
                <div style={{ marginTop: '15px' }}>
                    {html}
                </div>
            </>
        )
    }
    
    export default MyEditor

2. 阿里云oss

oss第三方的 资源云存储

使用

首先在阿里云官网 找到对象存储oss 创建Bucked列表

  • 在项目组安装ali-oss

    pnpm i ali-oss

    创建OSS客户端实例

     // 创建OSS客户端实例
        const ossRequest = new OSS({
          region: 'oss-cn-beijing', // OSS区域,如华东1(杭州)
          accessKeyId: '你的OSS Access Key ID', // OSS Access Key ID
          accessKeySecret: '你的OSS Access Key Secret', // OSS Access Key Secret
          bucket: 'mytext-text' // OSS存储空间名称 创建Bucked的名字
        });

    AccessKeyId

    • 登录阿里云控制台。

    • 点击页面右上角的头像,进入“

      AccessKey管理页面

    • 在该页面中,您可以查看或创建主账号的

需要注意是 accessKeyId accessKeySecret 是私密信息 这样直接写在前端 会有安全问题

可以将 这两个 放到后端token 中进行获取 并设置时效性 即使暴露也会失效

  1. 配置环境变量:将敏感信息存储在.env文件中,避免硬编码。

    ACCESS_KEY_ID=阿里云主账号AK
    ACCESS_KEY_SECRET=阿里云主账号SK
    ROLE_ARN=RAM角色ARN如acs:ram::123456:role/oss-upload
  2. 生成STS临时凭证:使用阿里云STS SDK,创建服务端接口,返回临时访问凭证。

    // services/sts.js
    const OSS = require('ali-oss');
    
    exports.getSTSToken = async () => {
      const sts = new OSS.STS({
        accessKeyId: process.env.ACCESS_KEY_ID,
        accessKeySecret: process.env.ACCESS_KEY_SECRET
      });
    
      return sts.assumeRole(process.env.ROLE_ARN, {
        TimeoutSeconds: 3600, // 1小时有效期
        Policy: JSON.stringify({
          Statement: [{
            Effect: "Allow",
            Action: ["oss:PutObject"],
            Resource: ["acs:oss:*:*:your-bucket/*"]
          }]
        })
      });
    };
  3. 身份验证中间件:使用JWT或会话管理来验证用户,确保只有授权用户能获取凭证。

    首先更具用户信息生成token 返回给前端

    然后判断前端请求头中是否带有有效token 有就返回 oss相关信息

    // routes/oss.js
    const express = require('express');
    const { getSTSToken } = require('../services/sts');
    const router = express.Router();
    
    // 身份验证中间件(示例) // 判断toKen是否有效
    const authCheck = (req, res, next) => {
      if (req.headers.authorization === 'YOUR_JWT_TOKEN') next();
      else res.status(401).send('Unauthorized');
    };
    
    router.get('/sts-token', authCheck, async (req, res) => {
      try {
        const token = await getSTSToken();
        res.json({
          accessKeyId: token.credentials.AccessKeyId,
          accessKeySecret: token.credentials.AccessKeySecret,
          stsToken: token.credentials.SecurityToken,
          bucket: 'your-bucket',
          region: 'oss-cn-beijing'
        });
      } catch (error) {
        res.status(500).json({ error: 'STS服务异常' });
      }
    });
  4. 前端直传OSS:前端从后端获取凭证后,直接上传到OSS,不经过后端服务器。

oss配置跨域

上传文件

// 执行分片上传
    const result = await ossRequest.multipartUpload(fileName, file, options);

分片上传

当我们上传文件过大是时候 就需要将文件进行分片进行上传

分片逻辑

const chunk = file.slice(start, end);
  • startOffset:当前分片起始字节
  • endOffset:分片结束字节(不包含) 通过循环切割,直至覆盖整个文件。

分片拼接

  • 每个分片生成唯一标识

  • 服务端合并步骤

    临时存储:分片保存至临时目录,按文件名+分片序号命名

    顺序校验:根据分片序号排序,确保合并顺序正确。

    完整性检查:比对总分片数与实际接收数,缺失则触发重传

 /**
 * 分片上传文件
 * @param {File} file - 上传文件对象
 * @param {string} customPath - 自定义存储路径(如: 'videos/202405/')
 */
export const multipartUpload = async (file, customPath = '') => {
  try {
    // 生成唯一文件名
    const fileName = `${customPath}${Date.now()}_${Math.random().toString(36).substr(2)}.${
      file.name.split('.').pop()
    }`;

    // 分片上传配置
    const options = {
      progress: (p, checkpoint) => {
        console.log(`进度:${(p * 100).toFixed(1)}%`);
        localStorage.setItem('uploadCheckpoint', JSON.stringify(checkpoint)); // 断点续传支持[6](@ref)
      },
      partSize: 1024 * 1024 * 5, // 每个分片5MB[7](@ref)
      parallel: 4, // 并发上传线程数[8](@ref)
      meta: {
        'Content-Type': file.type, // 自动识别MIME类型[8](@ref)
        'x-oss-object-acl': 'public-read' // 设置文件访问权限
      }
    };

    // 执行分片上传
    const result = await ossRequest.multipartUpload(fileName, file, options);
    
    return {
      success: true,
      url: result.res.requestUrls[0].split('?')[0], // 去除签名参数[7](@ref)
      fileName
    };
  } catch (err) {
    // 断网异常处理
    if(err.code === 'ConnectionTimeoutError') {
      window.addEventListener('online', () => {
        const checkpoint = JSON.parse(localStorage.getItem('uploadCheckpoint'));
        ossClient.multipartUpload(checkpoint.uploadId, checkpoint); // 断点续传[6](@ref)
      });
    }
    return { success: false, error: err.message };
  }
};

注意 分片上传 需要再请求头中加 ETagx-oss-request-id

ETag 的作用

1. 数据完整性校验

  • 分片级校验:每个分片上传后,OSS 会计算该分片的 MD5 哈希值并返回 ETag客户端需保存所有分片的 ETag,用于最终合并时验证分片是否完整且未被篡改
  • 防止覆盖错误:若同一分片号重复上传,ETag 可帮助识别新旧分片差异,确保最终合并的是最新有效分片

2.断点续传支持

  • 上传中断后,ETag 与分片号(PartNumber)共同构成 PartETag,作为断点恢复的依据。客户端可通过 ETag 判断哪些分片已成功上传,避免重复传输

3. 合并校验

  • 完成分片上传时,需提交所有分片的 ETag 列表。OSS 会比对 ETag 的哈希值,若与服务器计算结果不一致,则返回 InvalidDigest 错误,防止合并错误数据

分片上传过程中在请求头中添加 ETagx-oss-request-id 是出于数据完整性校验、请求追踪和错误排查等关键需求。以下是具体原因及作用:

二、x-oss-request-id 的作用

1. 请求唯一标识

  • 每个分片上传请求都会生成唯一的 x-oss-request-id,用于服务端日志记录和客户端请求追踪。当上传失败或出现异常时,开发者可通过此 ID 快速定位问题

2. 错误排查

  • 若分片上传失败(如网络超时、权限不足),OSS 返回的错误响应中会包含 x-oss-request-id,开发者可将其提交至阿里云技术支持,加速问题诊断

3. 并发控制

  • 在高并发分片上传场景中,x-oss-request-id 帮助服务端区分不同客户端的请求,避免分片数据混淆或覆盖

断点续传

主要利用 localStorage.setItem('uploadCheckpoint', JSON.stringify(*checkpoint*));存储分片

当遇到网路问题 或其他问题 会读取 继续上传

// 断网异常处理
    if(err.code === 'ConnectionTimeoutError') {
      window.addEventListener('online', () => {
        const checkpoint = JSON.parse(localStorage.getItem('uploadCheckpoint'));
        ossClient.multipartUpload(checkpoint.uploadId, checkpoint); // 断点续传
      });
    }
    return { success: false, error: err.message };
  }
};