标签:
最近发现一些网站的验证码全部换成了“极验”和“点触”的,发现QQ的注册也是与“点触”的相似。就想尝试实现一个。
先上效果图:
下面贴上主要思路及代码:
第一步:得到常用汉字列表
public static List<string> GetChineseWordList() { string cacheKey = "VCode_ChineseWordList"; return cache.GetCacheByFuc_NoRemove<List<string>>(cacheKey, new CacheFunc(delegate() { var list = new List<byte>(); for (int area = 16; area <= 55; area++) { for (int place2 = (area == 55) ? 89 : 94, place = 1; place <= place2; place++) { list.Add((byte)(area + 0xa0)); list.Add((byte)(place + 0xa0)); } } string words = Encoding.GetEncoding("GB2312").GetString(list.ToArray()); return words.Select(m => m.ToString()).ToList(); })); }
第二步:获取随机背景图片列表
public static List<string> GetBackGroundImageList(string imageDir) { string cacheKey = "VCode_BackGroundImageList"; return cache.GetCacheByFuc_NoRemove<List<string>>(cacheKey, new CacheFunc(delegate() { List<string> list = new List<string>(); DirectoryInfo theFolder = new DirectoryInfo(imageDir); FileInfo[] fileInfo = theFolder.GetFiles(); foreach (FileInfo file in fileInfo) { list.Add(file.FullName); } return list; })); }
第三步:获取随机汉字列表
public static List<string> GetRandChineseWordList(List<string> chineseWordList, int length) { List<string> list = new List<string>(); for (var i = 0; i < length; i++) { Random rnd = new Random((i + 1) * unchecked((int)DateTime.Now.Ticks)); int index = rnd.Next(0, chineseWordList.Count()); list.Add(chineseWordList[index]); } return list; }
第四步:生成汉字水印并返回需要验证的字符及位置信息
public static List<WordPos> DrawWord(Graphics picture, Bitmap bmp, List<string> randWordList, Random rnd) { picture.CompositingQuality = CompositingQuality.HighQuality; picture.SmoothingMode = SmoothingMode.AntiAlias; StringFormat format = StringFormat.GenericDefault; List<WordPos> wordPosList = new List<WordPos>(); //需要副本 List<Point> areaPointList = Utils.GetAreaPointList().Select(m => m).ToList(); List<Point> verAreaPointList = Utils.GetVerAreaPointList(); List<int> fontSizeList = Utils.GetFontSizeList(Config.FontSize); List<Color> fontColorList = Utils.GetFontColorList(Config.FontColor); List<string> fontFamilyList = Utils.GetFontFamilyList(Config.FontFamily); List<int> verifyLengthList = Utils.GetVerifyLengthList(Config.VerifyLength); List<string> tipFontFamilyList = Utils.GetTipFontFamilyList(Config.TipFontFamily); List<Color> tipFontColorList = Utils.GetTipFontColorList(Config.TipFontColor); var verifyLengthIndex = rnd.Next(0, verifyLengthList.Count); for (int i = 0; i < randWordList.Count; i++) { int sizeIndex = rnd.Next(0, fontSizeList.Count); int colorIndex = rnd.Next(0, fontColorList.Count); int familyIndex = rnd.Next(0, fontFamilyList.Count); int areaPointIndex = rnd.Next(0, areaPointList.Count); Font font = new Font(fontFamilyList[familyIndex], fontSizeList[sizeIndex], FontStyle.Bold, GraphicsUnit.Pixel); SizeF sizef = picture.MeasureString(randWordList[i], font); Point point = areaPointList[areaPointIndex]; areaPointList.RemoveAt(areaPointIndex); Rectangle rect = new Rectangle(point.X, point.Y, (int)sizef.Width, (int)sizef.Height); using (GraphicsPath path = GetStringPath(randWordList[i], rect, font, format)) { RectangleF off = rect; off.Offset(1, 1);//阴影偏移 using (GraphicsPath offPath = GetStringPath(randWordList[i], off, font, format)) { Brush b = new SolidBrush(Color.White); picture.FillPath(b, offPath); b.Dispose(); } SolidBrush semiTransBrush = new SolidBrush(fontColorList[colorIndex]); picture.DrawPath(Pens.WhiteSmoke, path);//绘制轮廓(描边) picture.FillPath(semiTransBrush, path);//填充轮廓(填充) } wordPosList.Add(new WordPos() { Word = randWordList[i], X = point.X, Y = point.Y, Width = sizef.Width, Height = sizef.Height }); } //输出需要验证的字 List<WordPos> verWordList = wordPosList.Take(verifyLengthList[verifyLengthIndex]).ToList(); for (int i = 0; i < verAreaPointList.Count && i < verWordList.Count; i++) { int colorIndex = rnd.Next(0, tipFontColorList.Count); int tipFamilyIndex = rnd.Next(0, tipFontFamilyList.Count); Font font = new Font(tipFontFamilyList[tipFamilyIndex], Config.TipFontSize, FontStyle.Bold, GraphicsUnit.Pixel); SizeF sizef = picture.MeasureString(randWordList[i], font); Point point = verAreaPointList[i]; Rectangle rect = new Rectangle(point.X, point.Y, (int)sizef.Width, (int)sizef.Height); var color = tipFontColorList[colorIndex]; using (GraphicsPath path = GetStringPath(randWordList[i], rect, font, format)) { SolidBrush semiTransBrush = new SolidBrush(color); if ((color.R + color.G + color.B) > 382) { picture.DrawPath(Pens.Black, path); } else { picture.DrawPath(Pens.White, path); } picture.FillPath(semiTransBrush, path); } } return verWordList; } private static GraphicsPath GetStringPath(string s, RectangleF rect, Font font, StringFormat format) { GraphicsPath path = new GraphicsPath(); path.AddString(s, font.FontFamily, (int)font.Style, font.Size, rect, format); return path; }
其中需要注意的是,汉字的水印位置,我的作法是将背景图片先划分成X个区域,然后画一个区域,将其排除掉。
第五步:生成图片
public void Create() { _httpContext.Response.CacheControl = "no-cache"; _httpContext.Session["VCode_ISValidate"] = false; List<string> chineseWordList = Utils.GetChineseWordList(); List<string> backGroundImageList = Utils.GetBackGroundImageList(_httpContext.Server.MapPath(Config.BackGroundImageDir)); var randWordList = Utils.GetRandChineseWordList(chineseWordList, Config.RandLength); Random rnd = new Random(unchecked((int)DateTime.Now.Ticks)); var imgIndex = rnd.Next(0, backGroundImageList.Count); string imagePath = backGroundImageList[imgIndex]; using (Image img = Image.FromFile(imagePath)) { using (Graphics g = Graphics.FromImage(img)) { using (Bitmap bmp = new Bitmap(imagePath)) { List<WordPos> verWordList = Utils.DrawWord(g, bmp, randWordList, rnd); _httpContext.Session["VCode_Word"] = verWordList; } MemoryStream ms = new MemoryStream(); img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); _httpContext.Response.ClearContent(); _httpContext.Response.ContentType = "image/Jpeg"; _httpContext.Response.BinaryWrite(ms.ToArray()); } } }
最后,验证环节
JS来获取到相应的位置坐标传到后端后,通过比较相应的两点距离在一定范围内则算通过。
public bool Validate(dynamic pos) { bool isValidate = false; _httpContext.Session["VCode_ISValidate"] = false; if (pos.Length > 0 && _httpContext.Session["VCode_Word"] != null) { bool isNotMath = false; List<WordPos> verWordList = (List<WordPos>)_httpContext.Session["VCode_Word"]; _httpContext.Session["VCode_ISValidate"] = true; _httpContext.Session["VCode_Word"] = null; if (pos.Length == verWordList.Count) { for (int i = 0; i < pos.Length; i++) { var _posLeft = Convert.ToInt32(pos[i]["left"]) + 20 / 2; var _posTop = Convert.ToInt32(pos[i]["top"]) + 23 / 2; Point _posCenter = new Point(_posLeft, _posTop); var _ckLeft = Convert.ToInt32(verWordList[i].X + verWordList[i].Width / 2); var _ckTop = Convert.ToInt32(verWordList[i].Y + verWordList[i].Height / 2); Point _ckPosCenter = new Point(_ckLeft, _ckTop); if (Utils.GetDistance(_posCenter, _ckPosCenter) > 23) { isNotMath = true; break; } } if (isNotMath == false) { isValidate = true; } } } return isValidate; }
当然,实际上QQ注册的需要验证字都是一个词组,要做到这一点可能只有做一个词库才能完成了。
标签:
原文地址:http://www.cnblogs.com/dengxi/p/5341466.html