行为式验证码(成语点选)(C#版和Java版)

一、先看效果图

二、背景介绍

图形验证码网上有挺多,比如:网易易盾、腾讯防水墙、阿里云验证码等等。参考了一下,自己实现了一个简单的成语点选的模式。

三、实现思路

1.选择若干张图片(这里使用的是320x160的尺寸),随机从中抽取一张作为背景图。

2.整理一个成语库,用作验证码里的字。

3.将选择的成语随机(位置随机,字体随机,颜色随机)绘制到背景图上,记录每个字的坐标范围,后面用于验证用户是否选择正确。

4.将成语及图片返回给前端。

5.前端点击后,将点击坐标点传回后端,后端进行验证。 

四、实现代码

C# ASP.NET MVC版

1.后端生成验证码图片

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;namespace RC.Controller
{public class ValidateHelper{#region 检测选中的位置是否为后台设置的文字位置(判断验证码输入是否有效)/// <summary>///     检测选中的位置是否为后台设置的文字位置(判断验证码输入是否有效)/// </summary>/// <param name="input"></param>/// <param name="range"></param>/// <returns></returns>public static bool Validate(string input, string range){if (input.Length != 24) return false;if (!new Regex("^\\d{24}$",RegexOptions.CultureInvariant| RegexOptions.Compiled).IsMatch(input))return false;var list = new List<int>();for (var i = 0; i < input.Length; i += 3)list.Add(i + 3 <= input.Length ? int.Parse(input.Substring(i, 3)) : int.Parse(input.Substring(i)));//输入的点坐标var inputPointDic = new Dictionary<string, string>();var index = 0;for (var i = 0; i < list.Count; i += 2){var x = list[i];var y = list[i + 1];inputPointDic.Add("P" + index, x + "," + y);index++;}//每个点的坐标范围var rangeDic = new Dictionary<string, string>(); //格式:Xmin-Xmax,Ymin-Ymax|...";var arr = range.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries);for (var i = 0; i < arr.Length; i++)rangeDic.Add("P" + i, arr[i]);var passed = 0;if (rangeDic.Count == inputPointDic.Count)//遍历判断每个点的坐标foreach (var pair in inputPointDic){var pos = pair.Value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);var score = rangeDic[pair.Key].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);if (pos.Length == 2 && score.Length == 2){//坐标点var x = int.Parse(pos[0]);var y = int.Parse(pos[1]);//坐标范围var xcore = score[0].Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries);var ycore = score[1].Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries);if (xcore.Length == 2 && x >= int.Parse(xcore[0]) && x < int.Parse(xcore[1]) &&ycore.Length == 2 &&y >= int.Parse(ycore[0]) && y < int.Parse(ycore[1]))passed++;}}return passed == inputPointDic.Count;}#endregion#region 随机获取成语验证码public static string GetWord(){var source ="心旷神怡|心平气和|十年寒窗|孙康映雪|协心戮力|埋头苦干|勤学苦练|冬寒抱冰|夏热握火|发奋图强|前功尽废|艰苦卓绝|坚苦卓绝|勤学苦练|同德一心|节俭力行|幼学壮行|急起直追|咬紧牙关|奋勇向前|志坚行苦|咬紧牙关|映雪读书|并心同力|分秒必争|形势逼人|身体力行|逆水行舟|学如登山|坐以待旦|来处不易|废寝忘食|朝夕不倦|发愤图强|躬体力行|不辞辛苦|学而不厌|百无一成|勉求多福|开足马力|听命由天|自强不息|咬紧牙根|穿壁引光|力争上游|得失在人|惊人之举|一篑之功|尽心竭力|磨穿铁砚|绝甘分少|手不释卷|刻苦耐劳|凿壁偷光|旗开得胜|一分为二|当仁不让|力争上游|望风响应|干劲冲天|奋发图强|争先恐后|四平八稳|坐而待毙|分秒必争|欢欣踊跃|坐以待亡|一马当先|自告奋勇|踊跃争先|马到功成|";source +="杯弓蛇影|鹤立鸡群|画蛇添足|生龙活虎|指鹿为马|雕虫小技|鸡毛蒜皮|千军万马|万马奔腾|虎口余生|狼虫虎豹|泥牛入海|成群打伙|狗急跳墙|气象万千|毒蛇猛兽|狐假虎威|马到成功|马到成功|鸡犬不宁|叶公好龙|藏龙卧虎|野性难驯|成帮结队|凤毛麟角|弱肉强食|瘦骨伶仃|予齿去角|伶牙利爪|杀鸡儆猴|抱头鼠窜|对牛弹琴|狡兔三窟|井底之蛙|龙飞凤舞|声名狼藉|车水马龙|虎头蛇尾|非池中物|狼吞虎咽|黔驴技穷|热地蚰蜒|一箭双雕|鹰击毛挚|好生之德|冷血动物|包罗万象|龟龙鳞凤|惊弓之鸟|盲人摸象|五毒俱全|一马当先|塞翁失马|调虎离窠|兜肚连肠|含沙射影|麟凤龟龙|万象更新|狼狈为奸|普渡众生|白驹过隙|打草惊蛇|跂行喙息|管中窥豹|守株待兔|鳄鱼眼泪|青梅竹马|骑虎难下|为鬼为蜮|为虎作伥|画龙点睛|鱼龙曼延|亡羊补牢|";var arr = source.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries);var random = new Random();var code = arr[random.Next(0, arr.Length)];return code;}#endregion#region 根据成语生成验证码图片(背景随机,颜色随机,位置随机,字体随机)/// <summary>///     根据成语生成验证码图片(背景随机,颜色随机,位置随机,字体随机)/// </summary>/// <param name="validCode"></param>/// <returns></returns>public static Dictionary<string, string> Create(string validCode){var o = new Dictionary<string, string>();var random = new Random();//第1步:随机取一张背景图var bg = GetMapPath("~/Content/images/validcode/" + random.Next(1, 16) + ".jpg");//字体颜色集合var colorArr = new List<Color>{HexToRGB("#5f4b50"),HexToRGB("#cf390f"),HexToRGB("#7b217a"),HexToRGB("#e3d457"),HexToRGB("#2a9557"),HexToRGB("#3a463a")};//字体集合 var fontArr = new List<Font>{new Font("幼圆", 32, FontStyle.Bold),new Font("隶书", 32),new Font("微软雅黑", 32, FontStyle.Bold),new Font("华文行楷", 32),new Font("华文楷体", 32),new Font("华文彩云", 32, FontStyle.Bold),new Font("楷体", 32, FontStyle.Bold)};var image = Image.FromFile(bg);image = AddWater(image);using (image){var width = image.Width;var height = image.Height;var sp = (width - 40) / 4;using (var bitmap = new Bitmap(image)){var graphics = Graphics.FromImage(bitmap);var arr = validCode.ToCharArray();var posArr = new List<PointF>();//计算出点坐标for (var i = 0; i < arr.Length; i++){var x = random.Next(i * sp + 20, (i + 1) * sp - 20);var y = random.Next(40, height - 60); //留点边距var point = new PointF(x, y);posArr.Add(point);}//将文字随机放到坐标点上var position = "";foreach (var c in arr){var font = fontArr[random.Next(fontArr.Count)];var size = graphics.MeasureString(c.ToString(), font);var j = random.Next(posArr.Count);var k = random.Next(colorArr.Count);var point = posArr[j];position += point.X + "-" + (int)(point.X + size.Width) + "," + point.Y + "-" +(int)(point.Y + size.Height) + "|"; //字点击范围//旋转角度var ret = random.Next(-70, 70);var matrix = graphics.Transform;matrix.RotateAt(ret, new PointF(point.X + size.Width / 2, point.Y + size.Height / 2));graphics.Transform = matrix;//写上文字graphics.DrawString(c.ToString(), font, new SolidBrush(colorArr[k]), point);//复原角度matrix = graphics.Transform;matrix.RotateAt(-ret, new PointF(point.X + size.Width / 2, point.Y + size.Height / 2));graphics.Transform = matrix;//移除已使用项,避免样式重复posArr.Remove(posArr[j]);colorArr.Remove(colorArr[k]);fontArr.Remove(font);}o.Add("ValidText", validCode);o.Add("ValidPos", position.TrimEnd('|'));o.Add("ValidImage", BitmapToBase64(bitmap));image.Dispose();//按流输出//using (var stream = new MemoryStream())//{//    bitmap.Save(stream, ImageFormat.Gif);//    Response.ClearContent();//    Response.ContentType = "image/Gif";//    Response.BinaryWrite(stream.ToArray());//}return o;}}}public static Image AddWater(Image source){var txt = "清山博客";var font = new Font("楷体", 16, FontStyle.Bold, GraphicsUnit.Pixel);var color = Color.FromArgb(180, 255, 255, 255);var brush = new SolidBrush(color);using (var graphics = Graphics.FromImage(source)){var size = graphics.MeasureString(txt, font);var x = source.Width - (int)size.Width;var y = source.Height - (int)size.Height;var point = new Point(x, y);var format = new StringFormat();graphics.DrawString(txt, font, brush, point, format);using (var stream = new MemoryStream()){source.Save(stream, ImageFormat.Jpeg);source = Image.FromStream(stream);}}return source;}#endregion#region 辅助方法protected static string GetMapPath(string strPath){if (strPath.ToLower().StartsWith("http://")) return strPath;if (HttpContext.Current != null) return HttpContext.Current.Server.MapPath(strPath);strPath = strPath.Replace("/", "\\");if (strPath.StartsWith("\\"))strPath = strPath.TrimStart('\\');else if (strPath.StartsWith("~")) strPath = strPath.Substring(1).TrimStart('\\');return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, strPath);}protected static string BitmapToBase64(Bitmap bmp){try{byte[] arr;using (var ms = new MemoryStream()){bmp.Save(ms, ImageFormat.Jpeg);arr = new byte[ms.Length];ms.Position = 0;var read = ms.Read(arr, 0, (int)ms.Length);ms.Close();}return Convert.ToBase64String(arr);}catch (Exception){return null;}}protected static Color HexToRGB(string strHxColor){try{if (strHxColor.Length == 0)return Color.FromArgb(0, 0, 0); //设为黑色return Color.FromArgb(int.Parse(strHxColor.Substring(1, 2), NumberStyles.AllowHexSpecifier),int.Parse(strHxColor.Substring(3, 2), NumberStyles.AllowHexSpecifier),int.Parse(strHxColor.Substring(5, 2), NumberStyles.AllowHexSpecifier));}catch{return Color.FromArgb(0, 0, 0);}}#endregion}
}

2.调用层:Controller

using System.Web.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RC.Framework;namespace RC.Website.Controller.Front
{public class DemoController : System.Web.Mvc.Controller{public ActionResult Validcode(){return View("~/views/Demo/Validcode.cshtml");}public ActionResult GetValidcode(){var code = ValidateHelper.GetWord();var dic = ValidateHelper.Create(code);Session["ValidText"] = dic["ValidText"];Session["ValidImage"] = dic["ValidImage"];Session["ValidPos"] = dic["ValidPos"]; //坐标位置,用于校验var res = new JObject{["ValidText"] = dic["ValidText"],["ValidImage"] = dic["ValidImage"]//["ValidPos"] = dic["ValidPos"].ToStr()};return Content(res.ToString(Formatting.None));}public ActionResult ValidcodeForm(){var code = Request.Params["code"];var pos = Session["ValidPos"].ToString();JObject res;if (code.IsEmpty()){res = new JObject{["IsSuccess"] = false,["Body"] = "抱歉,请输入验证码"};return Content(res.ToString(Formatting.None));}var check = ValidateHelper.Validate(code, pos);res = new JObject{["IsSuccess"] = check,["Body"] = check ? "验证码校验通过" : "抱歉,验证码输入不正确"};return Content(res.ToString(Formatting.None));}}
}

3.视图:Validcode.cshtml


@model dynamic
@{Layout = null;
}
<html>
<head runat="server"><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1" /><script src="~/Content/jquery-easyui-1.9.9/jquery.min.js"></script><title>验证码测试</title><style type="text/css">body { padding: 0; margin: 0; font-size: 12px; }.head { width: 90%; margin: 10px auto; border: 0px solid gainsboro; padding: 10px; background-color: white; font-size: 18px; font-weight: bold; color: brown; border-bottom: 2px solid brown }.content { width: 90%; margin: 10px auto; border: 0px solid gainsboro; padding: 10px; background-color: white; }.footer { width: 90%; margin: 10px auto; border: 0px solid gainsboro; padding: 10px; background-color: #F7F7F7; text-align: center; color: gray; }ul { padding: 0 10px; }li { list-style: none; line-height: 22px; }th { border: gainsboro solid 1px; height: 25px; text-align: left; padding: 3px 5px; }td { border: gainsboro solid 1px; height: 25px; padding-left: 10px; }tr:hover td { background: none; }table { border: gainsboro solid 1px; border-collapse: collapse; width: 100%; margin-bottom: 15px; }a { text-decoration: none; }a:link { color: orangered; }a:visited { color: orangered; }a:active { color: orangered; }a:hover { color: orangered; }fieldset { border: 1px solid #cccccc; margin-bottom: 10px; padding: 10px; }.keyword { color: orangered; }.btnRefush { outline: none; }input { width: 250px; height: 32px; margin: 5px 0; border: 1px solid #ddd; }.btn { display: inline-block; width: 120px; height: 30px; line-height: 30px; margin: 5px 0; text-align: center; border: 1px solid #ddd; cursor: pointer; color: #fff; background: #af1818; }#CaptchaTwo .valid_contain { margin: 5px auto; }/*PC端*/@@media screen and (min-width:1200px) {.todo { display: flex; justify-content: flex-start; flex-wrap: wrap; align-content: flex-start; width: 100%; padding: 0; margin: 0 }.tag { border: 1px solid #ccc; width: 160px; margin-bottom: 15px; text-align: center; border-radius: 5px; padding: 20px 10px; margin-right: 15px }.tag .num { border: 5px solid #1378bd; display: block; border-radius: 50%; height: 50px; width: 50px; margin: 0 auto; line-height: 50px; font-size: 22px; font-weight: bold; margin-bottom: 10px }.tag .title { color: gray; font-size: 12px; }.tag .orange { border: 5px solid orange; }.btnRefush { width: 120px; height: 40px; border: 0; background: #1378bd; color: white; border-radius: 5px; display: block; margin: 40px auto; }}/*Pad端*/@@media screen and (min-width:800px) and(max-width:1200px) {.todo { display: flex; justify-content: flex-start; flex-wrap: wrap; align-content: flex-start; width: 100%; padding: 0; margin: 0 }.tag { border: 1px solid #ccc; width: 160px; margin-bottom: 15px; text-align: center; border-radius: 5px; padding: 20px 10px; margin-right: 15px }.tag .num { border: 5px solid #1378bd; display: block; border-radius: 50%; height: 50px; width: 50px; margin: 0 auto; line-height: 50px; font-size: 22px; font-weight: bold; margin-bottom: 10px }.tag .title { color: gray; font-size: 12px; }.tag .orange { border: 5px solid orange; }.btnRefush { width: 120px; height: 40px; border: 0; background: #1378bd; color: white; border-radius: 5px; display: block; margin: 40px auto; }}/*手机端*/@@media screen and (max-width:800px) {.head { font-size: 16px; }body { padding: 0; margin: 0; font-size: 14px; }.todo { display: flex; justify-content: space-evenly; flex-wrap: wrap; align-content: flex-start; width: 100%; padding: 0; margin: 0 }.tag { border: 1px solid #ccc; width: 40%; margin-bottom: 3.5%; text-align: center; border-radius: 5px; padding: 20px 10px; }.tag .num { border: 5px solid #1378bd; display: block; border-radius: 50%; height: 50px; width: 50px; margin: 0 auto; line-height: 50px; font-size: 22px; font-weight: bold; margin-bottom: 10px }.tag .title { color: gray; font-size: 14px; }.tag .orange { border: 5px solid orange; }.btnRefush { width: 40%; height: 40px; border: 0; background: #1378bd; color: white; border-radius: 5px; display: block; margin: 40px auto; }}</style><script src="/Content/captcha/captcha.js" type="text/javascript"></script><link href="/Content/captcha/captcha.css" rel="stylesheet" type="text/css" />
</head>
<body><div class="head">验证码测试(成语点选)</div><div class="content"><div class="todo"><table class="main-table scene1" style="width: 100%"><tr><th colspan="2">场景1:选择验证码后,验证码值写入表单隐藏域,点击按钮提交表单</th></tr><tr><td style="width: 40%; text-align: right; padding: 0 10px;">手机号码</td><td><input name="txtPhone" type="text" maxlength="11" class="txt_input" placeholder="请输入您的手机号码"></td></tr><tr><td style="width: 40%; text-align: right; padding: 0 10px;">密码</td><td><input name="txtPassword" type="password" maxlength="11" class="txt_input" placeholder="请输入您的密码"></td></tr><tr><td style="width: 40%; text-align: right; padding: 0 10px;">验证码</td><td><div id="CaptchaOne"></div><input name="txtCode" id="txtCode" type="hidden" /></td></tr><tr><td colspan="2" style="text-align: center"><div class="btn validBtn1">提交表单</div></td></tr></table><table class="main-table scene2" style="width: 100%"><tr><th colspan="2">场景2:点击提交按钮,显示验证码,选择验证码后,提交表单</th></tr><tr><td style="width: 40%; text-align: right; padding: 0 10px;">手机号码</td><td><input name="txtPhone" type="text" maxlength="11" class="txt_input" placeholder="请输入您的手机号码"></td></tr><tr><td style="width: 40%; text-align: right; padding: 0 10px;">密码</td><td><input name="txtPassword" type="password" maxlength="11" class="txt_input" placeholder="请输入您的密码"></td></tr><tr class="box2"><td colspan="2" style="text-align: center"><div id="CaptchaTwo" style="position: relative"><div class="btn validBtn2">提交表单</div><!-- <div class="valid_contain"><div class="valid_panel"><div class="valid_bgimg"><img class="valid_bg-img" /></div><div class="valid_loadbox"><div class="valid_loadbox__inner"><div class="valid_loadicon"></div><span class="valid_loadtext">加载中...</span></div></div><div class="valid_top"><button class="valid_refresh">刷新</button></div></div><div class="valid_control"><div class="valid_tips"><span class="valid_tips__icon"></span><span class="valid_tips__text">请依次点击图中成语</span></div></div></div> --></div></td></tr></table><table class="main-table scene3" style="width: 100%"><tr><th colspan="2">场景3:点击提交按钮,弹窗显示验证码,选择验证码后,提交表单</th></tr><tr><td style="width: 40%; text-align: right; padding: 0 10px;">手机号码</td><td><input name="txtPhone" type="text" maxlength="11" class="txt_input" placeholder="请输入您的手机号码"></td></tr><tr><td style="width: 40%; text-align: right; padding: 0 10px;">密码</td><td><input name="txtPassword" type="password" maxlength="11" class="txt_input" placeholder="请输入您的密码"></td></tr><tr class="box2"><td colspan="2" style="text-align: center"><div class="btn validBtn3" id="CaptchaThree">提交表单</div></td></tr></table></div></div><script type="text/javascript">// 场景1var captcha1 = null;$('#CaptchaOne').clickCaptcha({reset: true,imgUrl: '@Url.Action("GetValidcode", "Demo")', // 验证图片生成的请求地址onComplete: function(code, captcha) {console.log('点击完成后的验证码', code, captcha)// captcha.initImg(); // 调用该方法,刷新图片$(".scene1 #txtCode").val(code);captcha1 = captcha;}});$(".scene1 .validBtn1").click(function() {var param = {}param.phone = $(".scene1 [name='txtPhone']").val();param.password = $(".scene1 [name='txtPassword']").val();param.code = $(".scene1 [name='txtCode']").val();if(!param.code) {alert('请点击完成验证');return;}$.ajax({url: '@Url.Action("ValidcodeForm", "Demo")',type: 'POST',dataType: 'json',data: param,success: function(res) {if(res.IsSuccess) {alert(res.Body)} else {alert(res.Body)captcha1.initImg();}}});});// 场景2$('.scene2 .validBtn2').click(function() {var param = {}param.phone = $(".scene2 [name='txtPhone']").val();param.password = $(".scene2 [name='txtPassword']").val();$('#CaptchaTwo').clickCaptcha({mode: 'default', // 弹出方式 default-会替换页面指定区域内容;pop-弹窗imgUrl: '@Url.Action("GetValidcode", "Demo")', // 验证图片生成的请求地址/** 以下是联合提交的配置参数* 配置submitUrl后,会将验证码以及其他参数一同提交到后台* 注意:配置该参数后,onComplete回调会被忽略*/validFiled: 'code', // 验证码提交到后台的字段,默认为codesubmitUrl: '@Url.Action("ValidcodeForm", "Demo")',  // 提交数据地址submitData: param, // 表单的其他数据,例如:账号、密码,会和验证码一同提交onSubmit: function (res, captcha) {console.log('提交成功', res, captcha)  // res为后台返回给前台的完整数据if(res.IsSuccess) {alert(res.Body)} else {alert(res.Body)captcha.initImg(); // 调用该方法,刷新图片}}});});// 场景3$('.scene3 .validBtn3').click(function() {var param = {}param.phone = $(".scene3 [name='txtPhone']").val();param.password = $(".scene3 [name='txtPassword']").val();$('#CaptchaThree').clickCaptcha({mode: 'pop', // 弹出方式 default-会替换页面指定区域内容;pop-弹窗imgUrl: '@Url.Action("GetValidcode", "Demo")', // 验证图片生成的请求地址/** 以下是联合提交的配置参数* 配置submitUrl后,会将验证码以及其他参数一同提交到后台* 注意:配置该参数后,onComplete回调会被忽略*/validFiled: 'code', // 验证码提交到后台的字段,默认为codesubmitUrl: '@Url.Action("ValidcodeForm", "Demo")',  // 提交数据地址submitData: param, // 表单的其他数据,例如:账号、密码,会和验证码一同提交onSubmit: function (res, captcha) {console.log('提交成功', res, captcha)  // res为后台返回给前台的完整数据if(res.IsSuccess) {alert(res.Body)// captcha.close(); // 调用该方法关闭弹窗,仅在mode: pop时有效} else {alert(res.Body)// captcha.initImg(); // 调用该方法,刷新图片}}});});</script>
</body>
</html>

4.文件:captcha.css

:root {--Bg-Img: url('data:image/png;base64,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');
}.valid_panel .valid_loadbox .valid_loadbox__inner,
.valid_panel .valid_loadbox .valid_loadbox__inner .valid_loadicon,
.valid_panel .valid_tips__content,
.valid_contain.valid--success .valid_tips .valid_tips__icon,
.valid_contain.valid--error .valid_tips .valid_tips__icon {display: inline-block;*display: inline;zoom: 1;vertical-align: top;
}
@keyframes loading {0% {transform: rotate(0deg);}to {transform: rotate(1turn);}
}
.valid_contain {width: 320px;position: relative;
}
.valid_panel {width: 320px;height: 160px;overflow: hidden;position: relative;
}
.valid_panel .valid_icon-point {position: absolute;width: 26px;height: 33px;cursor: pointer;background-repeat: no-repeat;
}
.valid_panel .valid_icon-point.valid_point-1 {background-image: var(--Bg-Img);background-position: 0 -997px;background-size: 40px 1518px;
}
.valid_panel .valid_icon-point.valid_point-2 {background-image: var(--Bg-Img);background-position: 0 -1111px;background-size: 40px 1518px;
}
.valid_panel .valid_icon-point.valid_point-3 {background-image: var(--Bg-Img);background-position: 0 -1035px;background-size: 40px 1518px;
}
.valid_panel .valid_icon-point.valid_point-4 {background-image: var(--Bg-Img);background-position: 0 -1073px;background-size: 40px 1518px;
}
.valid_panel .valid_icon-point.valid_point-5 {background-image: var(--Bg-Img);background-position: 0 -1149px;background-size: 40px 1518px;
}.valid_panel .valid_top {position: absolute;right: 0;top: 0;max-width: 98px;*max-width: 68px;z-index: 2;background-color: rgba(0, 0, 0, 0.12);*background-color: transparent;_background-color: transparent;
}
.valid_panel .valid_top:hover {background-color: rgba(0, 0, 0, 0.2);*background-color: transparent;_background-color: transparent;
}
.valid_panel .valid_refresh {width: 30px;height: 30px;margin-left: 4px;cursor: pointer;font-size: 0;vertical-align: top;text-indent: -9999px;text-transform: capitalize;border: none;background-color: transparent;
}
.valid_panel .valid_refresh {float: left;background-image: var(--Bg-Img);/* background: var(--Bg-Img); */background-position: 0 -750px;background-size: 40px 1518px;
}
.valid_panel .valid_refresh:hover {background-image: var(--Bg-Img);background-position: 0 -785px;background-size: 40px 1518px;
}   
.valid_panel .valid_loadbox {display: none;position: absolute;top: 0;left: 0;width: 100%;height: 100%;text-align: center;background-color: #f7f9fa;        border-radius: 2px;
}
.valid_panel .valid_loadbox .valid_loadbox__inner {position: relative;top: 50%;margin-top: -25px;
}
.valid_panel .valid_loadbox .valid_loadbox__inner .valid_loadicon {width: 32px;height: 32px;background-repeat: no-repeat;
}
.valid_panel .valid_loadbox .valid_loadbox__inner .valid_loadtext {display: block;line-height: 20px;color: #45494c;font-size: 12px;
}
.valid_panel.valid--loading .valid_loadicon {background-image: var(--Bg-Img);background-position: 0 -960px;background-size: 40px 1518px;animation: loading 0.8s linear infinite;
}
.valid_panel.valid--loading .valid_refresh {cursor: not-allowed;
}
.valid_panel.valid--loadfail .valid_loadicon {background-image: var(--Bg-Img);background-position: 0 -890px;background-size: 40px 1518px;
}.valid_panel.valid--loadfail .valid_bgimg,
.valid_panel.valid--loading .valid_bgimg {display: none;
}
.valid_panel.valid--loadfail .valid_loadbox,
.valid_panel.valid--loading .valid_loadbox {display: block;
}
.valid_contain .valid_control {position: relative;width: 100%;height: 40px;margin-top: 10px;box-sizing: border-box;border: 1px solid #e4e7eb;background-color: #f7f9fa;
}
.valid_control .valid_tips {font-size: 14px;line-height: 40px;text-align: center;
}
.valid_control .valid_tips .valid_tips__text b {letter-spacing: 3px;font-weight: bold;
}
.valid_contain.valid--success .valid_control {border-color: #52ccba;background-color: #d2f4ef;
}
.valid_contain.valid--success .valid_tips {color: #52ccba;
}
.valid_contain.valid--success .valid_tips .valid_tips__icon {margin-right: 5px;width: 17px;height: 12px;vertical-align: middle;background-image: var(--Bg-Img);background-position: 0 -111px;background-size: 40px 1518px;
}
.valid_contain.valid--error .valid_tips {color: #f57a7a;
}
.valid_contain.valid--error .valid_control {border-color: #f57a7a;background-color: #fce1e1;
}
.valid_contain.valid--error .valid_tips .valid_tips__icon {margin-right: 5px;width: 12px;height: 12px;vertical-align: middle;background-image: var(--Bg-Img);background-position: 0 -77px;background-size: 40px 1518px;
}/* 弹出模式 */
.valid_popup {position: fixed; top: 0;left: 0;width: 100%;height: 100%;z-index: 9999;text-align: center;
}
.valid_popup .valid_popup__mask {touch-action: none;position: absolute;top: 0;left: 0;width: 100%;height: 100%;background-color: #000;transition: opacity .3s linear;will-change: opacity;opacity: 0.3;
}
.valid_popup .valid_modal {position: relative;top: 50%;margin: 0 auto;transform: translate(0, -50%);width: 350px;box-sizing: border-box;border-radius: 2px;border: 1px solid #e4e7eb;background-color: #fff;box-shadow: 0 0 10px rgba(0,0,0,.3);touch-action: none;
}
.valid_popup .valid_modal__header {padding: 0 15px;height: 50px;text-align: left;font-size: 0;color: #45494c;border-bottom: 1px solid #e4e7eb;white-space: nowrap;position: relative;
}
.valid_popup .valid_modal__title {font-size: 16px;line-height: 50px;vertical-align: middle;white-space: normal;
}
.valid_popup .valid_modal__close {position: absolute;top: 0;right: 0;width: 40px;height: 100%;text-align: center;border: none;background: transparent;padding: 0;cursor: pointer;
}
.valid_popup .valid_modal__close .valid_icon-close {display: inline-block;width: 11px;height: 11px;font-size: 0;text-indent: -9999px;text-transform: capitalize;margin: auto;vertical-align: middle;background-image: var(--Bg-Img);background-position: 0 -61px;background-size: 40px 1518px;
}
.valid_popup .valid_modal__close:hover .valid_icon-close {background-image: var(--Bg-Img);background-position: 0 -45px;background-size: 40px 1518px;
}
.valid_popup .valid_modal__body {padding: 15px;
}

5.文件:captcha.js

(function ($) {'use strict';var clickCaptcha = function (element, options) {this.$element = $(element);this.curIndex = 0;this.clickPoint = [];this.options = $.extend({}, clickCaptcha.DEFAULTS, options);this.initDOM();};clickCaptcha.VERSION = '1.0';clickCaptcha.DEFAULTS = {width: 320,height: 160,  mode: 'default',  // 渲染方式:default-嵌入式,pop-弹出 maxClick: 4, // 最多点击次数      loadingText: '加载中...',failedText: '加载失败',imgUrl: null, // 远程图片获取地址onComplete: null, // 点选完成的回调事件submitUrl: null,  // 提交地址submitData: {},  // 其他表单数据onSubmit: null, // 表单提交回调事件(submitUrl)onRefresh: null, // 刷新回调事件};function Plugin(option) {return this.each(function () {                    var $this = $(this);var data = $this.data('lgb.clickCaptcha');var options = typeof option === 'object' && option;if (data && !/reset/.test(option)) {// 弹窗模式下,关闭后再点开,需重新加载图片  data.initImg();               if(data.options.mode == 'pop') {                   $("body").find(".valid_popup").show();}                return;}if (!data) {$this.data('lgb.clickCaptcha', data = new clickCaptcha(this, options));} if (typeof option === 'string') {data[option]();} });}$.fn.clickCaptcha = Plugin;$.fn.clickCaptcha.Constructor = clickCaptcha;var _proto = clickCaptcha.prototype;_proto.initDOM = function () {       var el = this.$element;var domContainer = `<div class="valid_contain"><div class="valid_panel"><div class="valid_bgimg">                              <img class="valid_bg-img" /></div><div class="valid_loadbox"><div class="valid_loadbox__inner"><div class="valid_loadicon"></div><span class="valid_loadtext">加载中...</span></div></div><div class="valid_top">                                <button class="valid_refresh">刷新</button></div>                            </div><div class="valid_control"><div class="valid_tips"><span class="valid_tips__icon"></span><span class="valid_tips__text">请依次点击图中成语</span>                                    </div></div></div>`if(this.options.mode == 'pop') {var modelContainer = `<div class="valid_popup"><div class="valid_popup__mask"></div><div class="valid_modal"><div class="valid_modal__header"><span class="valid_modal__title">请完成安全验证</span><button type="button" class="valid_modal__close"><span class="valid_icon-close">关闭</span></button></div><div class="valid_modal__body">${domContainer}</div></div></div> `if($("body .valid_popup").length == 0) {$("body").append($(modelContainer));}                    $("body").find(".valid_popup").show();} else if(this.options.mode == 'hover') {el.append($(domContainer));el.find(".valid_contain").css({"position": "absolute","left": 0,"right": 0,"bottom": 0,"z-index": 99,"margin": "0 auto"})} else { el.html($(domContainer));}                this.initImg();this.bindEvents();};_proto.initImg = function() {var that = this;that.reset();var parentDom = that.options.mode == 'pop' ? $("body .valid_popup") : that.$element;parentDom.find(".valid_loadtext").text(that.options.loadingText);parentDom.find(".valid_tips__text").html(that.options.loadingText);parentDom.find(".valid_panel").addClass('valid--loading').removeClass("valid--loadfail");                $.ajax({url: that.options.imgUrl,type: 'GET',dataType: 'json',success: function(res) {parentDom.find(".valid_panel").removeClass('valid--loading');parentDom.find(".valid_bgimg").children(".valid_icon-point").remove();parentDom.find(".valid_bgimg").children(".valid_bg-img").attr("src", 'data:image/jpg;base64,'+res.ValidImage);parentDom.find(".valid_tips__text").html('请依次点击:<b>'+res.ValidText+'</b>');},error: function() {parentDom.find(".valid_panel").removeClass('valid--loading').addClass('valid--loadfail');parentDom.find(".valid_loadtext").text(that.options.failedText);}})};_proto.bindEvents = function () {var that = this;var parentDom = that.options.mode == 'pop' ? $("body .valid_popup") : that.$element;// 图片点击事件parentDom.find(".valid_bgimg").click(function(event) {if(that.curIndex >= 4) {return;}  that.curIndex++;          var html = '<div class="valid_icon-point valid_point-'+that.curIndex+'" style="left: '+(event.offsetX-13)+'px; top: '+(event.offsetY-23)+'px;"></div>';$(this).append(html);that.clickPoint.push([event.offsetX, event.offsetY]);// 成语点击完成if(that.curIndex == that.options.maxClick) {that.verify(dealMapArr(that.clickPoint))}});// 刷新事件parentDom.find(".valid_refresh").click(function() {that.initImg();if ($.isFunction(that.options.onRefresh)) {that.options.onRefresh.call(that.$element);} }); // hover事件if(that.options.mode == 'hover') {parentDom.hover(function() {parentDom.find(".valid_contain").show();}, function() {parentDom.find(".valid_contain").hide();});}  // 弹窗关闭事件if(that.options.mode == 'pop') {parentDom.find(".valid_modal__close").click(function() {$("body").find(".valid_popup").hide();});}        };_proto.verify = function (code) {var that = this;if(!code || code.length != 24) {console.error('生成的校验码不合法')return;}var parentDom = that.options.mode == 'pop' ? $("body .valid_popup") : that.$element;if(that.options.submitUrl) {var param = that.options.submitData;var filed = that.options.validFiled || 'code';param[filed] = code;parentDom.find(".valid_tips__text").html('验证中,请稍后...');$.ajax({url: that.options.submitUrl,type: 'POST',dataType: 'json',data: param,success: function(res) {                           if(res.IsSuccess) {parentDom.find(".valid_tips__text").html('验证成功');parentDom.find(".valid_contain").addClass("valid--success");} else {parentDom.find(".valid_tips__text").html('验证失败,请重试');parentDom.find(".valid_contain").addClass("valid--error");}if ($.isFunction(that.options.onSubmit)) {that.options.onSubmit.call(that.$element, res, that);}}});} else {if ($.isFunction(that.options.onComplete)) {that.options.onComplete.call(that.$element, code, that);}}              };_proto.reset = function() {var that = this;that.curIndex = 0;that.clickPoint = [];var parentDom = that.options.mode == 'pop' ? $("body .valid_popup") : that.$element;parentDom.find(".valid_contain").attr("class", "valid_contain");}_proto.close = function() {var that = this;if(that.options.mode == 'pop') {$("body").find(".valid_popup").hide();}}function dealMapArr(point) {var res = '';for(var i=0; i<point.length; i++) {res += coverNum(point[i][0], 3)res += coverNum(point[i][1], 3)}function coverNum(num, len) {num = num.toFixed(0);                if(num.length > len) {console.error('点击坐标值超过了处理长度')return num;}var index = num.length;while (index<len) {index++;num = '0'+num;}return num;}return res;}
})(jQuery);

Java版

1.后端生成验证码图片

package com.cdrc.service;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class ValidateHelper {public static boolean Validate(String input, String range) {if (input.length() != 24)return false;String pattern = "^\\d{24}$";if (!Pattern.matches(pattern, input))return false;ArrayList list = new ArrayList();for (int i = 0; i < input.length(); i += 3) {String tem = input.substring(i, i + 3);list.add(Integer.parseInt(tem));}// 输入的点坐标Hashtable inputPointDic = new Hashtable<String, String>();int index = 0;for (int i = 0; i < list.size(); i += 2) {int x = (int)list.get(i);int y = (int)list.get(i + 1);inputPointDic.put("P" + index, x + "," + y);index++;}// 每个点的坐标范围Hashtable rangeDic = new Hashtable<String, String>(); // 格式:Xmin-Xmax,Ymin-Ymax|...";String[] arr = range.split("\\|");for (int i = 0; i < arr.length; i++)rangeDic.put("P" + i, arr[i]);int passed = 0;if (rangeDic.size() == inputPointDic.size())for (Iterator iterator = inputPointDic.keySet().iterator(); iterator.hasNext(); ) {String key = (String) iterator.next();String value = (String)inputPointDic.get(key);String[] pos = value.split(",");String[] score =((String)rangeDic.get(key)).split(",");if (pos.length == 2 && score.length == 2) {// //坐标点int x = Integer.parseInt(pos[0]);int y = Integer.parseInt(pos[1]);// 坐标范围String[] xcore = score[0].split("-");String[] ycore = score[1].split("-");if (xcore.length == 2 && x >= Integer.parseInt(xcore[0]) && x < Integer.parseInt(xcore[1]) &&ycore.length == 2 && y >= Integer.parseInt(ycore[0]) && y < Integer.parseInt(ycore[1]))passed++;}}return passed == inputPointDic.size();}public static String GetWord() {String source = "奋发图强|持之以恒|坚持不懈|锲而不舍|力争上游|勇往直前|斗志昂扬|壮志凌云|坚定不移|自强不息|朝气蓬勃|发奋图强|百折不挠|大智大勇|奋不顾身|铁杵成针|标新立异|继往开来|独树一帜|勤学苦练|不屈不挠|悬梁刺股|闻鸡起舞|卧薪尝胆|改天换地|革故鼎新|发愤忘食|只争朝夕|一日千里|百尺竿头|推陈出新|别具匠心|别具一格|画龙点睛|鱼龙曼延|亡羊补牢|车水马龙|自强不息|咬紧牙根|马到成功|千军万马|万马奔腾|雕虫小技|心旷神怡|心平气和|十年寒窗|孙康映雪|同德一心|节俭力行|幼学壮行|急起直追|朋心合力|孜孜不辍|乐事劝功|志坚行苦|临池学书|奋身独步|坐以待旦|跛行千里|废寝忘食|折节读书|朝夕不倦|务农息民|久坐地厚|坐薪悬胆|躬体力行|学而不厌|心慕力追|";String[] arr = source.split("\\|");String code = arr[Rand(0, arr.length)];return code;}public static Hashtable<String, String> Create(String validCode) {Hashtable o = new Hashtable<String, String>();try {// 第1步:随机取一张背景图String path = Thread.currentThread().getContextClassLoader().getResource("").getPath().split("/bin")[0];String bg =path + ("/WebContent/js/captcha/images/" + Rand(1, 15) + ".jpg");BufferedImage image = ImageIO.read(new File(bg));// 字体颜色集合ArrayList colorArr = new ArrayList();colorArr.add(HexToRGB("#5f4b50"));colorArr.add(HexToRGB("#cf390f"));colorArr.add(HexToRGB("#7b217a"));colorArr.add(HexToRGB("#e3d457"));colorArr.add(HexToRGB("#2a9557"));colorArr.add(HexToRGB("#3a463a"));// 字体集合ArrayList fontArr = new ArrayList();fontArr.add(new Font("幼圆", Font.BOLD, 36));fontArr.add(new Font("隶书", Font.BOLD, 36));fontArr.add(new Font("微软雅黑", Font.BOLD, 36));fontArr.add(new Font("华文行楷", Font.BOLD, 36));fontArr.add(new Font("华文楷体", Font.BOLD, 36));fontArr.add(new Font("华文彩云", Font.BOLD, 36));fontArr.add(new Font("楷体", Font.BOLD, 36));// 获取画笔Graphics2D graphics = (Graphics2D) image.getGraphics();int width = image.getWidth();int height = image.getHeight();int sp = (width - 40) / 4;ArrayList posArr = new ArrayList<PointF>();// 计算出点坐标for (int i = 0; i < validCode.length(); i++) {int x = Rand(i * sp + 20, (i + 1) * sp - 20);int y = Rand(30, height - 40); // 留点边距PointF point = new ValidateHelper().new PointF(x, y);posArr.add(point);}// 绘制文字String position = "";for (int i = 0; i < validCode.length(); i++) {String c = String.valueOf(validCode.charAt(i));PointF point = (PointF) posArr.get(Rand(0, posArr.size() - 1));Font font = (Font) fontArr.get(Rand(0, fontArr.size() - 1));Color color = (Color) colorArr.get(Rand(0, colorArr.size() - 1));graphics.setFont(font);graphics.setColor(color);FontMetrics metrics = graphics.getFontMetrics();int w = metrics.stringWidth(c);int h = metrics.getHeight();// 旋转角度int degrees = Rand(-70, 70);AffineTransform transform = new AffineTransform();transform.rotate(Math.toRadians(degrees), ((int) point.X + (int) (w / 2)),((int) point.Y + (int) (h / 2)));graphics.setTransform(transform);// 写上文字graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿// 实际写入文字的高度与坐标的高度不一样,需转换// 参考:https://blog.csdn.net/qq_21567385/article/details/106078715int standY = point.Y + metrics.getAscent()- (metrics.getAscent() + metrics.getDescent() - font.getSize()) / 2;graphics.drawString(c, point.X, standY);// 实际写入文字的高度与坐标的高度不一样,需转换// System.out.println(i + " " + c + " x=" + point.X + " y=" + point.Y); //position += point.X + "-" + (int) (point.X + w) + "," + point.Y + "-" + (int) (point.Y + h) + "|"; // 字点击范围// 复原角度AffineTransform transform2 = new AffineTransform();transform.rotate(-Math.toRadians(degrees), ((int) point.X + (int) (w / 2)),((int) point.Y + (int) (h / 2)));graphics.setTransform(transform2);// 移除已使用项,避免样式重复colorArr.remove(color);fontArr.remove(font);posArr.remove(point);}// 将图片转换为base64String bitmap = "";ByteArrayOutputStream baos = new ByteArrayOutputStream();try {ImageIO.write(image, "jpg", baos);byte[] bytes = baos.toByteArray();bitmap = Base64.getEncoder().encodeToString(bytes);} catch (Exception e) {e.printStackTrace();} finally {try {if (baos != null) {baos.close();}} catch (IOException e) {e.printStackTrace();}}o.put("ValidText", validCode);o.put("ValidPos", TrimEnd(position, "|"));o.put("ValidImage", bitmap);} catch (Exception ex) {o.put("ValidText", "Error");o.put("ValidPos", "");o.put("ValidImage", ex.getMessage());}return o;}static Random random = new Random();public static int Rand(int min, int max) {int num = random.nextInt(max - min + 1) + min;return num;}public static String TrimEnd(String inStr, String suffix) {while (inStr.endsWith(suffix)) {inStr = inStr.substring(0, inStr.length() - suffix.length());}return inStr;}public static Color HexToRGB(String str) {str = str.toLowerCase();final Matcher mx = Pattern.compile("^#([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})$").matcher(str);if (!mx.find())throw new IllegalArgumentException("invalid color value");final int R = Integer.parseInt(mx.group(1), 16);final int G = Integer.parseInt(mx.group(2), 16);final int B = Integer.parseInt(mx.group(3), 16);Color color = new Color(R, G, B);return color;}public class PointF {public int X;public int Y;public PointF(int x, int y) {X = x;Y = y;}}
}

 2.调用层 :Controller

package com.cdrc.controller;import com.cdrc.service.IService;
import com.cdrc.service.ValidateHelper;
import com.liuw.common.CustomCommon;
import com.liuw.web.UrlEx;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Hashtable;@Controller
// 所有响应请求方法的父路径
@RequestMapping(value = {"/test/"})
public class TestController extends BaseController {@Overridepublic IService getIService(String servletPath) {return null;}@RequestMapping(value = {"/validate/demo"})public ModelAndView validatedemo(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) String bodyString) {return super.doGetModel(request, response, bodyString);}@RequestMapping(value = {"/validate/ajax"})public ModelAndView validateajax(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) String bodyString) {try {String urlParams = null != bodyString ? bodyString : request.getQueryString();String action = UrlEx.getQuery(urlParams, "action");String result = "";JSONObject o = new JSONObject();switch (action) {case "getimg":String word = ValidateHelper.GetWord();Hashtable tb = ValidateHelper.Create(word);o.put("IsSuccess", true);o.put("Body", "生成成功");o.put("ValidText", tb.get("ValidText"));o.put("ValidImage", tb.get("ValidImage"));request.getSession().setAttribute("ValidPos", tb.get("ValidPos"));//将验证码坐标写入Sessionresult = o.toString();break;case "validate":String code = UrlEx.getQuery(urlParams, "code");Object validPos = request.getSession().getAttribute("ValidPos");boolean res = ValidateHelper.Validate(code, String.valueOf(validPos));o.put("IsSuccess", res);o.put("Body", res ? "验证通过" : "抱歉,验证失败");result = o.toString();break;}com.liuw.web.ResponseEx.write(com.liuw.web.ContentTypeEnum.JSON, result, CustomCommon.CHARSET_DEFAULT);return null;} catch (Exception ex) {return null;}}
}

3.前端页面参考C# ASP.NET MVC 版。

五、运行效果

六、在线示例

 验证码测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/28511.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTPP入门教程||HTTP 状态码||HTTP content-type

HTTP 状态码 当浏览者访问一个网页时&#xff0c;浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前&#xff0c;此网页所在的服务器会返回一个包含 HTTP 状态码的信息头&#xff08;server header&#xff09;用以响应浏览器的请求。 HTTP 状态码的英文为…

Nacos服务注册和配置中心(Config,Eureka,Bus)2

Nacos数据模型 Nacos领域模型,Namespace命名空间、Group分组、集群这些都是为了进行归类管理&#xff0c;把服务和配置文件进行归类&#xff0c;归类之后就可以实现一定的效果&#xff0c;比如隔离。对于服务来说&#xff0c;不同命名空间中的服务不能够互相访问调用 N…

tql!一款Go编写的RAT主机管理工具

工具介绍 这是一款使用go编写的RAT主机群管理工具&#xff0c;已具备命令控制台、文件管理、屏幕截屏、开机启动服务、NPS代理等功能。 流量&#xff1a;支持TCP&#xff0c;UDP/KCP协议&#xff0c;通讯默认使用tls证明书进行加密 关注【Hack分享吧】公众号&#xff0c;回复…

Openlayers layer 基础及重点内容讲解

图层就像是含有文字或图形等元素的图片,一张张按顺序叠放在一起,组合起来形成页面的最终效果。 在 openlayers 中,图层是使用 layer 对象表示的,主要有 WebGLPoints Layer、热度图(HeatMap Layer)、图片图层(Image Layer)、切片图层(Tile Layer)和 矢量图层(Vector Layer…

Spring Boot环境配置Envirnoment

Srping Boot 中我们使用 EnvironmentAware 注入 Environment 对象后&#xff0c;可以在 Environment 中获得系统参数&#xff0c;命令行采参数&#xff0c;文件配置等信息。 Environment 是如何存储&#xff0c;管理这些值的呢&#xff1f;变量发生冲突怎么办呢&#xff1f;我…

Python生成exe文件运行出现黑框闪退如何查看运行bug?

cmd进行回车 第一&#xff1a;进入到可执行exe文件目录&#xff0c;如下图所示 第二&#xff1a;输入可执行文件名&#xff0c;然后就会出现报错提示

Java开发 - 深入理解Redis哨兵机制原理

前言 Redis的主从、哨兵模式、集群模式&#xff0c;在前文中都已经有了详细的搭建流程&#xff0c;可谓是手把手教程&#xff0c;也得到了很多朋友的喜欢。由于前文偏向于应用方面&#xff0c;就导致了理论知识的匮乏&#xff0c;我们可能会用了&#xff0c;但却不明所以&…

自定义实现list及其功能

#pragma once #include <iostream> #include <assert.h> using namespace std;namespace test {//******************************设置结点******************************template<class T>struct list_node{T _data;list_node<T>* _next;list_node&l…

ES6 Generator和Promise

目录 Generator 如何创建Generator函数 ? 模拟发起异步请求 Promise 实例化 实例方法 工厂函数 静态方法 Promise.all([p1,p2,....]) Promise.race([p1,p2,....]) Promise.any([p1,p2,....]) Promise.allSettled([p1,p2,....]) Generator Generator是ES6提供的一种…

云计算与大数据——MPI集群配置

什么是MPI集群&#xff1f; MPI&#xff08;消息传递接口&#xff09;是一种用于编写并行程序的标准&#xff0c;它允许在多个计算节点上进行通信和协作。MPI集群配置是指在一个或多个计算节点上设置MPI环境以实现并行计算。 MPI集群配置的步骤&#xff1a; 硬件选型&#x…

C++--day3(内联函数、结构体、类、封装、this、构造函数、析构函数)

#include <iostream>using namespace std;class My_stack { private:int *ptr; //指向堆区空间int top; //记录栈顶元素int size; public://有参构造My_stack(int size):ptr(new int[size]),top(-1){this->sizesize;cout<<"My_stack::有参构造&…

Django MultiValueDictKeyError 表单数据用request.POST 非表单数据用request.body

表单数据&#xff1a;Content-Type(请求头)为application/x-www-form-urlencoded的数据。 用request.POST获取 a request.POST.get(a) a request.POST[a] alist request.POST.getlist(a) 非表单数据&#xff1a;Content-Type(请求头)为非application/x-www-form-urlenco…