news 2026/10/14 5:25:05

Vue 3 集成 WangEditor 富文本编辑器:从踩坑到工程化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue 3 集成 WangEditor 富文本编辑器:从踩坑到工程化实战

先从一个真实的场景说起。去年我在做一个后台内容管理系统,技术栈锁定了 Vue 3,其中一个刚需就是富文本编辑器。当时我先想到的是在自己熟悉的编辑器和 Vue 3 之间找对接方案,试了一圈发现,要么是官方适配还停在 Vue 2,要么是样式深度定制成本太高,要么是一旦表单复杂一点,双向绑定就各种出问题。折腾到后面,我反而觉得 Vue 3 里集成富文本编辑器这件事,与其说是查一遍 API 就能搞定,不如说是一个需要把生命周期、响应式边界、组件通信、上传链路都理清楚的小工程。如果你现在也正准备在 Vue 3 项目里接入 WangEditor,又不想只是把 Demo 跑通就撒手,那这篇文章应该能帮你省不少弯路。我会把从选型到落地的完整过程,包括踩过的坑和想明白的道理,一次性讲透。

1. 选型前的真实纠结:为什么最后定下 WangEditor

1.1 市面上开源编辑器的短板在哪里

富文本编辑器的选择向来是一个让人头疼的问题。市面上能叫得上名的开源产品不少,但真正放到 Vue 3 + 企业后台这个场景里,各自的痛点都很明显。有的编辑器功能很全,但配置项深入到什么程度全靠文档,文档一旦脱节,你就只能去翻源码。有的编辑器生态不错,但默认样式带着强烈的自家风格,想要融合进现有设计系统,需要覆盖一大堆 CSS。还有的编辑器本身已经停止维护,当初选的版本升级到一半,发现插件体系跟 Vue 3 的组合式 API 完全不搭。

我在做技术选型时给自己定了一个原则:不追求功能最多,而是追求在 Vue 3 里能干净利落地工作,并且值得我花时间去扩展。换句话说,编辑器要能当作一个普通输入组件融入表单系统,而不是一个游离在外、独立于页面状态之外的“孤岛”。

1.2 为什么 WangEditor 能通过考验

WangEditor 的优势,在我看来有四点特别关键。

第一个是 Vue 3 的官方适配做得比较及时。它单独提供了一个面向 Vue 3 的适配组件,你不需要自己去封装创建、销毁、事件绑定的过程,只要把官方组件用起来,底层那些编辑器生命周期管理就不用操心了。第二是功能边界清晰。它默认就覆盖了常规后台需要的加粗、标题、列表、表格、代码块、图片、视频这些能力,不需要为了一个普通需求去组装七八个插件。第三是包体结构和样式有商有量。样式文件独立引入,不依赖某个特定的 UI 框架,这样视觉上可以相对容易地改造成和后台整体风格一致。第四个原因是它在中国开发者社区里的活跃度还算可以,遇到问题的时候,能搜到的经验贴和多语言文档比较多。

当然,它也不是没有缺点。后面我们在做图片上传、表单联动的时候,还是会发现一些文档里一笔带过、实际却很需要仔细处理的细节,这些在下文会专门展开。

1.3 版本选择这条捷径要把握好

我要特别提醒一下版本的坑。WangEditor 目前主流的版本是 v5,而面向 Vue 3 的适配包版本号里带@next标识。如果你跟着网上一些老的教程安装,很容易装成 Vue 2 版本的那一套,结果在项目里一跑,要么组件不渲染,要么控制台报一堆类型错误。正确的安装方式以编辑器本体和 Vue 3 适配包配合为准,建议直接用最新稳定版本而不是低版本。安装好之后,你可以先做一个最小页面验证版本是否匹配,再继续后面的功能开发,而不是等到功能堆到一半才发现基础库不对。

2. 动手前必须弄懂的两件事:生命周期边界和响应式劫持

2.1 编辑器实例不是响应式数据

很多人第一次写集成代码时,最容易犯的错误就是把编辑器实例直接丢进ref()里。表面上代码没什么问题,编辑器也能创建起来,但 Vue 3 的响应式系统会尝试把编辑器实例内部那些复杂的可观测对象统统代理一遍。编辑器内部有自己的一套模型和数据缓存,经过响应式劫持之后,轻则性能下降,重则出现诡异的更新错乱,比如输入的文字延迟显示、光标跳位、工具栏状态不同步。

