快速入门

本文介绍文档在线协作的功能特性、实现原理和使用方法等。

功能特性

文档在线协作支持多人实时在线预览或编辑同一个文档,文档多人协作,效率更高。其功能特性如下:

  • 更新内容即时同步,自动保存,避免出现文档内容丢失的情况。

  • 实时保存历史版本,您可以将文档恢复到指定的历史版本。

  • 应用覆盖全终端,手机、电脑、网页均可使用。

  • 上手门槛低,会使用Office办公软件操作即可使用文档在线协作。

  • 支持多种类型的文档预览或编辑。

    • 支持预览的输入文件格式:

      • 文字文档(Word):doc、dot、wps、wpt、docx、dotx、docm、dotm、rtf。

      • 演示文档(PPT):ppt、pptx、pptm、ppsx、ppsm、pps、potx、potm、dpt、dps。

      • 表格文档(Excel):xls、xlt、et、xlsx、xltx、csv、xlsm、xltm。

      • PDF文件(PDF):pdf。

    • 支持编辑的输入文件格式:

      • 文字文档(Word):doc、dot、wps、wpt、docx、dotx、docm、dotm。

      • 演示文档(PPT):ppt、pptx、pptm、ppsx、ppsm、pps、potx、potm、dpt、dps。

      • 表格文档(Excel):xls、xlt、et、xlsx、xltx、xlsm、xltm。

  • 文档协作时支持自定义配置,包括配置组件状态、事件、文字相关、表格相关等。

  • 文档预览和编辑支持的文档格式详细内容请参见文档限制

  • 若您不需要在线预览的方式,即文档在预览时无协作在线编辑,或预览时不需要更新协作在线编辑内容,可以选择缓存预览方式。缓存预览满足普通场景下的预览需求,设置开启缓存预览后,不会在预览时刷新在线编辑产生的文档变化内容。更多关于缓存预览的设置方式,请查看API文档

实现原理

要对接IMM的WebOffice服务,您的Web应用需要同时修改服务端和前端代码。

首先确保原始文档在OSS上,前端调用服务端封装的接口,获取文档的协作地址和AccessToken,然后调用js-sdk初始化(会在指定块元素下动态生成iframe),并设置AccessToken实现文档预览或编辑。

p100109

前提条件

  1. 已创建并获取AccessKey。具体操作,请参见获取AccessKey

  2. 已开通OSS服务、创建存储空间并上传文档到存储空间。具体操作,请参见上传文件

  3. 已开通智能媒体管理服务。具体操作,请参见开通产品

  4. 已通过智能媒体管理控制台创建项目。具体操作,请参见创建项目

    说明
  5. 当访问的OSS Bucket域名与预览引擎的域名不同时,需要在OSS控制台将预览服务域名添加到存储文档的OSS Bucket的跨域访问列表中。具体操作,请参见设置跨域访问

注意事项

使用方法

步骤一:服务端封装接口

