阿里云oss与@wangeditor-next富文本
官网: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 中进行获取 并设置时效性 即使暴露也会失效
配置环境变量:将敏感信息存储在.env文件中,避免硬编码。
ACCESS_KEY_ID=阿里云主账号AK ACCESS_KEY_SECRET=阿里云主账号SK ROLE_ARN=RAM角色ARN(如acs:ram::123456:role/oss-upload)
生成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/*"] }] }) }); };
身份验证中间件:使用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服务异常' }); } });
前端直传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 };
}
};
注意 分片上传 需要再请求头中加
ETag
和x-oss-request-id

ETag 的作用
1. 数据完整性校验
- 分片级校验:每个分片上传后,OSS 会计算该分片的 MD5 哈希值并返回 ETag客户端需保存所有分片的 ETag,用于最终合并时验证分片是否完整且未被篡改
- 防止覆盖错误:若同一分片号重复上传,ETag 可帮助识别新旧分片差异,确保最终合并的是最新有效分片
2.断点续传支持
- 上传中断后,ETag 与分片号(PartNumber)共同构成 PartETag,作为断点恢复的依据。客户端可通过 ETag 判断哪些分片已成功上传,避免重复传输
3. 合并校验
- 完成分片上传时,需提交所有分片的 ETag 列表。OSS 会比对 ETag 的哈希值,若与服务器计算结果不一致,则返回
InvalidDigest
错误,防止合并错误数据
分片上传过程中在请求头中添加 ETag 和 x-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 };
}
};