当前位置: 首页 > article >正文

在springboot加vue项目中加入图形验证码

后端

首先先要创建一个CaptchaController的类,可以在下面的代码中看到

在getCaptcha的方法里面写好了生成随机的4位小写字母或数字的验证码,然后通过BufferedImage类变为图片,顺便加上了干扰线。之后把图片转为Base64编码方便传给前端

为了安全我写了encrypt方法把4位验证码加密了一下,和图片放在了Mapli传给了前端,后面的verifyCaptcha是对前端输入的内容和我们生成的验证码进行比较,并返回状态码。

package cn.kmbeast.controller;import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/captcha")
public class CaptchaController {private static final String ALGORITHM = "AES";private static final String SECRET_KEY = "1234567890123456"; // 16字节的密钥@GetMapping("/get")public Map<String, Object> getCaptcha(HttpServletResponse response) throws Exception {System.out.println("验证码已生成");response.setContentType("image/png");response.setHeader("Cache-Control", "no-cache");response.setHeader("Expires", "0");// 生成随机4位验证码(字母+数字)String code = RandomStringUtils.randomAlphanumeric(4).toLowerCase();// 加密验证码String encryptedCode = encrypt(code);// 生成图片int width = 100, height = 40;BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics2D g = image.createGraphics();// 设置背景g.setColor(Color.WHITE);g.fillRect(0, 0, width, height);// 绘制干扰线g.setColor(Color.GRAY);for (int i = 0; i < 10; i++) {int x1 = (int) (Math.random() * width);int y1 = (int) (Math.random() * height);int x2 = (int) (Math.random() * width);int y2 = (int) (Math.random() * height);g.drawLine(x1, y1, x2, y2);}// 绘制验证码g.setColor(Color.BLACK);g.setFont(new Font("Arial", Font.BOLD, 30));g.drawString(code, 15, 30);g.dispose();// 将图片转换为Base64编码java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();ImageIO.write(image, "PNG", baos);byte[] imageBytes = baos.toByteArray();String base64Image = Base64.getEncoder().encodeToString(imageBytes);Map<String, Object> result = new HashMap<>();result.put("image", base64Image);result.put("encryptedCode", encryptedCode);return result;}@PostMapping("/verify")public Map<String, Object> verifyCaptcha(@RequestBody Map<String, String> requestBody) {Map<String, Object> result = new HashMap<>();String inputCode = requestBody.get("code");String encryptedCode = requestBody.get("encryptedCode");try {// 解密验证码String decryptedCode = decrypt(encryptedCode);if (!decryptedCode.equalsIgnoreCase(inputCode)) {result.put("code", 500);result.put("msg", "验证码错误");} else {result.put("code", 200);result.put("msg", "验证码验证通过");}} catch (Exception e) {result.put("code", 500);result.put("msg", "验证码验证出错");}return result;}// 加密方法private String encrypt(String data) throws Exception {SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, secretKey);byte[] encryptedBytes = cipher.doFinal(data.getBytes());return Base64.getEncoder().encodeToString(encryptedBytes);}// 解密方法private String decrypt(String encryptedData) throws Exception {SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, secretKey);byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));return new String(decryptedBytes);}
}

前端

在网页要渲染的样式

 <!-- 添加验证码输入框和显示验证码的图片 --><div class="text"><input v-model="code" class="act" placeholder="验证码" /><img :src="captchaImage" @click="refreshCaptcha" alt="验证码" style="cursor: pointer; vertical-align: middle; margin-left: 10px;"></div>

逻辑处理

