活动专题系统搭建过程中我的一些思考

CMS系统是由携程民宿(C),去哪儿民宿(Q),途家(T)共同维护的一个项目。运营在活动后台通过拖拽配置不同的功能模块,生成前台可直接访问的活动页面。

以往的专题活动页往往需要前端人力开发,费事费力,CMS旨在减轻开发人员开发成本。

CMS系统分前后台两个项目。搭建后台(B端)用于配置活动,渲染前台(C端)渲染实际页面。

后台共享一个配置后台系统,CQT活动均统一在配置系统内配置,功能模块统一维护。

CQT前台共享一套UI,代码需要各自适配各自的业务逻辑。实际生成的活动页面,其入口域名,接口服务,登录判断等均需走各自接口和sdk。前端资源文件可选择途家部署或独立部署。

CMS的核心思想就是B端生成一份JSON,C端根据JSON渲染页面。B端和C端对每一个组件都做了数据格式约定。

我主要承担了B端组件的设计开发和后期维护工作,以及C端页面在去哪儿部分的适配工作。本文记录了自己的一些关于CMS的设计思考。

B端

CMS需要支持图片,文本,红包,房屋列表,tab,楼层等多个常见活动页需要的功能。

主要区分3个概念:组件,模块,页面模板。
我们把诸如图片,tab这样的具体业务逻辑组件叫做组件, 而封装了组件,同时加入了其他辅助功能的B端组件叫做模块。一般来说一个模块都对应一个组件。页面模板则是为了减少每次搭建都需要从0开始选取组件的成本,而提供的一套默认集成了常见功能组件的模板效果。

B端UI主要有三部分:待选组件列表,可视化展示(预览效果), 以及组件编辑区域。每个功能模块都应该包含这3部分,这里主要谈一下模块的相关思想。

模块的设计

每个模块有3部分组成:

1
2
3
4
5
module
|
|-- config 模块基本数据,前后台约定的通信格式
|-- view 可视化页面,是一个C端组件的缩略,同时负责B端模块编辑的可视化展示
|-- controller 控制器,配置模块的基本信息,逻辑上可以认为是对config的操作,编辑组件

用户新增一个组件,CMS将根据该模块的config信息,初始化一个view组件和controller组件。用户编辑controller部分,view会实时更新。

虚拟模块

上面我们说到,B端在实现上将图片,tab, 楼层这样的组件都视为一个模块。但是tab和楼层不是简单独立的,一般内部都是有嵌套其他组件的,比如tab可以切换不同的房屋列表,楼层还可以指定跳转到页面不同的位置。所以这里引入了一个“虚拟模块”的概念,我把tab等模块内部嵌套的子模块称之为“虚拟模块”。

因此在编辑一个模块时,需要注意到这个模块到底是一个普通模块还是一个特殊的虚拟模块,这两者的行为是不一样的。普通模块较为简单,直接修改;而虚拟模块需要先找到其所属的父模块,然后进行更新设置。

多模块的管理

CMS内部维护了一个数组,用来收集所有已选择的模块组件,我们叫做selectedModulesList, 另外维护了一个对象editingModule, 指向正在编辑的组件。可以知道editingModule是selectedModulesList的子集。当保存活动时,其实就是把这个selectedModulesList持久化保存。

因此运营人员对活动的所有操作都可以归结为从代码层面上实现对selectedModulesList和editingModule的操作。

数据校验

作为一个成熟的系统,数据校验功能必不可少。不同的组件都有不同的校验逻辑,这些代码一般封装在具体的组件内。这里有一个问题:如何在保存提交活动时对数据做校验?这个问题并不好回答。

我们知道当前处于编辑状态的组件永远只有一个,这个句柄很好拿,拿到句柄后直接调用其内部校验逻辑即可。问题在于那些不在编辑状态的组件数据如何校验?

方案一:在切换组件时,立即执行校验,否则不予切换。这种方式最简单,但无疑用户体验是最差的,果断pass

方案二:提交活动时,遍历selectedModulesList,给予每个组件一次渲染,从而有机会拿到组件句柄,执行内部的校验操作。看起来似乎可行,但组件频繁切换只为执行校验,开销有点大,且前端展示控制不好很容易让用户看到编辑区频繁切换,一脸懵逼,也pass。

方案三:活动提交时肯定已经拿到了一个大JSON, 只需要校验这个JSON,校验失败立即退出校验逻辑。唯一的问题在于,这样校验逻辑可能会写两套,组件内部一套,根页面提交活动时又要写一套。

我最后还是采取了方案三,为了减少代码的重复性,组件内能重用的校验逻辑都被抽离出来成为一个单独的校验文件,放在模块目录下。这样我们的模块结构就发生了变化,现在的结构如下:

