本文档介绍如何发布一个大屏项目。通过大屏项目的发布功能,可以将已经开发完成的大屏发布到线上环境,并进行访问权限的设置,供其他人员在线观看。

操作步骤

  1. 登录DataV控制台
  2. 单击我的可视化
  3. 选择一个已有大屏项目,单击编辑进入画布编辑器内,单击界面右上角的发布图标。
    发布按键
  4. 发布对话框中,如果当前大屏从未发布过,则需先单击发布框内的发布大屏图标。
    发布大屏
  5. 在开启已发布开关后,获得分享链接。发布界面
  6. 单击分享链接右侧的复制
    说明 分享链接功能右侧新增了重新生成链接的功能。适用于页面分享后需要重新生成新的分享链接的场景,重新生成后,旧的分享链接不可用,请用新的分享链接访问。
  7. 在浏览器中粘贴复制的链接,即可在线访问您的可视化应用。
    大屏分享成功后,您还可以进行访问限制发布快照(仅企业版及以上用户可用)的配置。
    注意 为了更好地支持分享,DataV大屏分享页域名即将从datav.aliyun.com/share/example切换为 datav.aliyuncs.com/share/example,此变更可能对您的DataV大屏项目造成的影响,对您造成的不便敬请谅解。请参考DataV发布分享页域名变更问题及时排查并解决可能存在的问题。

访问限制

DataV的发布功能提供了三种分享大屏的方式:

请选择合适的方式以方便其他用户进行可视化应用的访问。

密码访问(仅企业版及以上用户可用)

  1. 发布对话框中,开启访问密码
    DataV访问密码配置
  2. 访问密码输入框中,输入您的验证密码。
    密码长度为6位以上,且必须具备以下三个条件:
    • 至少包含一个英文大写字母A~Z。
    • 至少包含一个英文小写字母a~z。
    • 至少包含一个数字0~9。

    密码设置成功后,系统会提示已设置访问限制

  3. (可选)配置验证有效期
    注意 只有密码设置成功或开启Token验证后,才可配置验证有效期
    • 开启验证有效期,可以设置密码的有效期,最长为32小时。访问者首次输入密码且成功访问大屏后,在设置的有效期时间内,可任意访问该大屏而无需输入密码。
    • 关闭验证有效期,每次访问都需要输入密码。
密码设置成功后,当您再次访问大屏的分享链接时,系统会提示需要输入密码。密码访问大屏页面

通过Token验证(仅企业版及以上用户可用)

您可以通过Token验证的方式,将大屏访问权限与您的权限体系进行集成。

发布对话框中,开启通过Token验证,即可开启Token验证。Token验证开启后,您可进行如下操作:
  • (可选)配置验证有效期
    • 开启验证有效期,可以设置Token验证的有效期,最长为32小时。访问者首次进行Token验证并成功访问大屏后,在设置的有效期时间内,可任意访问该大屏而无需再次进行验证。
    • 关闭验证有效期,每次访问都需要通过验证。
  • 获取Token验证码。
    开启Token 验证功能后,DataV会生成一个Token,如下图所示。您需要记录这个Token,以备后用。获取Token验证码
Token验证开启后,再次打开您所分享的页面,会收到一个Access Denied消息,表示您的访问被拒绝了。如果想要打开您的页面,需要完成下面几个步骤:
注意 为了防止重放攻击,请确保您的服务器时间为东8区标准时间。DataV只会提供1分钟的误差,如果时间误差超过1分钟将会验证失败。
  1. 发布大屏,记录大屏编码(URL的最后一段)。
  2. 将编码与当前时间(毫秒)连起来,并用 |(竖线)分隔开。
  3. 使用Token通过HMAC-SHA256 base64,对上一步得到的Token验证码进行加密。
  4. 将时间和加密后的签名分别命名为_datav_time_datav_signature
  5. 将它们依次放入url 的querystring中。
    注意 如果您的大屏URL中需要使用Get的方式传递参数,为了安全性,建议您使用DataV提供的Token参数签名校验,详情请参见DataV分享页Token参数签名校验
