咱们接着上一篇《从零实现一款12306刷票软件1.3》继续介绍。
同理,这里也给出验证用户名和密码的接口实现代码:
1int Client12306::loginAysnSuggest(const char* user, const char* pass, const char* vcode)
2{
3 string param = "loginUserDTO.user_name=";
4 param += user;
5 param += "&userDTO.password=";
6 param += pass;
7 param += "&randCode=";
8 param += vcode;
9 string strResponse;
10 string strCookie = "Cookie: ";
11 strCookie += m_strCookies;
12 if (!HttpRequest(URL_LOGINAYSNSUGGEST, strResponse, false, strCookie.c_str(), param.c_str(), false, 10))
13 {
14 LogError("loginAysnSuggest failed");
15 return 2;
16 }
17 ///** 成功返回
18 //HTTP/1.1 200 OK
19 //Date: Thu, 05 Jan 2017 07:49:53 GMT
20 //Server: Apache-Coyote/1.1
21 //X-Powered-By: Servlet 2.5; JBoss-5.0/JBossWeb-2.1
22 //ct: c1_103
23 //Content-Type: application/json;charset=UTF-8
24 //Content-Length: 146
25 //X-Via: 1.1 f186:10 (Cdn Cache Server V2.0)
26 //Connection: keep-alive
27 //X-Cdn-Src-Port: 48361
28 //邮箱不存在
29 //{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{},"messages":["该邮箱不存在。"],"validateMessages":{}}
30 //密码错误
31 //{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{},"messages":["密码输入错误。如果输错次数超过4次,用户将被锁定。"],"validateMessages":{}}
32 //登录成功
33 //{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"otherMsg":"",loginCheck:"Y"},"messages":[],"validateMessages":{}}
34 //WCHAR* psz1 = Utf8ToAnsi(strResponse.c_str());
35 //wstring str = psz1;
36 //delete[] psz1;
37 Json::Reader JsonReader;
38 Json::Value JsonRoot;
39 if (!JsonReader.parse(strResponse, JsonRoot))
40 return 2;
41 //{"validateMessagesShowId":"_validatorMessage", "status" : true,
42 //"httpstatus" : 200, "data" : {"otherMsg":"", loginCheck : "Y"}, "messages" : [], "validateMessages" : {}}
43 if (JsonRoot["status"].isNull())
44 return -1;
45 bool bStatus = JsonRoot["status"].asBool();
46 if (!bStatus)
47 return -1;
48 if (JsonRoot["httpstatus"].isNull() || JsonRoot["httpstatus"].asInt() != 200)
49 return 2;
50 if (JsonRoot["data"].isNull() || !JsonRoot["data"].isObject())
51 return 2;
52 if (JsonRoot["data"]["otherMsg"].isNull() || JsonRoot["data"]["otherMsg"].asString() != "")
53 return 2;
54 if (JsonRoot["data"]["loginCheck"].isNull() || JsonRoot["data"]["loginCheck"].asString() != "Y")
55 return 1;
56 return 0;
57}
这里还有个注意细节,就是通过POST请求发送的数据需要对一些符号做URL Encode,这个我在上一篇文章《从零实现一个http服务器》也详细做了介绍,还不清楚的可以参见上一篇文章。所以对于向图片验证码坐标信息中含有的逗号信息就要进行URL编码,从
answer=114,54,44,46&login_site=E&rand=sjrand
变成
answer=114%2C54%2C44%2C46&login_site=E&rand=sjrand
所以,在http包头中指定的Content-Length字段的值应该是编码后的字符串长度,而不是原始的长度,这个地方特别容易出错。
如果验证成功后,接下来就是查票和购票了。这里就不一一介绍了,所有的原理都是一样的,作者可以自行探索。当然,我已经将大多数的接口都探索完了,并实现了,我这里贴一下吧:
1/**
2 *@desc: 封装获取验证码、校验验证码、登录等12306各个请求的类,Client12306.h文件
3 *@author: zhangyl
4 *@date: 2017.01.17
5 */
6#ifndef __CLIENT_12306_H__
7#define __CLIENT_12306_H__
8
9#include <vector>
10#include <string>
11
12using namespace std;
13
14//车次类型
15#define TRAIN_GC 0x00000001
16#define TRAIN_D (0x00000001 << 1)
17#define TRAIN_Z (0x00000001 << 2)
18#define TRAIN_T (0x00000001 << 3)
19#define TRAIN_K (0x00000001 << 4)
20#define TRAIN_OTHER (0x00000001 << 5)
21#define TRAIN_ALL (TRAIN_GC | TRAIN_D | TRAIN_Z | TRAIN_T | TRAIN_K | TRAIN_OTHER)
22
23//票信息
24struct queryLeftNewDTO
25{
26 string train_no;
27 string station_train_code;
28 string start_station_telecode; //始发站
29 string start_station_name;
30 string end_station_telecode; //终点站
31 string end_station_name;
32 string from_station_telecode; //出发站
33 string from_station_name; //到达站
34 string to_station_telecode;
35 string to_station_name;
36 string start_time;
37 string arrive_time;
38 string day_difference;
39 string train_class_name;
40 string lishi;
41 string canWebBuy;
42 string lishiValue;
43 string yp_info;
44 string control_train_day;
45 string start_train_date;
46 string seat_feature;
47 string yp_ex;
48 string train_seat_feature;
49 string seat_types;
50 string location_code;
51 string from_station_no;
52 string to_station_no;
53 string control_day;
54 string sale_time;
55 string is_support_card;
56 string controlled_train_flag;
57 string controlled_train_message;
58 string train_type_code;
59 string start_province_code;
60 string start_city_code;
61 string end_province_code;
62 string end_city_code;
63 string swz_num; //商务座
64 string rz_num; //软座
65 string yz_num; //硬座
66 string gr_num; //高级软卧
67 string rw_num; //软卧
68 string yw_num; //硬卧
69 string tz_num; //特等座
70 string zy_num; //一等座
71 string ze_num; //二等座
72 string wz_num; //无座
73 string gg_num;
74 string yb_num;
75 string qt_num;
76};
77
78struct ticketinfo
79{
80 queryLeftNewDTO DTO;
81 string secretStr;
82 string buttonTextInfo;
83};
84
85//var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2
86struct stationinfo
87{
88 string code1;
89 string hanzi;
90 string code2;
91 string pingyin;
92 string simplepingyin;
93 int no;
94};
95
96struct passenager
97{
98 string code; //"8"
99 string passenger_name; //"范蠡"
100 string sex_code;// "M"
101 string sex_name; // "男"
102 string born_date; //"1989-12-08 00:00:00"
103 string country_code;// "CN"
104 string passenger_id_type_code;// "1"
105 string passenger_id_type_name; // "二代身份证"
106 string passenger_id_no; // "34262319781108812X"
107 string passenger_type; // "1"
108 string passenger_flag; // "0"
109 string passenger_type_name; // "成人"
110 string mobile_no; // "13917043320"
111 string phone_no;
112 string email; // "balloonwj@qq.com"
113 string address; // ""
114 string postalcode; // ""
115 string first_letter;// ""
116 string recordCount;// "13"
117 string total_times;// "99"
118 string index_id;// "0"
119};
120
121class Client12306
122{
123public:
124 static Client12306& GetInstance();
125private:
126 Client12306();
127 ~Client12306();
128private:
129 Client12306(const Client12306&);
130 Client12306& operator=(const Client12306&);
131public:
132 bool ReloadVCodeImage();
133 /**
134 * 游客查票
135 * https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-05-24&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT
136 * 应答:{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”messages”:[],”validateMessages”:{}}
137 *@param: train_date列车发车日期,格式:2017-01-28
138 *@param: from_station出发站,格式:SHH 对应上海
139 *@parma: to_station到站,格式:BJP 对应北京
140 *@param: purpose_codes 票类型,成人票:ADULT 学生票:0X00
141 *@param: v 查票结果
142 */
143 bool GuestQueryTicket(const char* train_date, const char* from_station, const char* to_station, const char* purpose_codes, vector<ticketinfo>& v);
144 /**
145 * 初始化session,获取JSESSIONID
146 */
147 bool loginInit();
148 bool DownloadVCodeImage(const char* module = "login");
149 /**
150 *@return 0校验成功;1校验失败;2校验出错
151 */
152 int checkRandCodeAnsyn(const char* vcode);
153 /**
154 *@return 0校验成功;1校验失败;2校验出错
155 */
156 int loginAysnSuggest(const char* user, const char* pass, const char* vcode);
157 /**
158 * 正式登录
159 */
160 bool userLogin();
161 /**
162 * 模拟12306跳转
163 */
164 bool initMy12306();
165 /**
166 * 拉取乘客买票验证码
167 */
168 //bool GetVCodeImage();
169 /**
170 * 拉取乘客买票验证码
171 */
172 /**
173 * 查询余票第一步
174 * https://kyfw.12306.cn/otn/leftTicket/log?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
175 * 应答:{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”messages”:[],”validateMessages”:{}}
176 *@param: train_date列车发车日期,格式:2017-01-28
177 *@param: from_station出发站,格式:SHH 对应上海
178 *@parma: to_station到站,格式:BJP 对应北京
179 *@param: purpose_codes 票类型,成人票:ADULT 学生票:0X00
180 */
181 bool QueryTickets1(const char* train_date, const char* from_station, const char* to_station, const char* purpose_codes);
182 /**
183 * 查询余票第二步
184 * 这几种情形都有可能,所以应该都尝试一下
185 * https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
186 * https://kyfw.12306.cn/otn/leftTicket/queryX?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
187 * https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
188 * {"status":false,"c_url":"leftTicket/query","c_name":"CLeftTicketUrl"}
189 * {"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"messages":["非法请求"],"validateMessages":{}}
190 * 应答中含有实际余票信息
191 *@param: train_date列车发车日期,格式:2017-01-28
192 *@param: from_station出发站,格式:SHH 对应上海
193 *@parma: to_station到站,格式:BJP 对应北京
194 *@param: purpose_codes 票类型,成人票:ADULT 学生票:0X00
195 */
196 bool QueryTickets2(const char* train_date, const char* from_station, const char* to_station, const char* purpose_codes, vector<ticketinfo>& v);
197 /**
198 * 检测用户是否登录
199 * https://kyfw.12306.cn/otn/login/checkUser POST _json_att=
200 * Cookie: JSESSIONID=0A01D967FCD9827FC664E43DEE3C7C6EF950F677C2; __NRF=86A7CBA739653C1CC2C3C3AA7C88A1E3; BIGipServerotn=1742274826.64545.0000; BIGipServerportal=3134456074.17695.0000; current_captcha_type=Z; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5357%u4EAC%2CNJH; _jc_save_fromDate=2017-01-22; _jc_save_toDate=2017-01-22; _jc_save_wfdc_flag=dc
201 * {"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"flag":true},"messages":[],"validateMessages":{}}
202 */
203 bool checkUser();
204 /**
205 * 预提交订单 POST
206 * https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest?secretStr=secretStr&train_date=2017-01-21&back_train_date=2016-12-23&tour_flag=dc&purpose_codes=ADULT&query_from_station_name=深圳&query_to_station_name=武汉&undefined=
207 */
208 bool submitOrderRequest(const char* secretStr, const char* train_date, const char* back_train_date, const char* tour_flag, const char* purpose_codes, const char* query_from_station_name, const char* query_to_station_name);
209 /**
210 * 模拟跳转页面InitDc,Post
211 */
212 bool initDc();
213 /**
214 * 拉取常用联系人 POST
215 * https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs?_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken
216 */
217 bool getPassengerDTOs(vector<passenager>& v);
218 /**
219 * 购票人确定
220 * https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo
221 @param oldPassengerStr oldPassengerStr组成的格式:乘客名,passenger_id_type_code,passenger_id_no,passenger_type,’_’
222 示例: 张远龙,1,342623198912088150,1_
223 @param passengerTicketStr passengerTicketStr组成的格式:seatType,0,票类型(成人票填1),乘客名,passenger_id_type_code,passenger_id_no,mobile_no,’N’
224 示例: O,0,1,张远龙,1,342623198912088150,13917043320,N 101
225 @tour_flag dc表示单程票
226 应答:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"ifShowPassCode":"N","canChooseBeds":"N","canChooseSeats":"N","choose_Seats":"MOP9","isCanChooseMid":"N","ifShowPassCodeTime":"1","submitStatus":true,"smokeStr":""},"messages":[],"validateMessages":{}}
227 */
228 bool checkOrderInfo(const char* oldPassengerStr, const char* passengerTicketStr, const char* tour_flag, bool& bVerifyVCode);
229 /**
230 * 准备进入排队
231 * https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount
232 _json_att 10
233 fromStationTelecode VNP 23
234 leftTicket enu80ehMzuVJlK2Q43c6kn5%2BzQF41LEI6Nr14JuzThrooN57 63
235 purpose_codes 00 16
236 REPEAT_SUBMIT_TOKEN 691c09b5605e46bfb2ec2380ee65de0e 52
237 seatType O 10
238 stationTrainCode G5 19
239 toStationTelecode AOH 21
240 train_date Fri Feb 10 00:00:00 UTC+0800 2017 50
241 train_location P2 17
242 train_no 24000000G502 21
243 应答:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"count":"4","ticket":"669","op_2":"false","countT":"0","op_1":"true"},"messages":[],"validateMessages":{}}
244 */
245 bool getQueueCount(const char* fromStationTelecode, const char* leftTicket, const char* purpose_codes, const char* seatType, const char* stationTrainCode, const char* toStationTelecode, const char* train_date, const char* train_location, const char* train_no);
246 /**
247 * 确认购买
248 * https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue
249 _json_att 10
250 choose_seats 13
251 dwAll N 7
252 key_check_isChange 7503FD317E01E290C3D95CAA1D26DD8CFA9470C3643BA9799D3FB753 75
253 leftTicketStr enu80ehMzuVJlK2Q43c6kn5%2BzQF41LEI6Nr14JuzThrooN57 66
254 oldPassengerStr 张远龙,1,342623198912088150,1_ 73
255 passengerTicketStr O,0,1,张远龙,1,342623198912088150,13917043320,N 101
256 purpose_codes 00 16
257 randCode 9
258 REPEAT_SUBMIT_TOKEN 691c09b5605e46bfb2ec2380ee65de0e 52
259 roomType 00 11
260 seatDetailType 000 18
261 train_location P2 17
262 应答:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"submitStatus":true},"messages":[],"validateMessages":{}}
263 */
264 bool confirmSingleForQueue(const char* leftTicketStr, const char* oldPassengerStr, const char* passengerTicketStr, const char* purpose_codes, const char* train_location);
265 /**
266 * 查询订单状态: https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random=1486368851278&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN=691c09b5605e46bfb2ec2380ee65de0e
267 GET
268 _json_att
269 random 1486368851278
270 REPEAT_SUBMIT_TOKEN 691c09b5605e46bfb2ec2380ee65de0e
271 tourFlag dc
272 响应:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"queryOrderWaitTimeStatus":true,"count":0,"waitTime":-1,"requestId":6234282826330508533,"waitCount":0,"tourFlag":"dc","orderId":"E061149209"},"messages":[],"validateMessages":{}}
273 */
274 bool queryOrderWaitTime(const char* tourflag, string& orderId);
275 /**
276 * https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForDcQueue POST
277 _json_att 10
278 orderSequence_no E061149209 27
279 REPEAT_SUBMIT_TOKEN 691c09b5605e46bfb2ec2380ee65de0e 52
280 {"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"submitStatus":true},"messages":[],"validateMessages":{}}
281 */
282 //bool resultOrderForDcQueue();
283 /**
284 * 未完成的订单页面 https://kyfw.12306.cn/otn/queryOrder/initNoComplete GET
285 * 获取未完成的订单 https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete POST _json_att=
286 */
287 /*
288 {
289 "validateMessagesShowId": "_validatorMessage",
290 "status": true,
291 "httpstatus": 200,
292 "data": {
293 "orderDBList": [
294 {
295 "sequence_no": "E079331507",
296 "order_date": "2017-02-09 10:10:55",
297 "ticket_totalnum": 1,
298 "ticket_price_all": 55300,
299 "cancel_flag": "Y",
300 "resign_flag": "4",
301 "return_flag": "N",
302 "print_eticket_flag": "N",
303 "pay_flag": "Y",
304 "pay_resign_flag": "N",
305 "confirm_flag": "N",
306 "tickets": [
307 {
308 "stationTrainDTO": {
309 "trainDTO": {},
310 "station_train_code": "G41",
311 "from_station_telecode": "VNP",
312 "from_station_name": "北京南",
313 "start_time": "1970-01-01 09:16:00",
314 "to_station_telecode": "AOH",
315 "to_station_name": "上海虹桥",
316 "arrive_time": "1970-01-01 14:48:00",
317 "distance": "1318"
318 },
319 "passengerDTO": {
320 "passenger_name": "范蠡",
321 "passenger_id_type_code": "1",
322 "passenger_id_type_name": "二代身份证",
323 "passenger_id_no": "34262319781108815X",
324 "total_times": "98"
325 },
326 "ticket_no": "E079331507110008B",
327 "sequence_no": "E079331507",
328 "batch_no": "1",
329 "train_date": "2017-02-11 00:00:00",
330 "coach_no": "10",
331 "coach_name": "10",
332 "seat_no": "008B",
333 "seat_name": "08B号",
334 "seat_flag": "0",
335 "seat_type_code": "O",
336 "seat_type_name": "二等座",
337 "ticket_type_code": "1",
338 "ticket_type_name": "成人票",
339 "reserve_time": "2017-02-09 10:10:55",
340 "limit_time": "2017-02-09 10:10:55",
341 "lose_time": "2017-02-09 10:40:55",
342 "pay_limit_time": "2017-02-09 10:40:55",
343 "ticket_price": 55300,
344 "print_eticket_flag": "N",
345 "resign_flag": "4",
346 "return_flag": "N",
347 "confirm_flag": "N",
348 "pay_mode_code": "Y",
349 "ticket_status_code": "i",
350 "ticket_status_name": "待支付",
351 "cancel_flag": "Y",
352 "amount_char": 0,
353 "trade_mode": "",
354 "start_train_date_page": "2017-02-11 09:16",
355 "str_ticket_price_page": "553.0",
356 "come_go_traveller_ticket_page": "N",
357 "return_deliver_flag": "N",
358 "deliver_fee_char": "",
359 "is_need_alert_flag": false,
360 "is_deliver": "N",
361 "dynamicProp": "",
362 "fee_char": "",
363 "insure_query_no": ""
364 }
365 ],
366 "reserve_flag_query": "p",
367 "if_show_resigning_info": "N",
368 "recordCount": "1",
369 "isNeedSendMailAndMsg": "N",
370 "array_passser_name_page": [
371 "张远龙"
372 ],
373 "from_station_name_page": [
374 "北京南"
375 ],
376 "to_station_name_page": [
377 "上海虹桥"
378 ],
379 "start_train_date_page": "2017-02-11 09:16",
380 "start_time_page": "09:16",
381 "arrive_time_page": "14:48",
382 "train_code_page": "G41",
383 "ticket_total_price_page": "553.0",
384 "come_go_traveller_order_page": "N",
385 "canOffLinePay": "N",
386 "if_deliver": "N",
387 "insure_query_no": ""
388 }
389 ],
390 "to_page": "db"
391 },
392 "messages": [],
393 "validateMessages": {}
394}
395 */
396 /**
397 * 已完成订单(改/退) : https://kyfw.12306.cn/otn/queryOrder/queryMyOrder POST
398 * queryType 1 按订票日期 2 按乘车日期
399 * 查询日期queryStartDate=2017-02-09&queryEndDate=2017-02-09
400 * come_from_flag: my_order 全部 my_resign 可改签 my_cs_resign 可变更到站 my_refund 可退票
401 * &pageSize=8&pageIndex=0&
402 * query_where G 未出行订单 H 历史订单
403 * sequeue_train_name 订单号/车次/乘客姓名
404 */
405 /* 历史订单格式
406 参见[历史订单.txt]
407 */
408 /**
409 * 获取全国车站信息
410 *@param si 返回的车站信息
411 *@param bForceDownload 强制从网络上下载,即不使用本地副本
412 */
413 bool GetStationInfo(vector<stationinfo>& si, bool bForceDownload = false);
414 /**
415 * 获取所有高校信息 https://kyfw.12306.cn/otn/userCommon/schoolNames POST provinceCode=11&_json_att=
416 */
417 /**
418 * 获取所有城市信息 https://kyfw.12306.cn/otn/userCommon/allCitys POST station_name=&_json_att=
419 */
420 /**
421 * 查询常用联系人
422 */
423 bool QueryPassengers(int pageindex = 2, int pagesize = 10);
424 bool GetVCodeFileName(char* pszDst, int nLength);
425private:
426 bool GetCookies(const string& data);
427 /**
428 * 发送一个http请求
429 *@param url 请求的url
430 *@param strResponse http响应结果
431 *@param get true为GET,false为POST
432 *@param headers 附带发送的http头信息
433 *@param postdata post附带的数据
434 *@param bReserveHeaders http响应结果是否保留头部信息
435 *@param timeout http请求超时时间
436 */
437 bool HttpRequest(const char* url, string& strResponse, bool get = true, const char* headers = NULL, const char* postdata = NULL, bool bReserveHeaders = false, int timeout = 10);
438private:
439 char m_szCurrVCodeName[256]; //当前验证码图片的名称
440 string m_strCookies;
441 string m_strGlobalRepeatSubmitToken;
442 string m_strKeyCheckIsChange;
443};
444#endif //!__CLIENT_12306_H__
最后当您实现了基本的登录和购票功能后,你就可以不断模拟某些请求去进行刷票了,这就是刷票的基本原理。如果您在开发的过程中遇到任何问题,可以在我的公众号中留言。
文章版权所有,转载请保留版权申明。