正确的做法很简单:编辑器实例应该使用shallowRef或者普通变量来保存。它只是在创建完成后被赋值一次,用于后续调用方法比如获取 HTML、清空内容,根本不需要被深层次地监听变化。你真正需要关心的响应式数据,是编辑器的内容字符串,也就是要交给表单去校验、提交的那个值。把这两类数据分清楚,后面的思路就顺了。

2.2 生命周期钩子应该选在哪个时机

编辑器是一个有明确创建和销毁过程的东西,它在 Vue 组件的生命周期里并不是简单声明一下就能自动清理。创建编辑器时,它会在自身 DOM 容器里构建完整的可编辑区域、菜单栏逻辑以及内部事件监听。如果你没有在组件卸载时手动销毁编辑器,编辑器内部注册的那些 DOM 监听器并不会随着组件卸载自动释放。这在单页应用里尤其明显,反复切换页面之后,浏览器的内存占用会一点点涨上去,最后表现为页面越用越卡。

所以一个基础且必要的规范是:在onBeforeUnmount里调用编辑器实例的销毁方法,并且把持有的实例引用置空。另一个容易被忽视的时机是异步组件加载:如果你把编辑器组件做成异步懒加载,一定要确保编辑器模块加载完成之后才开始创建实例,不要在组件还没挂载完成时就调用编辑器方法,否则会有一批“Cannot read properties of null”之类的报错等着你。

2.3 为什么不能直接在原生 DOM 上双向绑定

如果你之前用过 Vue 2 里的某些老封装,可能会习惯性地想给编辑器的可编辑区域挂一个v-model。但在 WangEditor 里,可编辑区域的内容是由编辑器内部维护的,它不是一个普通的受控输入框。Vue 的v-model只对标准输入控件有效,对编辑器内部的 contenteditable 区域并不适用。所以官方适配组件提供的v-model其实是组件层面的值绑定,它背后帮你完成了编辑器内容变化时的回调转发,以及外部值变化时需要调用编辑器 API 来更新内容的逻辑。理解这一点之后,你就不会天真地以为改一个输入框的事件名就能解决所有同步问题。

我特别喜欢用一个类比:编辑器内部的内容像是编辑自己手里的原稿,Vue 里的字符串只是这张原稿的照片。用户每次敲击键盘,编辑会把新的原稿拍一张照片交给 Vue;Vue 收到后拿这张照片去跟表单校验、提交逻辑协同。反过来,如果 Vue 想要修改原稿,不能直接改照片,而要调用编辑器提供的“修改原稿”的方法。数据是双向同步的,但不是通过同一套赋值机制来完成的。

3. 从零搭建一个可复用的编辑器组件:完整代码与逐步拆解

3.1 项目初始化和依赖安装

假设你已经有一个 Vue 3 + Vite 的项目。如果还没创建,可以用官方脚手架先初始化一个空项目,然后安装编辑器本体和 Vue 3 适配包。安装命令里务必看清楚适配包的版本标签,宁可多花一分钟在 npm 上确认版本,也不要装完再返工。安装完成后,在组件里确认两件事:一个是编辑器本体是否被正确引入,另一个是样式文件是否到位。样式这块经常有人漏掉,导致页面出现一个毫无样式可言的文本框,然后被误认为集成失败。

3.2 最小编辑器组件结构

一个可以被复用进任何表单页面的编辑器组件,核心结构大致如下:

<template> <div class="editor-wrapper"> <Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" mode="default" class="editor-toolbar" /> <Editor v-model="valueHtml" :defaultConfig="editorConfig" mode="default" class="editor-content" @onCreated="handleCreated" /> </div> </template> <script setup> import { onBeforeUnmount, ref, shallowRef, watch } from 'vue' import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import '@wangeditor/editor/dist/css/style.css' const props = defineProps({ modelValue: { type: String, default: '' }, placeholder: { type: String, default: '请输入内容' } }) const emit = defineEmits(['update:modelValue']) const editorRef = shallowRef() const valueHtml = ref(props.modelValue) const toolbarConfig = { excludeKeys: ['group-video', 'fullScreen'] } const editorConfig = { placeholder: props.placeholder, MENU_CONF: {} } const handleCreated = (editor) => { editorRef.value = editor } watch(() => props.modelValue, (val) => { if (val !== valueHtml.value && editorRef.value) { editorRef.value.setHtml(val) } }) watch(valueHtml, (val) => { emit('update:modelValue', val) }) onBeforeUnmount(() => { if (editorRef.value) { editorRef.value.destroy() editorRef.value = null } }) </script> <style scoped> .editor-wrapper { border: 1px solid #dcdfe6; border-radius: 4px; z-index: 100; } .editor-toolbar { border-bottom: 1px solid #dcdfe6; } </style>

