行为式验证码(成语点选)(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.Framework
{
    public class ValidateHelper
    {
        private static readonly Random Random = new Random();

        #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 = pos[0].ToInt();
                        var y = pos[1].ToInt();

                        //坐标范围
                        var xcore = score[0].Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries);
                        var ycore = score[1].Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries);
                        if (xcore.Length == 2 && x >= xcore[0].ToInt() && x < xcore[1].ToInt() && ycore.Length == 2 &&
                            y >= ycore[0].ToInt() && y < ycore[1].ToInt())
                            passed++;
                    }
                }

            return passed == inputPointDic.Count;
        }

        #endregion

        #region 随机获取成语验证码

        public static string GetWord()
        {
            var source =
                "心旷神怡|心平气和|十年寒窗|孙康映雪|埋头苦干|勤学苦练|发奋图强|前功尽废|艰苦卓绝|坚苦卓绝|勤学苦练|同德一心|节俭力行|幼学壮行|急起直追|奋勇向前|志坚行苦|咬紧牙关|映雪读书|并心同力|分秒必争|身体力行|逆水行舟|学如登山|废寝忘食|朝夕不倦|发愤图强|躬体力行|不辞辛苦|学而不厌|开足马力|听命由天|自强不息|穿壁引光|力争上游|得失在人|惊人之举|尽心竭力|刻苦耐劳|凿壁偷光|旗开得胜|一分为二|当仁不让|力争上游|干劲冲天|奋发图强|争先恐后|四平八稳|分秒必争|一马当先|自告奋勇|踊跃争先|";
            source +=
                "杯弓蛇影|鹤立鸡群|画蛇添足|生龙活虎|指鹿为马|雕虫小技|鸡毛蒜皮|千军万马|万马奔腾|泥牛入海|气象万千|马到成功|叶公好龙|藏龙卧虎|成帮结队|凤毛麟角|弱肉强食|对牛弹琴|狡兔三窟|井底之蛙|龙飞凤舞|车水马龙|虎头蛇尾|狼吞虎咽|黔驴技穷|一箭双雕|好生之德|包罗万象|惊弓之鸟|盲人摸象|一马当先|塞翁失马|含沙射影|万象更新|普渡众生|白驹过隙|打草惊蛇|管中窥豹|守株待兔|青梅竹马|骑虎难下|画龙点睛|亡羊补牢|";
            var arr = source.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries);

            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>();

            //第1步:随机取一张背景图
            var bg = GetMapPath("~/Content/image/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, // 刷新回调事件
        onClose: 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.options = $.extend(clickCaptcha.DEFAULTS, options);
                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();

                if ($.isFunction(that.options.onClose)) {
                    that.options.onClose.call(that.$element);
                }
            });

        }
    };

    _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 ($.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();
        }
    }

    _proto.showTip = function (flag, text) {
        var that = this;
        var parentDom = that.options.mode == 'pop' ? $("body .valid_popup") : that.$element;
        if (flag) {
            if (text == undefined) { text = "验证成功" }
            parentDom.find(".valid_tips__text").html(text);
            parentDom.find(".valid_contain").addClass("valid--success");
        } else {
            if (text == undefined) { text = "验证失败,请重试" }
            parentDom.find(".valid_tips__text").html(text);
            parentDom.find(".valid_contain").addClass("valid--error");
        }
    }

    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 版。

五、运行效果

六、在线示例

 验证码测试

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】生产者消费者模型 -- RingQueue

文章目录 1. 信号量1.1 信号量的引入1.2 信号量的概念1.3 信号量函数 2. 二元信号量模拟实现互斥功能3. 基于环形队列的生产消费模型3.1 空间资源和数据资源3.2 生产者和消费者申请和释放资源3.3 必须遵守的两个规则3.4 代码实现3.5 信号量保护环形队列的原理 1. 信号量 1.1 信…

Java 串口通讯 Demo

为什么写这篇文章 之前职业生涯中遇到的都是通过tcp协议与其他设备进行通讯&#xff0c;而这个是通过串口与其他设备进行通讯&#xff0c;意识到这里是承重板的连接&#xff0c;但实际上比如拉力、压力等模拟信号转换成数字信号的设备应该是有相当一大部分是通过这种方式通讯的…

6.溢出的文字省略号显示

6.1单行文本溢出显示省略号 必须满足三个条件 /*1. 先强制一行内显示文本*/ white-space: nowrap; &#xff08; 默认 normal 自动换行&#xff09; /*2. 超出的部分隐藏*/ overflow: hidden; /*3. 文字用省略号替代超出的部分*/ text-overflow: ellipsis;【示例代码】 <…

Redis学习(三)持久化机制、分布式缓存、多级缓存、Redis实战经验

文章目录 分布式缓存Redis持久化RDB持久化AOF持久化 Redis主从Redis数据同步原理全量同步增量同步 Redis哨兵哨兵的作用和原理sentinel&#xff08;哨兵&#xff09;的三个作用是什么&#xff1f;sentinel如何判断一个Redis实例是否健康&#xff1f;master出现故障后&#xff0…

Windows下PyTorch深度学习环境配置(GPU)

一&#xff1a;下载Anaconda &#xff08;路径最好全英文&#xff09; &#xff08;下载好后&#xff0c;可以创建其他虚拟环境&#xff0c;因为是自己学习&#xff0c;所以先不放步骤&#xff0c;有需要者可以参考B站up我是土堆的视频&#xff09; 二&#xff1a;利用 conda…

本地生活直播,和电商直播有什么不一样?

直播正在成为零售业的标配&#xff0c;当下最新的一条赛道是“本地生活直播”。 &#xff08;商家开始在美团等平台进行本地生活直播。摄影&#xff1a;李崧稷&#xff09; 今年618&#xff0c;在老牌电商平台拉着无数网店&#xff0c;拼尽全力想要堆高销量的时候&#xff0c;一…

