需求

最近遇到了这么一个需求,我们拥有一个主要容器是微信浏览器的移动端 WebAPP 。他的主要功能是运营一些活动比如说抢红包啦,答题啦(后台可以创建),所以我们需要对用户进入活动的来源进行统计。那么作为一个主要在微信之中运营的产品来讲,其主要的入口大致可以分为以下两点。

  1. 用户分享到朋友圈 (通过微信的分享功能)
  2. 用户直接分享给朋友(通过微信的分享功能)

当然我们实际的业务还会更加复杂,不过我们目前关注这两点就够了。

思路

对于这样的需求,我大致上拥有了一个思路

首先 从用户的进入开始,配置用户的分享地址(微信提供的 API),在后面手动加上一个参数 sourceType=1 ,通过不同的值来标识用户的入口,但是这里会出现一个问题,我在整个项目之中由于不同的分享需求,已经起码用了几十次微信的 API,这么一改动岂不是爆炸。我后来想到了利用 AOP 也就是面向切面编程的思想,去尝试重新包装微信的 API,如此做到类似于拦截器的功能。

下面我来分享一下具体的实现

分享控制

// main.js
// 对微信 API 进行 AOP

// 避免可能的this指向错误
const wxShareMessage = wx.onMenuShareAppMessage.bind(wx)
const wxShareTime = wx.onMenuShareTimeline.bind(wx)

wx.onMenuShareAppMessage = function(options) {
  // 有些地方我会传入一个对象应用(调用的时候直接调用两点,并传入同一个options对象) 这可能会造成与下面的分享到朋友圈 冲突,所以使用浅复制
  options = Object.assign(options)
  // 在某些情况下分享到地址可能是 http://x.c.com/#/home/
  // 需要手动删除最后的/ 方便后面加query
  if (options.link.split('')[options.link.length - 1] == '/') {
    options.link = options.link.slice(0, options.link.lnegth - 1)
  }
  if (!/sourceType/.test(options.link)) {
    // 分享出去的地址没有来源参数 手动加上 并做拼接方式判断
    if (/\?/.test(options.link)) {
      options.link += '&sourceType=2'
    } else {
      options.link += '?sourceType=2'
    }
  } else {
    // 分享出去的地址已经有了来源参数 需要覆盖替换
    options.link = options.link.replace(/sourceType=\d/, `sourceType=2`)
  }
  wxShareMessage(options)
}

// 和上面相同
wx.onMenuShareTimeline = function(options) {
  options = Object.assign(options)
  if (options.link.split('')[options.link.length - 1] == '/') {
    options.link = options.link.slice(0, options.link.lnegth - 1)
  }
  if (!/sourceType/.test(options.link)) {
    if (/\?/.test(options.link)) {
      options.link += '&sourceType=1'
    } else {
      options.link += '?sourceType=1'
    }
  } else {
    options.link = options.link.replace(/sourceType=\d/, `sourceType=1`)
  }
  wxShareTime(options)
}

好的,现在通过重新包装微信的 API,我们已经能够从一个切面彻底的修改我们的整个系统。目前我们需要的就是在被分享者进入的时候转发来源给后端

存储

目前我们需要如果某个用户通过分享进入,那么他的分享来源类型将会被加入到整个应用所有请求的请求头之中。但是,目前我们的来源还存在当前的 query 之中,一但我们使用 push 跳转,这个参数其实是会丢失的。

这里会有两个思路分支

  1. 利用 vue-router 的路由钩子 每次路由变换自动转发这个来源类型
  2. 利用 sessionStorage 存放当前的来源

我个人后续用了第二种,也就是 sessionStorage,因为这个方式相对而言比较简单,看起来也非常安全,一但页面关闭就自动删除

// app.vue
export default {
  watch: {
    $route() {
      if (this.$route.query.sourceType) {
        sessionStorage.setItem('sourceType', vm.$route.query.sourceType)
      }
    }
  }
}

微信中似乎有些奇怪的 BUG,我放置到了$route 的观察者之中来存放来源

转发

接下来就是非常简单的部分了,利用我们的请求拦截器,直接放置到 header 之中

Vue.http.interceptors.push((request, next) => {
  // 如果有来源信息 发送给后端
  let sourceType = sessionStorage.getItem('sourceType')
  if (sourceType) {
    request.headers.set('sourceType', sourceType)
  }
})

总结

到目前为止我们的整个功能就实现了,其实发现这种 AOP 的思路真的非常有用,非常的轻量可控实用。在Javascript这种灵活的语言之中更是相当棒!