这段代码看起来简单,但其实已经处理了很多关键问题:编辑器实例用shallowRef保存,避免响应式代理;外部传入的modelValue变化时,通过setHtml更新编辑器内容;编辑器内部内容变化时,通过emit通知父组件。这样组件就能像原生<input>一样直接使用v-model了。

3.3 工具栏配置和菜单定制

工具栏的默认能力已经满足八成场景,但实际项目里总有需要删除菜单的场景。比如内部使用场景里,视频上传往往涉及存储成本和审核问题,这时候直接在工具栏里去掉视频按钮,比在后端做拦截更省事。excludeKeys数组里填上要排除的菜单 key 即可。反过来,如果你想把某个常用功能放到更顺手的位置,可以借助insertKeys把自定义菜单插到固定位置。

还有一个细节值得注意:toolbarConfig和editorConfig里的配置对象在组件创建时会被读取,但如果你在编辑器创建之后再动态改动它们,编辑器不会响应式地重新渲染菜单。所以如果菜单配置依赖后端接口返回的数据,一定要确保数据在组件实例化之前就绪,或者在拿到数据后重新创建编辑器实例。这个顺序问题很容易被忽略。

3.4 样式布局和暗黑模式的考量

后台系统经常要适配暗黑模式,富文本编辑器在这个场景下是最容易翻车的。WangEditor 的默认样式是一套亮色方案,直接在暗色背景下使用时,会出现菜单文字看不清、内容区背景刺眼、边框几乎不可见的问题。处理思路有两种:一种是用 CSS 变量覆盖编辑器内部的基础颜色,另一种是为编辑器容器单独包一层,在暗黑模式下给编辑器容器增加一个局部主题类名,然后针对内部选择器做覆盖。第二种更稳一点,因为编辑器内部的 DOM 层级复杂,全局变量覆盖有可能误伤其他页面。

我踩过的坑是,给编辑器容器设置了z-index却忘记处理下拉菜单的层级。编辑器里的表格插入、颜色选择这类悬浮面板,默认层级可能低于我们页面的头部导航,结果一点开菜单就被遮住。这个问题的解法是把编辑器外层容器的z-index设置在一个合适的层级,同时确认内部的悬浮面板没有被嵌套 CSS 强制压低。

4. v-model 值同步与常见的踩坑场景

4.1 外部赋值和内部输入的冲突

把编辑器封装成v-model组件之后,最常遇到的矛盾是这样的:用户在编辑器里输入了一段内容,编辑器内部触发内容变化,组件把新内容通过emit抛给父组件;父组件更新自己维护的值;这个值又作为props传回给编辑器组件。如果你在组件里对modelValue做了 watch,并且无脑调用setHtml,就会形成一个回环:每次用户输入,编辑器被赋值,赋值操作又可能触发一次新的输入回调,导致光标跳动甚至无限更新。

我的处理方式是在 watch 外部modelValue时做一个比较,只有当前内部内容和外部传入内容不一致时,才调用setHtml,同时保留当前的光标状态。这个判断看似简单,却能在实际使用中省掉大量“输入一个字光标就跳到结尾”的投诉。如果你做的是实时保存功能,还需要在保存成功之后避免重复赋值,具体做法可以在保存返回后比对一下当前内容是否相同。

4.2 初始化时机中的空值问题

另一个常见坑是父组件请求接口拿到详情数据之后,再赋值给编辑器的modelValue。如果接口返回之前,编辑器组件已经创建完毕,那么setHtml调用时机没问题。但如果组件是条件渲染的,比如v-if="dataReady",那么编辑器是在拿到数据之后才创建的,此时props.modelValue已经有值了。可是编辑器初始化时不一定会立刻把这个值渲染进去,这就导致一个典型的“初次赋值丢失”问题。

