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的验证码就遭到各种吐槽.其实大部分验证码没有那么难,大家只是因为买不到票 发泄一下不满的情绪.于是各种恶搞的图就出现了,比如找二次元里人物的矮子,找好男 ...
随机推荐
- chromium high cpu usage
ctrl + esc 列出哪个进程占用了cpu,可以杀掉. --purge-memory-button可以在上边弹出的窗口上显示一个按钮,据说能释放内存.
- bootstrap建立响应式网站——tab选项卡
1.bootstrap给我们提供了标签页 细细看了一下bootstrap的标签页源码,对tab选项卡有了更深的理解.其实说来也简单,以前自己写js和css时没有意识到整体的划分.就是分为两部分:一部分 ...
- CSS的三种手段让元素脱离标准本文档流——浮动、绝对定位、固定定位
1.浮动 浮动是CSS中用到的最多的一个选项,他有三个性质.关于浮动我们要强调一点,永远不是一个东西单独浮动,浮动都是一起浮动,要浮动,大家都浮动. 1.1 浮动元素脱离标准文档流 1.1.1 大概描 ...
- appium通过WiFi连接真机进行测试
http://www.th7.cn/Program/Android/201507/514602.shtml appium通过WiFi连接真机进行测试 2015-07-24 19:43:07CSDN ...
- 数据降维技术(2)—奇异值分解(SVD)
上一篇文章讲了PCA的数据原理,明白了PCA主要的思想及使用PCA做数据降维的步骤,本文我们详细探讨下另一种数据降维技术—奇异值分解(SVD). 在介绍奇异值分解前,先谈谈这个比较奇怪的名字:奇异值分 ...
- 9、手把手教你Extjs5(九)使用MVVM特性控制菜单样式
菜单的样式多了,怎么可以灵活的切换是个问题. 在使用标准菜单的时候,在菜单最前面有二个按钮,可以切换到树状菜单和按钮菜单. 在树状菜单的显示区,可以切换换到标准菜单,以及折叠式菜单. 切换到按钮菜单之 ...
- iOS开发——工厂模式
工厂模式很好用,为表诚意,我直接搞个实用的例子放这,解析一个订单的数据,并且这个订单里面可能不止一件商品的做法. 还是直接上代码,不懂的地方,再提出来. 1.在MyOrderDeals.h文件中 #i ...
- js格式化时间戳
//js格式化时间戳,转换为时间格式 2017-1-15 4:10:15 function getLocalTime(nS) { var time = new Date(parseInt(nS) * ...
- Memcached源码分析之items.c
#include "memcached.h" #include <sys/stat.h> #include <sys/socket.h> #include ...
- 浅谈MySQL分表
关于分表:顾名思义就是一张数据量很大的表拆分成几个表分别进行存储. 我们先来大概了解以下一个数据库执行SQL的过程: 接收到SQL --> 放入SQL执行队列 --> 使用分析器分解SQL ...