先PS一下:最近小米手机火了,看起来好像地球人已经不能阻止它的发展趋势了

其实本文论述的技术也并非小米手机专用,只是用小米手机来做借鉴,但课题起源于朋友请求帮忙购买小米手机,于是借助专业知识写了一个抢购的工具,拿出来和大家分享一下。说叫抢购工具,其实就是自动下订单而已,因为小米手机网站的订单只要在72小时内完成支付就可以,所以在第一时间完成下单以后找闲暇时间进行支付就可以了。

再PS一下:【声明】本工具原理为模拟浏览器访问和执行页面,不包含任何入侵和破坏行为。本工具的优点只是自动化处理以及节省不必要的流量耗费。

1、首先分析任务实现需要操作的步骤:

  a、登录系统,此步骤中小米商城网站使用了通行证认证模式,即需要将表单提交到passport.xiaomi.com,认证成功后通过token传递回商城网站的callback页面。

  b、清空购物车,本步骤可选。因为是工具自动购买,防止错误下单,可在每次执行操作前自动清空购物车,购物车清空后会302到购物车页面,http://order.xiaomi.com/cart,页面提示“清空购物车成功”。

  c、将商品放入购物车。此步骤小米网站没有使用post,而是get,通过url将商品id以及数量传递给服务器,并且还使用了友好url,例如 http://order.xiaomi.com/cart/add/1016-0-1 是将 小米手机M1放入购物车,其中1016为小米手机的商品id,0表示此商品为单品(非组合套装),1表示购买一个。以此类推。放入购物车以后会自动302到购物车页面,不过此时url为 http://order.xiaomi.com/cart/index,显示购物车中的商品。

  d、提交订单。本步骤同样为get方式,直接请求地址 http://order.xiaomi.com/buy/checkout 即开始将购物车中的商品作为一个订单进行提交,此时进入订单详细信息填写页面,填写收货人、收货地址、电话、发票等信息,确认订单需要post,请求的地址仍然是 http://order.xiaomi.com/buy/checkout。

2、关键技术点分析及实现

  清楚了任务要求,接下来分析技术点,鉴于本人对C#比较熟悉,故选用C#语言实现,采用了Winform项目框架。

  a、核心需求即是通过http协议下载页面,以下为本人使用的核心代码:

抢购活动系统架构 抢购机制_小米手机

抢购活动系统架构 抢购机制_抢购活动系统架构_02

View Code