解决起来不算复杂:在handleCreated回调里,把当前modelValue的值同步到编辑器。或者更稳妥的做法是让父组件保证,在数据就绪之后才渲染编辑器组件,然后利用 watch 机制把初始值补进去。两种方式在大多数业务场景里都能跑通,关键是你要知道问题出在“编辑器创建时不会主动读取外部值”,否则排查半天可能都找不到原因。

4.3 清空内容不要用置空字符串硬怼

如果你尝试直接给编辑器组件的modelValue赋空字符串来清空内容,会发现有时候清不掉。这是因为编辑器内部空内容和<p><br></p>这种空段落结构之间存在差异,直接置空字符串可能导致编辑器内部结构残留不可见节点。更稳妥的方法是调用编辑器实例的clear()方法,或者调用setHtml('')之后再做一次内容获取确认。我习惯在清空按钮的处理函数里拿到编辑器实例引用,执行clear()之后再手动同步一次空字符串给父组件,这样表单校验和提交逻辑都能拿到一个干净的初始值。

顺便提一句,如果你做的是“重新编辑”功能,需要在清空后立刻填充另一段内容,最好不要连续调用clear()和setHtml(),因为这两者之间可能会触发冗余的变更通知。建议直接调用setHtml(newValue),内部自然会完成旧内容替换。

5. 图片上传的完整链路:本地转存、服务端存储与鉴权

5.1 默认的粘贴图片行为及其局限

编辑器默认状态下,用户可以直接从剪贴板粘贴图片,也可以点击工具栏选择本地文件。如果不做任何自定义配置,这些图片通常会被转换成 base64 格式直接嵌入 HTML 里。对于两三张小图片来说,这套机制应付 Demo 没问题,但一旦用户粘贴几十张高清截图,整个 HTML 字符串就会膨胀到上百 KB,后端存储也可能遇到字段长度限制。

所以生产环境的编辑器,几乎无一例外都要自己做图片上传:把图片文件上传到对象存储或自己的服务器,得到的 URL 再插入到编辑内容中。这个改动不在组件层,而在编辑器配置层。

5.2 自定义上传方法的配置位置

在editorConfig的MENU_CONF中,有一个针对上传图片的配置项,可以把系统默认的上传行为替换成自己的实现。下面是一个典型的自定义上传实例:

const editorConfig = { placeholder: '请输入正文内容', MENU_CONF: { uploadImage: { async customUpload(file, insertFn) { const formData = new FormData() formData.append('file', file) const res = await request.post('/api/upload/image', formData, { headers: { Authorization: getToken() } }) insertFn(res.data.url, file.name, res.data.url) } } } }

customUpload方法接收两个参数:一个是所选文件对象,另一个是insertFn。上传成功后,必须调用insertFn把图片地址传入编辑器。注意这里的insertFn有三个参数:url、alt 和 href。第三个参数常常被忽略,但它的作用是把图片包成一个可点击的超链接。如果你的业务里不希望图片被点击跳转,href 传空字符串即可。

5.3 鉴权信息和多图上传的处理

接口鉴权的方式五花八门,常见的是把 token 放在请求头里。由于customUpload内部用的是你自定义的请求封装,所以鉴权逻辑跟编辑器本身没有任何耦合,直接复用项目里现有的请求模块即可。这里反而是一个容易被忽略的注意点:上传方法里如果直接用fetch或者原生XMLHttpRequest,一定要自己处理超时、取消和错误提示,否则上传失败时编辑器里只会静默地不出现图片,用户根本没有感知。

多图上传指的是用户一次选择多张图片,或者连续选择多张。编辑器默认会为每个文件单独调用一次customUpload,所以不需要在组件层面做额外处理。但是这种情况下的并发控制需要留意:如果后端对单用户上传频率有限制,就可能出现一部分图片成功、一部分被拒绝。一个比较实用的做法是在封装上传请求时,加入一个简单的并发队列,限制同时上传 3 张左右,避免高峰时段频繁触发接口限流。

5.4 图片压缩和格式校验的前置逻辑

