2

背景

公司以前采用GrowingIo埋点,都是在每个项目中引入其提供的Web JS SDK,但是最近由于使用期限到了,加上一些其他层面的考虑,准备采用神策做数据埋点和数据分析,因此需要移除每个项目中引入的GrowingIo Web JS SDK,然后引入神策提供的Web JS SDK

方案

如果还是采用在每个项目中独立引入Web JS SDK,首先需要梳理出有多个项目有埋点需求,然后安排通知每个业务组的前端同学在迭代开发内引入。有没有觉得特别麻烦,明明是同一个Web JS SDK却需要多次引入,还涉及到跨组协作、任务分配、资源安排等问题。更重要的是,如果下次又更换采用另一个埋点工具,那重复的事情又要做一遍,吃力不讨好。

因此,我们希望能够在某个地方引入后,项目就不需要独立引入了,这个方案有没有想到跟网关服务、中间件、拦截器的思想特别像呢?我们是否能采用这类思想解决呢?答案是肯定的,通过在前端路由服务请求到的静态html脚本中注入神策Web JS SDK

路由服务引入神策.png

对于一些前后端分离项目(Vue、React)可以在前端路由服务中操作静态html脚本注入神策Web JS SDK,但对于一些前后端未分离的项目(例如freemarker项目)该怎么处理呢?可以在webagent网关做统一处理吗?其实也是可行的,拦截器可以拦截响应头Content-Type: text/html的响应,操作html注入神策Web JS SDK;如果不是此响应类型,则不做处理。但在实际情况中,鉴于前后端未分离项目较少,我们还是采用第一种方案,独立在项目中引入。

封装神策Web JS SDK

神策有提供全埋点功能,项目中希望使用启用该功能,因此需要再封装,初始化SDK,可以参考如下封装:

;(function () {
  /** 判断环境 */
  var hostname = window.location.hostname;
  var serverUrl = 'https://xxx?project=default'; // 测试环境数据接收地址
  if (hostname === 'www.cassmall.com' || hostname === 'h.cassmall.com') {
    serverUrl = 'https://xxx?project=production'; // 生产环境数据接收地址
  }
  // 开启全埋点,Web JS SDK 全埋点包括三种事件:Web 页面浏览、Web 元素点击、Web 视区停留
  function addShenCeScript() {
    window.cassSensors = window['sensorsDataAnalytic201505'];
        // 初始化 SDK
    window.cassSensors.init({
      server_url: serverUrl, // 数据接收地址
      is_track_single_page: true, // 单页面配置,默认开启,若页面中有锚点设计,需要将该配置删除,否则触发锚点会多触发 $pageview 事件
      heatmap: {
                /**
                * Web 元素点击($WebClick)
                * 是否开启点击图,default 表示开启,自动采集 $WebClick 事件,可以设置 'not_collect' 表示关闭。
                * 默认只有点击 a input button textarea 四种元素时,才会触发 $WebClick 元素点击事件
                */
        clickmap: 'default',
                /**
                * 视区停留事件($WebStay)
                * 是否开启触达图,default 表示开启,自动采集 $WebStay 事件,可以设置 'not_collect' 表示关闭。
                * 需要 Web JS SDK 版本号大于 1.9.1
                */
        scroll_notice_map: 'default',
                // 通过 collect_tags 配置是否开启其他任意元素的全埋点采集(默认不采集),其中 div 通过配置最多可以采集 3 层嵌套情况。
        collect_tags: { 
          div: {
            max_level: 3, // 默认是 1,即只支持叶子 div。可配置范围是 [1, 2, 3],非该范围配置值,会被当作 1 处理。
          },
          li: true,
          span: true,
          i: true,
          img: true
        },
      },
    })
        // 注册公共属性
    window.cassSensors.registerPage({
      platform_type: 'web',
      path_name: window.location.pathname,
    })
        /**
        * Web 页面浏览($pageview)
        * 设置之后,SDK 就会自动收集页面浏览事件,以及设置初始来源。
        */
    window.cassSensors.quick('autoTrack');

        /** 获取用户登录ID,用户登录后,开发人员调用login,将用户登录ID传给SDK,后续该设备上所有事件的distinct_id就会变成用户所对应的登录ID。*/
    ajax({
      url: '/webim/user/jwt_token',
      type: 'GET',
      success: function (res) {
        try {
          var data = JSON.parse(res);
          window.cassSensors.login(data.username);
        } catch (e) { }
      },
      error: function (error) { }
    });
  }

  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');

  var explorer = window.navigator.userAgent;
  if (explorer.indexOf('MSIE') >= 0) {
    // ie
    script.onreadystatechange = function () {
      if (this.readyState === 'loaded' || this.readyState === 'complete') {
        addShenCeScript();
      }
    }
  } else {
    // chrome
    script.onload = function () {
      addShenCeScript();
    }
  }
  script.setAttribute(
    'src',
    'https://mstatic.cassmall.com/assets/sensors/sensorsdata1.19.4.min.js'
  );
    
    /** 封装ajax请求 */
  function ajax(params) {
    params = params || {};
    params.data = params.data || {};
    // 判断是ajax请求还是jsonp请求
    var json = params.jsonp ? jsonp(params) : json(params);
    // ajax请求 
    function json(params) {
      // 请求方式,默认是GET
      params.type = (params.type || 'GET').toUpperCase();
      // 避免有特殊字符,必须格式化传输数据
      params.data = formatParams(params.data);
      var xhr = null;

      // 实例化XMLHttpRequest对象 
      if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
      } else {
        // IE6及其以下版本 
        xhr = new ActiveXObjcet('Microsoft.XMLHTTP');
      };

      // 监听事件,只要 readyState 的值变化,就会调用 readystatechange 事件
      xhr.onreadystatechange = function () {
        // readyState属性表示请求/响应过程的当前活动阶段,4为完成,已经接收到全部响应数据
        if (xhr.readyState == 4) {
          var status = xhr.status;
          // status:响应的HTTP状态码,以2开头的都是成功
          if (status >= 200 && status < 300) {
            var response = '';
            // 判断接受数据的内容类型
            var type = xhr.getResponseHeader('Content-type');
            if (type.indexOf('xml') !== -1 && xhr.responseXML) {
              response = xhr.responseXML; //Document对象响应 
            } else if (type === 'application/json') {
              response = JSON.parse(xhr.responseText); //JSON响应 
            } else {
              response = xhr.responseText; //字符串响应 
            };
            // 成功回调函数
            params.success && params.success(response);
          } else {
            params.error && params.error(status);
          }
        };
      };

      // 连接和传输数据 
      if (params.type == 'GET') {
        // 三个参数:请求方式、请求地址(get方式时,传输数据是加在地址后的)、是否异步请求(同步请求的情况极少);
        xhr.open(params.type, params.url + '?' + params.data, true);
        xhr.send(null);
      } else {
        xhr.open(params.type, params.url, true);
        //必须,设置提交时的内容类型 
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
        // 传输数据
        xhr.send(params.data);
      }
    }

    //格式化参数 
    function formatParams(data) {
      var arr = [];
      for (var name in data) {
        // encodeURIComponent() :用于对 URI 中的某一部分进行编码
        arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
      };
      // 添加一个随机数参数,防止CDN缓存 
      arr.push('v=' + random());
      return arr.join('&');
    }

    // 获取随机数 
    function random() {
      return Math.floor(Math.random() * 10000 + 500);
    }
  }
  if (!window.cassSensors) {
    document.getElementsByTagName('head')[0].appendChild(script);
  }
})()

记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。