在线文档 - 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

《Java+FlexPaper+swfTools仿百度文库文档在线预览系统设计与实现》

谷歌在线翻译

在线查看PDF和office文档的设计与实现