在customUpload拿到file之后,不要急着直接上传。先做一次客户端校验能帮你避免很多后端返工。比如检查文件类型是否在允许列表里、文件大小是否超过某个阈值、是否需要把大图压成 WebP 格式。如果项目里有现成的图片压缩工具,可以同步调用一下再放进 FormData。这样上传体积小,界面加载快,后端存储压力也小。

同样地,对于视频文件,Editor 配置里也有对应的uploadVideo菜单配置,处理逻辑与图片基本一致,只是视频文件通常更大,上传时间更长,建议配置一个进度提示。不要直接沿用图片上传的无进度模式,否则用户会以为页面卡死了。

6. 把编辑器塞进真实业务:表单校验、持久化和性能优化

6.1 富文本内容的必填校验与长度控制

后端接口往往需要正文内容,但又不能完全信任前端传过去的内容。表单校验侧最关键的一步是:不要把“HTML 字符串不等于空”当成校验通过。因为用户可能只点了一下编辑区域,内容就变成<p><br></p>,字符串并不是空的,但实际没有任何有效文字。比较稳妥的做法是先把 HTML 字符串里的标签去掉,对纯文本内容做 trim 之后再判断是否有实际内容。

关于长度控制也要明确一点:富文本的 HTML 长度和纯文本长度完全是两回事。如果不做纯文本提取,直接看 HTML 长度来决定字数,会出现“页面显示 2000 字,实际发送给后端的是 5000 字符的 HTML 标签”的情况。所以校验规则里需要定义清楚,限制的是纯文本长度,还是 HTML 长度。

6.2 防抖保存和异步数据回填

很多内容管理系统都要求“编辑后自动保存”。此时编辑器的onChange事件会非常频繁地触发,如果每次触发都向后端发一次保存请求,接口压力很大。更好的做法是在组件内部对内容变化做一次防抖,比如 800 毫秒内没有再输入,才把那一次的 HTML 发送出去。这个防抖逻辑不要放在父组件里,最好放在编辑器组件内部,这样每个表单实例都能独立处理自己的保存节奏。

异步回填的姿势同样需要注意。如果编辑内容是分两三个接口分别返回的,页面可能先渲染编辑器,再等待异步数据到达,然后通过modelValue更新内容。这时候如果恰好用户已经在编辑器里输入了一部分内容,异步回填会直接把用户输入覆盖掉。一个比较符合直觉的处理方式是:只有当外部值的变化来源确认为服务端数据而不是用户输入时,才执行覆盖。简单来说,可以在父组件里用单独字段保存“加载中的历史内容”,并通过某个布尔值控制在首次数据加载完成后才向编辑器赋值。

6.3 编辑器组件的懒加载和异步引入

编辑器本体不小,如果整个后台系统所有页面都在刚进入时加载,首屏体积会明显变大。工程上比较常规的做法就是把编辑器组件改成异步组件,只有用户真正用到编辑器时才加载相关脚本。在 Vue 3 中可以使用defineAsyncComponent,加载时的 loading 状态也要处理一下。我实践下来的效果很明显:首屏 JS 体积直接少了一两百 KB,编辑器组件从点击到完全可用也可能需要多等一两秒,但对于后台编辑页面来说,这个等待通常是可以接受的。

值得注意的是,编辑器本体和样式文件如果被多个页面引用,尽量从同一个模块导出,避免不同入口加载出多份样式副本。尤其是样式文件,重复引入会带来一些优先级冲突,定位起来非常费劲。

6.4 组件销毁和内存释放的实操细节

前面提到过onBeforeUnmount里调用销毁方法,这是组件销毁时的底线。但在某些业务场景里,编辑器藏在弹窗或抽屉组件中,弹窗关闭时节点被移除,如果弹窗组件没有正确触发卸载,编辑器实例可能不会被清理。这种情况的排查思路是:打开弹窗、关闭弹窗、重复多次,然后在浏览器任务管理器里观察内存变化。如果内存持续增长且不回落,基本就是编辑器实例没有销毁。

另一个容易忘掉的点是编辑器实例保存了对外部函数或父组件状态的引用。如果这些引用链没有被销毁干净,即使编辑器销毁了,闭包里的大对象依然无法被垃圾回收。所以自定义上传函数内部尽量不要引用大体积数据,比如把整张图片的 base64 保存在某个模块级变量里。