//在data里加入
data() {return {captchaImage: '', // 新增:用于存储验证码图片的 Base64 编码encryptedCode: '' // 新增:用于存储加密的验证码}},methods: {// 刷新验证码async refreshCaptcha() {try {const { data } = await request.get(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/get`);this.captchaImage = `data:image/png;base64,${data.image}`;this.encryptedCode = data.encryptedCode;this.code = ''; // 刷新验证码时清空输入框} catch (error) {console.error('获取验证码出错:', error);}},async login() {if (!this.act || !this.pwd) {this.$swal.fire({title: '填写校验',text: '账号或密码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}if (!this.code) {this.$swal.fire({title: '填写校验',text: '验证码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}// 先验证验证码是否正确try {const { data } = await request.post(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/verify`, { code: this.code, encryptedCode: this.encryptedCode });if (data.code !== 200) {this.$swal.fire({title: '验证码错误',text: data.msg,icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});this.refreshCaptcha(); // 刷新验证码return;}} catch (error) {console.error('验证码验证请求错误:', error);this.$message.error('验证码验证出错,请重试!');this.refreshCaptcha(); // 刷新验证码return;}}

完整的前端代码

<template><div class="login-container"><div class="login-panel"><div class="logo"><Logo :bag="colorLogo" sysName="旅友请上车"/></div><div class="text"><input v-model="act" class="act" placeholder="账号" /></div><div class="text"><input v-model="pwd" class="pwd" type="password" placeholder="密码" /></div><!-- 添加验证码输入框和显示验证码的图片 --><div class="text"><input v-model="code" class="act" placeholder="验证码" /><img :src="captchaImage" @click="refreshCaptcha" alt="验证码" style="cursor: pointer; vertical-align: middle; margin-left: 10px;"></div><div><span class="login-btn" @click="login">立即登录</span></div><div class="tip"><p>没有账号?<span class="no-act" @click="toDoRegister">点此注册</span></p></div></div></div>
</template><script>
const ADMIN_ROLE = 1;
const USER_ROLE = 2;
const DELAY_TIME = 1300;
import request from "@/utils/request.js";
import { setToken } from "@/utils/storage.js";
import md5 from 'js-md5';
import Logo from '@/components/Logo.vue';
export default {name: "Login",components: { Logo },data() {return {act: '',pwd: '',code: '', // 新增:用于存储用户输入的验证码colorLogo: 'rgb(38,38,38)',captchaImage: '', // 新增:用于存储验证码图片的 Base64 编码encryptedCode: '' // 新增:用于存储加密的验证码}},created() {// 页面加载时初始化验证码this.refreshCaptcha();},methods: {// 跳转注册页面toDoRegister(){this.$router.push('/register');},// 刷新验证码async refreshCaptcha() {try {const { data } = await request.get(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/get`);this.captchaImage = `data:image/png;base64,${data.image}`;this.encryptedCode = data.encryptedCode;this.code = ''; // 刷新验证码时清空输入框} catch (error) {console.error('获取验证码出错:', error);}},async login() {if (!this.act || !this.pwd) {this.$swal.fire({title: '填写校验',text: '账号或密码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}if (!this.code) {this.$swal.fire({title: '填写校验',text: '验证码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}// 先验证验证码是否正确try {const { data } = await request.post(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/verify`, { code: this.code, encryptedCode: this.encryptedCode });if (data.code !== 200) {this.$swal.fire({title: '验证码错误',text: data.msg,icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});this.refreshCaptcha(); // 刷新验证码return;}} catch (error) {console.error('验证码验证请求错误:', error);this.$message.error('验证码验证出错,请重试!');this.refreshCaptcha(); // 刷新验证码return;}const hashedPwd = md5(md5(this.pwd));const paramDTO = { userAccount: this.act, userPwd: hashedPwd };try {const { data } = await request.post(`user/login`, paramDTO);if (data.code !== 200) {this.$swal.fire({title: '登录失败',text: data.msg,icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}setToken(data.data.token);// 使用Swal通知登录成功,延迟后跳转// this.$swal.fire({//     title: '登录成功',//     text: '即将进入系统...',//     icon: 'success',//     showConfirmButton: false,//     timer: DELAY_TIME,// });// 根据角色延迟跳转setTimeout(() => {const { role } = data.data;this.navigateToRole(role);}, DELAY_TIME);} catch (error) {console.error('登录请求错误:', error);this.$message.error('登录请求出错,请重试!');}},navigateToRole(role) {switch (role) {case ADMIN_ROLE:this.$router.push('/admin');break;case USER_ROLE:this.$router.push('/user');break;default:console.warn('未知的角色类型:', role);break;}},}
};
</script><style lang="scss" scoped>
* {user-select: none;
}
.login-container {width: 100%;min-height: 100vh;background-color: rgb(255, 255, 255);display: flex;/* 启用Flexbox布局 */justify-content: center;/* 水平居中 */align-items: center;/* 垂直居中 */flex-direction: column;/* 如果需要垂直居中,确保子元素也是这样排列 */.login-panel {width: 313px;height: auto;padding: 40px 30px 16px 30px;border-radius: 10px;box-shadow: 0 4px 6px rgba(36, 36, 36, 0.1), 0 1px 3px rgba(40, 40, 40, 0.06);.logo {margin: 10px 0 30px 0;}.act,.pwd {margin: 8px 0;height: 53px;line-height: 53px;width: 100%;padding: 0 8px;box-sizing: border-box;border: 1px solid rgb(232, 230, 230);border-radius: 5px;font-size: 18px;padding: 0 15px;margin-top: 13px;}.act:focus,.pwd:focus {outline: none;border: 1px solid rgb(206, 205, 205);transition: 1.2s;}.role {display: inline-block;color: rgb(30, 102, 147);font-size: 14px;padding-right: 10px;}}.login-btn {display: inline-block;text-align: center;border-radius: 3px;margin-top: 20px;height: 43px;line-height: 43px;width: 100%;background-color: rgb(155, 191, 93);font-size: 14px !important;border: none;color: rgb(250,250,250);padding: 0 !important;cursor: pointer;user-select: none;}.tip {margin: 20px 0;p {padding: 3px 0;margin: 0;font-size: 14px;color: #647897;i{margin-right: 3px;}span {color: #3b3c3e;border-radius: 2px;margin: 0 6px;}.no-act:hover{color: #3e77c2;cursor: pointer;}}}}
</style>

结果展示

相关文章:

在springboot加vue项目中加入图形验证码

后端 首先先要创建一个CaptchaController的类&#xff0c;可以在下面的代码中看到 在getCaptcha的方法里面写好了生成随机的4位小写字母或数字的验证码&#xff0c;然后通过BufferedImage类变为图片&#xff0c;顺便加上了干扰线。之后把图片转为Base64编码方便传给前端 为了…...

23. AI-大语言模型

文章目录 前言一、LLM1. 简介2. 工作原理和结构3. 应用场景4. 最新研究进展5. 比较 二、Transformer架构1. 简介2. 基本原理和结构3. 应用场景4. 最新进展 三、开源1. 开源概念2. 开源模式3. 模型权重 四、再谈DeepSeek 前言 AI‌ 一、LLM LLM&#xff08;Large Language Mod…...

蓝桥杯备赛笔记(二)

这里的笔记是关于蓝桥杯关键知识点的记录&#xff0c;有别于基础语法&#xff0c;很多内容只要求会用就行&#xff0c;无需深入掌握。 文章目录 前言一、时间复杂度1.1 时间复杂度⭐1.2 空间复杂度1.3 分析技巧 二、枚举2.1 枚举算法介绍2.2 解空间的类型2.3 循环枚举解空间 三…...

MATLAB中的APPdesigner绘制多图问题解析?与逻辑值转成十进制

在matlab APPdesigner中绘图可以用UIAxes组件进行绘图&#xff0c;但是当想多张图时&#xff0c;只能提前绘制图像区域不方便。下面是几种办法&#xff1a; 为了操作可以添加Panl组件&#xff0c;方便操作。 1、当是要求的几个图像大小都是相同时刻采用函数&#xff1a; til…...

Spring Cloud-Sentinel

Sentinel服务熔断与限流 Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量控制、流量路由、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。 官网地址&#xff1a;home | Sentinelhttps://sen…...

Java中使用EasyExcel

Java中使用EasyExcel 文章目录 Java中使用EasyExcel一&#xff1a;EasyExcel介绍1.1、核心函数导入数据导出数据 1.2、项目实际应用导入数据导出数据 1.3、相关注解ExcelProperty作用示例 二&#xff1a;EasyExcel使用2.1、导入功能2.2、导出功能 三&#xff1a;EasyExcel完整代…...

Linux中退出vi编辑器的命令

在Linux中退出vi编辑器的命令有以下几种‌&#xff1a; ‌保存并退出‌&#xff1a;在命令模式下&#xff0c;按下Esc键退出插入模式&#xff0c;然后输入:wq或:x&#xff0c;按下回车键即可保存修改并退出vi编辑器‌。 ‌不保存退出‌&#xff1a;在命令模式下&#xff0c;按…...

建筑兔零基础自学python记录18|实战人脸识别项目——视频检测07

本次要学视频检测&#xff0c;我们先回顾一下图片的人脸检测建筑兔零基础自学python记录16|实战人脸识别项目——人脸检测05-CSDN博客 我们先把上文中代码复制出来&#xff0c;保留红框的部分。 ​ 然后我们来看一下源代码&#xff1a; import cv2 as cvdef face_detect_demo(…...

自定义基座实时采集uniapp日志

自定义基座实时采集uniapp日志 打测试包给远端现场(测试/客户)实际测试时也能实时看到日志了&#xff0c;也有代码行数显示。 流程设计 #mermaid-svg-1I5W9r1DU4xUsaTF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid…...

【java】方法:遍历/求最大值/判断是否存在

1.遍历 public class ArrayTraversal {// 定义一个静态方法用于遍历数组并在一行上显示元素public static void printArray(int[] arr) {for (int num:arr) {// 打印数组元素&#xff0c;不换行System.out.print(num" ");}// 遍历结束后换行System.out.println();}p…...

【ISO 14229-1:2023 UDS诊断全量测试用例清单系列:第六节】

ISO 14229-1:2023 UDS诊断服务测试用例全解析&#xff08;ReadDataByIdentifier0x22服务&#xff09; 作者&#xff1a;车端域控测试工程师 发布日期&#xff1a;2025年2月13日 关键词&#xff1a;UDS诊断协议、0x22服务、ReadDataByIdentifier、DID读取、ECU测试 一、服务功能…...

dedecms 开放重定向漏洞(附脚本)(CVE-2024-57241)

免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…...

AI知识库 - Cherry Studio

1 引言&#xff1a; 最近 DeepSeek 很火啊&#xff0c;想必大家都知道&#xff0c;DeepSeek 这个开源的模型出来后&#xff0c;因其高质量能力和R1 的思维链引发了大家本地部署的热潮。我也不例外&#xff0c;本地部署了一个 14B 的模型&#xff0c;然后把&#xff0c;感觉傻傻…...

20250213 隨筆 雪花算法

雪花算法&#xff08;Snowflake Algorithm&#xff09; 雪花算法&#xff08;Snowflake&#xff09; 是 Twitter 在 2010 年開發的一種 分布式唯一 ID 生成算法&#xff0c;它可以在 高併發場景下快速生成全局唯一的 64-bit 長整型 ID&#xff0c;且不依賴資料庫&#xff0c;具…...

(前端基础)HTML(一)

前提 W3C:World Wide Web Consortium&#xff08;万维网联盟&#xff09; Web技术领域最权威和具有影响力的国际中立性技术标准机构 其中标准包括&#xff1a;机构化标准语言&#xff08;HTML、XML&#xff09; 表现标准语言&#xff08;CSS&#xff09; 行为标准&#xf…...

pdf.js默认显示侧边栏和默认手形工具

文章目录 默认显示侧边栏(切换侧栏)默认手形工具(手型工具) 大部分的都是在viewer.mjs中的const defaultOptions 变量设置默认值,可以使用数字也可以使用他们对应的变量枚举值 默认显示侧边栏(切换侧栏) 在viewer.mjs中找到defaultOptions,大概在732行,或则搜索sidebarViewOn…...

学习总结三十三

括号序列 如果它是一个右括号&#xff0c;考察它与它左侧离它最近的未匹配的的左括号。如果该括号与之对应&#xff08;即小括号匹配小括号&#xff0c;中括号匹配中括号&#xff09;&#xff0c;则将二者配对。简单理解&#xff0c;找到一个右括号&#xff0c;向左找一个左括号…...

微信小程序 - 组件

组件通信事件 父传子 父组件如果需要向子组件传递指定属性的数据&#xff0c;在 WXML 中需要使用数据绑定的方式 与普通的 WXML 模板类似&#xff0c;使用数据绑定&#xff0c;这样就可以向子组件的属性传递动态数据。 父组件如果需要向子组件传递数据&#xff0c;只需要两…...

如何在Ubuntu中切换多个PHP版本

在Ubuntu环境下实现PHP版本的灵活切换&#xff0c;是众多开发者与系统管理员的重要技能之一。下面&#xff0c;我们将深入探讨如何在Ubuntu系统中安装、配置及管理多个PHP版本&#xff0c;确保您的开发环境随心所欲地适应各类项目需求。 开始前的准备 确保您的Ubuntu系统保持…...

解决DeepSeek服务器繁忙问题

目录 解决DeepSeek服务器繁忙问题 一、用户端即时优化方案 二、高级技术方案 三、替代方案与平替工具&#xff08;最推荐简单好用&#xff09; 四、系统层建议与官方动态 用加速器本地部署DeepSeek 使用加速器本地部署DeepSeek的完整指南 一、核心原理与工具选择 二、…...

Huatuo热更新--安装HybridCLR

1.自行安装unity编辑器 支持2019.4.x、2020.3.x、2021.3.x、2022.3.x 中任一版本。推荐安装2019.4.40、2020.3.26、2021.3.x、2022.3.x版本。 根据你打包的目标平台&#xff0c;安装过程中选择必要模块。如果打包Android或iOS&#xff0c;直接选择相应模块即可。如果你想打包…...

flink cdc2.2.1同步postgresql表

目录 简要说明前置条件maven依赖样例代码 简要说明 在flink1.14.4 和 flink cdc2.2.1下&#xff0c;采用flink sql方式&#xff0c;postgresql同步表数据&#xff0c;本文采用的是上传jar包&#xff0c;利用flink REST api的方式进行sql执行。 前置条件 1.开启logical 确保你…...

Golang协程调度模型MPG

深入解析Golang协程调度模型MPG&#xff1a;原理、实践与性能优化 一、为什么需要MPG模型&#xff1f; 在传统操作系统调度中&#xff0c;线程作为CPU调度的基本单位存在两个根本性挑战&#xff1a;1&#xff09;内核线程上下文切换成本高昂&#xff08;约1-5μs&#xff09;…...

纪念日倒数日项目的实现-【纪念时刻-时光集】

纪念日/倒数日项目的实现## 一个练手的小项目&#xff0c;uniappnodemysql七牛云。 在如今快节奏的生活里&#xff0c;大家都忙忙碌碌&#xff0c;那些具有特殊意义的日子一不小心就容易被遗忘。今天&#xff0c;想给各位分享一个“纪念日”项目。 【纪念时刻-时光集】 一…...

WPF的MVVMLight框架

在NuGet中引入该库&#xff1a; MVVMLight框架中的命令模式的使用&#xff1a; <StackPanel><TextBox Text"{Binding Name}"/><TextBox Text"{Binding Title}"/><Button Content"点我" Command"{Binding ShowCommand…...

DeepSeek从入门到精通(清华大学)

​ DeepSeek是一款融合自然语言处理与深度学习技术的全能型AI助手&#xff0c;具备知识问答、数据分析、编程辅助、创意生成等多项核心能力。作为多模态智能系统&#xff0c;它不仅支持文本交互&#xff0c;还可处理文件、图像、代码等多种格式输入&#xff0c;其知识库更新至2…...

【DeepSeek】DeepSeek R1 本地windows部署(Ollama+Docker+OpenWebUI)

1、背景&#xff1a; 2025年1月&#xff0c;DeepSeek 正式发布 DeepSeek-R1 推理大模型。DeepSeek-R1 因其成本价格低廉&#xff0c;性能卓越&#xff0c;在 AI 行业引起了广泛关注。DeepSeek 提供了多种使用方式&#xff0c;满足不同用户的需求和场景。本地部署在数据安全、性…...

windows平台上 oracle简单操作手册

一 环境描述 Oracle 11g单机环境 二 基本操作 2.1 数据库的启动与停止 启动: C:\Users\Administrator>sqlplus / as sysdba SQL*Plus: Release 11.2.0.4.0 Production on 星期五 7月 31 12:19:51 2020 Copyright (c) 1982, 2013, Oracle. All rights reserved. 连接到:…...

【弹性计算】弹性计算的技术架构

弹性计算的技术架构 1.工作原理2.总体架构3.控制面4.数据面5.物理设施层 虽然弹性计算的产品种类越来越多&#xff0c;但不同产品的技术架构大同小异。下面以当前最主流的产品形态 —— 云服务器为例&#xff0c;探查其背后的技术秘密。 1.工作原理 云服务器通常以虚拟机的方…...

RAG(检索增强生成)落地:基于阿里云opensearch视线智能问答机器人与企业知识库

文章目录 一、环境准备二、阿里云opensearch准备1、产品文档2、准备我们的数据3、上传文件 三、对接1、对接文本问答 一、环境准备 # 准备python环境 conda create -n opensearch conda activate opensearch# 安装必要的包 pip install alibabacloud_tea_util pip install ali…...