每天我都在烂尾和不烂尾间徘徊。每次我都想着累死累死累死,我这么彷徨这么无助这么凄凉,连路灯都用昏黄的灯光为我默泪,为什么我还要在这边继续着有人看或没人看的事,写能怎么样,断更又能怎么样。只是强迫症的独特天赋在鞭策着我,于是每次都会极不情愿地拿起键盘,强大的羞耻心在时刻地提醒着我,如果真的断更了,下一次上厕所你一定会忘记带纸,你就只能凄凉着等着有人路过大方地施舍给你一张草纸,因为你连人民币都没有,你从来不会在大号的时候带着钱包。说到这里,下次记得放一卷手纸在卫生间的角落里。。。
上一章写到已经成功获得了验证码并且输入。那么输入后能干嘛?当然先登录了
7.1 先完成一些辅助函数和登录界面
创建登录界面应该是极为简单的事情,就不多说了。我们这里设计个登录界面如下。
然后写上了俩调用函数。
partial class Login : Form { ServiceContext _context; public Login(ServiceContext context) { _context = context; InitializeComponent(); } private void btnOk_Click(object sender, EventArgs e) { } }
tsLogin.Click += (s, e) => new Login(_context).ShowDialog(this);
……好吧,我突然想起来好像登录函数还没写,咱继续回去写吧。
7.2 登录函数
上一章研究验证码的时候我们曾经保存了一个包,当时提到了下次还要用。现在我们翻出来那个包,再看一下,找找与登录相关的请求。
在验证码校验请求之后紧跟着两个请求,第一个包含了用户名密码的验证码,这货脑门上就写着“我是登录请求”了。至于第二个……不知道干嘛的,看起来就是POST一个空数据到对应的网页,然后对方302了一下。
从完整可靠性起见,我们还是完整复制流程吧……且慢,让我们先看一下正确登录和错误登录都是啥信息。
首先来看用户密码输错的时候。
{ "validateMessagesShowId": "_validatorMessage", "status": true, "httpstatus": 200, "data": { }, "messages": [ "密码输入错误。如果输错次数超过4次,用户将被锁定。" ], "validateMessages": { } }
再看正确的情况。
{ "validateMessagesShowId": "_validatorMessage", "status": true, "httpstatus": 200, "data": { "otherMsg": "", "loginCheck": "Y" }, "messages": [ ], "validateMessages": { } }
……好吧明白了。那我们就创建一个类来保存返回值吧。
class LoginAsyncResult { public string LoginCheck { get; set; } public string OtherMsg { get; set; } /// <summary> /// 获得登录是否成功 /// </summary> public bool IsSuceess {get { return LoginCheck == "Y"; } } }
然后我们在Session中写登录函数。代码位置随便放,就是酱紫随便
/// <summary> /// 登录。如果返回异常则说明登录失败 /// </summary> /// <param name="verifyPoints"></param> /// <returns></returns> public async Task<Exception> LoginAsync(string username, string password, List<Point> verifyPoints) { IsLogined = false; LoginInfo = new LoginInfo() { UserName = username, Password = password }; var loginData = new Dictionary<string, string>() { ["loginUserDTO.user_name"] = LoginInfo.UserName, ["userDTO.password"] = LoginInfo.Password, ["randCode"] = verifyPoints.Select(s => s.X + "," + s.Y).JoinAsString(",") }; var loginCheck = NetClient.Create<WebResponseResult<LoginAsyncResult>>( HttpMethod.Post, "https://kyfw.12306.cn/otn/login/loginAysnSuggest", "https://kyfw.12306.cn/otn/login/init", loginData ); await loginCheck.SendTask(); if (!loginCheck.IsValid()) { return loginCheck.Exception ?? new Exception("未能提交请求"); } if (!loginCheck.Result.Data.IsSuceess) { return new Exception(loginCheck.Result.GetErrorMessage()); } //登录成功 var postLogin = NetClient.Create<string>( HttpMethod.Post, "https://kyfw.12306.cn/otn/login/userLogin", "https://kyfw.12306.cn/otn/login/init" ); await postLogin.SendTask(); //这里的返回值我们不care .... //登录好了。等等。。我们好像想拿到显示的中文名? //所以多加一个请求吧。 var realNameCtx = NetClient.Create<string>( HttpMethod.Get, "https://kyfw.12306.cn/otn/index/initMy12306", "https://kyfw.12306.cn/otn/login/init" ); await realNameCtx.SendTask(); IsLogined = true; return null; }
等等。我突然想到在这里也许还要同时获得显示的中文名以便于后续显示。
我们看看这个中文名可以从哪里获得。貌似“我的12306”这个页面不错。
user_name
很明显就是名字了,虽然经过了编码。但是这个会难道我大C#吗,开玩笑。
//登录好了。等等。。我们好像想拿到显示的中文名? //所以多加一个请求吧。 var realNameCtx = NetClient.Create<string>( HttpMethod.Get, "https://kyfw.12306.cn/otn/index/initMy12306", "https://kyfw.12306.cn/otn/login/init" ); await realNameCtx.SendTask(); if (realNameCtx.IsValid()) { //匹配出名字信息 var realMatch = Regex.Match(realNameCtx.Result, @"user_name\s*=\s*['""]([^'""]+)['""]", RegexOptions.Singleline); if (realMatch.Success) { LoginInfo.DisplayName = realMatch.GetGroupValue(1).DecodeFromJsExpression(); } } //这里失败了我们就随便起个名字,嗯。 if (LoginInfo.DisplayName.IsNullOrEmpty()) LoginInfo.DisplayName = "路人甲";
然后我们在登录对话框中将逻辑写上。
private async void btnOk_Click(object sender, EventArgs e) { var username = txtUserName.Text; var password = txtPassword.Text; if (username.IsNullOrEmpty() || password.IsNullOrEmpty()) { MessageBox.Show("输入用户名和密码哦!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var vcdlg = new RequireVcDlg(_context, VerifyCodeType.Login); if (vcdlg.ShowDialog(this) != DialogResult.OK) return; var result = await _context.Session.LoginAsync(username, password, vcdlg.Points); if (result == null) { Close(); return; } MessageBox.Show("登录失败:" + result.Message, "提示", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); }
嗯,来试试看
额…… 登录成功了??
我还以为会登录失败提示网络繁忙呢。
好吧。既然登录成功那我这里就可以少写一点了。
管他呢。
提示:其实这里登录成功是一个比较奇怪的事儿,因为按照12306逻辑这里会校验一个请求。但为什么会不校验也成功……表示表示很明白为啥,后面再说吧。
7.3 注销登录
把注销代码加上。我懒得写网络请求了,所以注销么,直接销毁Session得了。
/// <summary> /// 注销登录 /// </summary> public void Logout() { LoginInfo = null; NetClient=new NetClient(); IsLogined = false; }
打算最近实践这个系列,不知道文章中的方法在两年后的今天是否还有效
["loginUserDTO.user_name"] = LoginInfo.UserName,出错
為什麼會一直返回"系统繁忙,请稍后重试!"?
我试过也是一样的提示,看参数都已经提交了
需要先Get Referer网址
果然可以了,另外想請教下為何在瀏覽器裏抓包看不到這個請求?還有登錄提交時不是應該要帶cookie嗎,為何沒有也能登錄成功?
我弄了一个算法,因为图片是固定大小的(293*190),那么把图片进行切分为8块,并去掉文字部分也就是30个像素,然后判定用户点击的在哪个区域内,根据区域取该块的中心位置作为验证码的x,y坐标。这样会有问题吗?
做法看不出问题,只要提交数据能通过验证就行了
请问下,登录一直返回 当前访问用户过多,请稍候重试是什么原因呢?
还是c#的大神,我是刚刚入门不久,写了MVC的网站和WinFrom的应用,也是写成系统型的,很多的地方都不是特别的明白,望鱼哥多多指教啊!
求更新啊。。。学习了
眼下比较忙。。后面看时间会更新的。
:idea:很赞呀,学习了。。。
66666六
不要断更啊 大神