在线文档 - Google 文档的数据协议设计
Posted 张驰Terry
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在线文档 - Google 文档的数据协议设计相关的知识,希望对你有一定的参考价值。
在线文档 - Google 文档的数据协议设计
Google 文档作为 G Suite 重要的产品套件之一,作为优秀的在线协作文档而经常被开发者所讨论,在 Google 文档背后,有着一整套优秀的相关架构设计支撑,数据协议设计就是其中之一,非常具有学习和研究价值。
前言
截至 2020 年,Google 旗下的 G Suite 用户量达到 20 亿,而 Google 文档作为其重要的产品套件之一,作为优秀的在线协作文档而经常被开发者所讨论,在Google文档背后,有着一整套优秀的相关架构设计支撑,数据协议设计就是其中之一,非常具有学习和研究价值,本文旨在向研发同学详细介绍 Google 文档的数据协议设计精髓。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-747W8POL-1642041334639)(https://docs.corp.kuaishou.com/d/loadimage/5343388864566933907)]
定义
在线协作文档的内容分类
在设计一个数据协议之前,需要将一个在线协作文档所包含的全部功能进行整理:
文字
- 文字内容
- 特殊转义符号
- 换行符,制表符等等
- 人类文字
- 文字样式
- 大小
- 字体
- 加粗
- …
文档基本信息
- 背景色
- 纸张尺寸
- 纸张内外间距
插件
- 图片
- 超链接
- 评论
- 目录
- 表格
- …
在线文档的用户操作分类
在对用户操作进行分类之前,我们需要引入操作指令(command) 这个概念。简单来说,用户每次改变文档的操作,都将抽象成一次 command 发送到服务端,再由服务端将这次 command 分发给其他协作者的客户端。
在线协作文档有查看历史、协作、撤销的产品特性,故用户的每一个command都需要被原子化。我们对 Google文档的 command 进行分类(用户的每一次操作都应该可以通过这些类别的 command 组合来清晰的表达):
create 创建
- create 创建图片、列表项等实体
- insertAfter 在指定的位置后插入
- insertBefore 在指定的位置前插入
update 更新
- 更新已有的属性
delete 删除
- deleteAt 在指定的位置删除
tether 绑定
- 将某些内容和create的实体绑定起来
分析用户对 Google 文档的操作
打开一个 Google 文档,正文书写为 ”快手,拥抱每一种生活“,并对文本进行一些样式修改,例如加粗,修改文本颜色等
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ylYGet8F-1642041334641)(https://docs.corp.kuaishou.com/d/loadimage/-1598048658281776554)]
然后,通过其提供的版本历史记录功能,我们可以获取查看一个文档的历史版本的请求接口
https://docs.google.com/document/u/0/d/$docId/showrevision
剔除掉一些非关键信息,我们得到以下 json 结构,很明显这是一个数组,接下来我们简称这个数组为 commands 并逐个进行分析
[
"ty":"is",
"ibi":1,
"s":"快手,拥抱每一种生活"
,
"ty":"as",
"st":"document",
"si":0,
"ei":0,
"sm":
"ds_pw":595.4399999999999,
"ds_lhs":1,
"ds_ph":841.68
,
"ty":"as",
"st":"headings",
"si":0,
"ei":0,
"sm":
"hs_h3":
"sdef_ps":
"ps_sb_i":false,
"ps_sb":16
,
"sdef_ts":
"ts_fgc":"#434343",
"ts_bd":false,
"ts_fgc_i":false,
"ts_bd_i":false
,
"hs_t":
"sdef_ps":
"ps_sb_i":false,
"ps_sa":3,
"ps_sa_i":false,
"ps_sb":0
,
"sdef_ts":
"ts_bd":false,
"ts_bd_i":true,
"ts_fs":26,
"ts_fs_i":false
,
"hs_h2":
"sdef_ps":
"ps_sa":6,
"ps_sa_i":false
,
"sdef_ts":
"ts_bd":false,
"ts_bd_i":false,
"ts_fs":16,
"ts_fs_i":false
,
"hs_h1":
"sdef_ps":
"ps_sb_i":false,
"ps_sb":20
,
"sdef_ts":
"ts_bd":false,
"ts_bd_i":true,
"ts_fs":20,
"ts_fs_i":false
,
"hs_nt":
"sdef_ps":
"ps_lslm":1,
"ps_lslm_i":false,
"ps_sm":0,
"ps_sm_i":false
,
"hs_st":
"sdef_ps":
"ps_sb_i":false,
"ps_sa":16,
"ps_sa_i":false,
"ps_sb":0
,
"sdef_ts":
"ts_ff_i":false,
"ts_it":false,
"ts_fs":15,
"ts_ff":"Arial",
"ts_it_i":false,
"ts_fs_i":false
,
"hs_h6":
"sdef_ps":
"ps_sb_i":false,
"ps_sa":4,
"ps_sa_i":false,
"ps_sb":12
,
"sdef_ts":
"ts_fgc":"#666666",
"ts_fgc_i":false,
"ts_it":true,
"ts_bd_i":true,
"ts_fs":11,
"ts_it_i":false,
"ts_bd":false,
"ts_fs_i":false
,
"hs_h5":
"sdef_ps":
"ps_sb_i":false,
"ps_sa":4,
"ps_sa_i":false,
"ps_sb":12
,
"sdef_ts":
"ts_fgc":"#666666",
"ts_bd":false,
"ts_fgc_i":false,
"ts_bd_i":true
,
"hs_h4":
"sdef_ps":
"ps_sb_i":false,
"ps_sa":4,
"ps_sa_i":false,
"ps_sb":14
,
"sdef_ts":
"ts_fgc":"#666666",
"ts_bd":false,
"ts_fgc_i":false,
"ts_bd_i":true
,
"ty":"as",
"st":"language",
"si":0,
"ei":0,
"sm":
"lgs_l":"zh_CN"
,
"ty":"as",
"st":"paragraph",
"si":11,
"ei":11,
"sm":
"ps_klt_i":true,
"ps_awao_i":true,
"ps_sm_i":true,
"ps_ls_i":true,
"ps_il_i":true,
"ps_ir_i":true,
"ps_al_i":true,
"ps_bl_i":true,
"ps_sd_i":true,
"ps_sb_i":true,
"ps_sa_i":true,
"ps_lslm_i":true,
"ps_br_i":true,
"ps_bbtw_i":true,
"ps_kwn_i":true,
"ps_bt_i":true,
"ps_ifl_i":true,
"ps_bb_i":true
,
"ty":"as",
"st":"text",
"si":0,
"ei":11,
"sm":
"ts_un":false,
"ts_un_i":true,
"ts_sc":false,
"ts_st_i":true,
"ts_bgc":null,
"ts_fs_i":true,
"ts_bgc_i":true,
"ts_ff_i":true,
"ts_bd_i":true,
"ts_va_i":true,
"ts_fs":11,
"ts_ff":"Arial",
"ts_bd":false,
"ts_tw":400,
"ts_it_i":true,
"ts_fgc":"#000000",
"ts_fgc_i":true,
"ts_it":false,
"ts_va":"nor",
"ts_st":false,
"ts_sc_i":true
,
"ty":"as",
"st":"text",
"si":1,
"ei":2,
"sm":
"ts_un":true,
"ts_fgc":"#00796b",
"ts_un_i":false,
"ts_fgc_i":false,
"ts_bd_i":false,
"ts_st":false,
"ts_bd":true,
"ts_st_i":false
,
"ty":"as",
"st":"text",
"si":3,
"ei":10,
"sm":
"ts_fgc":"#00796b",
"ts_st":false,
"ts_fgc_i":false,
"ts_st_i":false
,
"ty":"as",
"st":"text",
"si":11,
"ei":11,
"sm":
"ts_fgc":"#ff9900",
"ts_fgc_i":false
]
创建字符
commands[0]
"ty":"is",
"ibi":1,
"s":"快手,拥抱每一种生活"
首先 “ty” 是 “type” 的缩写, “is” 是 “insertSpacers” 的缩写,然后 “ibi” 是 “insertBeforeIndex” 的缩写, “s” 是 “spacers” 的缩写,那么这个重新理解下这个 command
"type":"insertSpacers",
"insertBeforeIndex":1,
"spacers":"快手,拥抱每一种生活"
含义:在文档字符内容索引 1 的位置前插入 “快手,拥抱每一种生活”
创建文档基本信息
command[1]
"ty":"as",
"st":"document",
"si":0,
"ei":0,
"sm":
"ds_pw":595.4399999999999,
"ds_lhs":1,
"ds_ph":841.68
我们继续分析下一个 command ,“as” 是 “applyStyle” 的缩写,“st” 是 “styleType” 的缩写, “si” 是 “startIndex” 的缩写,“ei” 是 “endIndex” 的缩写, “sm” 是 “styleMap” 的缩写,“ds_pw” 是 “documentStyle_pageWidth” 的缩写,“ds_pw” 是 “documentStyle_pageHeight” 的缩写,“ds_lhs” 是 “documentStyle_lineHeightStrategy” 的缩写。
"type":"applyStyle",
"styleType":"document",
"startIndex":0,
"endIndex":0,
"styleMap":
"documentStyle_pageWidth":595.4399999999999,
"documentStyle_lineHeightStrategy":1,
"documentStyle_pageHeight":841.68
含义:这是一个文档全局配置,描述文档的纸张宽度为 595 point,高度为 841 point,因为要兼容不同设备的尺寸。
看到这,大家可能好奇为什么我们能够一眼就看出这个协议的含义。实际上,通过 debugger 调试 google docs 压缩后的代码,能够比较快的找到线索。后面的 command 我们就直接写翻译结果了。
https://docs.google.com/static/document/client/js/3556551332-client_js_prod_kix_core__zh_cn.js
创建标题样式(默认)
commands[2]
"type":"applyStyle",
"styleType":"headings",
"startIndex":0,
"endIndex":0,
"styleMap":
"headStyle_h3":
"styleDefault_paragraphStyle":
"paragraphStyle_spacingBefore_inherit":false,
"paragraphStyle_spacingBefore":16
,
"styleDefault_textStyle":
"textStyle_foregroundColor":"#434343",
"textStyle_bold":false,
"textStyle_foregroundColor_inherit":false,
"textStyle_bold_inherit":false
,
"headStyle_title":
"styleDefault_paragraphStyle":
"paragraphStyle_spacingBefore_inherit":false,
"paragraphStyle_spacingAfter":3,
"paragraphStyle_spacingAfter_inherit":false,
"paragraphStyle_spacingBefore":0
,
"styleDefault_textStyle":
"textStyle_bold":false,
"textStyle_bold_inherit":true,
"textStyle_fontSize":26,
"textStyle_fontSize_inherit":false
,
"headStyle_h2":
"styleDefault_paragraphStyle":
"paragraphStyle_spacingAfter":6,
"paragraphStyle_spacingAfter_inherit":false
,
"styleDefault_textStyle":
"textStyle_bold":false,
"textStyle_bold_inherit":false,
"textStyle_fontSize":16,
"textStyle_fontSize_inherit":false
,
"headStyle_h1":
"styleDefault_paragraphStyle":
"paragraphStyle_spacingBefore_inherit":false,
"paragraphStyle_spacingBefore":20
,
"styleDefault_textStyle":
"textStyle_bold":false,
"textStyle_bold_inherit":true,
"textStyle_fontSize":20,
"textStyle_fontSize_inherit":false
,
"headStyle_normalText":
"styleDefault_paragraphStyle":
"paragraphStyle_lslm":1,
"paragraphStyle_lslm_i":false,
"paragraphStyle_spacingMode":0,
"paragraphStyle_spacingMode_inherit":false
,
"headStyle_subTitle":
"styleDefault_paragraphStyle":
"paragraphStyle_spacingBefore_inherit":false,
"paragraphStyle_spacingAfter":16,
"paragraphStyle_spacingAfter_inherit":false,
"paragraphStyle_spacingBefore":0
,
"styleDefault_textStyle":
"textStyle_fontFamily_inherit":false,
"textStyle_italic":false,
"textStyle_fontSize":15,
"textStyle_fontFamily":"Arial",
"textStyle_italic_inherit":false,
"textStyle_fontSize_inherit":false
,
"headStyle_h6":
"styleDefault_paragraphStyle":
"paragraphStyle_spacingBefore_inherit":false,
"paragraphStyle_spacingAfter":4,
"paragraphStyle_spacingAfter_inherit":false,
"paragraphStyle_spacingBefore":12
,
"styleDefault_textStyle":
"textStyle_foregroundColor":"#666666",
"textStyle_foregroundColor_inherit":false,
"textStyle_italic":true,
"textStyle_bold_inherit":true,
"textStyle_fontSize":11,
"textStyle_italic_inherit":false,
"textStyle_bold":false,
"textStyle_fontSize_inherit":false
,
"headStyle_h5":
"styleDefault_paragraphStyle":以上是关于在线文档 - Google 文档的数据协议设计的主要内容,如果未能解决你的问题,请参考以下文章
整理全网优秀的API接口设计及相关优秀的接口管理在线文档生成工具
国际化多语言方案i18n / class google sheets v4 api 在线文档同步json
国际化多语言方案i18n / class google sheets v4 api 在线文档同步json