在服务端分别封装GenerateWebofficeToken - 获取Weboffice凭证RefreshWebofficeToken - 刷新Weboffice凭证接口,用于获取编辑地址和AccessToken,方便前端直接调用。如下以项目名称为test-project,OSS文件地址为oss://test-bucket/test-object.docx为例,介绍封装接口的操作步骤。

  1. 调用GenerateWebofficeToken - 获取Weboffice凭证接口,获取协作地址。

    • 请求示例

      {
          "ProjectName": "test-project", //IMM项目名称。
          "SourceURI": "oss://test-bucket/test-object.docx", //OSS文件URI。
          "Filename": "test-object.docx", //文件名称。
          "UserData": "{\"fid\": \"123\"}", //用户数据,会在MNS通知中原样返回。
          "PreviewPages": 3, //预览的页数。
          "Permission": "{\"Rename\": \"true\", \"Readonly\": \"false\"}", //设置权限。
          "User": "{\"Id\": \"test\", \"Name\": \"testuser\", \"Avatar\": \"http://xx.com/avatar.jpg\"}", //在线协作显示的用户信息。
          "Watermark": "{\"Type\": \"1\", \"Value\": \"imm\"}" //设置水印。
      }
    • 返回示例

      {
          "RefreshToken": "f1fd1afd79ee445f95d3dd99f34f35ffv3",
          "RequestId": "BC63D209-5E53-00E9-8D24-7043943DBC89",
          "AccessToken": "3de242da81e1433abefbbea000aaae39v3",
          "RefreshTokenExpiredTime": "2022-07-06T23:18:52.856132358Z",
          "WebofficeURL": "https://office-cn-shanghai.imm.aliyuncs.com/office/w/7c1a7b53d6a4002751ac4bbaea69405a01475f4a?_w_tokentype=1",
          "AccessTokenExpiredTime": "2022-07-05T23:48:52.856132358Z"
      }
    • 完整示例代码(以1.27.3版本Python SDK为例)

      # -*- coding: utf-8 -*-
      import os
      from alibabacloud_imm20200930.client import Client as imm20200930Client
      from alibabacloud_tea_openapi import models as open_api_models
      from alibabacloud_imm20200930 import models as imm_20200930_models
      from alibabacloud_tea_util import models as util_models
      from alibabacloud_tea_util.client import Client as UtilClient
      
      
      class Sample:
          def __init__(self):
              pass
      
          @staticmethod
          def create_client(
              access_key_id: str,
              access_key_secret: str,
          ) -> imm20200930Client:
              """
              使用AccessKey ID和AccessKey Secret初始化账号Client。
              @param access_key_id:
              @param access_key_secret:
              @return: Client
              @throws Exception
              """
              config = open_api_models.Config(
                  access_key_id=access_key_id,
                  access_key_secret=access_key_secret
              )
              #填写访问域名。
              config.endpoint = f'imm.cn-shenzhen.aliyuncs.com'
              return imm20200930Client(config)
      
          @staticmethod
          def main() -> None:
              # 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
              # 强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
              # 本示例通过从环境变量中读取AccessKey,来实现API访问的身份验证。如何配置环境变量,请参见https://help.aliyun.com/document_detail/2361894.html。
              imm_access_key_id = os.getenv("AccessKeyId")
              imm_access_key_secret = os.getenv("AccessKeySecret")
              client = Sample.create_client(imm_access_key_id, imm_access_key_secret)
              #设置水印。
              weboffice_watermark = imm_20200930_models.WebofficeWatermark(
                  type=1,
                  value='imm'
              )
              #填写协作用户信息。
              weboffice_user = imm_20200930_models.WebofficeUser(
                  id='test',
                  name='testuser',
                  avatar='http://xx.com/avatar.jpg'
              )
              #设置权限。
              weboffice_permission = imm_20200930_models.WebofficePermission(
                  rename=True
              )
              get_weboffice_urlrequest = imm_20200930_models.GenerateWebofficeTokenRequest(
                  #填写IMM项目名称。
                  project_name='test-project',
                  #填写协作的文件URI。
                  source_uri='oss://test-bucket/test-object.docx',
                  #填写文件名称。
                  filename='test-object.docx',
                  #填写用户数据。
                  user_data='{"fid": "123"}',
                  preview_pages=3,
                  external_uploaded=False,
                  permission=weboffice_permission,
                  user=weboffice_user,
                  watermark=weboffice_watermark
              )
              runtime = util_models.RuntimeOptions()
              try:
                  #打印API的返回值。
                  response = client.get_weboffice_urlwith_options(get_weboffice_urlrequest, runtime)
                  print(response.body.to_map())
              except Exception as error:
                  #如有需要,请打印错误信息。
                  UtilClient.assert_as_string(error.message)
                  print(error)
      
      
      if __name__ == '__main__':
          Sample.main()
  2. 调用RefreshWebofficeToken - 刷新Weboffice凭证接口,刷新AccessToken。

    AccessToken具有时效性,当过期后前端需要调用服务端RefreshWebofficeToken接口重新刷新AccessToken,所以需要在服务端封装此接口。调用此接口的返回结果格式和调用GenerateWebofficeToken接口的相同。

步骤二:前端JS-SDK使用

