04 May 2014

场景是这样的:上周产品提了个快速需求,在播放页加一个逻辑:用户24小时内第三次及以后访问播放页时,页面中的app下载广告位替换显示为另一个推广广告(影视大全)。

今天产品过来,说大老板给意见了,让把app下载广告替换为影视大全广告的时机改为用户24小时内第6次及以后访问时再进行。

本来是一常规需求,但产品当时提变更的时候,说了一句:是不是你JS这边不用改,直接让cms那边改一下页面就行了?

我回想了一下,好像之前的JS代码就是针对第三次及以后访问进行开发的,因为把最近两次的访问时间给记录到cookie中了。要改成第6次的话,就要记录和检查最近五次的访问时间。之前没考虑到扩展,这次变更还是要先JS这边改逻辑,然后告知页面(这部分JS代码直接贴到页面中了)进行后续更改。所以就如实告诉产品了:要JS这边先改,然后cms那边再改。

但是产品很意外地向我吐槽:不就是改个数吗?怎么还要JS/CMS两边都改呢?

我顿时很无言,只能尴尬地告诉他:我当初逻辑只是按照24小时内第三次及以后访问的需求开发的,没考虑到后续的扩展性,把一些逻辑“硬编码了”,所以现在还是要改的。

现状:硬编码

下面就是之前的24小时内第三次出现的需求场景的JS代码:

var ysdqContainer = Q.$("#widget-ysdq-container");
var appDownContainer = Q.$('#widget-appdown-container');
var ckey = "TQC007";
var cval = Q.cookie.get(ckey);// cval里面记录最近两次打开的时间戳
var DAY = 24 * 60 * 60 * 1000;
var copt = {
  domain: '.iqiyi.com',
  path: '/',
  expires: DAY
};
var thisVal = +new Date;
var showYSDQ = false;
var valArr = [], valLen, oldestVal;
if(cval){//24小时内第一次打开
    Q.cookie.set(ckey, thisVal, copt);
}else{//24小时内打开过
  valArr = cval.split(",");
  valLen = valArr.length;
  oldestVal = parseInt(valArr[valLen - 1], 10);//最早访问的时间戳
  if(valLen == 1){//这里硬编码了
    valArr.unshift(thisVal - oldestVal);
    Q.cookie.set(ckey, valArr.join(","), copt);
  }else{
    //下面几行也是hard-coded
    var recentVal = parseInt(valArr[0], 10);//最近访问的时间戳delta
    recentVal += oldestVal; //恢复recentVal为时间戳
    valArr = [thisVal - recentVal, recentVal];
    if(thisVal - oldestVal <= DAY){
      showYSDQ = true;
    }
    Q.cookie.set(ckey, valArr.join(","), copt);
  }
}
if(showYSDQ){
  ysdqContainer.show();
  appDownContainer.hide();
}else{
  appDownContainer.show();
  ysdqContainer.hide(); 
}

其实上面是很普通的代码,没什么大亮点,也就cookie里面最早的访问时间记的是时间戳,后续的访问都是相对于前一次访问时间的增量。

扩展:可配置

下面改成可配置的:

