后台springboot
CaptureController
package com.example.controller;import com.example.common.Result;
import com.example.service.AuthCodeService;
import com.example.utils.CodeUtils;
import lombok.SneakyThrows;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/")
public class CaptureController {@AutowiredAuthCodeService service;/*** 获取验证码文本** @return*/@GetMapping("/getCode")public Result getAuthCode(HttpServletRequest request) {String text = CodeUtils.generateCode();service.bandUserAuthCode(request.getSession().getId(), text);return Result.success(text);}@GetMapping("/getImg")@SneakyThrowspublic ResponseEntity<byte[]> drawAuthCode(@Param("text") String text) {return service.drawAuthCodeImg(text);}@GetMapping("/verifyCode")public Result verifyAuthCode(HttpServletRequest request, @Param("code") String code) {// 获取sessionidString sessionId = request.getSession().getId();boolean isVerificationSuccess = service.authUserCode(sessionId, code);System.out.println("验证结果:" + isVerificationSuccess);if (isVerificationSuccess) { //truereturn Result.success(); // 返回状态码200表示验证成功} else {return Result.error(); // 返回状态码500表示验证失败}}}
AuthCodeService
package com.example.service.impl;import com.example.service.AuthCodeService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;@Service
public class AuthCodeServiceImpl implements AuthCodeService {private String findSession;/*** 绑定用户验证码** @param sessionId sessionId* @param authCode 验证码文本*/@Overridepublic void bandUserAuthCode(String sessionId, String authCode) {authCodeMap.put(sessionId, authCode);findSession = authCodeMap.get(sessionId);}/*** 验证用户验证码** @param sessionId sessionId* @param authCode 验证码文本*/@Overridepublic Boolean authUserCode(String sessionId, String authCode) {System.out.println("authUserCode: " + authCode);
// String text = authCodeMap.get(sessionId);String text = findSession;System.out.println(authCodeMap.get(sessionId));System.out.println("text: " + text);if (authCode.isEmpty() || !authCode.equals(text)) {//验证码为空或不匹配return false;}// 验证码验证通过,删除验证码authCodeMap.remove(sessionId);return true;}/*** 绘制验证码图片** @param text 文本内容* @return*/@Overridepublic ResponseEntity<byte[]> drawAuthCodeImg(String text) throws IOException {//创建一个115*45的画布BufferedImage canvas = new BufferedImage(115, 45, BufferedImage.TYPE_INT_RGB);//获取画布打画笔对象Graphics g2d = canvas.getGraphics();//创建颜色数组,用于绘制随机颜色ArrayList<Color> colorList = new ArrayList<>();colorList.add(Color.cyan);colorList.add(Color.PINK);colorList.add(Color.ORANGE);colorList.add(Color.green);//创建随机数对象Random rd = new Random();for (int i = 0; i < text.length(); i++) {//获取随机颜色索引int index = rd.nextInt(colorList.size());//设置画笔随机颜色g2d.setColor(colorList.get(index));//设置字体大小g2d.setFont(new Font(null, Font.BOLD, 15));//计算随机x坐标int xPoint = (i + 1) * 20;//计算随机y坐标int yPoint = rd.nextInt(canvas.getHeight() / 2) + 15;g2d.setFont(new Font(null, Font.BOLD, 25));//绘制验证码g2d.drawString(String.valueOf(text.charAt(i)), xPoint, yPoint);//绘制字母g2d.drawString(text.charAt(i) + "", xPoint, yPoint);}//绘制15条干扰线for (int i = 0; i < 15; i++) {g2d.setColor(colorList.get(rd.nextInt(colorList.size())));g2d.drawLine(rd.nextInt(canvas.getWidth()), rd.nextInt(canvas.getHeight()), rd.nextInt(canvas.getWidth()), rd.nextInt(canvas.getHeight()));}//将图片转换为输出流ByteArrayOutputStream baos = new ByteArrayOutputStream();ImageIO.write(canvas, "png", baos);//释放画布资源g2d.dispose();return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(baos.toByteArray());}
}
AuthCodeService
package com.example.service;import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public interface AuthCodeService {//用于临时存储用户绑定的验证码Map<String, String> authCodeMap = new HashMap<>();/*** 绑定用户验证码* @param sessionId sessionId* @param authCode 验证码文本*/void bandUserAuthCode(String sessionId, String authCode);/*** 验证用户验证码* @param sessionId sessionId* @param authCode 验证码文本*/Boolean authUserCode(String sessionId, String authCode);/*** 绘制验证码图片* @param text 文本内容* @return*/ResponseEntity<byte[]> drawAuthCodeImg(String text) throws IOException;
}
CodeUtils
package com.example.utils;import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;public class CodeUtils {/*** 生成4位字符验证码** @return*/public static String generateCode() {//创建字符数组,1-9,A-Z,a-zchar[] arr = {'1', '2', '3', '4', '5', '6', '7', '8', '9', // 数字 1 到 9'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', // 大写字母 A 到 Z'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' // 小写字母 a 到 z};//创建随机数对象Random rd = new Random();//创建SB对象,用于生成验证码StringBuilder sb = new StringBuilder();//循环四次,拼接四个字符for (int i = 0; i < 4; i++) {//获取随机索引int index = rd.nextInt(arr.length);sb.append(arr[index]);}//返回创建的验证码return sb.toString();}
}
前端Vue
templete模块
<el-form-item prop="captcha"><div style="display: flex; justify-content: center; align-items: center;"><el-input v-model="data.form.captcha" prefix-icon="key" style="margin-right:10px;"placeholder="请输入验证码"></el-input><el-image :src="captchaUrl" @click="fetchCaptchaImage" style="height: 33px; width: 140px;"></el-image></div>
</el-form-item>
js模块
<script setup>
import {reactive, ref, onMounted} from "vue";
import {ElMessage} from "element-plus";
import router from "../router";
import request from "@/utils/request";const data = reactive({form: {captcha: '',}
});const captchaUrl = ref('');// 获取验证码图片
const fetchCaptchaImage = () => {request.get('http://127.0.0.1:9090/getCode').then(res => {if (res.code === '200') {captchaUrl.value = `http://127.0.0.1:9090/getImg?text=${res.data}`;// 不需要将data.form.captcha设置为空字符串} else {handleCaptchaError('获取验证码图片失败');}}).catch(error => {handleCaptchaError('获取验证码图片时发生网络错误');});
};// 验证验证码
const verifyCaptcha = (captcha) => {return new Promise((resolve, reject) => {if (!captcha) {reject('验证码不能为空');} else {console.log(captcha);request.get(`http://127.0.0.1:9090/verifyCode?code=${captcha}`).then(res => {if (res.code === '200') {ElMessage.success('验证码匹配成功');resolve(); // 验证码匹配成功,执行 resolve 表示验证成功} else {console.log(res.code);console.log(captcha);ElMessage.error('验证码错误,请重新输入');reject('验证码错误'); // 验证码错误,执行 reject 表示验证失败}})}});
};// 刷新验证码
const refreshCaptcha = () => {fetchCaptchaImage();
};const executeLogin = () => {formRef.value.validate((valid) => {if (valid) {request.post('/login', data.form).then(res => {if (res.code === '200') {// 登录成功,保存用户信息到 localStoragelocalStorage.setItem('student-user', JSON.stringify(res.data));ElMessage.success('登录成功');router.push('/home'); // 跳转到主页} else {ElMessage.error(res.msg);}});}});
};const login = async () => {try {await formRef.value.validate(); // 首先验证用户名和密码await verifyCaptcha(data.form.captcha); // 等待验证码验证// 验证码验证成功,执行登录executeLogin();} catch (error) {console.error('登录失败:', error);}
};// 组件加载时刷新验证码
onMounted(refreshCaptcha);</script>