使用Java的hutool工具实现验证码登录
1.先说一下流程图
2.导入工具包
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
3.流程梳理
3.1前端模版代码
<template>
<form @submit.prevent="handleSubmit">
<div class="input-group">
<div class="captcha-wrapper">
<input
v-model="form.captcha"
type="text"
placeholder="请输入验证码"
:class="{ 'shake': formErrors.captcha }"
maxlength="4"
/>
<div class="captcha-container">
<img
:src="captchaUrl"
@click="refreshCaptcha"
class="captcha-img"
alt="验证码"
@error="handleCaptchaError"
/>
<button
type="button"
class="refresh-btn"
@click="refreshCaptcha"
title="刷新验证码"
>
</button>
</div>
</div>
<transition name="fade">
<p v-if="formErrors.captcha" class="error-message">{{ formErrors.captcha }}</p>
</transition>
</template>
3.2逻辑代码
-
前端挂载组件时发送请求到后端生成验证码
@RestController @RequestMapping("/api") public class AuthController { @GetMapping("/captcha") public void generateCaptcha(HttpServletResponse response, HttpSession session) { // 生成验证码 LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100, 40); // 将验证码存入session session.setAttribute("captcha", lineCaptcha.getCode()); try { // 输出到客户端 response.setContentType("image/png"); lineCaptcha.write(response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } }
-
, <img :src="captchaUrl”>动态绑定实现渲染验证码,button绑定的是这个refreshCaptcha事件,所以可以实现刷新验证码的操作
const captchaUrl = ref('') // 组件挂载时加载验证码 onMounted(() => { refreshCaptcha() }) const refreshCaptcha = () => { // 直接使用API地址,添加时间戳防止缓存 captchaUrl.value = `${request.defaults.baseURL}/api/captcha?t=${new Date().getTime()}` // 清空验证码输入 form.captcha = '' }
-
然后就是前段提交数据到后端做认证
const form = reactive({ email: '', password: '', remember: false, captcha: '' }) export function login(data) { return request({ url: '/api/login', method: 'post', data }) } try { // 发送登录请求 const res = await login({ username: form.email, password: form.password, captcha: form.captcha, remember: form.remember })
-
后端接收数据进行校验
@PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO, HttpSession session) { // 获取session中的验证码 String captcha = (String) session.getAttribute("captcha"); // 校验验证码 if (!loginDTO.getCaptcha().equalsIgnoreCase(captcha)) { return Result.error("验证码错误"); } // TODO: 进行登录逻辑处理 return null; }
前端所有代码
<template>
<div class="login-page">
<div class="login-container">
<!-- Left side with rocket -->
<div class="left-side">
<div class="logo-container">
<h1 class="logo">七禾页话</h1>
</div>
<div class="hero-text">
<h2>欢迎使用<br />学生管理系统</h2>
<p>让工作更高效!</p>
</div>
<!-- Animated rocket -->
<div class="rocket-container">
<div class="rocket" :class="{ 'rocket-hover': isRocketHovering }">
<div class="rocket-body">
<div class="rocket-main"></div>
<div class="rocket-base"></div>
<div class="rocket-side-left"></div>
<div class="rocket-side-right"></div>
</div>
</div>
<!-- Animated clouds -->
<div class="clouds">
<div v-for="i in 3" :key="i"
class="cloud"
:class="`cloud-${i}`"
:style="`--delay: ${i * 2}s`">
</div>
</div>
</div>
</div>
<!-- Right side with form -->
<div class="right-side">
<div class="form-container">
<h2>用户登录</h2>
<form @submit.prevent="handleSubmit">
<div class="input-group">
<input
v-model="form.email"
type="email"
placeholder="请输入用户名"
:class="{ 'shake': formErrors.email }"
/>
<transition name="fade">
<p v-if="formErrors.email" class="error-message">{{ formErrors.email }}</p>
</transition>
</div>
<div class="input-group">
<input
v-model="form.password"
type="password"
placeholder="请输入密码"
:class="{ 'shake': formErrors.password }"
/>
<transition name="fade">
<p v-if="formErrors.password" class="error-message">{{ formErrors.password }}</p>
</transition>
</div>
<div class="input-group">
<div class="captcha-wrapper">
<input
v-model="form.captcha"
type="text"
placeholder="请输入验证码"
:class="{ 'shake': formErrors.captcha }"
maxlength="4"
/>
<div class="captcha-container">
<img
:src="captchaUrl"
@click="refreshCaptcha"
class="captcha-img"
alt="验证码"
@error="handleCaptchaError"
/>
<button
type="button"
class="refresh-btn"
@click="refreshCaptcha"
title="刷新验证码"
>
<svg viewBox="0 0 24 24" class="refresh-icon">
<path d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" fill="currentColor"/>
</svg>
</button>
</div>
</div>
<transition name="fade">
<p v-if="formErrors.captcha" class="error-message">{{ formErrors.captcha }}</p>
</transition>
</div>
<div class="remember-me">
<input
v-model="form.remember"
type="checkbox"
id="remember"
/>
<label for="remember">记住我</label>
</div>
<button
type="submit"
:class="{ 'loading': isLoading }"
:disabled="isLoading"
>
<span v-if="!isLoading">登 录</span>
<span v-else class="loading-text">
<svg class="spinner" viewBox="0 0 50 50">
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
</svg>
登录中...
</span>
</button>
</form>
<div class="auth-links">
<p class="forgot-password">
<a href="#">忘记密码?</a>
</p>
<p class="register-link">
还没有账号? <router-link to="/register" class="text-primary">立即注册</router-link>
</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { login, getCaptcha } from '@/api/auth'
import request from '@/utils/request'
const router = useRouter()
const isRocketHovering = ref(true)
const isLoading = ref(false)
const captchaUrl = ref('')
const form = reactive({
email: '',
password: '',
remember: false,
captcha: ''
})
const formErrors = reactive({
email: '',
password: '',
captcha: ''
})
// 刷新验证码
const refreshCaptcha = () => {
// 直接使用API地址,添加时间戳防止缓存
captchaUrl.value = `${request.defaults.baseURL}/api/captcha?t=${new Date().getTime()}`
// 清空验证码输入
form.captcha = ''
}
// 处理验证码加载错误
const handleCaptchaError = () => {
console.error('验证码图片加载失败')
// 可以在这里添加重试逻辑或显示错误提示
}
// 组件挂载时加载验证码
onMounted(() => {
refreshCaptcha()
})
const handleSubmit = async () => {
// Reset errors
formErrors.email = ''
formErrors.password = ''
formErrors.captcha = ''
// Validate
if (!form.email) {
formErrors.email = '请输入用户名'
return
}
if (!form.password) {
formErrors.password = '请输入密码'
return
}
if (!form.captcha) {
formErrors.captcha = '请输入验证码'
return
}
// Show loading state
isLoading.value = true
try {
// 发送登录请求
const res = await login({
username: form.email,
password: form.password,
captcha: form.captcha,
remember: form.remember
})
// 存储 token
localStorage.setItem('token', res.data.token)
// 登录成功,跳转到首页
router.push('/dashboard')
} catch (error) {
// 根据错误类型显示不同的错误信息
if (error.code === 400) {
formErrors.captcha = '验证码错误'
await refreshCaptcha()
} else if (error.code === 401) {
formErrors.password = '用户名或密码错误'
await refreshCaptcha()
} else {
console.error('登录失败:', error)
formErrors.password = error.message || '登录失败,请稍后重试'
await refreshCaptcha()
}
} finally {
// Reset loading state
isLoading.value = false
}
}
// Start rocket hover animation
setInterval(() => {
isRocketHovering.value = !isRocketHovering.value
}, 2000)
</script>
后端代码
package com.qiheyehua.vuespringboot2.controller;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import com.qiheyehua.vuespringboot2.domain.dto.LoginDTO;
import com.qiheyehua.vuespringboot2.utils.Result;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
/**
* @author 七禾页话
* @date 2024/12/16 18:57
**/
@RestController
@RequestMapping("/api")
public class AuthController {
@GetMapping("/captcha")
public void generateCaptcha(HttpServletResponse response, HttpSession session) {
// 生成验证码
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100, 40);
// 将验证码存入session
session.setAttribute("captcha", lineCaptcha.getCode());
try {
// 输出到客户端
response.setContentType("image/png");
lineCaptcha.write(response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO, HttpSession session) {
// 获取session中的验证码
String captcha = (String) session.getAttribute("captcha");
// 校验验证码
if (!loginDTO.getCaptcha().equalsIgnoreCase(captcha)) {
return Result.error("验证码错误");
}
// TODO: 进行登录逻辑处理
return null;
}
}