`
huangzhir
  • 浏览: 125013 次
  • 性别: Icon_minigender_1
  • 来自: 福州
文章分类
社区版块
存档分类
最新评论

享受代码的快乐--小米抢购前端代码分析

阅读更多
    今天来个比较潮的,分析一下小米前端js排队代码;原来的代码和注解后的代码在附件里!
    话说小米手机不错,主要是miui不错。买个手机天天要抢。其实我是比讨厌这种营销。
把手机价格搞的低低的,吸引注意。好长一段时间,你很难抢的到手机。
相比小米,我更喜欢魅族,虽然我用的是小米1s.以后有点闲钱,换手机买一部魅族。
    下面来享受这段代码。
    10.15号的抢购js代码,都写到一个js文件中.里面的代码是mini版本的,查个工具把他格式化一下就可以了。
   http://p.www.xiaomi.com/open/131009/mi3/buy/process.min.js
    小米为了增加代码阅读的复杂性(也可能为了转义字符),把一些变量定义成一个数组,而且这个数组用16进制表示。
    做过白帽子的,应该很熟悉。用16进制表示字符找出很多xss漏洞,sql注入漏洞吧。可以自己写(其实alert或console.info 16进制就可以得到字符串)或找个工具转成字符串,增加代码的可读写性
    好了,下面就来分析代码了。
    以10.15的抢购代码为例
    为了阅读性我们两成两段,一个是他的数组变量_$,另一段是就是其它代码
_$ 变量太长了,我贴一点点,不完整,要看的见附件!
var _$ = ["\x44\x4f\x4d\x43\x6f\x6e\x74\x65\x6e\x74\x4c\x6f\x61\x64\x65\x64", "\x6f\x6e\x72\x65\x61\x64\x79\x73\x74\x61\x74\x65\x63\x68\x61\x6e\x67\x65", "\x44\x4f\x4d\x43\x6f\x6e\x74\x65\x6e\x74\x4c\x6f\x61\x64\x65\x64", "\x6f\x6e\x72\x65\x61\x64\x79\x73\x74\x61\x74\x65\x63\x68\x61\x6e\x67\x65", '\x6c\x65\x66\x74', "\x73\x74\x72\x69\x6e\x67", "\x5b\x6f\x62\x6a\x65\x63\x74\x20\x4f\x62\x6a\x65\x63\x74\x5d", '\x6e\x75\x6d\x62\x65\x72', '\x3d', '\x3b\x20\x65\x78\x70\x69\x72\x65\x73\x3d', '', '\x3b\x20\x70\x61\x74\x68\x3d'];



/**
 * 代码作者    小米公司
 * 代码分析者 huangzhir@gmail.com
 */



//是否回滚状态  即时间等待是否结束  这个是用来做重新进入活动的状态
var isRollStatus = false,
//是否购买手机
isPhone = false,
//是否购买盒子,有时用来做是否购买电视
isBox = false;
/**
 *  (function() {})(); 自调匿名函数用法,可以减少局部变量
 *   注意,这是m可不是局部变量,虽然定义在自调匿名函数里,
 *   ,因为不是用 var m={};而是用直接 用m={};
 *
 */
(function() {
   m = {
        arrelems: [],//元素数组 在js 里用 {} 表示一个对象,用[]表示一个数组  这里这个都没用到
        ready: null,
        /*
         * doms 这个方法写了这么一大堆,一眼看你还看不出这个要做什么,
         * 如果你看jquery的源码,肯定很清楚,这个就是Jquery.read() 方法的源代码 搬过来
         * 那为什么要写这么重新写,而不用jquery呢,我想是因为这是一个抢购页面,访问次数非常的大,
         * 不去引用jquery,减少页面的访问连接数,你看这个页面的源代码就知道,css ,javascript 都没引用其它链接。
         * 这个页面的js,也是10.15这次,才写到一个js文件里,在页面做引用的。以前也都放在一个页面里,这样可以减少服务器的负担
         * 这方面做的比较差或者说很差的就是12306,有用没用都引用一大堆的 css 和 javascript
         * 除去这段Jquery.read() 方法的源代码 代码,你会发现小米的排队代码还是很简单,很容易的理解的
         */
        doms: function() {
            var _this = this;//  this 这是指m
            var isReady = false;//    addDOMLoadEvent 是否已经完成,是否准备好
            var readyList = [];// 这个用来在存放  addDOMLoadEvent 未完成之时,马上要执行的方法
            var timer;  //利用 doScroll() 方法来模拟 addDOMLoadEvent 事件的方案 的timer 临时定时器
            _this.ready = function(fn) {    // 设置m.ready 为一个方法
                if (isReady) {
                    fn.call(document);//执行 fn方法 相当于 fn(document);
                } else {
                    readyList.push(function() {   //数组新加一个方法 这个方法为调用 fn(this) 反返回值
                        return fn.call(this);
                    });
                };
                return this;
            };
            var onDOMReady = function() {   // 这样的写法相当于  function onDOMReady()  执行  readyListx 方法,然后清空
                //执行 readyList 数组里面的所有方法  方法名.call()和 方法名.apply() 作用差不多, call()传参数是一个人个传的,apply()是传一个参数对象数组
                for (var i = 0x0; i < readyList.length; i++) {  //0x0 是16进制的表示方法 就是0
                    readyList[i].apply(document)
                };
                readyList = null;
            };
            var bindReady = function(evt) {
                if (isReady) {
                    return
                };
                isReady = true;
                onDOMReady.call(window);  //触发所有要要执行的方法
                if (document.removeEventListener) {   //  非ie 浏览器
                    document.removeEventListener(_$[0], bindReady, false)    //_$[0]=DOMContentLoaded ,移除邦定DOMContentLoaded方法 ,DOMContentLoaded 与 onload 有点类似,DOMContentLoaded 仅当DOM加载完成,不包括样式表,图片,flash
                } else if (document.attachEvent) {    //  ie 浏览器
                    document.detachEvent(_$[1], bindReady);  //_$[1]=onreadystatechange; ie的写法
                    if (window == window.top) {
                        clearInterval(timer);  //清楚定时器
                        timer = null
                    }
                }
            };

            if (document.addEventListener) {
                document.addEventListener(_$[2], bindReady, false) // _$[2]=DOMContentLoaded  添加 DOMContentLoaded 绑定  bindReady 方法
            } else if (document.attachEvent) {
                //_$[3]=onreadystatechange    功能同上
                //利用 doScroll() 方法来模拟 addDOMLoadEvent 事件的方案,且现在主流的 JavaScript 框架(JQuery、YUI等)基本都采用的这一解决方案。
                document.attachEvent(_$[3], function() {
                    if ((/loaded|complete/).test(document.readyState)) bindReady()
                });
                if (window == window.top) {
                    timer = setInterval(function() {
                        try {
                            isReady || document.documentElement.doScroll(_$[4]) //_$[4]=left
                        } catch (e) {
                            return
                        };
                        bindReady()
                    }, 0x5)
                }
            }
        },
        $: function() {       //_$[5]=String  如果方法参数是字符串返回document.getElementById()对像,如果不是返回第1个参数对象
            return typeof arguments[0x0] === _$[5] ? document.getElementById(arguments[0x0]) : arguments[0x0]
        },
        extend: function(target, options) {  //对象属性copy
            for (name in options) {
                target[name] = options[name]
            };
            return target
        },
        //这个方法是不是也有点似曾相识的感觉,这个方法跟jquery.cookie.js 插件用法,代码基本也一样,后面一段实现方式有点不同
        cookie: function(key, value, options) {
            if (arguments.length > 0x1 && String(value) !== _$[6]) {  //_$[6]='[object Object]' 如果伟入的参数大于1个,value 不是对象类型
                if (value === null || value === undefined) {
                    options.expires = -0x1
                };
                if (typeof options.expires === _$[7]) {  // _$[7]='number'
                    var days = options.expires,
                        t = options.expires = new Date();
                    t.setDate(t.getDate() + days)
                };
                value = String(value);
                //_$[8]='='    _$[9]='; expires='  _$[10]=''  _$[11]='; path='  _$[12]='' _$[13]='; domain='    $[14]=''   $[15]='; secure'  $[16]=''   $[17]=''
                //  key, value, options 拼接成cookie串, cookie='name=value;name2=value2;name3=value3;...' ;
                return (document.cookie = [encodeURIComponent(key), _$[8], options.raw ? value : encodeURIComponent(value),
                    options.expires ? _$[9] + options.expires.toUTCString() : _$[10],
                    options.path ? _$[11] + options.path : _$[12],
                    options.domain ? _$[13] + options.domain : _$[14],
                    options.secure ? _$[15] : _$[16]].join(_$[17]))
            };
            //以下是从现有的document.cookie 中取key的value值
            // 也就是说如果只有一个参数,如调用 cookie(key) 就从 document.cookie 取 key的value值
            // ps:是不是似曾相识,这要买 jquery 里的 val() attr() 方法一样
            options = value || {};
            var result, decode = options.raw ? function(s) {
                return s
            } : decodeURIComponent;
            //  _$[18]= '(?:^|; )'   _$[19]='=([^;]*)'
            //括号 "( )" 内的子表达式,如果希望匹配结果不进行记录供以后使用,可以使用 "(?:xxxxx)" 格式
            //用 [^ ] 包含一系列字符,则能够匹配其中字符之外的任意一个字符
            //    (?:^|;)key=([^;]*)     正则图形 http://www.regexper.com/#(%3F%3A%5E%7C%3B%20)key%3D(%5B%5E%3B%5D*)
            // 正则表达式用来"key=keyvalue; name1=value1; name2=value2; name3=value3" 中取出  key=keyvalue
            //  _$[18]= '(?:^|; )' ,在;后面多了个空格,因为cookie的存放方式就是就分号后面有个空格,原来误解了!
            // result[0x1] 取的就是value 匹配的第二组 第1组就是key=keyvalue
            return (result = new RegExp(_$[18] + encodeURIComponent(key) + _$[19]).exec(document.cookie)) ? decode(result[0x1]) : null
        },
        addEvent: (function() {    //为参数里的元素增加绑定方法
            if (document.addEventListener) {
                return function(el, type, fn) {
                    el.addEventListener(type, fn, false)
                }
            } else {
                //_$[20]='on'
                return function(el, type, fn) {
                    el.attachEvent(_$[20] + type, function() {
                        return fn.call(el, window.event)
                    })
                }
            }
        })(),
        randomDiff: function(option) {
            var paras = {
                    randomS: 0x1,
                    randomE: 0x5
                },
                options = m.extend(paras, option || {}),    //把 option copy到 paras
                //随机从  randomS 到  randomS 取一个数,这里就是随机取1-5
                randomNum = Math.random() * (options.randomE - options.randomS + 0x1) + options.randomS;
            return parseInt(randomNum)
        },
        //取url中查询参数name的value
        getQueryString: function(name) {
            //_$[21] ='(^|&)'   _$[22]='=([^&]*)(&|$)'  _$[23]='i'
            //  (^|&)name=([^&]*)(&|$) 正则图形 http://www.regexper.com/#(%5E%7C%26)name%3D(%5B%5E%26%5D*)(%26%7C%24)
            //RegExp 的第二个参数 i	执行对大小写不敏感的匹配。 g	执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。  m	执行多行匹配
            var reg = new RegExp(_$[21] + name + _$[22], _$[23]);
            //window.location.search 表示浏览器url中 包括?后面的参数 如:?pat=aa&key=value
            var r = window.location.search.substr(0x1).match(reg);
            //取出第二组的值,即([^&]*)的值
            if (r != null) return unescape(r[0x2]);
            return null
        },
        // 生成一个js <javascript id="elemid" src="src"/>  播放到body中
        // 这个主要是用jsonp 跨域跟服务交互, 小米用这种方式把数据提交到服务器,在服务做排列
        creatJs: function(src, elemid) {
            var Scrip = document.createElement(_$[24]);  //_$[24]='script'
            Scrip.src = src;
            if ( !! elemid) {
                Scrip.id = elemid;
                if (m.$(elemid)) {
                    document.body.removeChild(m.$(elemid))
                }
            };
            document.body.appendChild(Scrip)
        },
        //这个表示 如果浏览器是手机,跳转到参数url,如果 orgin='mapp' 就清空
        phone: function(url) {
            url = url || _$[25]; // _$[25]='http://m.xiaomi.com' 这种很经典的写法,如果url 没值 取_$[25] 为默认值
            //如果 cookie 中 orgin 的值为 mapp ,就设置orgin 为空
            if (m.cookie(_$[26]) === _$[27]) { // _$[26]='orgin' _$[27]='mapp'
                m.cookie(_$[28], null, {   //_$[28]='orgin'
                    path: _$[29], //_$[29]='/'
                    domain: _$[30]    //_$[30]='.xiaomi.com'
                });
                return
            };
            var sUserAgent = navigator.userAgent;
            //   $[31]=  'Android', $[32]= 'iPhone', $[33]='iPod', $[34]= 'Symbian'
            // 如果浏览器是移动设备,跳转到url
            if (sUserAgent.indexOf(_$[31]) > -0x1 || sUserAgent.indexOf(_$[32]) > -0x1 || sUserAgent.indexOf(_$[33]) > -0x1 || sUserAgent.indexOf(_$[34]) > -0x1) {
                location.href = url
            }
        },
        //初始化方法
        init: function() {
            var _this = this;
            _this.doms()
        }
    };
    m.init()
})();

//jsonp的回调函数  json为服务端返回的数据
function hdcontrol(json) {
    var phonestart = json.status.miphone.hdstart,  //是否手机抢购开始
        phonestop = json.status.miphone.hdstop,    //是否手机抢购结束
        boxstart = json.status.mibox.hdstart,      //是否盒子抢购开始
        boxstop = json.status.mibox.hdstop;        //是否盒子抢购结束
    servertime = downServertime = json.stime;
    //服务端与客户端的时差
    var diffTime = parseInt(servertime - miphoneBuy.localTime());
    //_$[35]='xm_difft_hd' _$[36]='/'  _$[37]='.xiaomi.com'  写入xm_difft_hd=时差 到cookie中
    m.cookie(_$[35], diffTime, {
        path: _$[36],
        domain: _$[37],
        expires: 0x1 //一天后过期
    });
    //如果等待已经结束
    if (isRollStatus) {
        if (json.status.allow) {  //排队成功,服务端返回排队成功,这个真的太幸福,你基本抢到了!跳转到那个购买页面,选择一下产品和验证码提交就ok了
            if (isPhone === true) { //买手机
                isPhone === false;   //还是一个bug,我认为,这里应该用 isPhone=false; 不过这里也没什么关系  ps:小米程序员也是人,虽然 6X12 工作,但偶而也会笔误 ,哈哈
                //_$[38]=''  _$[39]='http://t.hd.xiaomi.com/s/'
                if (json.status.miphone.hdurl == null || json.status.miphone.hdurl == _$[38]) {   //如果 hdurl为空,重新刷新页面
                    window.location.reload()
                } else {
                    //_$[39]='http://t.hd.xiaomi.com/s/'
                    //浏览器url 转向到购买小米页面
                    //注意如果真访问这个url是没用的,系统会自动跳转到排序页面,只有排列成功才能进入这个页面 ps:小米人家也不是吃素的,
                    location.href = _$[39] + json.status.miphone.hdurl
                }
            } else if (isBox === true) {   //买盒子,或电视,其它第二选项
                isBox === false; //同上
                ////_$[40]=''    _$[41]='http://t.hd.xiaomi.com/s'
                if (json.status.mibox.hdurl == _$[40] || json.status.mibox.hdurl == null) {
                    window.location.reload()
                } else {
                    location.href = _$[41] + json.status.mibox.hdurl
                }
            }
        };
        isRollStatus = false
    };
    //只有手机抢购   写入cookie
    if (phonestart === true && phonestop === false && boxstart === false && boxstop === true) {
        // 盒子已售完     'xm_xt_obox',//42   '/',//43   '.xiaomi.com',//44
        m.cookie(_$[42], 0x1, {
            path: _$[43],
            domain: _$[44],
            expires: 0x1
        });
        //'xm_xt_ophone',//45    '/',//46     '.xiaomi.com',//47
        m.cookie(_$[45], null, {
            path: _$[46],
            domain: _$[47]
        });
        miphoneBuy.box(false);
        stepHtml.five()
    };
    //只有盒子抢购
    if (phonestart === false && phonestop === true && boxstart === true && boxstop === false) {
        //手机已售完 'xm_xt_ophone',//48     '/',//49    '.xiaomi.com',//50
        m.cookie(_$[48], 0x1, {
            path: _$[49],
            domain: _$[50],
            expires: 0x1
        });
        //'xm_xt_obox',//51     '/',//52    '.xiaomi.com',//53
        m.cookie(_$[51], null, {
            path: _$[52],
            domain: _$[53]
        });
        miphoneBuy.box(false);
        stepHtml.six()
    };
    //手机,盒子 都没了
    if (phonestart === false && phonestop === true && boxstart === false && boxstop === true) {
        // 'xm_xt_pre',//54  '/',//55  '.xiaomi.com',//56
        m.cookie(_$[54], 0x1, {
            path: _$[55],
            domain: _$[56],
            expires: 0x1
        });
        // 'xm_xt_obox',//57    '/',//58    '.xiaomi.com',//59
        m.cookie(_$[57], null, {
            path: _$[58],
            domain: _$[59]
        });
        // 'xm_xt_ophone',//60    '/',//61    '.xiaomi.com',//62
        m.cookie(_$[60], null, {
            path: _$[61],
            domain: _$[62]
        });
        miphoneBuy.saleOut()
    }
};
var miphoneBuy = {
    localTime: function() {       //本地时间
        return parseInt(new Date().getTime() / 0x3e8) //0x3e8 是16进制 表示10进行的1000
    },
    jsonInter: function() {  //利用jsonp 调用服务端  这个除了排队处,还用来做时差,还做手机和盒子抢购的状态
        var timestamp = new Date().getTime();
        //'http://tc.hd.xiaomi.com/hdget?callback=hdcontrol&_=',//63  这个地址就是服务排列的url
        // 'jsonp' //64
        m.creatJs(_$[63] + timestamp, _$[64]);
    },
    checkCookie: function() {
        var _this = this;
        //    'xm_xt_pre',//65  如果有值,说明小米和盒子已经出售完
        if (m.cookie(_$[65])) {
            _this.saleOut()
        } else {
            // 'xm_difft_hd',//66
            if (!m.cookie(_$[66])) {  //如果cookie 中  xm_difft_hd 没有值,就提交服务端,取时间或者排队
                _this.jsonInter()
            };
            setTimeout(function() {
                // 'xm_difft_hd',//67  服务器时间=本地时间+时差
                servertime = downServertime = miphoneBuy.localTime() + parseInt(m.cookie(_$[67]));
                _this.jugeStatus()
            }, 0x64) //0x64 就是100
        }
    },
    saleOut: function() {
        if (window.timeInter) {
            clearInterval(timeInter)
        };
        stepHtml.four();
        miphoneBuy.box(false)
    },
    jugeStatus: function() { //状态判断 ,定时器每秒查一下状态
        var _this = this;
        _this.timeNode();
        timeInter = window.setInterval(function() {
            _this.timeNode();
            servertime++
        }, 0x3e8) // 0x3e8 =1000 每秒执行一次
    },
    configs: {
        //    '10/15/2013  12:00:00',//68   开抢的开始时间
        startDate: new Date(_$[68]).getTime(),
        preLogMin: 0x1e    // 0x1e=30
    },
    timeNode: function() {   //时间结点 ,根据本地的服务器时间,判断现在处于哪一步,然后执行哪一步
        var _this = this;
        var _this = this;
        var nowTime = servertime * 0x3e8, //服务器的当前时间
            startTime = _this.configs.startDate,
            //开始倒计时时间
            preLogTime = startTime - _this.configs.preLogMin * 0xea60;    //  0xea60 =60000
        if (m.cookie(_$[69])) { // 全部都售完   'xm_xt_pre',//69
            _this.saleOut();
            return
        };
        if (m.cookie(_$[70])) {     //如果有时差,'xm_difft_hd',//70      'xm_difft_hd',//71
            //服务器的时间 =本地时间+ 时间差 ,倒计时都是用服务器时间
            downServertime = miphoneBuy.localTime() + parseInt(m.cookie(_$[71]))
        };
        if (nowTime < preLogTime) {      //进入倒计时,还没即将开始,暂时可以不用登录
            stepHtml.one();
            _this.timeOut()
        } else if (nowTime >= preLogTime && nowTime < startTime) {  //已经进入倒计时,即将开始,但还没开始
            stepHtml.two();
            _this.timeOut()
        } else if (nowTime >= startTime) { //已经开始抢购
            if (m.cookie(_$[72])) {       // 盒子已售完 'xm_xt_obox',//72
                stepHtml.five()
            } else if (m.cookie(_$[73])) {  //手机已售完 'xm_xt_ophone',//73
                stepHtml.six()
            } else {            //盒子和手机都还有
                stepHtml.three()
            }
        }
    },
    timeOut: function() {   //如果有倒计时间,重新触发倒计时
        var _this = this;
        _this.SetRemainTime();
        if (window.InterValObj) {
            clearInterval(InterValObj)
        };
        //  InterValObj 表示离开始抢购的赶时间倒计时定时器
        InterValObj = window.setInterval(function() {
            _this.SetRemainTime()
        }, 0x3e8)
    },
    SetRemainTime: function() {      //倒计时,设置倒计时时间
        var _this = this,
            endtime = _this.configs.startDate,
            setEndtime = endtime / 0x3e8;
        surplusTime = setEndtime - downServertime;
        if (surplusTime >= 0x0) {  //还在倒计
            var second = Math.floor(surplusTime % 0x3c),   //分钟
                minite = Math.floor((surplusTime / 0x3c) % 0x3c),   //秒
                hour = Math.floor((surplusTime / 0xe10) % 0x18),    //小时
                day = Math.floor((surplusTime / 0x15180) % 0x1e);   //天
            downServertime++;
            var timeArray = [second.toString(), minite.toString(), hour.toString(), day.toString()];
            // '<span><ins>',//74   '</ins>小时<ins>',//75   '</ins>分<ins>',//76   '</ins></span>秒后开始',//77
            var timeText = _$[74] + timeArray[0x2] + _$[75] + timeArray[0x1] + _$[76] + timeArray[0x0] + _$[77];
            if (hour === 0x0) {  //小时等于0 只要展示分钟和秒钟
                // '<span><ins>',//78   '</ins>分<ins>',//79  '</ins></span>秒后开始',//80
                timeText = _$[78] + timeArray[0x1] + _$[79] + timeArray[0x0] + _$[80];
                if (minite === 0x0) {  //如果不到一分,只民法秒钟
                    //'<span><ins>',//81   '</ins></span>秒后开始',//82
                    timeText = _$[81] + timeArray[0x0] + _$[82]
                }
            };
            //'surTime',//83  'surTime',//84
            // !!对象  对象是否存在的用法   时间结点变化成新的倒计时间
            if ( !! m.$(_$[83])) {
                m.$(_$[84]).innerHTML = timeText
            }
        } else {   //停止倒计时
            if (window.InterValObj) {
                clearInterval(InterValObj)
            }
        }
    },
    box: function(bool) {
        if (bool) {   //显示 正在排序中 div
            //<div id="boxbg"></div><div id="box"><span class="close" onclick="miphoneBuy.box(false);" title="关闭">X</span><div class="buyNext_wrap"><h3>正在排队中...</h3>您已在队伍中,倒计时后请重新进入,稍安勿躁:-)<a id="reback" class="reback_btn">重新进入(<span id="initCount"> </span>)</a></div></div>
            //'boxbg',//85    'box',//86
            var mboxbg = m.$(_$[85]); //排队div bg
            var mbox = m.$(_$[86]); //排队div
            //    'px',//87
            mboxbg.style.height = Math.max(document.documentElement.clientHeight, document.documentElement.scrollHeight) + _$[87];
            // 'block',//88  'block',//89
            mboxbg.style.display = _$[88];
            mbox.style.display = _$[89]
        } else {    //隐藏 正在排序中 div
            // 'boxbg',//90   'box',//91    'none',//92    'none',//93
            var mboxbg = m.$(_$[90]);
            var mbox = m.$(_$[91]);
            mboxbg.style.display = _$[92];
            mbox.style.display = _$[93];
            if (window.rollInter) {
                clearInterval(rollInter)   //清楚 排除等待
            };
            Util.retime();
            // 'reback',//94  '重新进入(<span id='initCount'>',//95  '</span>)',//96
            m.$(_$[94]).innerHTML = _$[95] + count + _$[96]
        }
    },
    init: function(option) {
        var _this = this;
        _this.checkCookie()
    }
};
var stepHtml = {
    one: function() {     //第一步 倒计时 即将开始,
        var mhtml = stepHtml.htmlString();
        // 'hdBtns',//97  'hdSubTitle',//98 'hdLnks',//99  'miphone',//100  'mitv',//101 'hdMsg',//102
        m.$(_$[97]).innerHTML = mhtml.btn[0x0];
        m.$(_$[98]).innerHTML = mhtml.subTitle[0x0];
        m.$(_$[99]).innerHTML = mhtml.lnks[0x0];
        m.$(_$[100]).innerHTML = mhtml.phonebtn[0x0];
        m.$(_$[101]).innerHTML = mhtml.tvbtn[0x0];
        m.$(_$[102]).innerHTML = mhtml.msg[0x0];
        m.$(_$[103]).innerHTML = mhtml.bottombtn[0x0]
    },
    two: function() {      //第二步,倒计时 ,离只有不到preLogMin(30)分),与第一步步有点不同,就是如果你还没登录,让你提前登录
        var mhtml = stepHtml.htmlString();
        var miID = (m.cookie(_$[104])); //    'userId',//104
        if (miID) {
            m.$(_$[105]).innerHTML = mhtml.btn[0x0]  //    'hdBtns',//105   倒计时
        } else {
            m.$(_$[106]).innerHTML = mhtml.btn[0x1]     //    'hdBtns',//106    提前登录
        };
        //'hdLnks',//107   'hdSubTitle',//108   'miphone',//109   'mitv',//110    'hdMsg',//111    'bottombtn',//112
        m.$(_$[107]).innerHTML = mhtml.lnks[0x0];
        m.$(_$[108]).innerHTML = mhtml.subTitle[0x0];
        m.$(_$[109]).innerHTML = mhtml.phonebtn[0x0];
        m.$(_$[110]).innerHTML = mhtml.tvbtn[0x0];
        m.$(_$[111]).innerHTML = mhtml.msg[0x0];
        m.$(_$[112]).innerHTML = mhtml.bottombtn[0x0]
    },
    three: function() {        //第三步,开始抢,两个都还有
        var mhtml = stepHtml.htmlString();
        // 'hdBtns',//113  'hdSubTitle',//114   'miphone',//115  'mitv',//116    'hdMsg',//117  'hdLnks',//118    'bottombtn',//119
        m.$(_$[113]).innerHTML = mhtml.btn[0x2];
        m.$(_$[114]).innerHTML = mhtml.subTitle[0x0];
        m.$(_$[115]).innerHTML = mhtml.phonebtn[0x1];
        m.$(_$[116]).innerHTML = mhtml.tvbtn[0x1];
        m.$(_$[117]).innerHTML = mhtml.msg[0x4];
        m.$(_$[118]).innerHTML = mhtml.lnks[0x0];
        m.$(_$[119]).innerHTML = mhtml.bottombtn[0x1]
    },
    four: function() {       //第四步 ,支付
        //'hdBtns',//120  'miphone',//121  'mitv',//122   'hdSubTitle',//123  'hdMsg',//124   'hdLnks',//125  'bottombtn',//126
        var mhtml = stepHtml.htmlString();
        m.$(_$[120]).innerHTML = mhtml.btn[0x3];
        m.$(_$[121]).innerHTML = mhtml.phonebtn[0x2];
        m.$(_$[122]).innerHTML = mhtml.tvbtn[0x2];
        m.$(_$[123]).innerHTML = mhtml.subTitle[0x3];
        m.$(_$[124]).innerHTML = mhtml.msg[0x3];
        m.$(_$[125]).innerHTML = mhtml.lnks[0x3];
        m.$(_$[126]).innerHTML = mhtml.bottombtn[0x2]
    },
    five: function() {    //第五步, 小米电视或盒子已售罄,但还有小米手机
       //'hdBtns',//127   'miphone',//128    'hdSubTitle',//129   'mitv',//130   'hdMsg',//131    'hdLnks',//132   'bottombtn',//133
        var mhtml = stepHtml.htmlString();
        m.$(_$[127]).innerHTML = mhtml.btn[0x4];
        m.$(_$[128]).innerHTML = mhtml.phonebtn[0x1];
        m.$(_$[129]).innerHTML = mhtml.subTitle[0x2];
        m.$(_$[130]).innerHTML = mhtml.tvbtn[0x2];
        m.$(_$[131]).innerHTML = mhtml.msg[0x1];
        m.$(_$[132]).innerHTML = mhtml.lnks[0x1];
        m.$(_$[133]).innerHTML = mhtml.bottombtn[0x1]
    },
    six: function() {   //第六部, 小米手机已售罄,但还有小米电视或盒子
        //'hdBtns',//134  'miphone',//135   'hdSubTitle',//136   'mitv',//137  'hdMsg',//138  'hdLnks',//139   'bottombtn',//140
        var mhtml = stepHtml.htmlString();
        m.$(_$[134]).innerHTML = mhtml.btn[0x5];
        m.$(_$[135]).innerHTML = mhtml.phonebtn[0x2];
        m.$(_$[136]).innerHTML = mhtml.subTitle[0x1];
        m.$(_$[137]).innerHTML = mhtml.tvbtn[0x1];
        m.$(_$[138]).innerHTML = mhtml.msg[0x2];
        m.$(_$[139]).innerHTML = mhtml.lnks[0x2];
        m.$(_$[140]).innerHTML = mhtml.bottombtn[0x2]
    },
    htmlString: function() {
        var htmlSum = {
            //'<span class="untime"><label id="surTime" class="unbtn"></label></span>',//141
            //'<a class="unbtn" target="_blank" href="http://p.www.xiaomi.com/zt/xm_account/limitfacade.html?third=http%253A%252F…MmRiNzY0ZWY0MDJlYTVkODBlZA%252C%252C&sign=3C15pt35v9KK5SR8saFsKQ89uRo%253D" title="提前登录" >提前登录</a>',//142
            //'<a class="btn1 cu" onclick="showBox(\'phone\');" id="buyNext">购买手机>><span class="value">1999元</span></a><span class="line">|</span><a class="btn2 cu" onclick="showBox('box');"  title="购买盒子">购买电视>><span class="value">2999元</span></a>',//143
            //'<a class="btn1 cu" href="http://t.hd.xiaomi.com/r/?_a=payment_check" target="_blank"><span class="value">1999元</span>支付手机>></a><span class="line">|</span><a class="btn2 cu" href="http://t.hd.xiaomi.com/r/?_a=payment_check_tv" target="_blank">支付电视>><span class="value">2999元</span></a>',//144
            //'<a class="btn1 cu" onclick="showBox(\'phone\');">购买手机>><span class="value">1999元</span></a><span class="line">|</span></a><a class="btn2 cu" href="http://t.hd.xiaomi.com/r/?_a=payment_check_tv" target="_blank">支付电视>><span class="value">2999元</span></a>',//145
            //'<a class="btn1 cu" onclick="showBox(\'box\');">购买电视>><span class="value">2999元</span></a><span class="line">|</span><a class="btn2 cu" href="http://t.hd.xiaomi.com/r/?_a=payment_check" target="_blank">支付手机>><span class="value">1999元</span></a>',//146
            btn: [_$[141], _$[142], _$[143], _$[144], _$[145], _$[146]],
            //'',//147
            //'小米手机3已售罄,您可继续购买小米电视',//148
            //'小米电视已售罄,您可继续购买小米手机3',//149
            //'全部小米手机3及小米电视已售罄',//150
            subTitle: [_$[147], _$[148], _$[149], _$[150], ],
            //'',//151
            //'购机成功用户请在3小时内下单,3小时内支付。[url=http://t.hd.xiaomi.com/r/?_a=payment_check ]支付手机》[/url]',//152
            //'购机成功用户请在3小时内下单,3小时内支付。[url=http://t.hd.xiaomi.com/r/?_a=payment_check_tv]支付电视》[/url]',//153
            //'购机成功用户请在3小时内下单,3小时内支付。',//154
            //'购机成功用户请在3小时内下单,3小时内支付。[url=http://t.hd.xiaomi.com/r/?_a=payment_check ]支付手机》[/url][url=http://t.hd.xiaomi.com/r/?_a=payment_check_tv]支付电视》[/url]',//155
            msg: [_$[151], _$[152], _$[153], _$[154], _$[155]],
            //'[url=http://t.hd.xiaomi.com/r/?_a=20131009&_op=check]手机预约查询[/url][url=http://t.hd.xiaomi.com/r/?_a=20131009_tv&_op=check]电视预约查询[/url]',//156
            //'[url=http://t.hd.xiaomi.com/r/?_a=20131009&_op=check]手机预约查询[/url][url= http://t.hd.xiaomi.com/c/?_a=20131015_mi_tv_3a52v09cc&_op=check]电视购买查询[/url]',//157
            //'[url=http://t.hd.xiaomi.com/c/?_a=20131015_m3_7e52109fc&_op=check]手机购买查询[/url][url=http://t.hd.xiaomi.com/r/?_a=20131009_tv&_op=check]电视预约查询[/url]',//158
            //'[url=http://t.hd.xiaomi.com/c/?_a=20131015_m3_7e52109fc&_op=check]手机购买查询[/url][url= http://t.hd.xiaomi.com/c/?_a=20131015_mi_tv_3a52v09cc&_op=check]电视购买查询[/url]',//159
            //' ',//160
            lnks: [_$[156], _$[157], _$[158], _$[159], _$[160], ],
            //'<a class="btn de"><span>1999元(16GB)</span>即将开始</a>',//161
            //'<a id="hdredPhone" onClick="showBox(\'phone\');" class="btn cu"><span>1999元(16GB)</span>立即购买</a>',//162
            //'<a class="btn" href="http://t.hd.xiaomi.com?_a=payment_check " target="_blank"><span>1999元(16GB)</span>立即支付</a>',//163
            phonebtn: [_$[161], _$[162], _$[163]],
            //'<a class="btn de"><span>2999元</span>即将开始</a>',//164
            //'<a onClick="showBox(\'box\');" class="btn cu"><span>2999元</span>立即购买</a>',//165
            //'<a class="btn" href="http://t.hd.xiaomi.com/r/?_a=payment_check_tv" target="_blank"><span>2999元</span>立即支付</a>',//166
            tvbtn: [_$[164], _$[165], _$[166]],
            //'<label class="btn unbtn">即将开始</label>',//167
            //'<a id="hdblackPhone" onClick="showBox(\'phone\');" class="btn cu">购买手机</a>',//168
            //'<a class="btn cu" href="http://t.hd.xiaomi.com/r/?_a=payment_check " target="_blank">支付手机</a>',//169
            bottombtn: [_$[167], _$[168], _$[169]]
        };
        return htmlSum
    }
};
var randomCount = parseInt(Math.random() * (0xa - 0x5 + 0x1) + 0x5),  //随机取5 到 11 中的整数
    count = randomCount,
    CONFIG = {
        count: randomCount
    },
    Util = {
        time: function() {   // 进入活动(排列)时间倒计
            var b = m.$(_$[170]);  //  'reback',//170
            if (count === 0x0) {  //如果倒计时时间为0
                b.innerHTML = _$[171];  // '进入活动',//171
                b.className = _$[172];  // 'reback_btn_next',//172
                this.start();
                return false
            };
            count = count - 0x1;
            b.innerHTML = _$[173] + count + _$[174]//'重新进入(<span id=\'initCount\'>',//173   '</span>)',//174
        },
        start: function() {    //进入活动(开始排列抢购)
            var me = this,
                reback = m.$(_$[175]);  // 'reback',//175
            reback.onclick = function() {  //点击进入活动按钮,
                isRollStatus = true;
                miphoneBuy.jsonInter();   //提交服务端排队
                me.retime();
                reback.onclick = null;
                return false
            }
        },
        retime: function() {       //重新计时 count时间重新设
            m.$(_$[176]).className = _$[177]; //'reback',//176      'reback_btn',//17
            count = CONFIG.count
        }
    };
var loginInfo = {
    data: {
        userId: 0x0,
        userName: _$[178]     //'',//178
    },
    init: function() {
        this.data.userId = m.cookie(_$[179]); // 'userId',//179 从cookie中取userid
        if (!this.data.userId) return false;   //没取到,直接返回
        //'XM_',//180  '_UN',//181   '',//182
        this.data.userName = (this.data.userId) ? m.cookie(_$[180] + this.data.userId + _$[181]) : _$[182];
        // '',//183      如果有用户id,没用用户名,通过jsonp 取用户信息,回调  loginInfo.getAccountInfo 方法
        if (this.data.userName == null || this.data.userName == _$[183]) {
            var script = document.createElement(_$[184]);   // 'script',//184
            //'https://account.xiaomi.com/pass/userInfoJsonP?userId=',//185  '&callback=loginInfo.getAccountInfo',//186
            script.src = _$[185] + this.data.userId + _$[186];
            script.type = _$[187]; //'text/javascript',//187
            script.async = true;   //异步
            document.getElementsByTagName(_$[188])[0x0].appendChild(script) // 'head',//188
        } else {
            this.upUserInfo()
        }
    },
    upUserInfo: function() {
        var nickName = this.data.userName;
        if ( !! m.$(_$[189])) {      // 'LoginArea',//189
            //'LoginArea',//190  '欢迎您 ',//191  '!<a href=\'http://order.xiaomi.com/site/logout\'>退出</a>',//192
            m.$(_$[190]).innerHTML = _$[191] + nickName + _$[192];
            //'LoginArea',//193  '12px',//194
            m.$(_$[193]).style.paddingLeft = _$[194]
        }
    },
    getAccountInfo: function(data) {
        if (data.userId) {
            this.data.userName = (data.uniqName) ? data.uniqName : data.userId;
            var option = {
                path: _$[195],    // '/',//195
                domain: _$[196]    //'.xiaomi.com',//196
            };
            //'XM_',//197  '_UN',//198
            m.cookie(_$[197] + this.data.userId + _$[198], this.data.userName, option);
            this.upUserInfo()
        }
    }
};

function showBox(gtype) {   //弹出人太多了div   gtype 什是手机还是盒子
    if (m.cookie(_$[199])) { // 'userId',//199
        if (gtype === _$[200]) {   //  'phone',//200
            isPhone = true
        } else if (gtype === _$[201]) {  // 'box',//201
            isBox = true
        };
        miphoneBuy.box(true);
        m.$(_$[202]).innerHTML = CONFIG.count;   //    'initCount',//202
        //重新进入活动定时器
        rollInter = window.setInterval(_$[203], 0x3e8)   //    'Util.time()',//203
    } else {
        location.href = _$[204]  //'http://p.www.xiaomi.com/zt/xm_account/limitfacade.html?third=http%253A%252F…0ZWY0MDJlYTVkODBlZA%252C%252C&sign=3C15pt35v9KK5SR8saFsKQ89uRo%253D',//204
    }
};
m.ready(function() {
    m.phone(_$[205]); // 如果是手机浏览器,跳转到参数的url 'http://p.www.xiaomi.com/m/zt/open/index.html'//205
    miphoneBuy.init();
    loginInfo.init()
})


    这段代码除了m.doms的方法之外,其它都还比较简单。让我还没开始享受就完了。也让我成了标题党。
    总结一下这段代码的意思。
    先看一下,10.15,排列的url(其中1382068597029表示当前时间的getTime()值)
    http://tc.hd.xiaomi.com/hdget?callback=hdcontrol&_=1382068597029
    返回值
hdcontrol({"stime":1382068631,"status":{"allow":false,"miphone":{"hdstart":false,"hdstop":false,"hdurl":"","duration":null},"mibox":{"hdstart":false,"hdstop":false,"hdurl":"","duration":null}}})

    这段代码只是抢购js的前端排队代码,真正的排除代码当然是放在后台的服务器上了。也就是说,你完全知道这段代码,对你抢购也没太大的帮助。如果排队没成功(json.status.allow=false),直接访问购买的页面,服务端会让你跳转到排队页面。
那服务端的排列规则,只小米自己知道。有时可能公平,也可能不公平(也人说他是直接标志的,我觉得这个可能性比较低)。
    以10.15这次抢购为例子,可以肯定小米给每个帐号的概率肯定是不一样的。我想这样做的目的只防止黄牛,减少不满。但很多时候也会误伤。如果黄牛的特点跟普通用户一样,或者普通用户也当小黄牛。这时误伤的概率就比较大了。这个要靠小米的后台分析能力了。他们最好的办法就是增加供给。
    扯远了,又跑题了。这段代码,进入页面ready 后就执行miphoneBuy.init方法,方法里面里的checkCookie,如果第一次登录,调用就取一下miphoneBuy.jsonInter()服务器的时间。然后启动定时器,每秒钟执行一次,查看现在的状态,根据服务器的时间与开始抢的时间(如果已经开始抢购了,还要根据cookie中手机和盒子是否已经saleout ,这个cookie是由调用miphoneBuy.jsonInter()后,服务器的返回值 json.status.miphone.hdstart,json.status.miphone.hdstop, json.status.mibox.hdstart, json.status.mibox.hdstop;决定写入的,判断出展示 stepHtml的哪一步的方法。共6步,代表意思见代码注解。
    当倒计时完成,开始抢购时,展示第三步(展示购买按钮),点击购买手机,或盒子(TV),发现页面没用直接调用服务端排列。
而是var randomCount = parseInt(Math.random() * (0xa - 0x5 + 0x1) + 0x5),  //随机取5 到 11 中的整数。随机取5-11秒,让页面再次等待5-11秒,才提交服务端排队。如果没排上又是等第一次取的randomCount秒。

    那了解这段代码,我们能做些什么。可以在控制台上输入。
        isPhone = true;
        //或者isBox=true;
        isRollStatus = true;
        miphoneBuy.jsonInter();   //提交服务端排队
    跳过页面等待,直接提交服务端排队

    或者把等待进入活动的等待时间修改成更小的值如
    count=0;CONFIG.count=0;

    或者写个程序,登录后,保持sesion,直接访问排除url,返回成功直接进入购买页面。

    那这样我们这样有没有作用呢?
    这取决于服务端的规则。
    1,如果服务端完全公平,都不考虑黄牛,不考虑你调用次数,这个代码很有作用。这以现在情况我觉得不太可能
    2,如果服务端,有做防止黄牛情况,而且根据不同的帐号给不同的概率,而且还做了防频繁提交。
     那我们的代码只能设计成count=5;CONFIG.count=5;但作用有只有一点点点。
     如果我是开发者,这种情况比可能性最大
    3,如果服务端,是直接分配好了,打标志。那我们代码根据没作用。我觉得这个不太可能。
    以上纯属理论分析。
7
2
分享到:
评论
21 楼 platona 2013-11-27  
Mossad 写道
platona 写道
Mossad 写道
platona 写道
试了一下下面的简单方法,目的是使得再次进入的时候没有等待时间每隔1s(自己设置时间)检查count,把它设置成0。在抢购的时候运行下面简单代码
function setCount(){if(count!==0){count=0;}setTimeout("setCount()",1000);}
setTimeout("setCount()",1000)
就可以一直点击请求数据,能够看到返回请求的数据,不过我还是没抢到,郁闷
其实我一直不明白返回的数据中那个时间long 1383034188为1970年-01月-17日-33秒-08时-10分是干什么用的,感觉也不像是做时间延迟处理的

答案就是:那个时间的单位是

我知道是 秒,我把他转换为年月日了

那你转换的时候乘以1000了吗?

为什么要乘以1000?直接new date把值传进去不就行了吗
20 楼 Mossad 2013-11-20  
platona 写道
Mossad 写道
platona 写道
试了一下下面的简单方法,目的是使得再次进入的时候没有等待时间每隔1s(自己设置时间)检查count,把它设置成0。在抢购的时候运行下面简单代码
function setCount(){if(count!==0){count=0;}setTimeout("setCount()",1000);}
setTimeout("setCount()",1000)
就可以一直点击请求数据,能够看到返回请求的数据,不过我还是没抢到,郁闷
其实我一直不明白返回的数据中那个时间long 1383034188为1970年-01月-17日-33秒-08时-10分是干什么用的,感觉也不像是做时间延迟处理的

答案就是:那个时间的单位是

我知道是 秒,我把他转换为年月日了

那你转换的时候乘以1000了吗?
19 楼 platona 2013-11-16  
Mossad 写道
platona 写道
试了一下下面的简单方法,目的是使得再次进入的时候没有等待时间每隔1s(自己设置时间)检查count,把它设置成0。在抢购的时候运行下面简单代码
function setCount(){if(count!==0){count=0;}setTimeout("setCount()",1000);}
setTimeout("setCount()",1000)
就可以一直点击请求数据,能够看到返回请求的数据,不过我还是没抢到,郁闷
其实我一直不明白返回的数据中那个时间long 1383034188为1970年-01月-17日-33秒-08时-10分是干什么用的,感觉也不像是做时间延迟处理的

答案就是:那个时间的单位是

我知道是 秒,我把他转换为年月日了
18 楼 Mossad 2013-11-07  
zrmpop 写道
请教有没工具能快速解析 _$ 里面的16禁止内容, 除了alert()方法

去我的博客里看看我写的那篇文章吧,可能对你有所帮助。
17 楼 caizz520 2013-11-06  
还是程序员好,要逆天了。
我也要加入破解排队的队伍
16 楼 zrmpop 2013-11-05  
请教有没工具能快速解析 _$ 里面的16禁止内容, 除了alert()方法
15 楼 duducs 2013-11-05  
今天的miphoneBuy.jsonInter();失效了  解密后 还是找不到类似的
14 楼 huangzhir 2013-11-04  
Mossad 写道
你说小米的正则中多了一个空格是Bug是不对的,document.cookie读出来的cookie的分隔符就是分号加空格,所以这里没有问题。

确实是这样的,是我误解了,原以为cookie中的文件中昨是只用分号隔开,没注意分号后面还有一个空隔。看人家的正则就下结论,确实不对!谢谢上面的同学提醒!
13 楼 Mossad 2013-10-30  
platona 写道
试了一下下面的简单方法,目的是使得再次进入的时候没有等待时间每隔1s(自己设置时间)检查count,把它设置成0。在抢购的时候运行下面简单代码
function setCount(){if(count!==0){count=0;}setTimeout("setCount()",1000);}
setTimeout("setCount()",1000)
就可以一直点击请求数据,能够看到返回请求的数据,不过我还是没抢到,郁闷
其实我一直不明白返回的数据中那个时间long 1383034188为1970年-01月-17日-33秒-08时-10分是干什么用的,感觉也不像是做时间延迟处理的

答案就是:那个时间的单位是
12 楼 platona 2013-10-30  
试了一下下面的简单方法,目的是使得再次进入的时候没有等待时间每隔1s(自己设置时间)检查count,把它设置成0。在抢购的时候运行下面简单代码
function setCount(){if(count!==0){count=0;}setTimeout("setCount()",1000);}
setTimeout("setCount()",1000)
就可以一直点击请求数据,能够看到返回请求的数据,不过我还是没抢到,郁闷
其实我一直不明白返回的数据中那个时间long 1383034188为1970年-01月-17日-33秒-08时-10分是干什么用的,感觉也不像是做时间延迟处理的
11 楼 Mossad 2013-10-30  
你说小米的正则中多了一个空格是Bug是不对的,document.cookie读出来的cookie的分隔符就是分号加空格,所以这里没有问题。
10 楼 雄风铁骑 2013-10-22  

小米抢购(简单版v0.1)-登录并验证抢购权限,以及获取真实抢购地址
http://www.cnblogs.com/zengxiangzhan/p/3382234.html
9 楼 platona 2013-10-21  
icyapc 写道
platona 写道
我觉得可以点第一次排队的时候得到json请求的连接,然后一直请求json,直到返回排队成功为止,后台应该没做限制,又要考虑时间延迟又要做限制访问频率,这样做不科学

有没有试一下?应该不行吧 要是我做后台程序,我会按帐号返回的请求时间做计算,想作个 弊的就没机会了,你最多也只是把重新连接的时间改成5秒。

上次抢购就试了一下,结果页面死了,不知道是代码不对还是什么原因,有待研究
8 楼 icyapc 2013-10-20  
platona 写道
我觉得可以点第一次排队的时候得到json请求的连接,然后一直请求json,直到返回排队成功为止,后台应该没做限制,又要考虑时间延迟又要做限制访问频率,这样做不科学

有没有试一下?应该不行吧 要是我做后台程序,我会按帐号返回的请求时间做计算,想作个 弊的就没机会了,你最多也只是把重新连接的时间改成5秒。
7 楼 icyapc 2013-10-20  
写的好详细! 

引用
如果服务端完全公平,都不考虑黄牛,不考虑你调用次数

但是根据抢到过 Mi2S 的同事所反映的,他开了4个页面抢到的,理论上多开几个页面和不考虑调用次数是一样的吧
6 楼 pochonlee 2013-10-18  
引用
可以肯定小米给每个帐号的概率肯定是不一样的

你想多了。
5 楼 cuippan 2013-10-17  
good
4 楼 feiyu86 2013-10-17  
很牛。
3 楼 platona 2013-10-17  
我觉得可以点第一次排队的时候得到json请求的连接,然后一直请求json,直到返回排队成功为止,后台应该没做限制,又要考虑时间延迟又要做限制访问频率,这样做不科学
2 楼 white_crucifix 2013-10-17  
这么长,不明觉厉啊

相关推荐

Global site tag (gtag.js) - Google Analytics