1
2
3
4
5
6
module
|
|-- config 模块基本数据,前后台约定的通信格式
|-- view 可视化页面,是一个C端组件的缩略,同时负责B端模块编辑的可视化展示
|-- controller 控制器,配置模块的基本信息,逻辑上可以认为是对config的操作,编辑组件
|-- validate 组件数据的校验逻辑

校验JSON的过程,其实就是个遍历操作,遍历过程我们能很容易的拿到出错组件的位置,错误内容等信息,这给我们提供一个全局的校验提示提供了可能。当发现校验失败时,我们把拿到的出错组件相关信息展示出来,帮助用户更好更快的定位到已有数据的问题,并将编辑区域直接切换为当前出错组件。

安全

作为一个涉及大量表单操作的配置系统,很多输入可能都会直接输出到前台,比如文本组件需要直接输出文本到前台。如何防范常见web攻击,如xss攻击是必须要解决的问题。

C端

文章开头我们说道C端的UI组件是三家共享的,但业务逻辑却彼此各不相同。因此如何开发组件,部署是个问题。

组件开发

一个C端组件有两部分组成,UI组件和业务组件。这样三家共享一套UI, 维护各自的业务组件,只需要关心自己的业务就行了,极大的解耦。

部署

我主要介绍下去哪儿部分的部署方案。

前端资源部署在途家cdn上,利用途家的jenkins统一打包。这样做的目的是为了尽量减少后期代码的维护成本,如果去哪儿独立部署,每次其他平台比如途家有需求变动或者修改了一个公共bug,去哪儿这边需要手动同步拷贝途家源代码到自己的项目中,然后再走去哪儿自己的项目发布流程,这个拷贝项目代码的过程很痛苦。

前端资源虽然部署在了途家上,但后端服务还得走去哪儿自己的服务。因此前后端需要通过一个模板文件做关联。

这里有个问题,由于前后端是基于模板文件做关联的,模板文件如何同步前端资源文件的版本号就是个问题,难道手动替换多个script标签,以更新版本?

事实上jenkins发布之后,我们已经打出了一个项目模板文件,只要将这个模板文件放在后端工程下即可关联最新资源。但即使只需要手动拷贝一个模板文件,也很麻烦。每次发完前端,都需要手动拷贝模板,替换掉后端工程目录下的模板文件,然后再提交后端代码,发布后端。

有没有更简单的?

这里我写了一个小工具 sync-file-cli, 以简化手动拷贝代码的工作,具体用法参见该包的readme说明。

为什么要放在途家部署?业务原因,去哪儿民宿,携程民宿都是途家民宿业务矩阵的一部分。

效果图

CMS从11月份以来开始开发,到现在基本稳定,已上线了多个活动页。
这是一个通过CMS配置出来的活动



其他思考

难点

1.正确组织页面组件的层级嵌套关系

要正确组织组件间嵌套关系,防止组件间错误嵌套导致渲染异常(比如一个行内元素嵌套了块级元素)或者逻辑错误(tab组件下嵌套了楼层组件)。
考虑到移动端的特点,组件UI的基本效果都是宽度铺满,高度不定,页面上下有滚动效果。所以渲染前台基本上是根据json配置数据从上到下依次渲染组件的。但不可避免有些复杂的业务组件,比如楼层,tab组件等存在嵌套的需求。搭建后台指定了一些组件间嵌套和约束规则,规定了楼层和tab组件的嵌套子组件允许类型。

2.如何将搭建后台和渲染前台的前端渲染框架进行解耦

基于json配置,前后台只通过json通信,互不关心对方技术实现。

3.实时预览

实现实时预览功能,我想到了两种解决方案:后台直接渲染和第三方渲染。后台直接渲染就是编辑器需要引入组件库组件源码,由后台模拟前台组件的真实渲染环境。这样的缺点就在于后台和前台的技术选型需要一致,同时后续组件库更新,前后台要同步更新。第三方渲染就是利用iframe嵌入真实效果页面或另开一个浏览器窗口打开真实效果页面。这其实相当于走了一遍整体流程,因此需要保证效果页的渲染速度。

cms这两种方式兼而有之,后台可视化区域未加载真实数据,只是渲染了一个大致的缩略图/骨架屏效果,并没有引入真实组件渲染效果。如果需要查看真实数据效果,则打开一个预览窗口来完成真实的数据渲染。

可以优化的点

1.后台组件和前台组件其实是一一对应的,能否利用同构的思想,前后台只维护一套组件库,这样每次新增组件,只是在后台动态添加组件就好,无需渲染侧前台进行发布改动?肯定是可以的,只是这种方案不适合现有的业务场景。

-------------本文结束 感谢您的阅读-------------