1                     Uri uri = rawUri;
  2                     HttpWebSession session = new HttpWebSession()
  3                     {
  4                         RequestUri = uri,
  5                     };
  6                     var _request = session.Request =
  7                       (HttpWebRequest)WebRequest.Create(uri);
  8                     _httpWebSessions.Add(session);
  9                     if (Proxy != null)
 10                     {
 11                         _request.Proxy = Proxy;
 12                     }
 13                     _request.AllowAutoRedirect = false;
 14                     if (Uri.UriSchemeHttps == uri.Scheme)
 15                     {
 16                         ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(RemoteCertificateValidation);//
 17                     }
 18                     if (Credentials != null)
 19                     {
 20                         _request.Credentials = Credentials;
 21                     }
 22                     if (!string.IsNullOrEmpty(Accept))
 23                     {
 24                         _request.Accept = Accept;
 25                     }
 26 
 27                     if (!string.IsNullOrEmpty(AcceptLanguage))
 28                     {
 29                         _request.Headers.Add("Accept-Language", AcceptLanguage);
 30                     }
 31                     if (!string.IsNullOrEmpty(AcceptCharset))
 32                     {
 33                         _request.Headers.Add("Accept-Charset", AcceptCharset);
 34                     }
 35                     _request.KeepAlive = KeepAlive;
 36                     if (!string.IsNullOrEmpty(RefererUrl))
 37                     {
 38                         _request.Referer = RefererUrl;
 39                     }
 40                     switch (_acceptEncoding)
 41                     {
 42                         case AcceptEncoding.GZIP:
 43                             {
 44                                 _request.Headers.Add("Accept-Encoding", "gzip");
 45                                 break;
 46                             }
 47                         case AcceptEncoding.DEFLATE:
 48                             {
 49                                 _request.Headers.Add("Accept-Encoding", "deflate");
 50                                 break;
 51                             }
 52                         case AcceptEncoding.BOTH:
 53                             {
 54                                 _request.Headers.Add("Accept-Encoding", "gzip,deflate");
 55                                 break;
 56                             }
 57                     }
 58                     if (_request.CachePolicy == null || _request.CachePolicy.Level != _cacheLevel)
 59                     {
 60                         _request.CachePolicy = new RequestCachePolicy(_cacheLevel);
 61                     }
 62                     if (_headers != null && _headers.Count > 0)
 63                     {
 64                         foreach (KeyValuePair<string, string> item in _headers)
 65                         {
 66                             try
 67                             {
 68                                 _request.Headers.Add(item.Key, item.Value);
 69                             }
 70                             catch
 71                             { }
 72                         }
 73                     }
 74                     _request.UserAgent = _userAgent;
 75                     _request.Referer = RefererUrl;
 76                     if (TimeOut != 0)
 77                     {
 78                         _request.Timeout = TimeOut;
 79                     }
 80                     if (postData == null)
 81                     {
 82                         if (postDictionary != null)
 83                         {
 84                             foreach (var item in postDictionary)
 85                             {
 86                                 postText += item.Key + "=" + item.Value + "&";
 87                             }
 88                             postText = postText.Trim('&');
 89                         }
 90                         if (!string.IsNullOrEmpty(postText))
 91                         {
 92                             postData = _encoding.GetBytes(postText);
 93                         }
 94                     }
 95                     if (_cookieContainer != null)
 96                     {
 97                         string strCookieHeader = _cookieContainer.GetCookieHeader(uri);
 98                         if (!string.IsNullOrEmpty(strCookieHeader))
 99                         {
100                             _request.Headers["Cookie"] = strCookieHeader;
101                         }
102                     }
103                     if (postData != null)
104                     {
105                         session.PostedData = postData;
106                         _request.Method = "POST";
107                         _request.ContentType = "application/x-www-form-urlencoded";
108                         _request.ContentLength = postData.Length;
109                         Stream requestStream = _request.GetRequestStream();
110                         requestStream.Write(postData, 0, postData.Length);
111                         requestStream.Close();
112                     }
113                     var _response = session.Response = (HttpWebResponse)_request.GetResponse();
114                     if (_cookieContainer != null)
115                     {
116                         string strSetCookieHeader = _response.Headers["Set-Cookie"];
117                         if (!string.IsNullOrEmpty(strSetCookieHeader))
118                         {
119                             _cookieContainer.SetCookies(_response.ResponseUri, strSetCookieHeader);
120                         }
121                     }
122                     StatusCode = _response.StatusCode;
123                     _requestCookieCollection = _cookieContainer.GetCookieCollection(rawUri);
124                     _response.Cookies = _cookieContainer.GetCookieCollection(_response.ResponseUri);
125                     if (IsEncodingAccepted(_response, AcceptEncoding.GZIP))
126                     {
127                         responseStream = new GZipStream(_response.GetResponseStream(), CompressionMode.Decompress);
128                     }
129                     else if (IsEncodingAccepted(_response, AcceptEncoding.DEFLATE))
130                     {
131                         responseStream = new DeflateStream(_response.GetResponseStream(), CompressionMode.Decompress);
132                     }
133                     else
134                     {
135                         responseStream = _response.GetResponseStream();
136                     }
137 
138                     if (StatusCode == HttpStatusCode.Redirect || StatusCode == HttpStatusCode.Moved)
139                     {
140                         if (_allowAutoRedirect)
141                         {
142                             responseStream.Close();
143                             string strRedirectUrl = _response.Headers["Location"];
144                             var stream = GetStream(new Uri(rawUri, strRedirectUrl), true, null, null, null, null, null);
145                             return stream;
146                         }
147                         // return GetStream(
148                     }
149                     else if (StatusCode == HttpStatusCode.Unauthorized)
150                     {
151                         string strAuthorizition = OnAuthorizition();
152                         if (!string.IsNullOrEmpty(strAuthorizition))
153                         {
154 
155                         }
156                     }

  上述代码是本人多年积累的一个类库中的一点核心代码,解决了诸多常见问题,比如cookieContainer对domain识别异常,https访问等等,如有需要本人可提供下载。

  b、逻辑处理的自动机

   鉴于web访问过程中容易出现的各种不确定性因素,经常会出现请求同一个地址却返回不一样的内容,多数为302活301。本人使用了注册处理函数的方式,每次请求完以后,根据返回的页面url找到相应的处理函数进行操作,比如登录时,发送的是指向http://passport.xiaomi.com/index.php?...的一个请求,但返回结果往往变成 http://www.xiaomi.com/,在相应的处理函数中就需要进行清空购物车或者将商品放入购物车。

 