示例代码如下:
  • PHP:
    <?php
      $token = "kBwoX9rFX9v4zbOT0Gjd_wr65DZ3P_WW";
      $screenID = "03d1b68faeb09671046d1ef43f588c33";
      $time = time()*1000;
      $stringToSign = $screenID.'|'.$time;
      $signature = urlencode(base64_encode(hash_hmac('sha256', $stringToSign, $token, true)));
      $url = "http://datav.aliyuncs.com/share/".$screenID."?_datav_time=".$time."&_datav_signature=".$signature;
    ?>
    <iframe width=100% height=100% src="<?=$url?>"/>
  • Node.js:
    const crypto = require('crypto');
    var token = "Ev97wOUSAtJusc3Vsd9O2ngr_vfVFH67";
    var screenID ="14c5448c00ecde02b065c231d1659f38";
    var time = Date.now();
    var stringToSign = screenID +'|'+ time;
    var signature = crypto.createHmac('sha256', token).update(str).digest().toString('base64');
    var url="http://datav.aliyuncs.com/share/"+ screenID +"?_datav_time="+time+"&_datav_signature="+ encodeURIComponent(signature);
  • Java:
    package com.company;
    import java.security.*;
    import java.util.Date;
    import javax.crypto.*;
    import javax.crypto.spec.SecretKeySpec;
    import org.apache.commons.codec.binary.Base64;
    import java.net.URLEncoder;
    public class TokenTest {
        public static String getSignedUrl(String screenID, String token){
            Date date = new Date();
            Long time = date.getTime();
            String stringToSign = screenID + "|" + time;
            String signature = HMACSHA256(stringToSign.getBytes(), token.getBytes());
            String url = "http://datav.aliyuncs.com/share/"+ screenID +"?_datav_time="+time+"&_datav_signature="+ signature;
            return url;
        }
        /**
         *  使用java原生的摘要实现SHA256加密。
         * @param str加密后的报文。
         * @return
         */
        public static String HMACSHA256(byte[] data, byte[] key)
        {
            try  {
                SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256");
                Mac mac = Mac.getInstance("HmacSHA256");
                mac.init(signingKey);
                return URLEncoder.encode(byte2Base64(mac.doFinal(data)));
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            }
            return null;
        }
        private static String byte2Base64(byte[] bytes){
            return Base64.encodeBase64String(bytes);
        }
        public static void main(String[] args) throws Exception {
            System.out.println(getSignedUrl("screenId", "token"));
        }
    }
  • C#:
    using System;
    using System.Security.Cryptography;
    using System.Text.RegularExpressions;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Text;
    
    namespace datavToken
    {
        class Program
        {
            static void Main(string[] args)
            {
                var dic = new Dictionary<string, string>();  // 自定义参数。
                dic.Add("datav_sign_no", "123998");         // datav_sign_开头,需要签名。
                dic.Add("datav_sign_lo", "kk");
                dic.Add("datav_sign_ao", "xx");
    
                dic.Add("name", "123");   // 不需要签名。
                // 分享页前缀,屏幕分享id、token,自定义参数字典。
                Console.WriteLine(GenerateUrl("https://datav.aliyun.com/share/", "ca74bea5e45503070d607795e0******", "66DsL2qjrXRHluSJScv_flOUhn******", dic));
            }
            private static string GenerateUrl(string datavBase, string screenId, string token, Dictionary<string, string> customeParams)
            {
                string pattern = @"^datav_sign_.*";
                string timestamp = GetTimeStamp();
    
                // 参数排序。
                Dictionary<string, string>.KeyCollection keyCol = customeParams.Keys;
                List<string> signKeys = new List<string>();
    
                foreach (var item in keyCol.ToList())
                {
                    if (Regex.IsMatch(item, pattern))
                    {
                        signKeys.Add(item);
                    }
                }
    
                // 按照key排序。
                signKeys = signKeys.OrderBy(k => k).ToList();
    
                string paramsSignStr = signKeys.Aggregate("", (total, key) =>
                {
                    if (total != "")
                    {
                        total += "&";
                    }
                    total += key + "=" + customeParams[key];
                    return total;
                });
    
                string signStr = screenId + "|" + timestamp + "|" + paramsSignStr;
    
                var encoding = new System.Text.ASCIIEncoding();
                byte[] keyByte = encoding.GetBytes(token);
                byte[] messageBytes = encoding.GetBytes(signStr);
                string signature;
                using (var hmacsha256 = new HMACSHA256(keyByte))
                {
                    byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
                    signature = Convert.ToBase64String(hashmessage);
                }
    
    
                var paramDic = new Dictionary<string, string>();
                paramDic.Add("_datav_time", timestamp);
                paramDic.Add("_datav_signature", signature);
    
                foreach (var item in customeParams)
                {
                    paramDic.Add(item.Key, item.Value);
                }
                return datavBase + screenId + "?" + ParseToString(paramDic);
            }
            public static string GetTimeStamp()
            {
                TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
                return Convert.ToInt64(ts.TotalMilliseconds).ToString();
            }
            static public string ParseToString(IDictionary<string, string> parameters)
            {
                IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
                IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
    
                StringBuilder query = new StringBuilder("");
                while (dem.MoveNext())
                {
                    string key = dem.Current.Key;
                    string value = dem.Current.Value;
                    if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
                    {
                        query.Append(key).Append("=").Append(HttpUtility.UrlEncode(value)).Append("&");
                    }
                }
                string content = query.ToString().Substring(0, query.Length - 1);
    
                return content;
            }
        }
    }

