写点什么

闲鱼易用高可扩的文章发布工具建设

  • 2021-05-12
  • 本文字数:3856 字

    阅读完需:约 13 分钟

闲鱼易用高可扩的文章发布工具建设

背景

闲鱼会玩社区是一个以分享个人趣味生活为主的内容社区,在社区的运营过程中,经常有一些文章形式的内容在会玩广场上投放。此前文章创作是在一个主要承载营销搭建的平台上完成的,这种方式首先不是专门为文章场景服务的,导致搭建出来的页面是一个静态页面,数据无法被理解,也无法进入审核和分发链路。而且搭建工具无法对外使用,从而无法让外部的创作者参与文章创作。因此,我们想从零开始开发一个能快速发布且高可扩展文章结构形式的发布工具。


null

调研

业界也有许多同类型的产品,如常见的知乎、掘金、微信公众号等。微信公众号的文章无论从发布体验、消费体验以及内容结构扩展上都做得很好。我们总结微信公众号文章有以下几个特点:


1.文章内容形式丰富度高;

2.浏览体验极好;

3.支持第三方内容模版;


闲鱼会玩文章希望能在内容形式,浏览体验上尽可能对齐微信公众号文章的标准。但闲鱼会玩文章与微信公众号也有一定的区别,微信公众号面向不同的自媒体,不同垂类的自媒体对文章的展示调性是不同的,因此对文章的内容样式不会做太多限制,而闲鱼的社区有着自己的调性以及自己的品牌主题色,内容形式会更加收敛。

null

目标

文章内容结构自由度高,内容排列可以是标题、文字段落、图片以及交互组件(如投票器、链接卡片等)的排列组合,因此高可扩展性非常重要。此外,发布器作为文章创作入口,需要存储创作者全部内容信息,包括字体、字号、颜色、段落、图片等。还有展示页在端上的性能体验也是非常重要的指标。因此我们定了以下几个系统的关键目标:


1.信息还原度百分之百

2.内容结构可扩展

3.展示页秒开体验


文章发布链路大致流程如下所示:


null

方案

围绕既定的设计目标,如何实现呢?我们的方案是根据一套约定的 schema 协议来表达文章内容的所有信息,协议记录和表达所有需要在客户端还原展示的信息,最后通过这套协议把发布器和展示页连接起来。可以看出,设计一套通用、简洁的文章内容协议是方案的关键。文章发布器负责产生 schema,结构化存储后,文章展示页获取到该 schema 然后在端上解析协议并展示出对应的内容信息。方案大图如下所示,标橘黄色表示协议相关的部分,它出现在了几乎整个链路中。


null

协议设计

梳理出协议设计目标如下:

1.规则简洁易懂。规则越简单,则根据协议创建 schema 数据以及解析 schema 数据就越容易。

2.可扩展。未来的文章形式可以是非常丰富的,要求用这套协议表达任意的内容形式。

3.结构化存储。将图文内容可以进行结构化抽取和存储,文章内容的 schema 可能非常大,而数据库存储

字段一般会有字符上限,需要压缩 schema。此外文章的实体内容需要进入安全审核链路,这也要求我们必须进行内容结构化抽取。


围绕着以上三个诉求,我们我们基于钉钉的富文本协议,定制了一套符合要求的协议规则。

协议逻辑

null


现有元素标签

•root - 根元素

•p - 正文标签

•span - 文字标签

•img - 图片标签

•h2 - 二级段落标签

•card - 卡片标签

•idleVideoCard - 视频卡片

现有属性

1.常规属性

null

2.图片属性

null

3.视频属性

null

如何扩展

我们规定未来所有新增的插件,都作为一种子类的卡片,可以自定义卡片类型,卡片数据统一放到 metadata 字段中,然后端上根据卡片类型做对应的组件,并将 metadata 中的卡片数据信息作为参数导入组件中去,从而做到未来任意插件,都能映射到协议里。

结构化存储

协议是一种 json schema,这种在关系型数据库中存储是一个比较头疼的问题。图文内容存储会将内容分成三个字段,分别是文本、图片数组和自定义扩展字段。文本和图片的结构化信息会用于安全审核以及算法推荐识别,自定义字段用来存储业务上的其他信息。我们的第一版方案是将文本和图片内容提取出来单独存储,并将整个 json 字符串全部放到自定义字段中,这样只需要一存一读就可以了。然而,真实场景中,自定义字段有字符数限制。因此需要对 json 字符串进行一定的转译和压缩,只保留必要的样式和排版信息即可。

null

发布器

发布器的核心是一个富文本编辑器,市场上主流的 react 编辑器有开源的 slate.js、facebook 的 draft.js,阿里内部比较成熟的富文本工具有语雀富文本编辑器和钉钉文档团队的 we-editor。从协议的复杂度和可扩展性上考虑,我们选型了钉钉的 we-editor。


