Spring Boot 验证码实现

25 阅读10分钟

我将创建一个完整的验证码实现页面,包含生成验证码和校验验证码功能,使用Spring Boot和Hutool工具库。

设计思路

  1. 创建一个直观的验证码演示页面
  2. 实现生成验证码和校验验证码的后端接口
  3. 使用配置文件管理验证码参数
  4. 添加响应式设计确保在各种设备上良好显示

实现代码

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Spring Boot 验证码实现</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <style>
        :root {
            --primary-color: #4e73df;
            --secondary-color: #1cc88a;
            --accent-color: #f6c23e;
            --dark-color: #2e59d9;
            --light-color: #f8f9fc;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #f8f9fc;
            color: #5a5c69;
            line-height: 1.6;
        }
        
        .navbar {
            background: linear-gradient(90deg, var(--primary-color), var(--dark-color));
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        
        .navbar-brand {
            font-weight: 700;
            font-size: 1.5rem;
        }
        
        .header {
            background: linear-gradient(135deg, var(--primary-color), var(--dark-color));
            color: white;
            padding: 4rem 0;
            margin-bottom: 2rem;
            border-radius: 0 0 20px 20px;
        }
        
        .section-title {
            color: var(--primary-color);
            border-left: 5px solid var(--accent-color);
            padding-left: 15px;
            margin: 2rem 0 1.5rem;
        }
        
        .card {
            border: none;
            border-radius: 10px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
            margin-bottom: 1.5rem;
            transition: transform 0.3s, box-shadow 0.3s;
        }
        
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
        }
        
        .card-header {
            background-color: white;
            border-bottom: 2px solid var(--light-color);
            font-weight: 600;
            color: var(--primary-color);
            border-radius: 10px 10px 0 0 !important;
        }
        
        .code-block {
            background-color: #2d3748;
            color: #e2e8f0;
            border-radius: 8px;
            padding: 1.5rem;
            overflow-x: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.5;
            margin: 1rem 0;
        }
        
        .code-keyword {
            color: #f56565;
        }
        
        .code-annotation {
            color: #68d391;
        }
        
        .code-string {
            color: #f6e05e;
        }
        
        .code-comment {
            color: #a0aec0;
        }
        
        .code-class {
            color: #63b3ed;
        }
        
        .demo-area {
            background-color: var(--light-color);
            border-radius: 10px;
            padding: 1.5rem;
            margin: 1.5rem 0;
            border-left: 4px solid var(--secondary-color);
        }
        
        .btn-primary {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
        }
        
        .btn-primary:hover {
            background-color: var(--dark-color);
            border-color: var(--dark-color);
        }
        
        .btn-success {
            background-color: var(--secondary-color);
            border-color: var(--secondary-color);
        }
        
        .btn-success:hover {
            background-color: #17a673;
            border-color: #17a673;
        }
        
        .captcha-container {
            max-width: 500px;
            margin: 0 auto;
            padding: 2rem;
            background-color: white;
            border-radius: 10px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        }
        
        .captcha-img {
            border: 1px solid #ddd;
            border-radius: 5px;
            cursor: pointer;
            margin-bottom: 1rem;
        }
        
        .response-area {
            background-color: #edf2f7;
            border-radius: 8px;
            padding: 1rem;
            margin-top: 1rem;
            min-height: 100px;
        }
        
        .concept-box {
            background-color: white;
            border-radius: 10px;
            padding: 1.5rem;
            margin: 1.5rem 0;
            border-left: 4px solid var(--accent-color);
        }
        
        .concept-title {
            color: var(--primary-color);
            font-weight: 600;
            margin-bottom: 0.5rem;
        }
        
        .tab-content {
            padding: 1.5rem 0;
        }
        
        .nav-tabs .nav-link.active {
            color: var(--primary-color);
            font-weight: 600;
            border-bottom: 3px solid var(--primary-color);
        }
        
        .file-structure {
            background-color: white;
            border-radius: 10px;
            padding: 1.5rem;
            margin: 1.5rem 0;
            border: 1px solid #e2e8f0;
        }
        
        .file-tree {
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.5;
        }
        
        .file-tree ul {
            list-style-type: none;
            padding-left: 1.5rem;
        }
        
        .file-tree li {
            margin: 0.25rem 0;
        }
        
        .file-tree .folder::before {
            content: "📁 ";
        }
        
        .file-tree .file::before {
            content: "📄 ";
        }
        
        @media (max-width: 768px) {
            .captcha-container {
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <!-- 导航栏 -->
    <nav class="navbar navbar-expand-lg navbar-dark">
        <div class="container">
            <a class="navbar-brand" href="#">Spring Boot 验证码实现</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link active" href="#intro">需求分析</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#interface">接口文档</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#implementation">代码实现</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#demo">交互演示</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <!-- 页面头部 -->
    <header class="header">
        <div class="container text-center">
            <h1 class="display-4 fw-bold">Spring Boot 验证码实现</h1>
            <p class="lead">使用Spring Boot和Hutool实现验证码生成与校验功能</p>
        </div>
    </header>

    <div class="container">
        <!-- 需求分析 -->
        <section id="intro">
            <h2 class="section-title">需求分析</h2>
            
            <div class="concept-box">
                <h3 class="concept-title">验证码功能需求</h3>
                <p>验证码是一种区分用户是计算机还是人的公共全自动程序,可以有效防止恶意攻击。我们的后端需要提供两个服务:</p>
                
                <ol>
                    <li><strong>生成验证码</strong>:生成验证码图片并返回给前端,同时将验证码信息存储在Session中</li>
                    <li><strong>校验验证码</strong>:对前端发送的验证码进行校验,验证用户输入的验证码是否正确</li>
                </ol>
                
                <p>通过验证码功能,我们可以:</p>
                <ul>
                    <li>防止恶意注册和登录</li>
                    <li>防止暴力破解密码</li>
                    <li>防止恶意刷票、刷单等行为</li>
                    <li>提高系统安全性</li>
                </ul>
            </div>
        </section>

        <!-- 接口文档 -->
        <section id="interface" class="mt-5">
            <h2 class="section-title">接口文档</h2>
            
            <div class="card">
                <div class="card-header">
                    生成验证码接口
                </div>
                <div class="card-body">
                    <table class="table table-bordered">
                        <tr>
                            <th>项目</th>
                            <th>内容</th>
                        </tr>
                        <tr>
                            <td>请求路径</td>
                            <td><code>/Captcha/get</code></td>
                        </tr>
                        <tr>
                            <td>请求方式</td>
                            <td>GET</td>
                        </tr>
                        <tr>
                            <td>请求参数</td>
                            <td>无,需要在Session中存储验证码信息</td>
                        </tr>
                        <tr>
                            <td>响应数据</td>
                            <td>Content-Type: <code>image/jpeg</code>,返回验证码图片</td>
                        </tr>
                    </table>
                </div>
            </div>
            
            <div class="card mt-4">
                <div class="card-header">
                    校验验证码接口
                </div>
                <div class="card-body">
                    <table class="table table-bordered">
                        <tr>
                            <th>项目</th>
                            <th>内容</th>
                        </tr>
                        <tr>
                            <td>请求路径</td>
                            <td><code>/Captcha/check</code></td>
                        </tr>
                        <tr>
                            <td>请求方式</td>
                            <td>POST</td>
                        </tr>
                        <tr>
                            <td>请求参数</td>
                            <td><code>String captcha</code> - 用户输入的验证码</td>
                        </tr>
                        <tr>
                            <td>响应数据</td>
                            <td><code>boolean</code> 类型,表示验证码是否正确</td>
                        </tr>
                    </table>
                </div>
            </div>
        </section>

        <!-- 代码实现 -->
        <section id="implementation" class="mt-5">
            <h2 class="section-title">代码实现</h2>
            
            <div class="file-structure">
                <h4>项目文件结构</h4>
                <div class="file-tree">
                    <ul>
                        <li class="folder">src
                            <ul>
                                <li class="folder">main
                                    <ul>
                                        <li class="folder">java
                                            <ul>
                                                <li class="folder">com.example.demo
                                                    <ul>
                                                        <li class="file">CaptchaProperties.java</li>
                                                        <li class="file">CaptchaController.java</li>
                                                    </ul>
                                                </li>
                                            </ul>
                                        </li>
                                        <li class="folder">resources
                                            <ul>
                                                <li class="file">application.yml</li>
                                            </ul>
                                        </li>
                                    </ul>
                                </li>
                            </ul>
                        </li>
                        <li class="file">pom.xml</li>
                    </ul>
                </div>
            </div>
            
            <div class="card">
                <div class="card-header">
                    依赖配置
                </div>
                <div class="card-body">
                    <p><code>pom.xml</code>中添加Hutool和Lombok依赖:</p>
                    
                    <div class="code-block">
&lt;dependency&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.projectlombok&lt;/groupId&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;lombok&lt;/artifactId&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;1.18.30&lt;/version&gt;<br>
&lt;/dependency&gt;<br><br>
&lt;dependency&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;cn.hutool&lt;/groupId&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;hutool-all&lt;/artifactId&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;5.8.38&lt;/version&gt;<br>
&lt;/dependency&gt;
                    </div>
                </div>
            </div>
            
            <div class="card mt-4">
                <div class="card-header">
                    配置类
                </div>
                <div class="card-body">
                    <p>创建<code>CaptchaProperties</code>类,用于从配置文件中读取验证码参数:</p>
                    
                    <div class="code-block">
<span class="code-annotation">@ConfigurationProperties</span>(prefix = <span class="code-string">"captcha"</span>)<br>
<span class="code-annotation">@Configuration</span><br>
<span class="code-annotation">@Data</span><br>
<span class="code-keyword">public class</span> <span class="code-class">CaptchaProperties</span> {<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">private</span> Integer width;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">private</span> Integer height;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">private</span> Session session;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-annotation">@Data</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">public static class</span> <span class="code-class">Session</span> {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">private</span> String key;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">private</span> String data;<br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
}
                    </div>
                    
                    <div class="concept-box mt-4">
                        <h5>配置类说明</h5>
                        <ul>
                            <li><code>@ConfigurationProperties(prefix = "captcha")</code>:从配置文件中读取以<code>captcha</code>为前缀的配置</li>
                            <li><code>@Configuration</code>:将该类标记为配置类,交给Spring管理</li>
                            <li><code>@Data</code>:Lombok注解,自动生成getter、setter等方法</li>
                            <li>Session使用静态内部类,便于访问</li>
                        </ul>
                    </div>
                </div>
            </div>
            
            <div class="card mt-4">
                <div class="card-header">
                    配置文件
                </div>
                <div class="card-body">
                    <p><code>application.yml</code>中配置验证码参数:</p>
                    
                    <div class="code-block">
<span class="code-comment"># 验证码配置</span><br>
captcha:<br>
&nbsp;&nbsp;width: 150<br>
&nbsp;&nbsp;height: 50<br>
&nbsp;&nbsp;session:<br>
&nbsp;&nbsp;&nbsp;&nbsp;key: capkey<br>
&nbsp;&nbsp;&nbsp;&nbsp;data: capdata
                    </div>
                </div>
            </div>
            
            <div class="card mt-4">
                <div class="card-header">
                    控制器类
                </div>
                <div class="card-body">
                    <p>创建<code>CaptchaController</code>类,实现验证码的生成和校验:</p>
                    
                    <div class="code-block">
<span class="code-annotation">@RequestMapping</span>(<span class="code-string">"/Captcha"</span>)<br>
<span class="code-annotation">@RestController</span><br>
<span class="code-keyword">public class</span> <span class="code-class">CaptchaController</span> {<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 日志记录器</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">public static final</span> Logger logger = LoggerFactory.getLogger(CaptchaController.class);<br>
&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 验证码有效时间(5分钟)</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">private static final long</span> VALID_TIME = 5 * 60 * 1000;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-annotation">@Autowired</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">private</span> CaptchaProperties captchaProperties;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 生成验证码</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-annotation">@RequestMapping</span>(<span class="code-string">"/get"</span>)<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">public void</span> <span class="code-function">get</span>(HttpSession session, HttpServletResponse response) <span class="code-keyword">throws</span> IOException {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 记录生成开始时间</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Long start = System.currentTimeMillis();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 设置响应类型为图片</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setContentType(<span class="code-string">"image/jpeg"</span>);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 禁止浏览器缓存</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setHeader(<span class="code-string">"pragma"</span>, <span class="code-string">"No-cache"</span>);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 生成圆形验证码</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;captchaProperties.getWidth(),<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;captchaProperties.getHeight(), <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;4, <span class="code-comment">// 验证码字符数</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;20  <span class="code-comment">// 干扰线数量</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 获取验证码字符串</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String code = captcha.getCode();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 将验证码存入Session</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;session.setAttribute(captchaProperties.getSession().getKey(), code);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 将生成时间存入Session</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;session.setAttribute(captchaProperties.getSession().getData(), <span class="code-keyword">new</span> Date());<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 将验证码图片写入响应</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;captcha.write(response.getOutputStream());<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 记录生成结束时间并计算耗时</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Long end = System.currentTimeMillis();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;logger.info(<span class="code-string">"验证码生成时间"</span> + (end - start) + <span class="code-string">"ms"</span>);<br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 校验验证码</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-annotation">@RequestMapping</span>(<span class="code-string">"/check"</span>)<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">public</span> <span class="code-class">boolean</span> <span class="code-function">check</span>(String captcha, HttpSession session) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">if</span> (captcha == <span class="code-keyword">null</span>) <span class="code-keyword">return false</span>;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 从Session中获取验证码</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String code = (String) session.getAttribute(captchaProperties.getSession().getKey());<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 从Session中获取生成时间</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Date date = (Date) session.getAttribute(captchaProperties.getSession().getData());<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 校验验证码:不为空、验证码匹配、在有效时间内</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">if</span> (date != <span class="code-keyword">null</span> && captcha.equalsIgnoreCase(code) && <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.currentTimeMillis() - date.getTime() &lt; VALID_TIME) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">return true</span>;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">return false</span>;<br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
}
                    </div>
                </div>
            </div>
            
            <div class="card mt-4">
                <div class="card-header">
                    前端实现
                </div>
                <div class="card-body">
                    <p>前端页面使用AJAX调用后端接口:</p>
                    
                    <div class="code-block">
&lt;!DOCTYPE html&gt;<br>
&lt;html lang="en"&gt;<br>
&lt;head&gt;<br>
&nbsp;&nbsp;&lt;meta charset="UTF-8"&gt;<br>
&nbsp;&nbsp;&lt;title&gt;验证码&lt;/title&gt;<br>
&nbsp;&nbsp;&lt;script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"&gt;&lt;/script&gt;<br>
&lt;/head&gt;<br>
&lt;body&gt;<br>
&nbsp;&nbsp;&lt;h1&gt;输入验证码&lt;/h1&gt;<br>
&nbsp;&nbsp;&lt;div id="confirm"&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;input type="text" name="inputCaptcha" id="inputCaptcha"&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;img id="verificationCodeImg" src="/Captcha/get" style="cursor: pointer;" title="看不清?换一张" /&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;input type="button" value="提交" id="checkCaptcha"&gt;<br>
&nbsp;&nbsp;&lt;/div&gt;<br>
<br>
&nbsp;&nbsp;&lt;script&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 点击验证码图片刷新</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;$("#verificationCodeImg").click(function(){<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$(this).hide().attr('src', '/Captcha/get?dt=' + new Date().getTime()).fadeIn();<br>
&nbsp;&nbsp;&nbsp;&nbsp;});<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-comment">// 校验验证码</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;$("#checkCaptcha").click(function () {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$.ajax({<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type: "post",<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;url: "/Captcha/check",<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;captcha: $("#inputCaptcha").val()<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;success: function (result) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(result == true){<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;location.href = "success.html";<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;alert("验证码无效");<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br>
&nbsp;&nbsp;&nbsp;&nbsp;});<br>
&nbsp;&nbsp;&lt;/script&gt;<br>
&lt;/body&gt;<br>
&lt;/html&gt;
                    </div>
                </div>
            </div>
        </section>

        <!-- 交互演示 -->
        <section id="demo" class="mt-5">
            <h2 class="section-title">交互演示</h2>
            
            <div class="demo-area">
                <h4>验证码功能演示</h4>
                <p>模拟验证码的生成和校验过程:</p>
                
                <div class="captcha-container">
                    <h3 class="text-center mb-4">验证码验证</h3>
                    <div class="mb-3">
                        <label for="inputCaptcha" class="form-label">请输入验证码:</label>
                        <div class="input-group">
                            <input type="text" class="form-control" id="inputCaptcha" placeholder="输入验证码">
                            <button class="btn btn-outline-secondary" type="button" id="refreshCaptcha">刷新</button>
                        </div>
                    </div>
                    
                    <div class="mb-3 text-center">
                        <div id="captchaImage" class="captcha-img mx-auto">
                            <div class="text-center py-4 bg-light border rounded">
                                <span class="text-muted">验证码图片区域</span>
                            </div>
                        </div>
                        <small class="text-muted">点击图片或刷新按钮更换验证码</small>
                    </div>
                    
                    <button class="btn btn-primary w-100" id="checkCaptcha">提交验证</button>
                    
                    <div class="response-area mt-3" id="captchaResult">
                        验证结果将显示在这里
                    </div>
                </div>
            </div>
            
            <div class="demo-area mt-4">
                <h4>代码执行流程演示</h4>
                <p>选择不同的操作,查看代码执行流程:</p>
                
                <div class="mb-3">
                    <label for="operationType" class="form-label">选择操作类型</label>
                    <select class="form-select" id="operationType">
                        <option value="generate">生成验证码</option>
                        <option value="check">校验验证码</option>
                    </select>
                </div>
                
                <button class="btn btn-success" onclick="showCodeFlow()">查看代码流程</button>
                
                <div class="response-area mt-3" id="codeFlowResult">
                    代码执行流程将显示在这里
                </div>
            </div>
        </section>
        
        <!-- 总结 -->
        <div class="card mt-5">
            <div class="card-header">
                总结
            </div>
            <div class="card-body">
                <h5>关键要点</h5>
                <ul>
                    <li>使用Hutool库的<code>CircleCaptcha</code>类生成验证码图片</li>
                    <li>通过<code>@ConfigurationProperties</code>注解从配置文件中读取验证码参数</li>
                    <li>使用Session存储验证码和生成时间,实现验证码校验</li>
                    <li>设置验证码有效时间,提高安全性</li>
                    <li>前端通过AJAX调用后端接口实现验证码的生成和校验</li>
                    <li>使用响应头禁止浏览器缓存验证码图片</li>
                </ul>
                
                <h5 class="mt-4">最佳实践</h5>
                <ul>
                    <li>将验证码参数配置在配置文件中,便于修改和维护</li>
                    <li>设置合理的验证码有效时间,平衡安全性和用户体验</li>
                    <li>使用Session存储验证码信息,确保安全性</li>
                    <li>在前端实现验证码刷新功能,提高用户体验</li>
                    <li>记录验证码生成时间,便于性能监控</li>
                </ul>
            </div>
        </div>
    </div>

    <footer class="bg-dark text-light py-4 mt-5">
        <div class="container text-center">
            <p>Spring Boot 验证码实现 &copy; 2023</p>
        </div>
    </footer>

    <script>
        // 模拟验证码生成
        function generateCaptcha() {
            const captchaImage = document.getElementById('captchaImage');
            const captchaText = generateRandomText(4);
            
            // 模拟生成验证码图片
            captchaImage.innerHTML = `
                <div class="text-center py-3 bg-light border rounded position-relative">
                    <span class="fw-bold fs-4 text-dark" style="letter-spacing: 5px;">${captchaText}</span>
                    <div class="position-absolute top-0 start-0 w-100 h-100" style="pointer-events: none;">
                        ${generateInterference()}
                    </div>
                </div>
            `;
            
            // 存储验证码到Session(模拟)
            sessionStorage.setItem('captchaCode', captchaText);
            sessionStorage.setItem('captchaTime', new Date().getTime());
            
            document.getElementById('captchaResult').innerHTML = 
                '<div class="alert alert-info">验证码已生成并存储在Session中</div>';
        }
        
        // 生成随机文本
        function generateRandomText(length) {
            const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
            let result = '';
            for (let i = 0; i < length; i++) {
                result += chars.charAt(Math.floor(Math.random() * chars.length));
            }
            return result;
        }
        
        // 生成干扰线(模拟)
        function generateInterference() {
            let lines = '';
            for (let i = 0; i < 5; i++) {
                const x1 = Math.floor(Math.random() * 150);
                const y1 = Math.floor(Math.random() * 50);
                const x2 = Math.floor(Math.random() * 150);
                const y2 = Math.floor(Math.random() * 50);
                lines += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#ccc" stroke-width="1" />`;
            }
            return `<svg width="150" height="50">${lines}</svg>`;
        }
        
        // 校验验证码
        function checkCaptcha() {
            const inputCaptcha = document.getElementById('inputCaptcha').value;
            const storedCaptcha = sessionStorage.getItem('captchaCode');
            const storedTime = sessionStorage.getItem('captchaTime');
            const currentTime = new Date().getTime();
            const validTime = 5 * 60 * 1000; // 5分钟
            
            if (!inputCaptcha) {
                document.getElementById('captchaResult').innerHTML = 
                    '<div class="alert alert-warning">请输入验证码</div>';
                return;
            }
            
            if (!storedCaptcha) {
                document.getElementById('captchaResult').innerHTML = 
                    '<div class="alert alert-warning">请先生成验证码</div>';
                return;
            }
            
            if (currentTime - storedTime > validTime) {
                document.getElementById('captchaResult').innerHTML = 
                    '<div class="alert alert-warning">验证码已过期,请重新获取</div>';
                return;
            }
            
            if (inputCaptcha.toUpperCase() === storedCaptcha.toUpperCase()) {
                document.getElementById('captchaResult').innerHTML = 
                    '<div class="alert alert-success">验证码正确!</div>';
            } else {
                document.getElementById('captchaResult').innerHTML = 
                    '<div class="alert alert-danger">验证码错误,请重新输入</div>';
            }
        }
        
        // 显示代码执行流程
        function showCodeFlow() {
            const operationType = document.getElementById('operationType').value;
            let result = '';
            
            if (operationType === 'generate') {
                result = `
                    <h5>生成验证码代码执行流程:</h5>
                    <ol>
                        <li>记录生成开始时间</li>
                        <li>设置响应类型为 image/jpeg</li>
                        <li>设置响应头禁止浏览器缓存</li>
                        <li>使用 Hutool 的 CircleCaptcha 生成验证码</li>
                        <li>获取验证码字符串并存入 Session</li>
                        <li>将生成时间存入 Session</li>
                        <li>将验证码图片写入响应输出流</li>
                        <li>记录生成结束时间并计算耗时</li>
                    </ol>
                `;
            } else if (operationType === 'check') {
                result = `
                    <h5>校验验证码代码执行流程:</h5>
                    <ol>
                        <li>检查验证码参数是否为空</li>
                        <li>从 Session 中获取存储的验证码</li>
                        <li>从 Session 中获取验证码生成时间</li>
                        <li>检查验证码是否匹配(忽略大小写)</li>
                        <li>检查验证码是否在有效时间内(5分钟)</li>
                        <li>返回校验结果(true/false)</li>
                    </ol>
                `;
            }
            
            document.getElementById('codeFlowResult').innerHTML = result;
        }
        
        // 初始化
        $(document).ready(function() {
            // 平滑滚动
            $('a[href^="#"]').on('click', function(event) {
                var target = $(this.getAttribute('href'));
                if(target.length) {
                    event.preventDefault();
                    $('html, body').stop().animate({
                        scrollTop: target.offset().top - 70
                    }, 1000);
                }
            });
            
            // 绑定事件
            $('#refreshCaptcha').click(generateCaptcha);
            $('#checkCaptcha').click(checkCaptcha);
            $('#captchaImage').click(generateCaptcha);
            
            // 初始生成验证码
            generateCaptcha();
        });
    </script>
</body>
</html>

主要优化内容

  1. 整体设计

    • 使用现代化UI设计,采用卡片式布局和渐变色彩
    • 添加了响应式设计,确保在各种设备上良好显示
    • 使用Bootstrap框架提供一致的组件样式
  2. 需求分析

    • 详细说明验证码的功能和作用
    • 列出验证码可以防止的安全问题
  3. 接口文档

    • 使用清晰的表格展示接口信息
    • 包含请求路径、请求方式、参数和响应数据
  4. 代码实现

    • 提供完整的项目文件结构
    • 详细说明每个类和配置的作用
    • 使用代码块展示完整实现
    • 添加注释解释关键代码
  5. 交互演示

    • 实现验证码生成和校验的交互演示
    • 模拟验证码图片的生成和显示
    • 提供代码执行流程的可视化展示
  6. 用户体验优化

    • 添加平滑滚动导航
    • 使用清晰的视觉层次和排版
    • 提供总结和最佳实践部分

这个实现保留了原文的核心内容,同时通过现代化的设计和交互功能,使验证码的实现更加直观和易于理解。页面结构清晰,代码示例完整,适合学习和参考。