发布快照(仅企业版及以上用户可用)

设置分享链接后,可以配置发布快照,指定访问者看到的大屏版本(默认为快照发布版本)。屏幕的内容会锁定在快照创建的那一刻,存档之后,屏幕内容的编辑和修改不会同步到历史快照中,可作为稳定预览版本的备份。

具体操作方式如下:
  1. 发布对话框中。
    • 如果当前大屏没有快照历史,系统会先生成当前编辑器内容的快照,然后发布。
    • 如果有过发布的快照历史,则会自动发布最新一条快照。
  2. 单击发布快照下拉框,选择一个已存档的历史快照即可完成该历史快照的发布。
  3. 单击下方覆盖已发布快照把已发布快照内的大屏的内容变成当前编辑页下的内容。
  4. 单击下方自动新增快照并发布图标,自动新增一个快照并选中新增的快照后立刻发布。
  5. 单击发布快照栏右下角的管理快照,可在管理快照界面管理多个历史快照,详情请参考下方快照管理

快照管理

单击发布框内的管理快照图标或大屏编辑器右上角生成快照图标右侧管理快照下拉框管理快照下拉框,然后在快照管理弹框界面内可进行如下快照管理操作。
  • 查看额度:弹框右上角可以查看管理快照界面内剩余可创建快照的额度。一旦快照数量达到上限后无法新增快照,需要删除不需要的快照。
    说明 基础版用户有1个快照额度,企业版用户有3个快照额度,专业版用户有10个快照额度。
  • 新增快照:单击弹窗右下角的新增快照图标或大屏编辑器上的生成快照生成快照图标即可新增一个历史快照。
    注意 通过大屏编辑器的快捷键生成的历史快照可在弹窗出直接单击设置成为发布的快照。快速设置发布快照
  • 覆盖已发布快照:单击覆盖已发布快照,编辑器的内容覆盖发布页快照原有的内容,即更新发布页内容,并且实时生效。更新内容包括画布编辑器配置、数据源配置、蓝图编辑器配置。覆盖快照
  • 发布/关闭快照:选中管理栏内某个历史快照,单击右侧发布快照图标可快捷发布大屏或切换发布的快照,切换后,发布页内容将切换成对应快照的内容,再次单击图标即可关闭快照。
    说明 已发布的快照无法被删除。
  • 锁定快照:单击某个历史快照右侧的锁定快照图标即可锁定快照,快照锁定后,不可删除和覆盖。
  • 批量删除:在管理界面内多选或全选快照后,单击下方批量删除图标可进行快照批量删除。批量删除功能无法删除已发布和被锁定的快照。批量删除
    警告 快照目前仅支持切换,不具备回滚的功能,删除后将无法恢复,请谨慎操作。
  • 注释快照:可单击注释图标,自定义添加快照的注释内容。注释图标

常见问题

原先已经实时发布的大屏如何迁移?

  • 没有在首页或编辑页单击发布按钮的大屏,保持实时发布。
  • 需要变更发布的大屏,单击进入发布页之前,会对当前在编辑大屏自动打快照。跳转发布页后,选择快照,即可完成快照发布