var ysdqContainer = Q.$("#widget-ysdq-container");
var appDownContainer = Q.$('#widget-appdown-container');
var showAfter = ysdqContainer.attr("data-display-after");
if(showAfter){
  showAfter = parseInt(showAfter, 10);
}
var ckey = "TQC007";
var cval = Q.cookie.get(ckey);// cval里面记录最近两次打开的时间戳
var DAY = 24 * 60 * 60 * 1000;
var copt = {
    domain: '.iqiyi.com',
    path: '/',
    expires: DAY
};
var thisVal = +new Date;
var showYSDQ = false;
if(!cval){//24小时内第一次打开,以本次访问时间戳为基准时间戳
  Q.cookie.set(ckey, thisVal, copt);
}else{//24小时内打开过
  var valArr = cval.split(",");
  var valLen = valArr.length;
  //cookie中最早访问的时间戳,基准时间戳
  var oldestVal = parseInt(valArr[valLen - 1], 10);
  var mostRecentVal = 0; //最近访问的时间戳
  for(var i = 0; i < valLen; i++){
    //valArr中除了最后一个基准时间戳,其它都是增量时间戳
    mostRecentVal += parseInt(valArr[i], 10);
  }
  valArr.unshift(thisVal - mostRecentVal);//记录当前访问的增量时间戳
  valLen++;
  if(valLen > showAfter){//cookie中已存够了策略所需次数的访问时间戳记录
    var lastOldestVal;
    //如果策略调整时showAfter变小,比如5次访问之后出现更改为2次访问之后再出现,
    //那么cookie中最早的3次访问记录要被清理掉,并重设基准时间戳。
    while(valLen > showAfter){
      lastOldestVal = oldestVal;//记录最早的可用于策略判断的访问时间戳
      oldestVal += parseInt(valArr[valLen - 2], 10);
      valArr[valLen - 2] = oldestVal;//更新基准时间戳
      valArr.pop();//丢弃最早的访问记录
      valLen--;
    }
    if(thisVal - lastOldestVal <= DAY){
      showYSDQ = true;
    }
  }
  Q.cookie.set(ckey, valArr.join(","), copt);
}
if(showYSDQ){
  ysdqContainer.show();
  appDownContainer.hide();
}else{
  ysdqContainer.hide();
  appDownContainer.show();
}

优化

补充:除了用一个cookie项记录最近几次的访问时间,还可以用另外一种方式:每次访问都用单独的一个cookie项记录,每个cookie项目的过期时间都设置为24小时。这样只要能读到N个cookie项,就说明24小时内已经访问N次了。

为了节省空间,只用5个cookie项(比如TQC001,TQC002,...,TQC005)进行记录(如果展示逻辑设置为24小时内第6次及以后访问时出现),每个cookie项目的值为1。再用一个cookie项(TQC000)记录最老的cookie项的序号,比如前5次访问时,TQC000的值是1,表示TQC001项是最老的cookie项。第6次访问时,重写TQC001,设置TQC000的值是2;第7次访问时,重写TQC002,设置TQC000的值为3,以此类推,代码如下:

var ysdqContainer = Q.$("#widget-ysdq-container");
var appDownContainer = Q.$('#widget-appdown-container');
var showAfter = ysdqContainer.attr("data-display-after");
if(!showAfter){
  return;
}
showAfter = parseInt(showAfter, 10);
var ckey_prefix = "TQC01";
var nextSeq = 1;
var lastShowAfter = 0;
var meta = Q.cookie.get(ckey_prefix + "0");
if(meta && meta.indexOf(",") > 0){
  meta = meta.split(",");//meta里面存储 nextSeq,lastShowAfter
  nextSeq = parseInt(meta[0]);
  lastShowAfter = parseInt(meta[1]);
}
var DAY = 24 * 60 * 60 * 1000;
var copt = {
    domain: '.iqiyi.com',
    path: '/',
    expires: DAY
};

var counter = 0;//记录24小时内访问的次数
for(var i = 1; i <= lastShowAfter; i++){
  if(Q.cookie.get(ckey_prefix + i)){
    counter++;
  }
}
if( counter < showAfter){//24小时内访问次数没达到showAfter次
  ysdqContainer.hide();
  appDownContainer.show();
}else{
  ysdqContainer.show();
  appDownContainer.hide();
}

Q.cookie.set(ckey_prefix + nextSeq, 1, copt);//记录本次访问
nextSeq = nextSeq + 1;
if(nextSeq > showAfter){
  nextSeq = 1;
}
Q.cookie.set(ckey_prefix + "0", [nextSeq, showAfter].join(","), copt);

第二种方法比第一种更简单,也更节省cookie空间!虽然第一种只占了一个cookie项,但其值中包含时间戳和增量时间戳,其实比showAfter+1个value长度为1的cookie更耗费空间。


TODO,明天验证优化后代码的逻辑正确性。



blog comments powered by Disqus