CKEditor 5 自定义上传适配器Custom Upload Adapter实战指南从接口契约到自有服务器接入【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5本文面向需要在 CKEditor 5 富文本编辑器中接入自有文件上传服务、存储或业务逻辑的开发者。通过阅读本文你将掌握上传适配器Upload Adapter的完整工作方式它如何在编辑器、FileRepository、FileLoader与你自己的服务器之间充当桥梁UploadAdapter接口的精确契约以及如何编写、注册并调试一个可用的自定义上传适配器。文章以仓库内packages/ckeditor5-upload包的源码实现为主线结合官方SimpleUploadAdapter、Base64UploadAdapter等实现作为对照参考。为什么需要自定义上传适配器默认情况下CKEditor 5 并不知道你的文件该发往哪里、以何种格式发送、由谁鉴权。官方提供的几种上传适配器面向通用场景而实际项目往往有专属需求服务器集成上传接口的 URL、请求方法、表单字段、响应结构是后端既定的前端必须对齐安全与认证需要附带 Token、CSRF 头或withCredentials跨域凭据业务逻辑文件需要预处理、命名、附加业务字段或在上传后做额外处理。CKEditor 5 为此提供了开放 API一组用于上传适配器的类与接口覆盖从用户拖入/选择文件到服务器返回响应、图片出现在编辑器内容中的全过程。这套机制的全部核心实现都集中在仓库的 packages/ckeditor5-upload/src 目录下。上传架构中的三个角色从源码结构看上传机制由三个核心角色协作完成均在 packages/ckeditor5-upload/src/filerepository.ts 中定义角色职责关键定义位置FileRepository插件上传的中央管理点持有所有FileLoader、维护上传总量与进度、注册上传适配器工厂FileRepository类第 38 行起FileLoader控制单个文件读取 → 上传的过程维护状态机与可观察的上传进度属性FileLoader类第 267 行起UploadAdapter接口你编写的自定义逻辑负责与服务器通信发送文件、处理响应UploadAdapter接口第 587 行起FileRepository适配器的注册入口FileRepository是upload包的核心插件它依赖PendingActions用于在上传期间把编辑器标记为有未完成操作见其requires声明第 111-113 行。它对外暴露的关键工厂属性public declare createUploadAdapter?: ( loader: FileLoader ) UploadAdapter;这正是自定义适配器的注册点任何上传插件都会在init()阶段给createUploadAdapter赋值把如何为一个文件创建适配器实例的逻辑交给FileRepository。例如官方Base64UploadAdapter在 packages/ckeditor5-upload/src/adapters/base64uploadadapter.ts 中是这样注册的public init(): void { this.editor.plugins.get( FileRepository ).createUploadAdapter loader new Adapter( loader ); }如果用户拖入文件时createUploadAdapter尚未定义FileRepository.createLoader()会记录一条filerepository-no-upload-adapter警告并返回null见 filerepository.ts。换句话说启用了图片上传等需要上传能力的功能但没有注册任何上传适配器上传将不会发生。FileLoader状态机与进度FileLoader封装单个文件的生命周期其status是一个可观察属性取值为idle | reading | uploading | aborted | error合法流转为读取idle → reading → idle或aborted/error上传idle → uploading → idle或aborted/error。它提供的三个对外方法read()使用内部FileReader包装类见 packages/ckeditor5-upload/src/filereader.ts把文件读为 Data URL供需要读取内容的适配器使用upload()状态切换到uploading后调用你适配器实例上的upload()方法见 filerepository.tsreturn this.file .then( () this._adapter.upload() ) .then( data { this.uploadResponse data; this.status idle; return data; } );abort()终止读取或上传若处于uploading状态且适配器实现了abort()会同步调用适配器的方法第 515-533 行。此外FileLoader暴露可观察的uploaded已上传字节数、uploadTotal总字节数可能因请求头等额外数据与文件大小不同而暂不可用与派生的uploadedPercent属性官方适配器通过更新前两者来驱动进度条。UploadAdapter 接口契约你编写的自定义适配器必须实现 UploadAdapter 接口它只有两个成员export interface UploadAdapter { upload(): PromiseUploadResponse; abort?(): void; } export type UploadResponse Recordstring, unknown;upload()必须实现upload()执行真正的上传过程返回一个在数据上传成功时 resolve 的 Promise。resolve 的值是包含上传文件信息的对象。最简形式是一个default字段{ default: http://server/default-size.image.png }当服务器为同一张图片生成了多个尺寸时可以按像素宽度作为键返回多尺寸 URL{ default: http://server/default-size.image.png, 160: http://server/size-160.image.png, 500: http://server/size-500.image.png, 1000: http://server/size-1000.image.png, 1052: http://server/default-size.image.png }注意源码中的一条硬性约定返回多张图片时最宽的一张必须与default相同否则图片的width属性无法被正确设置。如果还需要透传服务器返回的其他自定义属性则需把 URL 放进urls对象其余属性放在urls之外平级返回{ myCustomProperty: foo, urls: { default: http://server/default-size.image.png, 500: http://server/size-500.image.png } }abort()可选但强烈建议abort()用于中止上传调用后应让upload()返回的 Promise 被 reject例如中止底层 XHR。当用户在图片上传过程中删除图片时FileLoader.abort()会触发该回调见 filerepository.ts。实战编写一个 XMLHttpRequest 自定义适配器结合接口契约下面是一个最小可用的完整适配器实现结构与官方SimpleUploadAdapter内部实现一致可对照 packages/ckeditor5-upload/src/adapters/simpleuploadadapter.ts 阅读// MyUploadAdapter.ts import { FileLoader, UploadAdapter, UploadResponse } from ckeditor/ckeditor5-upload; export class MyUploadAdapter implements UploadAdapter { public loader: FileLoader; private xhr?: XMLHttpRequest; constructor( loader: FileLoader ) { this.loader loader; } public upload(): PromiseUploadResponse { return this.loader.file .then( file new Promise( ( resolve, reject ) { this._initRequest(); this._initListeners( resolve, reject, file! ); this._sendRequest( file! ); } ) ); } public abort(): void { if ( this.xhr ) { this.xhr.abort(); } } private _initRequest(): void { const xhr this.xhr new XMLHttpRequest(); xhr.open( POST, https://your-server.com/upload, true ); xhr.responseType json; } private _initListeners( resolve, reject, file: File ): void { const xhr this.xhr!; const loader this.loader; const genericErrorText Couldnt upload file: ${ file.name }.; xhr.addEventListener( error, () reject( genericErrorText ) ); xhr.addEventListener( abort, () reject() ); xhr.addEventListener( load, () { const response xhr.response; if ( !response || response.error ) { return reject( response response.error response.error.message ? response.error.message : genericErrorText ); } // 归一化响应支持 { url } 或 { urls } 两种服务器返回形态。 const urls response.url ? { default: response.url } : response.urls; resolve( { ...response, urls } ); } ); // 驱动 FileLoader 的上传进度。 if ( xhr.upload ) { xhr.upload.addEventListener( progress, evt { if ( evt.lengthComputable ) { loader.uploadTotal evt.total; loader.uploaded evt.loaded; } } ); } } private _sendRequest( file: File ): void { const data new FormData(); data.append( upload, file ); this.xhr!.send( data ); } }再把适配器注册到编辑器配置中通过editor.plugins.get( FileRepository )注入工厂// editor.ts import { ClassicEditor } from ckeditor/ckeditor5-editor-classic; import { FileRepository } from ckeditor/ckeditor5-upload; import { MyUploadAdapter } from ./MyUploadAdapter; ClassicEditor .create( document.querySelector( #editor )!, { plugins: [ /* 图片上传、段落等插件 */ ], // 由框架在适配器工厂被调用时传入。 // 更常见的做法定义一个插件在其 init() 中注册 createUploadAdapter。 } ) .then( editor { editor.plugins.get( FileRepository ).createUploadAdapter loader new MyUploadAdapter( loader ); } ) .catch( err console.error( err ) );更符合 CKEditor 5 插件规范的做法是自建一个小插件在init()里完成注册与官方Base64UploadAdapter、SimpleUploadAdapter、CKFinderUploadAdapter的写法保持一致——它们无一例外都在init()中给FileRepository.createUploadAdapter赋值分别见 base64uploadadapter.ts 与 packages/ckeditor5-adapter-ckfinder/src/uploadadapter.ts。官方适配器对照SimpleUploadAdapter 与 Base64UploadAdapter在动手写自定义适配器之前先确认官方方案是否已满足需求SimpleUploadAdapter最接近自建方案的参考SimpleUploadAdapter使用XMLHttpRequest以POST方式把文件作为FormData的upload字段发送到指定服务器见 simpleuploadadapter.ts其配置项定义在 packages/ckeditor5-upload/src/uploadconfig.ts配置项类型说明simpleUpload.uploadUrlstring必填服务器处理上传的 URL缺失时记录simple-upload-adapter-missing-uploadurl警告simpleUpload.headersobject \| (file) object随请求发送的 HTTP 头可静态指定也可基于文件动态返回是实现认证与 CSRF 防护的推荐位置simpleUpload.withCredentialsboolean默认false仅影响跨站请求开启后允许携带 Cookie 等凭据对应 XHR 的withCredentials示例ClassicEditor.create( document.querySelector( #editor )!, { simpleUpload: { uploadUrl: http://example.com, headers: { X-CSRF-TOKEN: CSRF-Token, Authorization: Bearer JSON Web Token }, withCredentials: true } } );动态请求头写法simpleUpload: { uploadUrl: http://example.com, headers: ( file ) ( { X-File-Name: file.name, X-File-Size: file.size } ) }注意SimpleUploadAdapter是商业授权功能源码中标注为isPremiumPlugin如果你的项目是开源许可编写自定义适配器或使用 Base64 方案通常是更合适的选择。Base64UploadAdapter免服务器方案Base64UploadAdapter不发起任何网络请求而是用window.FileReader的readAsDataURL把图片转成 Base64 字符串直接写进编辑器输出图片随文本一同存储浏览器无需额外请求即可显示见 base64uploadadapter.ts。它适合内容体积小、无后端的演示场景但会显著增大数据量。进度、取消与错误处理要点进度在upload()内部通过更新loader.uploadTotal与loader.uploaded驱动进度FileRepository会聚合所有 loader 的字节数到自身的uploaded、uploadTotal属性并按uploaded / uploadTotal * 100派生出uploadedPercent见 filerepository.ts同时通过PendingActions展示Upload in progress N%的待处理状态第 244-259 行。取消在适配器中持有xhr引用abort()时调用this.xhr.abort()FileLoader.abort()只会在uploading状态且适配器提供abort方法时才调用它。错误upload()的 Promise 应被 reject 以进入error状态服务器返回{ error: { message } }时官方实现会将该消息作为拒绝原因这提示自定义适配器应约定一致的服务端错误结构参考 simpleuploadadapter.ts。排查与常见问题拖入图片无反应且控制台出现filerepository-no-upload-adapter警告说明启用了图片上传功能但未注册任何适配器既未配置官方适配器也未设置createUploadAdapter工厂。该警告的完整说明见 filerepository.ts。read()/upload()抛filerepository-read-wrong-status/filerepository-upload-wrong-status说明在状态非idle时重复调用了方法属于误用。上传成功但图片不显示优先检查upload()resolve 的对象是否包含default字段、多尺寸时最宽图片是否与default一致以及返回的 URL 是否可公开访问。进一步阅读图片上传功能的完整总览官方适配器对比、集成方式docs/features/image-upload.mdUploadAdapter接口与FileLoader状态机的源码级细节packages/ckeditor5-upload/src/filerepository.tsSimpleUploadConfig各配置项的类型定义与注释packages/ckeditor5-upload/src/uploadconfig.ts官方适配器的测试用例可作为自定义适配器行为验证的参考packages/ckeditor5-upload/tests/filerepository.js 与 packages/ckeditor5-upload/tests/adaptersCKFinder 服务端连接器适配器的实现另一份完整的 XHR 上传实现范例packages/ckeditor5-adapter-ckfinder/src/uploadadapter.ts上传适配器的本质是一段文件进、URL 出的桥接代码只要严格遵守upload()/ 可选abort()的契约与响应格式约定就能在完全不动编辑器内核的前提下把 CKEditor 5 的文件上传无缝接入任何后端系统。【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考