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

news2024/11/23 21:19:18

一、先看效果图

二、背景介绍

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

三、实现思路

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">
        // 场景1
        var 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', // 验证码提交到后台的字段,默认为code
                submitUrl: '@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', // 验证码提交到后台的字段,默认为code
                submitUrl: '@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/106078715
                int 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);
            }

            // 将图片转换为base64
            String 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 {
    @Override
    public 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"));//将验证码坐标写入Session
                    result = 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.coloradmin.cn/o/766531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一篇搞懂steam/csgo搬砖原理

接触csgo游戏搬砖项目三年了&#xff0c;也有在别的论坛交流心得。让我无语的是有些已经游戏搬砖差不多半年&#xff0c;却还告诉我没有赚到钱&#xff0c;又或者说时常到可出售的时候利润少的可怕&#xff0c;总是说这个行业说水太深了&#xff01;那么请你告诉我&#xff0c;…

透过完美世界,再看游戏企业的主线价值

这段时间&#xff0c;游戏圈颇不平静。 虽说行业整体几家欢喜几家愁&#xff0c;但这至少反映出&#xff0c;版号发放常态化后&#xff0c;游企活力更足了&#xff0c;卯足了劲寻找突破点。 其中&#xff0c;相对于成熟作品带来的业绩成果&#xff0c;在研项目和新游表现实际…

JS:数组里面有多个子数组,想要获取每个子数组的第一个元素

前言 数组里面有多个子数组&#xff0c;想要获取每个子数组的第一个元素&#xff0c;例如&#xff1a;需要获取每个数组里面的水果 var data[["苹果", "猪","西兰花"],["草莓", "牛","黄瓜"],["樱桃"…

低代码或将颠覆开发行业?

文章目录 前言一、什么是低代码开发平台二、强大的平台总结 前言 传统的软件开发过程往往需要耗费大量的时间和精力&#xff0c;因为开发人员需编写复杂的代码以完成各种功能。 低代码行业的发展&#xff0c;正好解决了这个问题&#xff0c;让复杂的代码编写一去不复返了。 …

通过Windows WSL在GPU上运行tensorflow 2.12

背景 从tensorflow 2.10开始&#xff0c;已经没有tensorflow-gpu相应的版本在Window GPU运行了&#xff0c;只能通过在window上安装WSL2&#xff0c;在wsl2里运行tensorflow的方式调用GPU.当然你也可以回退到老的tensorflow-gpu的版本&#xff0c;不过你如果要用新的tensorflo…

go初识iris框架(二) - get,post请求和数据格式

继初步了解iris后 文章目录 获取url路径获取数据get请求post请求获取JSON数据格式JSON返回值获取XML数据格式XML返回值 获取url路径 package mainimport "github.com/kataras/iris/v12"func main(){app : iris.New()app.Get("/hello",func(ctx iris.Conte…

旅行社优惠卡小程序软件开发

旅游业的不断发展&#xff0c;越来越多的旅行社开始提供各种优惠卡小程序软件&#xff0c;以吸引更多的游客。这些小程序软件可以为游客提供各种优惠&#xff0c;例如门票折扣、酒店预订折扣、旅游线路折扣等等。 开发旅行社优惠卡小程序软件需要考虑以下几个方面&#xff…

java项目之贝儿米幼儿教育管理系统(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的贝儿米幼儿教育管理系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java…

Ceph 分布式应用2

一、创建 CephFS 文件系统 MDS 接口 1、服务端操作 1&#xff09;在管理节点创建 mds 服务 [rootadmin ceph]# cd /etc/ceph [rootadmin ceph]# ceph-deploy mds create node01 node02 node03 [ceph_deploy.conf][DEBUG ] found configuration file at: /root/.cephdeploy.c…

129、仿真-基于51单片机数字万用表测电压电流电阻仿真设计(Proteus仿真+程序+参考论文+配套资料等)

方案选择 单片机的选择 方案一&#xff1a;STM32系列单片机控制&#xff0c;该型号单片机为LQFP44封装&#xff0c;内部资源足够用于本次设计。STM32F103系列芯片最高工作频率可达72MHZ&#xff0c;在存储器的01等等待周期仿真时可达到1.25Mip/MHZ(Dhrystone2.1)。内部128k字节…

二、DDL-5.小结

一、数据库操作 1、查询 查询所有数据库 show databases; 查询目前所处数据库 select database(); 2、进入 进入某个数据库 use itcast; USE 数据库名; 3、创建 创建数据库 create database itcast; create database [if not exists] itcast; create database [if not …

Spring Actuator 监控管理

Spring Actuator 监控管理 Spring Actuator 是 Spring Boot 提供的一个功能强大的监控和管理端点。它提供了一系列的 HTTP 端点&#xff0c;可以用来监控应用程序的运行状态、健康状况、性能指标等信息。这些端点可以通过 HTTP 或 JMX 访问。 <dependency><groupId&g…

Java:字符串String详解(用法+例子)

目录 String字符串拼接字符串截取与数字相接不可变字符串检测字符串是否相等字符串常量池空串和null串码点与代码单元String API字符串数组构建字符串buffer机制String、StringBuilder、StringBuffer 区别 比特流、网络流、文件流 String 字符串拼接 除了基本类型都叫引用类型…

python 笔记:you-get

下载视频/音乐/图片 使用pip安装you-get pip3 install you-get 之后在命令行执行下载操作 1 主要命令行参数 -n --no-merge 如果视频分p&#xff0c;不进行合并--no-caption不下载弹幕、歌词等 -f --force 覆盖已存在的文件 -F STREAM_ID --format STREAM_ID 指定视频下载…

TypeError: issubclass() arg 1 must be a class

在使用spacy加载模型时报错&#xff0c;错误&#xff1a;TypeError: issubclass() arg 1 must be a class 解决方法 降低spacy的版本&#xff0c;我之前是3.5.3&#xff0c;降到3.5.2即可。

Monocular 3D Object Detection with Depth from Motion 论文学习

论文链接&#xff1a;Monocular 3D Object Detection with Depth from Motion 1. 解决了什么问题&#xff1f; 从单目输入感知 3D 目标对于自动驾驶非常重要&#xff0c;因为单目 3D 的成本要比多传感器的方案低许多。但单目方法很难取得令人满意的效果&#xff0c;因为单张图…

[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序

Volo.Abp 配置应用层自动生成Controller&#xff0c;增删查改服务&#xff08;CrudAppService&#xff09;将会以RESTful Api的方式生成对应的接口 (官方文档)&#xff0c;这与旧版本的Abp区别很大。RESTful固然好&#xff0c;虽然项目里新的Api会逐步使用RESTful Api代替旧的&…

JavaScript运算符

减法运算符 在JavaScript中&#xff0c;我们可以使用简单的减法来计算一下你的出生年龄&#xff0c;例如&#xff1a; const zhangSan 2023 - 2000; console.log(zhangSan);● 当然&#xff0c;我们也可以在控制台中&#xff0c;一次性传入多个值&#xff1b; const zhangS…

测试模型中理解压力测试和负载测试

压力测试 对应的性能测试模式就是固定线程&#xff0c;通过使用固定线程的模式对服务进行性能测试&#xff0c;或者使用阶梯型的线程递增模式进行性能测试。 通过控制线程数来进行不同场景的测试。 关注指标&#xff1a;处理能力&#xff08;QPS或者说TPS&#xff09;&#…

matplotlib绘制点线图

代码&#xff1a; import numpy as np import matplotlib.pyplot as pltX, Y (np.linspace(-3, 3, 100),np.linspace(-3, 3, 100))U, V np.mgrid[-3:3:100j, 0:0:100j]seed_points np.array([[-2, 0, 1], [-2, 0, 1]])fig0, ax0 plt.subplots() strm ax0.streamplot(X, Y…