本系列文章
- 12306订票客户端 FOR .NET 演示项目 【7】登录9年前 (2015-08-18)
- 12306订票客户端 FOR .NET 演示项目 【6】验证码输入9年前 (2015-08-12)
- 12306订票客户端 FOR .NET 演示项目 【5】获得余票数据9年前 (2015-06-10)
- 12306订票客户端 FOR .NET 演示项目 【4】界面框架&基础数据初始化9年前 (2015-06-08)
- 12306订票客户端 FOR .NET 演示项目 【3】流程分析和项目规划9年前 (2015-05-28)
- 12306订票客户端 FOR .NET 演示项目 【2】准备工具9年前 (2015-05-22)
- 12306订票客户端 FOR .NET 演示项目 【1】项目概况9年前 (2015-05-19)
6.1 验证码流程分析
我们照例打开Fiddler抓包,在跟踪登录和提交流程后,我们可以很容易看到相关的请求。我们先到登录12306的页面上看看样子。
看到了让人抓狂的图片验证码。其实我对这验证码还是好评的。让我们先来试一下验证码错误是什么情况。两行四列图片,我们先点击第一行第一张的左上角,和第二行第一张的左下角,也就是截图中标记的两个区域。这么做的原因是为了待会儿的提交测试做坐标映射(因为提交出去的信息不一定就是相对于图片本身的原点的)。也就是这样。
提交,当然是错的,很明显我们选的俩都不是电子秤。
然后我们输入用户名和密码,第一次故意输入错误的,然后提交,当然会提示密码错误。然后我们再来一遍,输入正确的。
此时,我们转过头来看Fiddler,在记录中扒相关的请求。验证码是个图片,顺着我们刚才的操作流程,很容易找到如下的请求。
注意这个记录,我们需要保存。下一章分析登录也需要这些记录,这一章我们先只分析和验证码相关的请求。
从上图中我们可以看到验证码是这样的一个地址:/passcodeNew/getPassCodeNew?module=login&rand=sjrand&0.796578265959397
。真正起作用的是前面的一段,后面的随机数在巨大多数情况下,都并没有实际的逻辑意义。按照同样的方式,我们可以抓到订单提交页的验证码图片地址。
嗯……你会看到地址大概有变化,不过不大,这里的地址是 /passcodeNew/getPassCodeNew?module=passenger&rand=randp&0.6715538722928613
,中间一个参数发生了变化。
现在回过头看刚才的验证码校验请求,关键看第一个请求。
我们可以看到很明显是两个点的坐标被直接组成字符串提交了上去。这里提交的是 X1,Y1,X2,Y2
。也就是说,我们点的是 (6,10)
以及 (5,151)
两个点。再来看看验证码图片。
排除掉误差,我们可以推断出提交的坐标x坐标和实际的位置是一样的,而y坐标则是比实际的图片坐标上移了30个像素。实际上相当于去掉了上方的文字(也就是分隔线上方)的部分。
而验证码校验结果中,result
很明显是结果,0为错误。
6.2 加载验证码
先定义一个枚举,用来标记验证码类型。
/// <summary> /// 验证码类型 /// </summary> enum VerifyCodeType { /// <summary> /// 登录验证码 /// </summary> Login = 0, /// <summary> /// 提交订单验证码 /// </summary> SubmitOrder = 1 }
然后在VerifyCodeService
类中写上加载图片验证码代码。
/// <summary> /// 加载验证码 /// </summary> /// <param name="type">验证码类型</param> /// <returns></returns> public async Task<Image> LoadVerifyCodeImage(VerifyCodeType type) { var module = "login"; var randType = "sjrand"; //为了准确起见,我们使用真正的引用页 var urlRefer = "https://kyfw.12306.cn/otn/login/init"; if (type == VerifyCodeType.SubmitOrder) { module = "passenger"; randType = "randp"; urlRefer = "https://kyfw.12306.cn/otn/confirmPassenger/initDc"; } var ctx = Session.NetClient.Create<Image>(HttpMethod.Get, string.Format("https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module={0}&rand={1}&{2}", module, randType, new Random().NextDouble().ToString()), urlRefer ); await ctx.SendTask(); return ctx.Result; }
6.3 校验验证码
首先我们定义一个校验验证码结果实体类。
namespace Ticket12306Demo.Service.Entities.Web { /// <summary> /// 验证码校验结果 /// </summary> class VerifyCodeCheckResult { public int Result { get; set; } public string Msg { get; set; } } }
然后在VerifyCodeService
类中写上验证码校验代码。
/// <summary> /// 校验验证码 /// </summary> /// <param name="type">验证码类型</param> /// <param name="points">验证码点位置</param> /// <returns></returns> public async Task<bool> CheckVerifyCode(VerifyCodeType type, params Point[] points) { var randType = "sjrand"; //为了准确起见,我们使用真正的引用页 var urlRefer = "https://kyfw.12306.cn/otn/login/init"; if (type == VerifyCodeType.SubmitOrder) { randType = "randp"; urlRefer = "https://kyfw.12306.cn/otn/confirmPassenger/initDc"; } var ctx = Session.NetClient.Create<WebResponseResult<VerifyCodeCheckResult>>( HttpMethod.Post, "https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn", urlRefer, new { randCode = points.Select(s => s.X + "," + s.Y).JoinAsString(","), rand = randType } ); await ctx.SendTask(); return ctx.IsValid() && ctx.Result.Data.Result == 1; }
6.4 设计验证码输入界面
考虑到复用性,我们这里将验证码输入创建为一个单独的对话框,并将验证码校验功能集成到里面去。
而在验证码输入中,我们使用最简单的方案,也就是将一个用于显示验证码的图片放在一个容器中,然后当点击验证码的时候,计算出相应的坐标并创建一个指示图标放在对应的位置。
界面设计如下。
接着改写此对话框的构造函数,加入必须的属性。
partial class RequireVcDlg : FormBase { /// <summary> /// 获得验证码类型 /// </summary> public VerifyCodeType RandType { get; private set; } /// <summary> /// 获得输入的坐标位置 /// </summary> public Point[] Points { get; private set; } public RequireVcDlg(ServiceContext context, VerifyCodeType randType) : base(context) { RandType = randType; InitializeComponent(); } }
下一步,开始加载验证码。
void RequireVcDlg_Load(object sender, EventArgs e) { LoadVerifyCode(); btnRefresh.Click += (x1, x2) => LoadVerifyCode(); } /// <summary> /// 加载验证码 /// </summary> async void LoadVerifyCode() { stStatus.Text = "正在加载验证码..."; //清空之前的图片和状态 Points = null; imgContainer.Controls.Cast<Control>().Where(s => s != pbVc).ToArray().ForEach(s => imgContainer.Controls.Remove(s)); imgContainer.Enabled = false; btnOK.Enabled = false; btnRefresh.Enabled = false; //加载 var img = await ServiceContext.VerifyCodeService.LoadVerifyCodeImage(RandType); if (img == null) { stStatus.Text = "验证码加载失败, 点击重新加载..."; } else { pbVc.Image = img; stStatus.Text = "验证码加载成功"; btnOK.Enabled = true; } btnRefresh.Enabled = true; imgContainer.Enabled = true; }
然后我们干嘛呢。编写点击添加点位置的代码吧。这里我偷懒,标记图片就借用订票助手.NET的了。
private void PbVc_MouseClick(object sender, MouseEventArgs e) { //如果验证码没有成功加载,则视为点击刷新 if (pbVc.Image == null) { LoadVerifyCode(); return; } //添加mark var point = e.Location + new Size(0, -30); if (point.X == 0 || point.Y == 0) return; //非法坐标 (Points ?? (Points = new List<Point>())).Add(point); //添加marker var marker = new PictureBox() { Location = e.Location + new Size(-16, -16), Image = Properties.Resources.vc_marker, SizeMode = PictureBoxSizeMode.AutoSize, Tag = point }; imgContainer.Controls.Add(marker); marker.BringToFront(); //添加marker移除事件 marker.Click += (x, y) => { Points.Remove((Point)(x as PictureBox).Tag); imgContainer.Controls.Remove(x as PictureBox); btnOK.Enabled = Points.Count > 0; }; btnOK.Enabled = true; }
最后我们在 btnOk
的点击处理事件中写上校验代码,确保成功后才会关闭对话框。
private async void BtnOK_Click(object sender, EventArgs e) { btnRefresh.Enabled = false; btnOK.Enabled = false; stStatus.Text = "正在校验验证码..."; var checkResult = await ServiceContext.VerifyCodeService.CheckVerifyCode(RandType, Points.ToArray()); if (!checkResult) { //校验失败 stStatus.Text = "验证码校验失败!请重试。"; await Task.Delay(2000); //延迟两秒 LoadVerifyCode(); return; } //成功! stStatus.Text = "校验成功!"; DialogResult = DialogResult.OK; Close(); }
6.5 测试
最后,我们在主窗口的加载代码中临时写上一句话测试。
var result = new RequireVcDlg(_context, VerifyCodeType.Login).ShowDialog(); MessageBox.Show(result.ToString());
确定。
OK。自此,验证码获得、输入和校验,均已完成。
FSLIB.NETWORK 网络库系列文章
- 12306订票助手.NET V10.6.1 发布8年前 (2016-09-01)
- 开源 FSLIB.NETWORK 库 2.2.0.08年前 (2016-08-02)
- FSLIB.NETWORK手册(1) · 基本概念和流程8年前 (2016-05-05)
- 原创FSLib.Network库更新 2.0.0 版8年前 (2016-04-05)
- 原创FSLib.Network库更新 1.6.0版(目前专注于HTTP的高性能高易用性网络库)9年前 (2015-12-13)
- 玩具系列:批量QQ群签到工具v2 (暂时屏蔽自定义位置功能)9年前 (2015-08-29)
- 玩具系列:批量QQ群签到工具(支持自定义位置)9年前 (2015-08-28)
- 12306订票助手.NET 8.0.8 发布9年前 (2015-08-21)
- 12306订票客户端 FOR .NET 演示项目 【7】登录9年前 (2015-08-18)
- 12306订票客户端 FOR .NET 演示项目 【6】验证码输入9年前 (2015-08-12)
- 12306订票客户端 FOR .NET 演示项目 【5】获得余票数据9年前 (2015-06-10)
- 原创FSLib.Network库发布 1.5 版9年前 (2015-06-09)
- 12306订票客户端 FOR .NET 演示项目 【4】界面框架&基础数据初始化9年前 (2015-06-08)
- 12306订票客户端 FOR .NET 演示项目 【3】流程分析和项目规划9年前 (2015-05-28)
- 12306订票客户端 FOR .NET 演示项目 【2】准备工具9年前 (2015-05-22)
- 12306订票客户端 FOR .NET 演示项目 【1】项目概况9年前 (2015-05-19)
- 原创FSLib.Network库发布 1.4 版9年前 (2015-05-08)
- 放一个抓取网页的信息监控小工具源码9年前 (2015-04-27)
- FSLib.Network网络库使用教程[2] 实例教程·美女们快到硬盘里来!10年前 (2015-01-30)
- FSLib.Network网络库使用教程[1] 基本使用10年前 (2015-01-19)
- 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库)10年前 (2015-01-18)
超哥,谢谢您哇
又更新了
努力学习中