通过JS-SDK将返回的预览地址挂载到HTML块状元素中并设置AccessToken。

  1. 引入JS-SDK。

    示例中的${x.y.z}表示JS-SDK的最新版本号,请根据实际填写,最新版本请参见JS-SDK版本

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Demo</title>
    </head>
    <body>
      <script src="https://g.alicdn.com/IMM/office-js/${x.y.z}/aliyun-web-office-sdk.min.js"></script>
      <script>
        console.log('引入后可以开始使用JS-SDK了!');
        console.log(aliyun); //全局变量名。
      </script>
    </body>
    </html>
  2. 初始化。

    通过在服务端封装的GenerateWebofficeToken接口获取tokenInfo对象。

    假设tokenInfo对象和调用GenerateWebofficeToken接口返回的结构一致。如下代码以服务端封装的GenerateWebofficeToken接口为/getTokenInfo为例。

    //获取协作地址和AccessToken。
    var tokenInfo = await $.get('http://example.com/getTokenInfo')
    
    // 初始化
    let instance = aliyun.config({
      url: tokenInfo.WebofficeURL //设置文档协作URL地址。
    })
  3. 自定义Office(iframe)挂载点。

    说明

    DOMContentLoaded事件被触发后,请确保挂载节点存在再执行初始化操作。

    iframe 默认会挂载到body下,您可以根据需要自定义iframe 的挂载点。

    <div id="container"></div>
    let instance = aliyun.config({
      mount: document.querySelector('#container'),
      url: '文档协作URL地址' //即步骤2示例中的文档协作URL地址(tokenInfo.WebofficeURL)。
    })

    如果需要对iframe对象做特殊处理,可以通过JS-SDK实例化对象快速获取到iframe的DOM(Document Object Model)对象。

    var instance = aliyun.config({
        mount: document.querySelector('#container')
       //...
    })
    console.log(instance.iframe)
  4. 设置令牌(Token)。

    重要
    • 2023-05-01之前创建的项目使用WebOffice进行文档预览和在线协作按预览打开次数计费,打开一次记一次。

    • 2023-05-01之后创建的项目使用WebOffice进行文档预览和在线协作按接口调用次数计费,调用GenerateWebofficeTokenRefreshWebofficeToken接口时进行计费。

    • JS-SDK会提前5分钟调用RefreshWebofficeToken接口刷新Token方法,所以设置的timeout应在20分钟以上(20 * 60 * 1000以上,单位为毫秒),token有效期默认30分钟,避免刷新Token过快产生额外费用。

    • 在获取协作地址后,需要设置令牌才能在线协作。

    • 每次刷新令牌后,也需要通过此方法设置令牌。

    // https://help.aliyun.com/document_detail/611302.htm?spm=a2c4g.478147.0.0.794d5d6dqtWTzI
    //根据业务需求通过异步请求或者模板输出的方式获取Token。
    var token = 'yourToken'; 
    //设置Token。
    instance.setToken({
      token: token, 
      timeout: 25 * 60 * 1000, // 必须设置,Token超时时间,单位为ms,示例设置超时时间为25分钟。可配合refreshToken配置函数使用,在超时前5分钟调用refreshToken重新刷新Token。
    }) 
  5. 超时更新令牌(Token)。

    通过在服务端封装的RefreshWebofficeToken接口获取tokenInfo对象。假设tokenInfo对象和调用RefreshWebofficeToken接口返回的结构一致。

    您可以通过传入获取Token的函数,在Token超时时,JS-SDK会自动调用传入的函数重新获取Token,返回一个promise或者object。

    如下示例以服务端封装的GetWebofficeToken接口为/refreshTokenInfo为例。

    //缓存上次的tokenInfo,用于刷新Token。
      let lastTokenInfo = tokenInfo
    
    //获取Token函数。
    //注意:refreshToken目前不支持async, 只支持返回Promise或者{token,timeout}对象。
      const refreshToken = function() {
        return new Promise(function(resolve){
          //业务处理逻辑,调用服务端封装的refreshToken接口。
          $.get('http://example.com/refreshTokenInfo',{
            RefreshToken: lastTokenInfo.RefreshToken,
            AccessToken: lastTokenInfo.AccessToken,
            //....
          }).then(function(tokenInfo){
            lastTokenInfo = tokenInfo
            resolve({
              token: tokenInfo.AccessToken, //必须设置。
              timeout: 25 * 60 * 1000, // 必须设置,Token超时时间,单位为ms,示例设置超时时间为25分钟。可配合refreshToken配置函数使用,在超时前5分钟调用refreshToken重新刷新Token。
            })
          })
        })
      }
    //配置超时获取Token函数。
    aliyun.config({
      //...
      refreshToken
    })

示例代码

JS-SDK文档预览完整示例如下。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Demo</title>
  <meta name="viewport" content="width=device-width,initial-scale=1.0"> 
  <style>
    html,body{
      margin:0;
      height:100%;
    }
    #container{ 
      height: 100%;
      width: 100%; 
    }
    iframe {
      width: 100% !important;
      height: 100% !important;
    }
  </style>
</head>
<body>
  <script src="https://g.alicdn.com/IMM/office-js/1.1.19/aliyun-web-office-sdk.min.js"></script>
  <div id="container"></div>
  <script>
    window.onload = init;
    async function init() {
      preview(
        //GenerateWebofficeToken返回的预览Token信息。
        {
            "RefreshToken": "f1fd1afd79ee445f95d3dd99f34f35ffv3",
            "RequestId": "BC63D209-5E53-00E9-8D24-7043943DBC89",
            "AccessToken": "3de242da81e1433abefbbea000aaae39v3",
            "RefreshTokenExpiredTime": "2022-07-06T23:18:52.856132358Z",
            "WebofficeURL": "https://office-cn-shanghai.imm.aliyuncs.com/office/w/7c1a7b53d6a4002751ac4bbaea69405a01475f4a?_w_tokentype=1",
            "AccessTokenExpiredTime": "2022-07-05T23:48:52.856132358Z"
        }

      )
    }
    
    function preview(tokenInfo) {
      let mount = document.querySelector('#container');
      let instance = aliyun.config({
        mount,
        url: tokenInfo.PreviewURL || tokenInfo.EditURL || tokenInfo.WebofficeURL,
        mode: 'normal',
        // refreshToken
      });
      instance.setToken({ token: tokenInfo.AccessToken, timeout: 25 * 60 * 1000 });

      instance.on('fileOpen', function(data) {
        console.log(data);
      });

      instance.on('error', (err) => {
        console.log('发生错误:', err);
      });

    }
  </script>
</body>
</html>