《TCP IP网络编程》第六章

《TCP IP网络编程》第六章&#xff1a;基于 UDP 的服务端/客户端 UDP 套接字的特点&#xff1a; 通过寄信来说明 UDP 的工作原理&#xff0c;这是讲解 UDP 时使用的传统示例&#xff0c;它与 UDP 的特点完全相同。寄信前应先在信封上填好寄信人和收信人的地址&#xff0c;之后…

pytorch+CRNN实现

最近接触了一个仪表盘识别的项目&#xff0c;简单调研以后发现可以用CRNN来做。但是手边缺少仪表盘数据集&#xff0c;就先用ICDAR2013试了一下。 结果遇到了一系列坑。为了不使读者和自己在以后的日子继续遭罪。我把正确的代码发到下面了。 1&#xff09;超参数请不要调整&am…

抖音新号起号正确方法,如何操作?

抖音上有着越来越多的卖家注册账号&#xff0c;但刚开始在注册账号后&#xff0c;新号是没有什么流量的&#xff0c;所以想要获得更多的流量的话&#xff0c;在刚开始进行起号的时候就需要按照以下方式进行&#xff0c;下面就一起了解清楚。 第一个找对标内容&#xff0c;抖音…

04 QT坐标系

在QT中默认左上角为原点&#xff0c;即&#xff08;0,0&#xff09;点。x轴右侧为正方向&#xff0c;y轴以下侧为正方向

解锁编程世界的魔法密码:探索算法的奥秘与应用

一个程序员一生中可能会邂逅各种各样的算法&#xff0c;但总有那么几种&#xff0c;是作为一个程序员一定会遇见且大概率需要掌握的算法。今天就来聊聊这些十分重要的“必抓&#xff01;”算法吧~* 一&#xff1a;引言 算法是解决问题和优化程序性能的核心&#xff0c;它是一…

Notepad++ 配置python虚拟环境(Anaconda)

Notepad配置python运行环境步骤&#xff1a; 打开Notepad ->”运行”菜单->”运行”按钮在弹出的窗口内输入以下命令&#xff1a; 我的conda中存在虚拟环境 (1) base (2) pytorch_gpu 添加base环境至Notepad中 cmd /k chdir /d $(CURRENT_DIRECTORY) & call cond…

LCD—STM32液晶显示(2.使用FSMC模拟8080时序)

目录 使用STM32的FSMC模拟8080接口时序 FSMC简介 FSMC NOR/PSRAM中的模式B时序图 用FSMC模拟8080时序 重点&#xff1a;HADDR内部地址与FSMC地址信号线的转换&#xff08;实现地址对齐&#xff09; 使用STM32的FSMC模拟8080接口时序 ILI9341的8080通讯接口时序可以由STM32使…

Java项目查询统计表中各状态数量

框架&#xff1a;SpringBoot&#xff0c;Mybatis&#xff1b;数据库&#xff1a;MySQL 表中设计2个状态字段&#xff0c;每个字段有3种状态&#xff0c;统计这6个状态各自的数量 sql查询语句及结果如图 SQL&#xff1a; SELECT SUM(CASE WHEN A0 THEN 1 ELSE 0 END) AS A0…

准备WebUI自动化测试面试?这30个问题你必须掌握(一)

本文共有8600字&#xff0c;包含了前十五个问题&#xff0c;如需要后十五个问题&#xff0c;可查看文末链接~ 1. 什么是WebUI自动化测试&#xff1f; WebUI自动化测试是指使用自动化测试工具和技术来模拟用户在Web用户界面&#xff08;UI&#xff09;上执行操作&#xff0c;并…

(转载)BP神经网络的非线性系统建模(matlab实现)

本博客的完整代码获取&#xff1a; https://www.mathworks.com/academia/books/book106283.html 1案例背景 在工程应用中经常会遇到一些复杂的非线性系统,这些系统状态方程复杂,难以用数学方法准确建模。在这种情况下,可以建立BP神经网络表达这些非线性系统。方法把未知系统看…

深度学习环境安装|PyCharm,Anaconda,PyTorch,CUDA,cuDNN等

本文参考了许多优秀博主的博客&#xff0c;大部分安装步骤可在其他博客中找到&#xff0c;鉴于我本人第一次安装后&#xff0c;时隔半年&#xff0c;我忘记了当时安装的许多细节和版本信息&#xff0c;所以再一次报错时&#xff0c;重装花费了大量时间。因此&#xff0c;我觉得…

【JAVA】方法的使用:方法语法、方法调用、方法重载、递归练习

&#x1f349;内容专栏&#xff1a;【JAVA从0到入门】 &#x1f349;本文脉络&#xff1a;JAVA方法的使用&#xff0c;递归练习 &#x1f349;本文作者&#xff1a;Melon_西西 &#x1f349;发布时间 &#xff1a;2023.7.19 目录 1. 什么是方法(method) 2 方法定义 2.1 方法…

自洽性改善语言模型中的思维链推理7.13、7.14

自洽性改善语言模型中的思维链推理 摘要介绍对多样化路径的自洽实验实验设置主要结果当CoT影响效率时候&#xff0c;SC会有所帮助与现有方法进行比较附加研究 相关工作总结 原文&#xff1a; 摘要 本篇论文提出了一种新的编码策略——自洽性&#xff0c;来替换思维链中使用的…

echarts x轴文字过长 文字换行显示

xAxis: {type: "category",data: [四美休闲娱乐文化场馆, 资讯, 大咖分享],axisLabel: {show: true,fontSize: 10,interval: 0,color: "#CAE8EA",formatter: function (params) {var newParamsName "";var paramsNameNumber params.length;var…