vivipure / blog

Funkun's blog.
https://funkun.hashnode.dev/
2 stars 0 forks source link

编辑器重构,数据建模实践 #11

Open vivipure opened 2 years ago

vivipure commented 2 years ago

一. 业务背景

日常的工作有很大一部分都是编辑器的开发。由于业务需求,今年推动了全景编辑器2.0的重构。本文会主要讲述此次重构的全部细节。

作为一款编辑器,老版全景编辑器的功能存在很多问题。

  1. 数据实时变化,用户操作后立即保存。不能进行撤销,也无历史记录功能
  2. 前后端耦合严重,新功能开发需要依赖前后端共同协作
  3. 用户使用体验不好,操作后数据流转和接口请求逻辑混乱,有明显的卡顿
  4. 业务打包历史包袱重,各种大组件和事件传递,维护成本高

当前的业务架构不在满足后续业务的开发,于是我决定重构。

二. 重构目标

作为编辑器的主程,产品在规划2.0版的新功能时,我基于当前业务现状和主管提出了重构的想法。通过重构后,主要能够实现的目标有:

  1. 满足2.0的产品需求,不进行实时保存,在前端存储数据,由用户决定数据的保存
  2. 减少前后端耦合,减少接口请求。后续新功能可以完全有前端进行开发。
  3. 性能优化,提高用户的使用体验
  4. 全景逻辑和UI框架逻辑抽离,让全景编辑的逻辑完全独立,即时使用其他UI框架也能快速实现编辑器

三. 具体实操

3.1 数据建模

首先是数据,和H5编辑器不同,老版本的全景编辑器在前端存储的数据是很零散的。完全通过 krapno 读取全景的数据。在数据更改时直接提交后端,更改配置 XML 文件,前后端耦合非常严重。

因此数据方面我完全放弃从 krpano 实例中读取数据,而是在进入编辑器时获取该物料的 XML配置文件,然后将 XML 转化为 DOM,从 DOM 中获取数据构建整个全景的 JSON 树,后续的编辑器操作都基于 JSON 操作。

XMLJSON 的转化过程中,我对全景中所有的组件都进行数据建模。包括 热点,图层,标尺等,声明的类大约 20余种。每个类都提供解析 XML 获取 JSON 的方法,最终对整个 XML 进行操作,就获取到所有的组件数据了。

class Hotspot {
    static parseDOM(DOM) {
        ...
        return {...}
    }
}

对数据建模的好处, 不止在于解析数据。上面我也提到,我希望将全景编辑的逻辑和UI逻辑抽离,而通过类来对相关的组件进行数据和逻辑的封装就非常好用。我将 krpano 相关的操作全部封装到对应的类中,操作数据时,直接调用相关的类和实例的方法即可

class Hotspot {
    add() {}
    remvoe() {}
    update(...) {}
}

这样就不用在 Vue 代码中写 krpano 的相关逻辑了,界面表单在更新 JSON 数据时,使用封装好的类同步预览的视图即可。

3.2 组件拆分

旧代码中包含很多的大组件,例如热点组件,热点的类型有7种,每种热点的属性各有异同。老组件中直接包含所有热点的配置,各种状态判断,十分混乱。

这里的重构逻辑很简单,拆分组件,每种类型热点对应相关的属性组件,编辑时通过数据类型标识识别对应组件然后用动态组件进行展示。

对于其他的大组件,我们也采用一样的方法进行抽离,保证组件的颗粒度,保证组件的业务复杂度不能过高。

3.3 切图处理

用户上传的全景图是一张2:1的图片,无法直接被全景使用。我们使用 krpano 官方提供的切图工具在服务器进行切图,然后放到对应的物料文件夹中。

重构时有考虑前端切图,普通切图将全景图分割为6张图片是可以实现的,但是业务场景更多的需要的是高清切图,由于算法问题,最终还是放弃了这个小优化。

老版本切图存在一个问题,就是用户在进行切图时,对服务器的压力特别大,后端在队列管理上也处理的不好,没有进行异步处理。这就导致多用户多图上传时,等待的时间可能会特别久,如果接口超时失败,前端也未进行相关处理。10张切图,有一张失败了都会造成整体操作失败,用户体验十分糟糕。

我这边的处理也很简单,切图时不使用遮罩阻止用户操作。将每个切图都变成任务,每个任务有各自的进度条,任务之间互不影响。这样用户在编辑物料时也能进行上传的操作,用户使用体验大幅提高。

同时为了缓解服务器压力,前端封装了一个 任务调度器 scheduler, 用户任务最多并行两个。 当然这个处理的收益还是比较小的,也不能真正解决问题,还是需要后端进行优化。

3.4 最终保存

重构一开始的目的就是为了最终保存,用户拥有数据保存的决定权。在编辑器对 JSON进行编辑后,我们需要进行转换。将 JSON 重新转化为 XML,这里同样使用类的序列化方法

class Hotspot {
    serialize(data) {
        ...
        return `<hotspot ... />`
    }
}

这里在进行开发时还是遇到了坑, 一开始我们使用的是对属性进行遍历,然后通过字符串组装成 XML,由于业务场景不需要关心 XSS 的问题。但是在代码提测之后,问题接踵而来。当输入特殊字符或者引号之类的特殊符号时,由于 XML 的格式要求,XML 就变成了非法的 XML 文档了,这也造成了全景保存失败。

因此我们选择通过将 JSON 转化成通过 DOM 提供的能力来进行序列化

const hotspot = document.createElement('hotspot') 
...
    hotspot.setAttribute(key, value)
...
return hotspot.outerHTML

这种方法解决了大部分的问题,但是还是会有不少的坑,例如 word 中的特殊空格,就是无法进行保存。定位到问题后,只能根据 unicode 进行匹配替换。

还有富文本的情况,富文本通过属性进行保存时也会存在问题,要么是预览异常,要么是回显到富文本编辑器异常。最终还是决定针对富文本进行字符串拼接,然后将&,<,>,"符号进行实体化处理,上线后到目前为止暂时无任何异常。

四.最后结果

经过2.0 的重构,在业务架构上减少了后端的依赖,大幅提高了用户的使用体验。 让用户可以通过全景编辑器快速制作出全景漫游物料,添加丰富的交互效果。

在前端代码设计上,让全景编辑的逻辑完全独立,针对全景模块进行数据建模,对后续维护和新增功能十分的友好。

当然通过这次大的重构,无论是业务架构设计还是组件拆分,我都有了巨大的收获。

当然重构到目前为止,编辑器已经重构的十分强大了。但是还是缺少一个最重要的功能: 历史记录。后面我会有专文介绍历史记录功能的实现,和具体业务难点。

110sec commented 1 year ago

这个重构的2.0编辑器能看看嘛

vivipure commented 1 year ago

这个重构的2.0编辑器能看看嘛

这个是公司的内部项目,不对外开发。编辑器的核心逻辑无非是数据驱动,UI层通过交互改动一份JSON数据,最终JSON数据转化为真正所需要结构。和大部分的H5编辑器没有特别大的区别