作者:光脚丫思考
一、概述
一直以来对于验证码这玩意都是使用了别人编写好的代码,最多也就是稍微的做点修改罢了。虽然别人做的东西并不是非常的适合自己使用,但还是给将就将就了一番。这几天呢?不知道是哪里高兴了,终于是好好的把一些别人早就已经使用过的验证码技术给好好的拿来学习学习了一番。很明显,在别人已经做的基础上,我还可以在做其他更多的发挥,不过,在这之前先让我来总结总结已经有的学习成果吧!
这生成验证码的技术至少有2个关键技术是要解决的。
1、如何生成随机的验证码。
2、如何将生成的随机验证码绘制到图片上。
一旦这个包含了随机验证码的图片生成完毕了,就可以在网页当中使用了。当然,如果是WINFORM程序的话,稍微做一些改动也就是了。
二、生成验证码咱们先来说说这第一个关键技术吧!其实这个问题是比较好解决的,所谓生成随机验证码无非也就是在那几个数字、字母,再复杂点也就是中文汉字中随机选择几个,然后组合成一个字符串而已。对于这种验证码的生成相对来说还是简单的。当然了,如果要想生成那种相当BT的公式之类的验证码则就稍微显得麻烦点,这只是我个人的一点浅见而已。或许对于其他一些来说那也是相当简单的。所以,这里就不讨论这种极为BT的验证码生成技术了,单单说前面那种较为简单的验证码,即只包含了数字,或者字母,或者中文汉字,或者这几种的组合。
可能是因为这样的验证码生成起来是比较简单的吧,所以方法也是多种多样的。在我所看到的代码中至少包含了2种生成方式。
第一种方式就是从代码中产生一个随机数,然后把这个数除以2,从而来判断这个数是奇数?还是偶数?如果是奇数则生成一个随机的数字,如果是偶数则生成一个随机的字母,反之亦然。这样说起来显得是相当的晦涩,不如来看看这样的一段代码:
/// <summary>
/// 生成指定长度的随机验证码。
/// </summary>
/// <param name="Length">所生成验证码的长度。</param>
/// <returns>返回生成的指定长度的随机验证码。</returns>
public string CreateValidationCode(int Length)
{
int RandomNumber;
char Code;
string ValidationCode = string.Empty;
Random random = new Random();
for (int Index = 0; Index < Length; Index++)
{
RandomNumber = random.Next();
if (RandomNumber % 2 == 0)
{
// 生成的随机数是偶数,则生成一个数字字符,数字为余数。
Code = (char)('0' + (char)(RandomNumber % 10));
}
else
{
// 生成的随机数是奇数,则生成一个英语字母。
Code = (char)('A' + (char)(RandomNumber % 26));
}
ValidationCode += Code.ToString();
}
return ValidationCode;
}
我初开始学习这段代码的时候,被它那个除以10和除以26个迷惑了,一时摸不着头脑。但随着进一步的思考,才恍然大悟。为何生成的验证码是数字的时候便要除以10呢?因为这样就保证了它的余数是一个小于10的非负数,即从0到9,这不刚好就是0~9的10个数字吗?至于除以26的道理和这个也就是一回事了。
上面的这种方法非常的不错,但当我看到另一段代码的时候才发现还有更不错的方案。代码如下:
/// <summary>
/// 生成指定长度的随机验证码。
/// </summary>
/// <param name="Length">验证码的长度。</param>
/// <returns>返回指定长度的随机验证码。</returns>
public string CreateValidationCode(int Length)
{
string AllChar = "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z";
string[] AllChars = AllChar.Split(',');
string ValidationCode = string.Empty;
string RandomCode = string.Empty;
Random random = new Random();
int RandomIndex = 0;
for (int Index = 0; Index < Length; Index++)
{
RandomIndex = random.Next(AllChars.Length - 1);
RandomCode = AllChars[RandomIndex];
ValidationCode += RandomCode;
}
return ValidationCode;
}
这段代码要比之前的那个代码更加的精简,而且从运算上来说,它可能还会更加的高速。这便是第2种生成验证码的方式了。首先用一个数组将生成验证码的一些基本元素存储起来,这些基本元素无非就是数字,字母,字母还可以来个大写字母和小写字母之分的。接着就随机产生一个值,这个值是一个索引值,也就是在数组中的索引值,然后通过这个随机的索引值来获取数组中的对应原则。因此这也就对这个随机生成的索引值提出了要求,即不能大于数组的最大索引值。通常数组的索引值是从0开始的,因此最大索引值也就是数组的元素个数减去1。注意上面代码的这句:
RandomIndex = random.Next(AllChars.Length - 1);
现在是不是觉得第2种方法更为简单快速呢?但是如果要把以上这2种方法用到中文汉字上,那可就惨咯。第一种肯定是不行的,它也就是适合搞搞数字和英文字母之类的,第二种方法虽然可行,可是中文汉字有多少呀!把这些都装到数组里是不是有点太过火了?放到数据库里当然可以了,但是还是显得麻烦了。至少它不会是一个节省资源的做法。
针对这个问题还这就有人真的找到了一个非常不错的解决办法。
具体做法是这样的:首先被他发现了中文汉字编码的一些规律,然后就用这些规律在代码中生成某个中文汉字的编码,接着再把这个编码转换汉字。具体的原理以及做法可以参看这篇文章:http://www.chinaz.com/Program/.NET/031510V912010.html仍然还是来看一段代码吧:
/// <summary>
/// 生成中文汉字验证码。
/// </summary>
/// <param name="Length">中文汉字验证码的个数。</param>
/// <returns>返回指定个数的中文汉字。</returns>
public string CreateValidationCode(int Length)
{
// 随机生成中文汉字编码。
string[] CodeBases = new string[16]{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
Random random = new Random();
object[] CodeBytes = new object[Length];
for (int Index = 0; Index < Length; Index++)
{
int RandomIndex1 = random.Next(11, 14);
string RandomString1 = CodeBases[RandomIndex1].Trim();
random = new Random(RandomIndex1 * unchecked((int)DateTime.Now.Ticks) + Index);
int RandomIndex2;
if (RandomIndex1 == 13)
RandomIndex2 = random.Next(0, 7);
else
RandomIndex2 = random.Next(0, 16);
string RandomString2 = CodeBases[RandomIndex2].Trim();
random = new Random(RandomIndex2 * unchecked((int)DateTime.Now.Ticks) + Index);
int RandomIndex3 = random.Next(10, 16);
string RandomString3 = CodeBases[RandomIndex3].Trim();
random = new Random(RandomIndex3 * unchecked((int)DateTime.Now.Ticks) + Index);
int RandomIndex4;
if (RandomIndex3 == 10)
RandomIndex4 = random.Next(1, 16);
else if (RandomIndex3 == 15)
RandomIndex4 = random.Next(0, 15);
else
RandomIndex4 = random.Next(0, 16);
string RandomString4 = CodeBases[RandomIndex4].Trim();
byte LowByte = Convert.ToByte(RandomString1 + RandomString2, 16);
byte HighByte = Convert.ToByte(RandomString3 + RandomString4, 16);
byte[] CodeByte = new byte[] { LowByte, HighByte };
CodeBytes.SetValue(CodeByte, Index);
}
// 将随机生成的汉字编码转换成汉字。
string CodeString = string.Empty;
Encoding GB = Encoding.GetEncoding("GB2312");
for (int Index = 0; Index < CodeBytes.Length; Index++)
{
CodeString += GB.GetString((byte[])Convert.ChangeType(CodeBytes[Index], typeof(byte[])));
}
return CodeString;
}
这段代码,我就不多做解释了。可以参看原文,然后自个琢磨琢磨。其中一些代码逻辑无非也就是按照中文汉字的编码规律来编写的。所以,在阅读原文的时候最好和中文汉字编码表对比着来看,这样将有助于理解。
三、绘制验证码图片
以上的文章我们讨论了一些生成验证码的方法,到这里并没有结束,我们还需要将所生成的验证码绘制到图片上,那样才算大功告成了。接下来我们主要以网页中使用的验证码为例来讨论如何生成验证码图片,以及如何将验证码图片显示到网页上。至于WINFORM的相关技术自个可以琢磨琢磨。
说起这绘图来,那就不能不介绍几个类了。虽然我对这几个类的研究也是相当肤浅的,但是对于那些没有接触过这些类的兄弟姐妹们,或者接触过了,但是比我的认识还要肤浅的,稍微做个介绍还是很有必要的嘛!^_^
Bitmap:封装GDI+位图,此位图由图形图像及其属性的像素数据组成,是用于处理由像素数据定义的图像的对象。
我的理解是这个就相当于是那个画布一样,你怎么画最后都画到它上面了。
Graphics:封装一个GDI+绘图图面。
这个就好像是一个绘图的工具箱一样,诸如你想要在画布上写几个字呀?画条线呀?画个矩形呀?圆呀什么的,就找它吧。
所以说这2个类对于实现绘制验证码图片的这种效果来说是相当相当之重要的。
其他还有几个类这里就不专门介绍了,等会遇到了,顺带说一下。现在开始绘图吧……
首先,我们要知道图片的大小吧!如果连图片的大小都不知道那怎么绘图呢?^_^而这个图片的大小却有不是随便来确定的,它取决于要绘制的验证码字符个数,以及所选择的字体及字体大小。所以说,这个大小还不能事先就固定好,而是要根据这些因素在代码中进行计算的,这就需要用到Graphics类的MeasureString()方法了。这个方法主要是用来测量字符串的大小,比如长度和高度等。代码如下:
// 确定生成图片的大小。
Bitmap bitmap = new Bitmap(1, 1);
Graphics graphic = Graphics.FromImage(bitmap);
Font font = new Font("微软雅黑", 20, FontStyle.Regular);
SizeF ValidationCodeSize = graphic.MeasureString(ValidationCode, font);
bitmap.Dispose();
bitmap = new Bitmap(Convert.ToInt32(ValidationCodeSize.Width) + 6,
Convert.ToInt32(ValidationCodeSize.Height) + 6);
graphic.Dispose();
graphic = Graphics.FromImage(bitmap);
上面这段代码我自己看起来都觉得有些别扭。首先创建了一个长1个像素高1个像素的图片,然后又给销毁了,接着创建了一次。也首先创建了一个Graphics对象,最后又销毁了重新创建。为何要这样呢?当然就是为了计算最终生成图片的大小了。没有Graphics对象便没法调用MeasureString()方法,要想有个Graphics对象最简单的做法是搞个Bitmap对象,所以就这样的折腾了一番。是不是有其他更好的替代方案呢?各位兄弟姐妹谁要是知道的话,请别忘了告诉我一声呀!^_^
图片的大小也确定了,也把画布准备好了。接着需要给画布绘制一个背景色。代码如下:
graphic.Clear(Color.Black);
这是在给绘制背景色吗?倒像是把背景色给清除了。没错,就是把背景色给清除了,不过清除之后马上就有给配上黑色了。^_^
到这里才算是把画布彻彻底底的准备好了,接下来就让咱们再上面绘制验证码吧。还是先看代码:
// 在图片上生成验证码。
SolidBrush SBrush = new SolidBrush(Color.White);
graphic.DrawString(ValidationCode, font, SBrush, 3, 3);
SBrush.Dispose();
用到的是Graphics对象的DrawString()方法。也用到了一个单色画刷对象(SolidBrush),当然也可以把它换成那种渐变色的画刷,或者其他的画刷。
到这里基本上也就绘出了一个基本的验证码图片了,其实还有很多的事情要做的,不过本文我打算就先做到这里。接下来就把它输出到客户端吧。
// 输出图片到客户端。
MemoryStream MStream = new MemoryStream();
bitmap.Save(MStream, ImageFormat.Gif);
this.Response.ClearContent();
this.Response.ContentType = "image/Gif";
this.Response.BinaryWrite(MStream.ToArray());
MStream.Dispose();
// 释放资源。
bitmap.Dispose();
graphic.Dispose();
当成功的输出到客户端之后,麻烦你别忘记了把一些忒耗费资源的对象给销毁了。^_^
到了这一步是不是就完成了呢?没有,当然还没有呢。要怎样在客户端现实呢?这个不难,一点也不难。在客户端的页面上弄一个ImageButton控件,然后把它的ImageUrl设置为生成验证码图片的那个页面就成了。可以参考如下的代码:
<asp:ImageButton ID="ValidationCodeImageButton" runat="server"
ImageUrl="~/ValidationCode.aspx" />
那我要使用Image控件可以吗?当然也可以了。作法和前面的基本一致。
好了,现在让我们分别来看看这些验证码的效果吧。
验证码:英文字母+数字
验证码:中文汉字
怎么样?看起来还过意的去吧!那到这里就算是结束了吧?完了吧?怎么能呢,我如此这般认真的学习研究它,怎么就会到此为止呢?不过,欲知后事如何,且听下回分解。^_^