MVC伪一个12306图片验证码
本文的来由主要是满足自己的好奇心,而不是证明什么东西,如果涉及到什么官方性的事情,麻烦通知我谢谢;本篇将要和大家分享的是一个看起来通12306图片验证码相似的效果,这篇应该是今年农历最后一篇分享文章了,楼主明天就要坐火车回家了,预祝各位:新年快乐,明年事事顺利,如果可以给我发个红包吧呵呵,希望大家能够喜欢,也希望各位多多"扫码支持"和"推荐"谢谢;
» 效果图展示及分析
» C#合并多张图片和获取图片验证码粗略的算法
» MVC如何使用图片验证码
» 2016年一句总结
» 2017年一句展望
下面一步一个脚印的来分享:
» 效果图展示及分析
首先,咋们来看一个图片验证码效果地址:http://lovexins.com:1001/home/ValidCode,及效果图:
上图是最终的示例图样,选中图片截图的网上的(如侵权,请及时联系作者);这里看起来类似于火车票网站的验证码,咋们先来分析下这种验证码是怎么把用户选中的信息传递个后端的吧,直接用12306官网为例,打开该网址并查看验证码,锁定验证码对应的html元素,然后尝试这点击某个图片,能看到如图增加的html节点:
、
然后,咋们再看“登录”按钮对应的js文件https://kyfw.12306.cn/otn/resources/merged/login_js.js,通过查找对应的登录按钮id=“loginSub”,然后能够找到其对应有一个验证码验证的操作:
很明显这里是通过id=“randCode”传递进来选中的验证码参数,然后我们再通过此id去查找html页面中对应的html元素是:
通过这里对应元素里面的value="106,115,182,113,252,47"和我们选中按钮是生成的div对应的left,top的值对比能发现数量上和值都很接近,并且left对比都相差3,top值对比相差16,再加上登录按钮js事件中用到了其对应的id=“randCode”,由此我们可以大胆猜测这个隐藏文本randCode对应的值就是传递给后端对比验证码是否匹配的值,这里较以往验证码不同的是,是使用坐标来确定选中验证图片是否匹配,由此引发了咋们对后台对比验证码是否正确的猜想,后台应该是每张单元格小图片都会对应一组起始,终止的坐标,就只能是这样才能判断出用户选中的图片坐标是否包含在此单元格小图片允许范围内,这种猜想的流程是否符合逻辑还请各位多多指正;有了上面的猜想,下面我们就可以来实现具体的代码了,鉴于篇幅影响下面只给出重要的几个方法;
» C#合并多张图片和获取图片验证码粗略的算法
看到12306的图片验证码图片,每张上面都有很多小图片组成,因此有了两种猜想:1.真的是由工作人员处理后把所有小图片弄成一个大的静态真实图片;2.通过程序由多张小图片合并成一个大图片流;不难看出前者如果处理起来需要耗费大量的工作周期(当然火车票那么来钱,说不定就是这么干的呢,谁知道呢),反正我是选择了后者通过程序处理合并多张图片,因此有了以下代码:
/// <summary>
/// 生成验证码图片流
/// </summary>
/// <param name="imgCode">单元格图片集合</param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns>图片流</returns>
public static byte[] CreateImgCodeStream(ref List<MoImgCode> imgCode, int width = , int height = )
{
var bb = new byte[];
//初始化画布
var padding = ;
var lenNum = ;
var len = imgCode.Count;
var len_len = len / lenNum;
var image = new Bitmap(width, height);
var g = Graphics.FromImage(image);
try
{
var random = new Random();
//清空背景色
g.Clear(Color.White);
var ii = ;
var everyX = width / len_len;
var everyY = height / lenNum;
foreach (var item in imgCode)
{
var img = Image.FromFile(item.ImgUrl); var x2 = everyX * (ii > len_len ? ii - len_len : ii);
var y2 = everyY * (ii > len_len ? : ) + (ii > len_len ? padding : );
//中横向线
if (ii == len_len)
{
g.DrawLine(new Pen(Color.Silver), , everyY, width, everyY);
} var x1 = x2 - everyX + padding;
var y1 = y2 - everyY; g.DrawImage(img, x1, y1, everyX, everyY); //赋值选中验证码坐标
if (item.IsChoice)
{
item.Point_A = new Point()
{
X = x1,
Y = y1
};
item.Point_B = new Point
{
X = x1 + everyX,
Y = y1 + everyY
};
} ii++;
}
//画图片的前景干扰点
for (int i = ; i < ; i++)
{
var x = random.Next(image.Width);
var y = random.Next(image.Height);
image.SetPixel(x, y, Color.FromArgb(random.Next()));
}
//画图片的边框线
g.DrawRectangle(new Pen(Color.Silver), , , image.Width - , image.Height - ); //保存图片流
var stream = new MemoryStream();
image.Save(stream, ImageFormat.Jpeg);
//输出图片流
bb = stream.ToArray();
}
catch (Exception ex) { }
finally
{
g.Dispose();
image.Dispose();
}
return bb;
}
通过传递小图片集合,然后内部通过画布把小图片画到同一个大图片中去,并且返回其对应在大图片所在的坐标(前面咋们提到的起始坐标,终止坐标):
由图能看出每张小图片都有自己相对于大图片原点的坐标,这也是咋们判断用户选择的图片点是否在每个小图片坐标范围内的依据,因此需要通过画图片的时候获取出来;
再来,咋们有了画图片的方法还不够,还需要有一个获取随机小图片的方法,我这里代码简单并非是最好的获取随机小图片方法仅供参考,先上获取程序文件夹下面图片的方法:
/// <summary>
/// 初始化图片源
/// </summary>
private static List<MoImgCode> listCode
{
get
{
var list = new List<MoImgCode>();
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "image");
var info = new DirectoryInfo(path);
foreach (var item in info.GetFiles())
{
list.Add(new MoImgCode
{
Index = item.Name,
IndexType = item.Name.Split('_')[],
ImgUrl = item.FullName
});
}
return list;
}
}
获取随机小图片验证码方法:
/// <summary>
/// 获取小图片验证码
/// </summary>
/// <param name="indexType"></param>
/// <param name="strLen"></param>
/// <returns></returns>
public static List<MoImgCode> CreateImgCode(string indexType, int strLen = )
{
var choiceCodeList = new List<MoImgCode>();
try
{
//备选验证码
var compareList = new List<MoImgCode>();
var imgCodeLen = listCode.Count; //最大选中数量5,最小大于等于1
var maxChoiceNum = ;
var minChoiceNum = ;
var rdChoiceNum = rm.Next(minChoiceNum, maxChoiceNum);
//获取待选中对象
var choiceList = listCode.Where(b => b.IndexType == indexType).Take(rdChoiceNum);
foreach (var item in choiceList)
{
compareList.Add(new MoImgCode
{
Index = item.Index,
ImgUrl = item.ImgUrl,
IndexType = item.IndexType,
IsChoice = true
});
} //剩余其他选项
var lessNum = strLen - choiceList.Count();
//获取其他选项
for (int i = ; i < lessNum; i++)
{
var lessCode = listCode.Where(b => !compareList.Any(bb => bb.IndexType == b.IndexType)).ToList();
var val = rm.Next(, lessCode.Count);
var otherItem = lessCode.Skip(val).Take().SingleOrDefault();
compareList.Add(new MoImgCode
{
Index = otherItem.Index,
ImgUrl = otherItem.ImgUrl,
IndexType = otherItem.IndexType,
IsChoice = false
});
} //随机排列
foreach (var item in compareList)
{
var lessCode = compareList.Where(b => !choiceCodeList.Any(bb => bb.Index == b.Index)).ToList(); var comparIndex = rm.Next(, lessCode.Count);
choiceCodeList.Add(lessCode[comparIndex]);
}
}
catch (Exception ex)
{
}
return choiceCodeList;
}
» MVC如何使用图片验证码
由于我们需要在页面上提示用户选择“xxx”类型的图片,所以需要通过后台返回验证码的图片和图片类型名称如:
显然一个action方法不能同时返回图片流和文字,所以我这里分开两个方法分别返回“企鹅”和图片,方法代码如:
/// <summary>
/// 获取图片验证码文字
/// </summary>
/// <returns></returns>
public JsonResult GetChoiceCode()
{
var data = new Stage.Com.Extend.StageModel.MoData(); var imgCode = ValidateCode.GetInitImgCode();
if (string.IsNullOrWhiteSpace(imgCode.Index)) { data.Msg = "请刷新页面获取验证码"; Json(data); } data.Data = imgCode.IndexType;
data.IsOk = true; return Json(data);
} /// <summary>
/// 获取验证码图片
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public FileResult GetValidateCode06(string code = "雏田")
{ var imgCode = new List<MoImgCode>();
var bb_code = ValidateCode.CreateImgValidateStream(code, ref imgCode, strLen: ); var choiceList = imgCode.Where(b => b.IsChoice).ToList();
var key = "imgCode";
if (Session[key] != null)
{
Session.Remove(key);
}
Session.Add(key, JsonConvert.SerializeObject(choiceList)); return File(bb_code, "image/jpeg");
}
图片方法中用到了 Session.Add(key, JsonConvert.SerializeObject(choiceList)); 在获取生成的图片验证码后用session保存对应的待匹配(需要用户选择的验证码图片类型,也就是“企鹅”对应的图片)的验证码坐标,用户在用户提交操作按钮(我这是登录)的时候用于匹配,因此就有了如下在登录时候匹配用户提交的坐标验证码代码如下:
[HttpPost]
public JsonResult UserLogin01(string code)
{
var data = new Stage.Com.Extend.StageModel.MoData();
//格式验证
if (string.IsNullOrWhiteSpace(code)) { data.Msg = "验证码不能为空"; return Json(data); }
if (Session["imgCode"] == null) { data.Msg = "验证码失效"; return Json(data); }
var compareImgCode = JsonConvert.DeserializeObject<List<MoImgCode>>(Session["imgCode"].ToString());
if (compareImgCode.Count<=) { data.Msg = "验证码失效!"; return Json(data); } //对比坐标确认验证码
var codeArr = code.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var item in codeArr)
{
var itemArr = item.Split(':');
if (itemArr.Length != ) { data.Msg = "验证码错误。"; break; } var x = Convert.ToInt32(itemArr[]);
var y = Convert.ToInt32(itemArr[]); var codeItem = compareImgCode.
Where(b => b.IsChoice).
Where(b =>
(b.Point_B.X > x && b.Point_B.Y > y) &&
(b.Point_A.X < x && b.Point_A.Y < y)).
SingleOrDefault();
if (codeItem == null) { data.Msg = "验证码错误"; break; } //验证正确
codeItem.IsChoice = false;
}
if (!string.IsNullOrWhiteSpace(data.Msg)) { return Json(data); }
//检查验证码是否都匹配成功
if (compareImgCode.Any(b => b.IsChoice)) { data.Msg = "验证码输入不完整,请重试"; return Json(data); } data.IsOk = true;
data.Msg = "图片验证码 - 验证成功";
return Json(data);
}
通过坐标的范围来确定用户选择的哪个小图片,这和传递验证码直接对比用户输入的信息和session保存的验证码信息对比逻辑差不多,只是图片验证码让第三方的一些识别软件很难破解,这里不得不说当初设计此验证码的大佬们的nb;下面我们再来看下mvc中的view中我代码是怎么写的:
<td>
请点击“<span id="spanCode" style="color:red"></span>”,<a style="cursor:pointer" id="a_imgCode">重获验证码</a><br />
<div id="codeNum" style=" position: relative; cursor: pointer; margin-bottom:30px; margin-top:10px">
<img id="code6" data-src="/home/GetValidateCode06" />
</div> <button id="btn01" class="btn btn-default">登 录</button>
<span id="msg01" style="color:red"></span>
</td>
布局视图就是文章开头的截图那样子,这里需要注意的是id=“codeNum”的div必须设置 position: relative ,然后我这里采用jquery绑定如果点击验证码图片就在这个div中增加一个标记点击的图标(我这里是新增div背景对应的是图标,这里注意这些div样式必须是 position: absolute不然无法呈现在id=“codeNum”的父级div上),下面贴出js代码:
//图片验证码点击
$("#code6").on("click", function (e) { //添加选择按钮
var container = $("#codeNum");
var x = e.offsetX;
var y = e.offsetY;
container.append('<div class="touclick-hov touclick-bgimg" style="left: ' + x + 'px; top: ' + y + 'px;"></div>'); //绑定移除选择按钮
$("#codeNum div").on("click", function () {
$(this).remove();
});
});
就我们登录而言如果验证码验证失败,那么需要重新获取验证码,或者无法选择识别的图片时候需要重新获取另外的验证码,因此有了下面js代码图片验证码切换:
//图片验证码切换
$("#a_imgCode").on("click", function () {
var img = $("#code6");
var nowTime = new Date().getTime();
//移除之前选择
$("#codeNum div").remove(); //先获取验证编码
$.post("/home/GetChoiceCode", function (result) {
if (result) {
if (result.IsOk) {
$("#spanCode").html(result.Data);
var src = img.attr("data-src") + "?t=" + nowTime + "&code=" + result.Data;
img.attr("src", src);
} else { console.log("获取验证码失败。"); }
}
})
});
$("#a_imgCode").click();
然后登录时候同样按照12306那样获取我们对应点击的图片坐标,并且传递给后端的Acton做登录验证:
//登录按钮事件
$("#btn01").on("click", function () { var msg = $("#msg01");
//获取坐标
var code = "";
var divs = $("#codeNum div");
for (var i = ; i < divs.length; i++) {
var item = $(divs[i]);
code += item.position().left + ":" + item.position().top + "|";
}
if (code.length <= ) { msg.html("请选择验证码图片"); return; }
// console.log(code); $.post("/home/UserLogin01", { code: code }, function (result) {
if (result) {
msg.html(result.Msg);
$("#a_imgCode").click();
}
});
});
好了重头戏的代码都已经发放完整了,下面给出示例整体代码文件包供大家下载:神牛 - 验证码实例
» 2016年一句总结
勤勤恳恳学知识,开开心心给大家
» 2017年一句展望
努力奋斗,挣点钱
--2017.02.04 应博友要求,添加我应用到的图片测试资源:图片
MVC伪一个12306图片验证码的更多相关文章
- MVC中登录页图片验证码总结
直接上代码了 using System;using System.Collections.Generic;using System.Drawing;using System.Drawing.Imagi ...
- 自己封装的一个java图片验证码
验证码生成器: package com.lz.Tools; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; ...
- Flask实战第40天:图片验证码生成技术
图片验证码生成 安装pillow pip install pillow 在utils下新建python package命名为captcha 把需要需要用到的字体放在captcha下 编辑captcha ...
- ASP.NET MVC 模块与组件(二)——定制图片验证码
本着简洁直接,我们就直奔主题吧! 下面是一个生成数字和字母随机组合的验证码类源代码: using System; using System.Drawing; using System.Drawing ...
- 在mvc中实现图片验证码的刷新
首先,在项目模型(Model)层中建立一个生成图片验证码的类ValidationCodeHelper,代码如下: public class ValidationCodeHelper { //用户存取验 ...
- 基于spring mvc的图片验证码实现
本文实现基于spring mvc的图片验证码,分后台代码和前端页面的展现以及验证码的验证. 首看后台实现代码: @RequestMapping({"authCode"}) publ ...
- MVC之图片验证码
MVC之图片验证码 controller中的action方法public ActionResult GetValidateCode() { ValidateCode vCode = new Valid ...
- 字符型图片验证码识别完整过程及Python实现
字符型图片验证码识别完整过程及Python实现 1 摘要 验证码是目前互联网上非常常见也是非常重要的一个事物,充当着很多系统的 防火墙 功能,但是随时OCR技术的发展,验证码暴露出来的安全问题也越 ...
- 用Unity写一个12306验证器的恶搞图生成软件
前言 前一阵子是买火车票的高峰期,然后12306的验证码就遭到各种吐槽.其实大部分验证码没有那么难,大家只是因为买不到票 发泄一下不满的情绪.于是各种恶搞的图就出现了,比如找二次元里人物的矮子,找好男 ...
随机推荐
- stm32 IAP + APP ==>双剑合一(转)
源:http://blog.csdn.net/yx_l128125/article/details/13591743 (扩展-IAP主要用于产品出厂后应用程序的更新作用,上一篇博文详细的对IAP 升级 ...
- JQuery实现仿腾讯的固定导航栏
1.描述 窗口滚动一定高度之后才让导航栏固定 2.要点 浏览器滚动的事件:$(window).scroll(functiuon(){ 文档滚过的高度: $(doucment).scrollTop(); ...
- css3盒模型学习--利用box自适应布局
box-flex是css3新添加的盒子模型属性,它的出现可以解决我们通过N多结构.css实现的布局方式.经典 的一个布局应用就是布局的垂直等高.水平均分.按比例划分. 目前box-flex属性还没 ...
- UIButton 关灯小实验
// 写在继承于UIViewController的子类中:创建单视图默认有ViewController类 // 实现:点击任何一颗UIButton,它四周的以及它自身都被变成红色,再点击就会变成原来的 ...
- centos 7用ss命令来查看端口占用和对应进程
mysqld进程在监听4567端口,进程id是2593:# ss -lnp|grep 4567tcp LISTEN 0 128 *:456 ...
- Linux中后台执行任务
执行时, 可以在命令最后添加 & 使其后台执行, 但是其输出依然会显示, 而且其运行是和当前shell绑定的 如果脚本已经运行, 可以使用Ctrl-Z暂停, 然后使用 bg 让其转入后台, ...
- java丢手帕 约瑟夫问题
一.问题描述: n个人围成一个圈,编号为1~n,从第一号开始报数,报到3的倍数的人离开,一直数下去,直到最后只有一个人,求此人编号. 二.问题提示: 使用一维数组,数组元素初始为1,从1开始 ...
- lpc1768的系统时钟
#define XTAL_FREQ 12000000 #define VECT_TAB_OFFSET 0x0000 void SystemInit(void) { //PLL0时钟配置 LPC_ ...
- NSDate详解及获取当前时间等常用操作
NSDate类用于保存时间值,同时提供了一些方法来处理一些基于秒级别时差(Time Interval)运算和日期之间的早晚比较等. 1. 创建或初始化可用以下方法 用于创建NSDate实例的类方法有 ...
- 在 WindowMobile 上的模拟LED 显示屏插件(转)
源:在 WindowMobile 上的模拟LED 显示屏插件 我在给一个对话框上的控件查找翻看合适的图标时,无形中看到了一个LED显示屏的图标,这里所说的LED显示屏是指由很多LED灯密集排列组成的点 ...