抢购活动系统架构 抢购机制_小米手机

抢购活动系统架构 抢购机制_抢购活动系统架构_02

View Code

1         protected virtual void ProcessHttpSessions(ProcessState state)
 2         {
 3             state.Processed = true;
 4             if (RunState == RunState.Pauseing)
 5             {
 6                 OnLog("==========被用户暂停==========");
 7                 ChangeState(RunState.Paused);
 8                 state.Break = true;
 9                 return;
10             }
11             if (RunState == RunState.PauseingByNetService)
12             {
13                 OnLog("==========被换IP服务暂停==========");
14                 ChangeState(RunState.PausedByNetService);
15                 state.Break = true;
16                 return;
17             }
18             if (RunState == RunState.Stopping)
19             {
20                 OnLog("==========被用户停止==========");
21                 ChangeState(RunState.Stoped);
22                 state.Break = true;
23                 return;
24             }
25 
26             if (_httpClient == null || _httpClient.HttpWebSessions == null)
27             {
28                 state.Continue = true;
29                 return;
30             }
31             int oldSessionCount = _httpClient.HttpWebSessions.Count;
32             Action<ProcessState> processerAction = null;
33             if (string.IsNullOrEmpty(_httpClient.RequestUrl))
34             {
35                 OnLogError("没有要处理的请求");
36                 state.Break = true;
37                 return;
38             }
39             if (httpSessionProcesser.ContainsKey(_httpClient.RequestUrl))
40             {
41                 processerAction = httpSessionProcesser[_httpClient.RequestUrl];
42             }
43             if (processerAction == null)
44             {
45                 foreach (var processer in httpSessionProcesser)
46                 {
47                     if (_httpClient.RequestUrl.StartsWith(processer.Key) ||
48                         Regex.IsMatch(_httpClient.RequestUrl, processer.Key))
49                     {
50                         processerAction = processer.Value;
51                         break;
52                     }
53                 }
54             }
55             if (processerAction != null)
56             {
57                 processerAction(state);
58                 if (state.Break || state.Continue)
59                 {
60                     return;
61                 }
62                 if (!state.Processed)
63                 {
64                     ProcessHttpSessions(state);
65                 }
66                 return;
67             }
68             OnLogError("出错了,RequestUrl:" + _httpClient.RequestUrl);
69             OnLogError("ResponseUrl:" + _httpClient.ResponseUrl);
70             OnLogError("ResponseText:" + _httpClient.ResponseText);
71             state.Break = true;
72         }

  
  经过实践和改进,本人已将上述部分分离成独立类库,如有需要可提供,以进行参考和交流。

 3、软件界面布局,界面截图已隐藏掉部分敏感信息

  

抢购活动系统架构 抢购机制_Code_05