- 浏览: 126218 次
- 性别:
- 来自: 福州
文章分类
最新评论
-
回归蔚蓝:
getParams 已经废弃了,现在应该用 RequestCo ...
HttpClient 4.0 保持登录session 访问网页 -
kuno321:
在用HttpClient 4.1,用get 的时,如果有重定向 ...
HttpClient 4.1 post 继续访问 重定向,301,302 的url -
platona:
Mossad 写道platona 写道Mossad 写道pla ...
享受代码的快乐--小米抢购前端代码分析 -
Mossad:
platona 写道Mossad 写道platona 写道试了 ...
享受代码的快乐--小米抢购前端代码分析 -
platona:
Mossad 写道platona 写道试了一下下面的简单方法, ...
享受代码的快乐--小米抢购前端代码分析
今天来个比较潮的,分析一下小米前端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的抢购代码为例
为了阅读性我们两成两段,一个是他的数组变量_$,另一段是就是其它代码
_$ 变量太长了,我贴一点点,不完整,要看的见附件!
这段代码除了m.doms的方法之外,其它都还比较简单。让我还没开始享受就完了。也让我成了标题党。
总结一下这段代码的意思。
先看一下,10.15,排列的url(其中1382068597029表示当前时间的getTime()值)
http://tc.hd.xiaomi.com/hdget?callback=hdcontrol&_=1382068597029
返回值
这段代码只是抢购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,如果服务端,是直接分配好了,打标志。那我们代码根据没作用。我觉得这个不太可能。
以上纯属理论分析。
话说小米手机不错,主要是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,如果服务端,是直接分配好了,打标志。那我们代码根据没作用。我觉得这个不太可能。
以上纯属理论分析。
- xiaomi.rar (26.5 KB)
- 下载次数: 132
相关推荐
除此之外,小米官网的前端代码可能还涉及到其他技术,如: - SEO优化:通过元标签(meta tags)和语义化HTML提高搜索引擎可见性。 - AJAX调用API:与后端服务器通信,获取产品信息、评论等实时数据。 - Web字体:...
小米抢购源代码, using System; using System.Threading.Tasks; using System.Windows.Forms; using System.Timers; using Newtonsoft.Json; using System.Text.RegularExpressions; using System.Configuration; ...
在这个“前端期末大作业”中,学生需要利用前端技术实现小米商城的界面复刻,包括响应式设计,确保网页在不同设备上都能正常显示和操作。这需要对前端框架、库和工具有一定的了解,例如Bootstrap、Vue.js或者React....
【Vue-Store高仿小米商城 v1.0 前端项目详解】 Vue-Store是一个基于Vue.js框架的电商网站前端实现,它高度模仿了小米商城的用户界面和交互体验,为开发者提供了一个实践Vue.js及其相关技术的实战案例。这个项目主要...
【标题】:“小米10月15日抢购前端代码分析” 这篇博客主要探讨的是小米在10月15日进行的抢购活动所使用的前端代码。在这个过程中,作者深入研究了网页背后的JavaScript、HTML和CSS等技术,揭示了抢购页面如何实现...
Xiaomi_Kernel_OpenSource,小米内核开源:cancro kk oss(包含mi 3w、mi 3c、mi 4系列、mi note)、armani jb oss(h1s)、dior kk oss(hm note-lte)、法拉利l-oss(小米4i)、thmoas kk oss(hm2 lte)、libra-l-...
在本项目中,我们主要关注的是“仿小米官网首页2021最新版本前端代码”。这个项目提供了构建一个与小米官网2021年最新设计风格相似的前端页面所需的所有资源,包括HTML、CSS和JavaScript文件。让我们逐一深入探讨...
【Android代码-安卓仿小米计算器APP】项目是一个用于学习和实践Android编程的示例,它模仿了小米手机上的计算器应用的界面和功能。这个项目主要涵盖了Android应用开发的基础知识,包括UI设计、事件处理、计算逻辑...
圣道小米抢购软件是一款免费高效的小米抢购秒杀工具。软件采用P2P加速技术,抢购更迅速,更稳定,更高速,更高效。软件绿色便捷,永久免费,是小米抢购神器。 圣道小米抢购软件 功能特点 1.高速抢购 P2P加速技术,...
- **硬件兼容**:DEMO特别提到支持小米和阿里云设备,意味着它包含了针对这两种品牌设备的适配代码,可能涉及到特定硬件功能的调用。 - **API级别适配**:Android系统版本多样,开发者需要确保应用能在不同API级别...
这篇文档将深入探讨《Android应用源码之小米便签源代码分享》的相关知识点,主要针对Android应用程序的开发、毕业设计以及移动应用APP的构建。小米便签作为一个实用的Android应用,其源代码提供了丰富的学习资源,有...
寒战小米抢购软件是一款目前为止最新发布的小米手机预约抢购软件,已有人成功抢购,有兴趣的朋友可以试试这款小米抢购软件。 使用方法 如果点击抢购,出现说明获取验证码渠道正常,小米官方没有更改验证码机制,...
【小米5安卓11最新系统lineage-18.1小米5.zip】是一个为小米5手机定制的基于Android 11的第三方系统固件。LineageOS是一个知名的开源项目,它基于Android源代码进行深度定制,提供了一系列优化和增强功能,以提升...
对于小米抢购页面的JS代码,混淆可能是为了防止恶意用户分析和利用抢购逻辑。 要对混淆的JS代码进行反混淆,我们需要遵循以下步骤: 1. **代码解析**:使用JavaScript解析库,如Esprima,将混淆的JS代码转化为抽象...
【AIoT-WIFI-BLE-ZIG-小米-涂鸦-阿里-生态.zip】这个压缩包文件包含了关于AIoT(人工智能物联网)领域的多个重要资料,主要涉及小米、涂鸦智能以及阿里巴巴等科技巨头在AIoT生态中的角色与策略。通过对这些文件的...
总之,通过分析小米官网的源代码,我们可以学习到前端开发的最新趋势和技术实践,包括但不限于前端框架的应用、响应式设计、性能优化、安全防护和SEO策略等。这对于提升自己的前端技能、了解行业标准以及解决实际...
系统工程小组展示-手机-小米SWOT-苹果PEST-层次分析法 本资源摘要信息主要介绍了系统工程小组展示,内容涵盖手机行业中的系统工程运用、SWOT分析、小米手机评价、AHP分析和PEST分析等。下面是详细的知识点概括: ...
小米-王福-小米服务端APM技术实践