null


富文本编辑器的使用上,除了要处理在编辑器上手写文章场景外,从外部粘贴内容进编辑器的场景复杂度会更高。因为内容本身就带有富文本样式,从而导致文章样式不可控,生成的富文本协议内容混乱,不利于结构化审核和端上展示页渲染。

粘贴场景处理

粘贴进来的内容是带上内联样式属性的标签,如 div、span、a、h1、h2、img、video 等。我们的做法是,在粘贴的时候,将所有的内联样式清除,并只处理格式范围内的标签。对于 img、video 标签,则需要更多的处理,因为 img 和 video 的 src 是一个地址链接,这些链接如果是站外连接,就会存在访问跨域,同时对平台来说也存在安全风险。做法是对站外资源做转存处理,即将站外链接下载后通过内部服务将资源转存到可靠的资源服务器上。


null

插件扩展

插件扩展通过定制编辑器插件来完成。我们只保留了基础的 redo/undo、字体加粗、字体对齐、添加图片等编辑器自带能力,其他如视频、连接能力通过自定义插件完成。通过对 we-editor 插件体系封装,开发者可以像开发 react 组件一样开发插件。封装过程是将扩展插件都当作一种卡片,在 schema 里指定工具栏内容,对应点击事件,以及插入富文本的卡片样式等,即可插入任意插件。以一个插入视频的插件为例:


null

点击该插件工具栏按钮的时候,选择插入 idleVideoCard 类型的卡片即可。


null


null

函数服务层

在发布器的发布链路中,我们设计了一层 faas 函数服务层。主要考虑以下几个原因。


1.安全问题。结构化信息抽取算法应该放到服务端计算,否则会存在刷接口绕过安审链路的漏洞。

2.抽取和还原都通过 js 实现。从而保证一套规则技术栈统一。


下图为大致的数据流转示意图:


null

文章展示

文章展示的原理是通过协议规则将经过处理的 schema 还原成真正的 schema 信息,并解析信息转化成对应的可视化组件。我将重点讲协议解析和性能优化。

协议解析

理论上只要能正确解析出富文本 schema 协议表达的富文本信息,在端上可以还原成任意对应的设计规范,这也是未来我们可以做集团统一文章发布工具的基础。即只要保证协议一致,端上的展示可以大不相同。还原函数伪代码如下所示:

体验优化

前端的性能性能优化话题是一个老生常谈的话题,限于篇幅有限,这里只讲这次在文章详情页的几个主要优化方案。性能数据结果上,跨端首屏渲染时间从 1700ms 优化到 1000ms 左右,做到了秒开。先上优化前后效果对比:

null

在讲具体方案前,我们先来看一下一个 h5 页面在 webview 里是加载流程:


null


在这个链路中,最消耗时间的是各种资源的 IO,包括页面文档的 IO,样式文件、js 文件和图片的 IO 以及数据接口请求的 IO。其次耗时的是 webview 的启动耗时。因此我们的优化主要围绕减少 IO 以及提前 IO 的思路去进行。


  1. 资源 combo

    页面加载中除了包含业务的 js 文件之外,还包含 jstracker 的资源,rax 的框架资源,安全相关的 js 等,将这些资源合并成一个资源请求,则可以减少很多次请求 IO,从而降低首屏渲染时间。

  2. 图片懒加载

    文章中一般会有很多图片,这些图片大部分不会在第一屏中就出现,因此可以将未出现在屏幕中的图片先不加载,等用户下滑至该图片出现在屏幕中时,才请求该图片资源。

  3. 本地资源缓存

    文档资源下载以及 js 资源一般来说是一个长时间不变的东西,如果这些资源提前在客户端空闲的时候就已经下载好,等到请求这些资源的时候,客户端发现本地已经有了同名的资源,就拦截这次资源请求,返回本地缓存好的资源,则可以大大降低首屏渲染时间。

  4. 数据预取

    首屏渲染的时间长短有一部分取决于第一次调用的接口的返回速度,而接口请求一般要等到 js 逻辑触发接口请求时才发出。如果首屏需要获取的请求是一个确定的参数,那么是否可以将请求接口这个时机提前呢?我们数据预取的方案就是在用户点开页面的请求 URL 中就带上首屏需要请求的接口参数,然后客户端获取到这个参数后异步地请求这个数据,将结果缓存到客户端上。等到 js 逻辑需要发出请求的时候,判断当前请求是否已经被请求过,若请求过,则直接返回缓存在客户端上的接口数据。

  5. 去掉客户端 loading

    客户端 loading 本来是 webview 自带的能力,表示当前页面还有资源在加载,但其实虽然有资源在加载,但首屏的页面信息其实已经加载出来的。去掉客户端的 loading,可以给用户带来更快的体感,虽然实际上并没有加快这个流过程。优化后的加载流程如下图所示:


null


性能优化无止尽,因此文章展示页的优化也会一直做下去,可以考虑提前启动 webview 容器,ESR、NSR 等方式优化。

展望

到目前为止已经完成了文章发布工具从 0 到 1 的建设,未来需要做的事情还有很多。基于这套高可扩展的协议,可以扩展出更多更丰富的文章内容形式以及交互玩法,如投票器、弹幕、文章模版等,最终可以沉淀出一套可开放的基于当前协议的模版插件开发体系,以适应不同的文章内容体系。此外,性能体验优化会继续以高标准的要求不断进行优化。 


再往上层看,文章内容只是会玩社区内容的其中一种承载形式,我们希望有一个包含更多创作能力的创作者站点工具。pc 侧的创作者中心和手机端上的创作者发布中心呈一个互补的态势。普通的创作者更倾向于即来即发,随手发,随时发,而对内容质量有更高追求的一些 pgc 创作者则更关注内容质量,发布效率,内容消费数据等比较专业的指标。 


因此在完成了基本的发布能力后,我们会逐渐完善整个创作者发布链路,集普通图文创作、视频内容创作、文章创作、内容管理、数据中心、热点内容发现于一体的完整专业创作者发布工具。


本文转载自:闲鱼技术(ID:XYtech_Alibaba)

原文链接:闲鱼易用高可扩的文章发布工具建设

2021-05-12 14:001641

评论

发布
暂无评论
发现更多内容

MatrixOne Layout 设计解读

MatrixOrigin

分布式数据库 MatrixOrigin MatrixOne

ihateniggers:针对Python开发者的Windows远控木马分析

墨菲安全

墨菲安全 软件供应链安全 投毒分析

ImageBind 横跨六种数据模式,用向量统一 AI 语言

Zilliz

Zilliz AIGC 向量数据库 Towhee imagebind

PoseiSwap:合规、隐私与支持更广泛的资产

BlockChain先知

知乎文章被百度收录有何意义?如何让知乎文章被百度收录?

石头IT视角

Java补充之MySQL入门必备知识

timerring

Java

dart-sass与node-sass的区别以及使用dart-sass可能会出现的问题

不叫猫先生

CSS SASS 三周年连更

奇葩的 Git 签名错误

HoneyMoose

C++ 教程

雪奈椰子

MySQL 安装配置

芯动大师

MySQL shell脚本 三周年连更

文心一言 VS chatgpt (16)-- 算法导论4.1 1~2题

福大大架构师每日一题

ChatGPT 文心一言

比起失业我更害怕自己“一无是处”|社区征文

轻口味

个人成长 移动端 三周年征文

利用 Amazon API Gateway 和 Amazon Lambda 处理 Cloudfront 的内容请求

亚马逊云科技 (Amazon Web Services)

Go语言中如何通过接口来实现单一职责原则

Jack

2023-05-12:存在一个由 n 个节点组成的无向连通图,图中的节点按从 0 到 n - 1 编号, 给你一个数组 graph 表示这个图, 其中,graph[i] 是一个列表,由所有与节点 i

福大大架构师每日一题

Go 算法 rust

专家共话:存力一体成“东数西算”全场景利器

脑极体

算力

涅槃重生,BitKeep如何闯出千万用户新起点

BlockChain先知

C++ 变量作用域

雪奈椰子

2023开源之夏|来自MatrixOne社区的邀请函

MatrixOrigin

MatrixOrigin MatrixOne 开源之夏

BitKeep逆势崛起:千万用户的信任,终点还未到来

鳄鱼视界

从 ChatGPT 大热看未来的云计算的发展趋势 | 社区征文

宇宙之一粟

ChatGPT 三周年征文

失业浪潮之下,不如一蓑烟雨任平生 | 社区征文

宇宙之一粟

Django笔记二十六之数据库函数之数学公式函数

Hunter熊

Python 数据库 django 公式函数

BitKeep逆势崛起:千万用户的信任,终点还未到来

西柚子

BitKeep逆势崛起:千万用户的信任,终点还未到来

小哈区块

新技术层出不穷,程序员到底该走哪条路|社区征文

阿泽🧸

职业规划 三周年征文

涅槃重生,BitKeep如何闯出千万用户新起点

股市老人

ES数据没了?谁动了我的数据?

极限实验室

elasticsearch 数据误删 数据丢失

一文讲明白路由器的基本概念、工作原理、主要部件以及应用场景

wljslmz

路由器 三周年连更

C++ 模板

雪奈椰子

闲鱼易用高可扩的文章发布工具建设_架构_闲鱼技术_InfoQ精选文章