后台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 {
@Autowired
AuthCodeService 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")
@SneakyThrows
public ResponseEntity<byte[]> drawAuthCode(@Param("text") String text) {
return service.drawAuthCodeImg(text);
}
@GetMapping("/verifyCode")
public Result verifyAuthCode(HttpServletRequest request, @Param("code") String code) {
// 获取sessionid
String sessionId = request.getSession().getId();
boolean isVerificationSuccess = service.authUserCode(sessionId, code);
System.out.println("验证结果:" + isVerificationSuccess);
if (isVerificationSuccess) { //true
return 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 验证码文本
*/
@Override
public void bandUserAuthCode(String sessionId, String authCode) {
authCodeMap.put(sessionId, authCode);
findSession = authCodeMap.get(sessionId);
}
/**
* 验证用户验证码
*
* @param sessionId sessionId
* @param authCode 验证码文本
*/
@Override
public 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
*/
@Override
public 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-z
char[] 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') {
// 登录成功,保存用户信息到 localStorage
localStorage.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>