本鱼拟成立工作室承接项目开发/软件定制/云设施开发运维/办公设备技术支持等,如您有相关需求,欢迎来询 | ::博客文章推荐::

12306订票客户端 FOR .NET 演示项目 【6】验证码输入

: DOT.NET 木魚 9751℃ 3评论

本系列文章

  1. 12306订票客户端 FOR .NET 演示项目 【7】登录9年前 (2015-08-18)
  2. 12306订票客户端 FOR .NET 演示项目 【6】验证码输入9年前 (2015-08-12)
  3. 12306订票客户端 FOR .NET 演示项目 【5】获得余票数据9年前 (2015-06-10)
  4. 12306订票客户端 FOR .NET 演示项目 【4】界面框架&基础数据初始化9年前 (2015-06-08)
  5. 12306订票客户端 FOR .NET 演示项目 【3】流程分析和项目规划9年前 (2015-05-28)
  6. 12306订票客户端 FOR .NET 演示项目 【2】准备工具9年前 (2015-05-22)
  7. 12306订票客户端 FOR .NET 演示项目 【1】项目概况9年前 (2015-05-19)
前言:这段时间太忙,这个系列几乎没抽出时间来写。现在来慢慢继续挤牙膏。关于FSLIB.NETWORK网络库或这个演示项目有任何问题的,请在问答社区(http://ask.fishlee.net/category-21)中反馈提问   66.gif 

6.1 验证码流程分析

我们照例打开Fiddler抓包,在跟踪登录和提交流程后,我们可以很容易看到相关的请求。我们先到登录12306的页面上看看样子。

ticket_12306_demo_6_1

看到了让人抓狂的图片验证码。其实我对这验证码还是好评的。让我们先来试一下验证码错误是什么情况。两行四列图片,我们先点击第一行第一张的左上角,和第二行第一张的左下角,也就是截图中标记的两个区域。这么做的原因是为了待会儿的提交测试做坐标映射(因为提交出去的信息不一定就是相对于图片本身的原点的)。也就是这样。

ticket_12306_demo_6_2

提交,当然是错的,很明显我们选的俩都不是电子秤。

ticket_12306_demo_6_3

然后我们输入用户名和密码,第一次故意输入错误的,然后提交,当然会提示密码错误。然后我们再来一遍,输入正确的。

此时,我们转过头来看Fiddler,在记录中扒相关的请求。验证码是个图片,顺着我们刚才的操作流程,很容易找到如下的请求。

ticket_12306_demo_6_4

注意这个记录,我们需要保存。下一章分析登录也需要这些记录,这一章我们先只分析和验证码相关的请求。

从上图中我们可以看到验证码是这样的一个地址:/passcodeNew/getPassCodeNew?module=login&rand=sjrand&0.796578265959397 。真正起作用的是前面的一段,后面的随机数在巨大多数情况下,都并没有实际的逻辑意义。按照同样的方式,我们可以抓到订单提交页的验证码图片地址。

ticket_12306_demo_6_5

嗯……你会看到地址大概有变化,不过不大,这里的地址是 /passcodeNew/getPassCodeNew?module=passenger&rand=randp&0.6715538722928613,中间一个参数发生了变化。

现在回过头看刚才的验证码校验请求,关键看第一个请求。

ticket_12306_demo_6_6

我们可以看到很明显是两个点的坐标被直接组成字符串提交了上去。这里提交的是 X1,Y1,X2,Y2。也就是说,我们点的是 (6,10) 以及 (5,151) 两个点。再来看看验证码图片。

ticket_12306_demo_6_7_spec

排除掉误差,我们可以推断出提交的坐标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<ImageLoadVerifyCodeImage(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}"modulerandTypenew Random().NextDouble().ToString()),
			urlRefer
		);
	await ctx.SendTask();
 
	return ctx.Result;
}

6.3 校验验证码

首先我们定义一个校验验证码结果实体类。

namespace Ticket12306Demo.Service.Entities.Web
{
	/// <summary>
	/// 验证码校验结果
	/// </summary>
	class VerifyCodeCheckResult
	{
		public int Result { getset}
 
		public string Msg { getset}
	}
}

然后在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 设计验证码输入界面

考虑到复用性,我们这里将验证码输入创建为一个单独的对话框,并将验证码校验功能集成到里面去。

而在验证码输入中,我们使用最简单的方案,也就是将一个用于显示验证码的图片放在一个容器中,然后当点击验证码的时候,计算出相应的坐标并创建一个指示图标放在对应的位置。

界面设计如下。

ticket_12306_demo_6_8

接着改写此对话框的构造函数,加入必须的属性。

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();
    }
 
}

下一步,开始加载验证码。

注意:频繁获得验证码的时候有可能会返回提示说你太繁忙,严格来说是算失败的。这里没有处理此类情况,有兴趣的同学可以研究下怎么处理。 71.gif 
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());

ticket_12306_demo_6_9

确定。

ticket_12306_demo_6_10

 

OK。自此,验证码获得、输入和校验,均已完成。

 

FSLIB.NETWORK 网络库系列文章

  1. 12306订票助手.NET V10.6.1 发布8年前 (2016-09-01)
  2. 开源 FSLIB.NETWORK 库 2.2.0.08年前 (2016-08-02)
  3. FSLIB.NETWORK手册(1) · 基本概念和流程8年前 (2016-05-05)
  4. 原创FSLib.Network库更新 2.0.0 版8年前 (2016-04-05)
  5. 原创FSLib.Network库更新 1.6.0版(目前专注于HTTP的高性能高易用性网络库)9年前 (2015-12-13)
  6. 玩具系列:批量QQ群签到工具v2 (暂时屏蔽自定义位置功能)9年前 (2015-08-29)
  7. 玩具系列:批量QQ群签到工具(支持自定义位置)9年前 (2015-08-28)
  8. 12306订票助手.NET 8.0.8 发布9年前 (2015-08-21)
  9. 12306订票客户端 FOR .NET 演示项目 【7】登录9年前 (2015-08-18)
  10. 12306订票客户端 FOR .NET 演示项目 【6】验证码输入9年前 (2015-08-12)
  11. 12306订票客户端 FOR .NET 演示项目 【5】获得余票数据9年前 (2015-06-10)
  12. 原创FSLib.Network库发布 1.5 版9年前 (2015-06-09)
  13. 12306订票客户端 FOR .NET 演示项目 【4】界面框架&基础数据初始化9年前 (2015-06-08)
  14. 12306订票客户端 FOR .NET 演示项目 【3】流程分析和项目规划9年前 (2015-05-28)
  15. 12306订票客户端 FOR .NET 演示项目 【2】准备工具9年前 (2015-05-22)
  16. 12306订票客户端 FOR .NET 演示项目 【1】项目概况9年前 (2015-05-19)
  17. 原创FSLib.Network库发布 1.4 版9年前 (2015-05-08)
  18. 放一个抓取网页的信息监控小工具源码9年前 (2015-04-27)
  19. FSLib.Network网络库使用教程[2] 实例教程·美女们快到硬盘里来!9年前 (2015-01-30)
  20. FSLib.Network网络库使用教程[1] 基本使用9年前 (2015-01-19)
  21. 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库)9年前 (2015-01-18)
喜欢 (17)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(3)个小伙伴在吐槽
  1. 超哥,谢谢您哇

    大白脸2015-10-12 13:46 回复
  2. 0020.gif又更新了

    那个同学2015-08-18 09:16 回复
  3. 努力学习中

    jyy2015-08-14 10:56 回复