6.5 生产环境构建时的几个坑

当项目进入生产构建阶段,像 Vite 或 webpack 这类构建工具对依赖分割和 CSS 提取会有不同策略。编辑器本体是个较大的依赖,建议在构建配置里把它单独拆成一个 chunk,避免和其他页面代码混在一起,这样浏览器可以长期缓存它。如果遇到“样式被 tree-shaking 掉”的诡异情况,先检查样式文件的引入方式,尽量使用完整的样式入口而不是按需引入片段。

还有一个小细节是压缩代码时,编辑器内部的某些属性名可能被混淆,这会引发线上环境的功能异常,而本地调试完全正常。遇到这类问题,先确认构建配置有没有对第三方库的压缩做过特殊处理,尤其是开启了一些激进的高级优化选项之后,问题概率会明显增加。保守一点的方案是给编辑器相关依赖设置较高的构建优先级,或者关闭高级选项的某几个开关。

7. 最后分享一点我的真实体会

把 WangEditor 接进 Vue 3 项目这件事,难点从来不在“把组件渲染出来”,而在于你愿不愿意花时间搞清楚它和 Vue 响应式体系、表单体系、网络请求体系之间的分界。编辑器实例、内容字符串、配置对象这三者,性质完全不同,能各自归位,大部分问题就能靠逻辑而非搜索解决。我在用了半年之后最深的感受是:它确实不是功能最全的编辑器,但它足够克制、足够稳定,只要你按照工程化的方式去封装,它能像拼图一样自然嵌进你的业务系统里。如果后面你要做更复杂的动态表格、代码块高亮、多人协同,也可以沿着我在上传、懒加载、组件销毁这几条线继续往深处扩展。希望这篇实战拆解能给你省下几个深夜排查的时辰。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 5:24:49

AI全自动剪辑软件V15.1:从部署到调优的完整指南

简介&#xff1a;人工智能全自动剪辑软件15.1版以智能算法辅助视频剪辑&#xff0c;主要面向自媒体运营者、短视频制作者和需要批量处理素材的后期人员&#xff0c;可自动完成镜头筛选、片段拼接和基础调色&#xff0c;降低手动剪辑的重复劳动。压缩包内总计707个文件&#xff…

作者头像 李华
网站建设 2026/10/14 5:24:24

拆解瑞幸咖啡研报:无人机与卫星背后的尽调方法论

简介&#xff1a;这份54页的PDF&#xff0c;是雪湖资本于2022年11月发布的瑞幸咖啡投资研究报告&#xff0c;适合关注咖啡赛道、消费复苏及中概股投资机会的投资者与研究人士阅读。报告以瑞幸“浴火重生”为主线&#xff0c;系统拆解其投资价值&#xff1a;饮料化咖啡显著扩大了…

作者头像 李华
网站建设 2026/10/14 5:23:25

AndroidAssetStudio大作业实操:构建、部署、路线选择与避坑

简介&#xff1a;AndroidAssetStudio-master 是一份面向高校移动开发课程的完整大作业项目&#xff0c;围绕 Android 资源素材自动生成场景设计&#xff0c;基于 Android Asset Studio 工具链改造而来&#xff0c;适合需要完成类似课题、熟悉前端构建流程或研究资源生成器实现的…

作者头像 李华
网站建设 2026/10/14 5:22:42

阿克曼纯跟踪算法

为了使车辆后轴中心点跟踪圆弧虚线路径到达C点&#xff0c;在三角形OAC中&#xff0c;需要满足的正弦定理关系&#xff1a;其中&#xff0c;车辆坐标系下预瞄点的朝向角 α ( t ) \alpha(t) α(t)为变量。另外&#xff0c;定义横向位置误差为车辆当前姿态和预瞄点在横向上的误差…

作者头像 李华
网站建设 2026/10/14 5:22:34

Spring Boot集成Dubbo构建社交应用后端:探花交友源码架构解析与实践

简介&#xff1a;一份基于Spring Boot与Dubbo构建的探花交友平台源码包&#xff0c;面向有一定Java基础、想学习分布式服务开发或积累社交类项目经验的后端开发者和求职者。项目围绕陌生人交友场景&#xff0c;实现了手机号一键登录、个人信息完善、黑名单管理、测灵魂测试题、…

作者头像 李华