java + html 图片点击文字验证码
文章目录
- html资源文件下载地址:
- 效果图
- 需要代码
- 后台java
- 前台Html
html资源文件下载地址:
链接:https://pan.baidu.com/s/1f5gaZ36XKYjsbtGpN3dyeg?pwd=2cx2
提取码:2cx2
效果图
需要代码
后台java
1.创建CreateVerifyTextModel
```javaimport java.util.List;/*** @description 创建文字和坐标返回对象* @author liuhongrong* @since 2025/7/21 15:37* @desc*/
public class CreateVerifyTextModel {/*** 汉字*/private String verifyText;/*** 汉字坐标*/private List<String> codeList;public String getVerifyText() {return verifyText;}public void setVerifyText(String verifyText) {this.verifyText = verifyText;}public List<String> getCodeList() {return codeList;}public void setCodeList(List<String> codeList) {this.codeList = codeList;}
}
2.创建GraphicsUtil 图像背景和汉字
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Random;/*** @description 生成 图像* @author liuhongrong* @since 2025/7/21 14:19* @desc*/
public class GraphicsUtil {/*** 生成背景图片* @return*/public static BufferedImage getBackGround(HttpServletRequest request){// 读取本地图片String realPath=request.getServletContext().getRealPath("/cms");// 1. 生成 1-6 的随机数Random random = new Random();int randomNumber = random.nextInt(9) + 1; // 1 <= randomNumber <= 9File inputFile = new File(realPath+"/vscode/imaegs/"+randomNumber+".png");BufferedImage image = null;try {image = ImageIO.read(inputFile);} catch (IOException e) {throw new RuntimeException(e);}return image;}public static Color getRandColor(int s,int e){Random random = new Random();if(s>255)s = 255;if(e>255)e = 255;int r = s+random.nextInt(e-s);int g = s+random.nextInt(e-s);int b = s+random.nextInt(e-s);return new Color(r, g, b);}/*** 图片 线条色* @param random* @param fc* @param bc* @return*/public static Color getRandColor(Random random, int fc, int bc){if (fc > 255)fc = 255;if (bc > 255)bc = 255;int r = fc + random.nextInt(bc - fc);int g = fc + random.nextInt(bc - fc);int b = fc + random.nextInt(bc - fc);return new Color(r, g, b);}/*** 创建顶部图片,提示验证文字*/public static BufferedImage headVerifyText(BufferedImage image,String verifyText){// 创建顶部图片BufferedImage bi = new BufferedImage(image.getWidth(), 25, BufferedImage.TYPE_INT_RGB);Graphics gra = bi.getGraphics();// 设置背景颜色
// gra.setColor(Color.WHITE);gra.setColor(Color.LIGHT_GRAY);// 填充区域gra.fillRect(0, 0, bi.getWidth(), bi.getHeight());// 设置边框颜色gra.setColor(Color.lightGray);// 设置边框区域gra.drawRect(1, 1, bi.getWidth() - 2, bi.getHeight() - 2);// 设置文字背景颜色Font font = new Font("Microsoft YaHei", Font.BOLD, 16);gra.setFont(font);gra.setColor(Color.BLACK);gra.drawString("请依次点击:" + verifyText, (bi.getWidth() - 10*font.getSize())/2,bi.getHeight()/2 + font.getSize()/2);//设置文字字体 与位子 居中return bi;}/*** 生成验证汉字* @return*/public static String getRandomChineseChar() {String str = null;int hs, ls;Random random = new Random();hs = (176 + Math.abs(random.nextInt(39)));ls = (161 + Math.abs(random.nextInt(93)));byte[] b = new byte[2];b[0] = (new Integer(hs).byteValue());b[1] = (new Integer(ls).byteValue());try {str = new String(b, "GBK"); //转成中文} catch (UnsupportedEncodingException ex) {ex.printStackTrace();}return str;}
3.创建CreateVerifyText 创建验证的汉字坐标
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.*;/*** @description 创建验证的汉字坐标* @author liuhongrong* @since 2025/7/21 15:31* @desc*/
public class CreateVerifyText {//生成汉字的个数private static Integer[] arr = new Integer[] {1, 2, 3, 4, 5};//汉字颜色随机范围private static Color[] colors = {Color.ORANGE, Color.GREEN, Color.CYAN};public static CreateVerifyTextModel create(BufferedImage image){//生成汉字StringBuilder verifyText= new StringBuilder();List<String> codeList = new ArrayList<String>();int hight = image.getHeight();Random random = new Random();//转成集合List<Integer> intList = Arrays.asList(arr);//重新随机排序Collections.shuffle(intList);int x = 0;int y = 0;//定义随机1到arr.length某一个字不参与校验int num = random.nextInt(arr.length)+1;for (int i = 0; i < arr.length; i++) { // 5个汉字,只点4个String ch = GraphicsUtil.getRandomChineseChar();int place = intList.get(i);if (place == 1) {x = new Random().nextInt(30) + 40; // 自己定义的位子坐标y = new Random().nextInt(30) + 40; // i=1的时候,y的值}if (place == 2) {x = new Random().nextInt(40) + 120; // 自己定义的位子坐标y = new Random().nextInt(30) + 50; // i=2的时候,y的值}if (place == 3) {x = new Random().nextInt(70) + 200; // 自己定义的位子坐标y = new Random().nextInt(50) + 100; // i=3的时候,y的值}if (place == 4) {x = new Random().nextInt(70) + 80; // i=4的时候,x的值y = new Random().nextInt(30) + 90; // 自己定义的位子坐标}if (place == 5) {x = new Random().nextInt(70) + 180; // i=4的时候,x的值y = new Random().nextInt(30) + 50; // 自己定义的位子坐标}System.out.println("x:" + x + ",y:" + y + ",hight:" + hight);Graphics graphics = image.getGraphics();// 设置颜色graphics.setColor(Color.red);graphics.setFont(new Font("宋体", Font.BOLD, 30));//字体颜色graphics.setColor(colors[random.nextInt(colors.length)]);graphics.drawString(ch, x, y);if (place != num) {verifyText.append(ch);codeList.add(x + "_" + y); // jsp页面坐标原点在字的中间,drawString方法坐标原点在中间}}//返回对象CreateVerifyTextModel textmodel=new CreateVerifyTextModel();textmodel.setVerifyText(verifyText.toString());textmodel.setCodeList(codeList);return textmodel;}
}
4.创建 VerificationCodeAction
import com.zkjw.core.modules.verificationCode.CreateVerifyText;
import com.zkjw.core.modules.verificationCode.CreateVerifyTextModel;
import com.zkjw.core.modules.verificationCode.GraphicsUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @description 创建-验证码和校验-验证码是否正确* @author liuhongrong* @since 2025/7/21 14:10* @desc*/
public class VerificationCodeAction extends MultiActionController {private static final String KEY = "randomCode";//定义点选文字图片验证码允许的误差值private static final int ERROR_AMOUNT = 12;// 定义允许的误差值,单位是px/**生成验证码*/public void getVerificationCode(HttpServletRequest request,HttpServletResponse response) {ServletOutputStream outStream = null;try {//生成背景图片BufferedImage image = GraphicsUtil.getBackGround(request);//生成文字坐标CreateVerifyTextModel textmodel= CreateVerifyText.create(image);String verifyText= textmodel.getVerifyText();List<String> codeList = textmodel.getCodeList();System.out.println("需要点击的汉字:" + verifyText);//放入session//将产生的随机汉字验证码存进session中进行保存String sessionid = request.getSession().getId();request.getSession().setAttribute(sessionid + KEY, codeList);//增加验证码请求次数
// RedisValidImgCodeUtils.increment(KEY_TOTAL, CACHE_SECONDS);// 可以将图片合并传入前端 也可以直接传数据汉字给前端BufferedImage bi=GraphicsUtil.headVerifyText(image,verifyText);BufferedImage combined = new BufferedImage(image.getWidth(), image.getHeight() + bi.getHeight(), BufferedImage.TYPE_INT_RGB);Graphics graphicsH = combined.getGraphics(); //合并图片//生成提示放在上边-------------------------------------
// graphicsH.drawImage(bi, 0, 0, null);
// graphicsH.drawImage(image, 0, bi.getHeight(), null);//生成提示放在下边-------------------------------------graphicsH.drawImage(image, 0, 0, null);graphicsH.drawImage(bi, 0, image.getHeight(), null);outStream= response.getOutputStream();ImageIO.write(combined, "jpg", outStream);} catch (IOException e) {e.printStackTrace();} finally {try {if (outStream != null) {outStream.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 验证验证码* @throws IOException*/public void verifycode(HttpServletRequest request,HttpServletResponse response) throws IOException {//返回信息Map<String, Object> reultMap=new HashMap<>();reultMap.put("status", 0);reultMap.put("msg", "验证失败");String value = CMSUtil.base16ToStr(request.getParameter("code"));String sessionid = request.getSession().getId();Object verifyList= request.getSession().getAttribute(sessionid + KEY);if (verifyList == null) {responseWriter(reultMap, response);return;}List<String> sValue = (List<String>) verifyList;//取到数据后直接清掉redisrequest.getSession().removeAttribute(sessionid + KEY);System.out.println("**前端请求数据***"+value);System.out.println("**后端实际数据**"+sValue);//为null 或者"" 或者 " "if (StringUtils.isBlank(value) || sValue == null || sValue.size() < 1) {responseWriter(reultMap, response);return;}String [] valueStr = value.split(",");if(valueStr.length != sValue.size() || valueStr.length != 4){responseWriter(reultMap, response);return;}/*判断坐标参数是否正确*/String str = "";for (int i = 0; i < valueStr.length; i++) {str = valueStr[i].toString();if(StringUtils.isBlank(str) || StringUtils.isBlank(sValue.get(i).toString())){responseWriter(reultMap, response);return;}String [] vL = valueStr[i].toString().split("_");String [] svL = sValue.get(i).toString().split("_");if(vL.length != svL.length || svL.length != 2){responseWriter(reultMap, response);return;}//x轴 y轴判断 坐标点在左上角 ,图片宽度30px 点击范围扩大12px, 范围在 x-13 < x <x+13 ;if(!(Integer.parseInt(svL[0])-ERROR_AMOUNT < Integer.parseInt(vL[0])+15 && Integer.parseInt(vL[0])-+15 < Integer.parseInt(svL[0])+ERROR_AMOUNT )|| !(Integer.parseInt(svL[1])-ERROR_AMOUNT < Integer.parseInt(vL[1])+15 && Integer.parseInt(vL[1])+15 < Integer.parseInt(svL[1])+ERROR_AMOUNT)){//增加验证失败次数
// RedisValidImgCodeUtils.increment(KEY_FAIL, CACHE_SECONDS);responseWriter(reultMap, response);return;}}//增加验证通过次数
// RedisValidImgCodeUtils.increment(KEY_SUCC, CACHE_SECONDS);reultMap.put("status", 1);reultMap.put("msg", "验证成功");responseWriter(reultMap, response);}/*** 返回信息*/private void responseWriter(Map<String, Object> reultMap,HttpServletResponse response){try {response.setCharacterEncoding("UTF-8");response.getWriter().write(JsonUtil.jsonObjectToString(reultMap));} catch (IOException e) {throw new RuntimeException(e);}}
前台Html
1.Html页面
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/><title>验证码</title><script type="text/javascript">var appPath="/"</script><link href="css/vscode.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<div class="verify-box"><div class="verify-btn" onclick="openCaptchaPopup()"><span class="verify_tips__icon">点此进行验证</span></div>
</div>
<!-- 弹窗容器 -->
<div class="popup-overlay" id="captchaPopup"><div class="popup-content"><!-- 原有验证码内容 --><div style="text-align:center;position:relative;"><div style="position:relative;display:inline-block;"><!-- 验证码图片 --><img id="codeT3" src="/VerificationCodeAction.do?m=getVerificationCode&t=123456789" /><div class="verify-controls"><!-- 刷新图标 --><button class="verify_refresh" onclick="getCodeTree();" ></button><%--关闭--%><button class="verify_colse" onclick="closeCaptchaPopup();" ></button></div></div></br><select id="codeSelect" style="display: none;"></select></div></div>
</div>
<script src="js/jquery-1.7.js" type="text/javascript"></script>
<script src="js/vscode.js" type="text/javascript"></script>
</body>
</html>
2.js页面
//点击次数
var number=0;// 打开验证码弹窗
function openCaptchaPopup() {document.getElementById('captchaPopup').classList.add('show-popup');
}// 关闭验证码弹窗
function closeCaptchaPopup() {number = 0;$(".text-point").remove();document.getElementById('captchaPopup').classList.remove('show-popup');
}//获取验证码3
function getCodeTree() {number = 0;$(".text-point").remove();document.getElementById("codeSelect").options.length=0;var timestamp = new Date().getTime();$("#codeT3").attr("src",appPath+"/VerificationCodeAction.do?m=getVerificationCode&t=" + timestamp);
}$(function() {$("#codeT3").bind("click", function(ev) {var oEvent = ev || event;//var number = $("#codeSelect option").length;number++;if (number > 4) {return;}var x = oEvent.pageX;var y = oEvent.pageY;var img = document.getElementById('codeT3'); //获取图片的原点var nodex = getNodePosition(img)[0];//原点x 与原点yvar nodey = getNodePosition(img)[1];var xserver = parseInt(x) - parseInt(nodex);var yserver = parseInt(y) - parseInt(nodey);$("#codeSelect").append("<option value='"+ (parseInt(number)+1) +"'>" + xserver + "_" + yserver+ "</option>");var oDiv = document.createElement('img');oDiv.style.left = (parseInt(x)-15) + 'px'; // 指定创建的DIV在文档中距离左侧的位置 图片大小30 左右移动5oDiv.style.top = (parseInt(y) -15) + 'px'; // 指定创建的DIV在文档中距离顶部的位置oDiv.className = 'text-point';//加class 点刷新后删除遮罩document.body.appendChild(oDiv);//第四次点击后自动提交if (number == 4) {cheakOutTree();}});})//校验验证码
function cheakOutTree() {var txt = "";$("#codeSelect option").each(function (){var text = $(this).text();if(txt == ""){txt = text;}else{txt = txt + "," + text;}});$.ajax({type:"post",url:appPath+"/VerificationCodeAction.do?m=verifycode",data : {"code" : base16Str(txt) },cache : false,success : function(data) {var jsonObj = JSON.parse(data);layer.msg(jsonObj.msg);if (jsonObj.status == 0) {getCodeTree();}else{}}});
}function getNodePosition(node) {var top = left = 0;while (node) {if (node.tagName) {top = top + node.offsetTop;left = left + node.offsetLeft;node = node.offsetParent;}else {node = node.parentNode;}}return [left, top];
}
3.css页面
.verify-btn {padding: 6px 12px;border: 1px solid #ccc;background-color: #f5f5f5;cursor: pointer;border-radius: 4px;text-align: center; /* 文字水平居中 */margin-left: 8px;
}.verify_tips__icon{display: block;margin: 0 auto;width: 155px;height: 30px;line-height: 30px;vertical-align: middle;background: url(../../vscode/imaegs/icon_verify.png) no-repeat left;background-position: 0 -370px;
}.verify-btn:hover {background-color: #e9e9e9;
}
/* 弹窗遮罩层样式 */
.popup-overlay {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(0, 0, 0, 0.5);display: none;justify-content: center;align-items: center;z-index: 999;
}/* 弹窗内容样式 */
.popup-content {background-color: white;padding: 20px;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}/* 显示弹窗的类 */
.show-popup {display: flex;
}/* 触发弹窗的按钮样式 */
.open-popup-btn {padding: 8px 16px;background-color: #4285f4;color: white;border: none;border-radius: 4px;cursor: pointer;margin: 20px;
}
.open-popup-btn:hover {background-color: #3367d6;
}/* 透明背景容器样式 - 优化为完全透明 */
.verify-controls {position: absolute;top: 0;right: 0;background-color: transparent; /* 完全透明背景 */padding: 5px;z-index: 10; /* 确保在图片上方显示 */display: flex; /* 使用flex布局确保按钮并排显示 */gap: 2px; /* 按钮之间的间距 */
}
/* 刷新按钮样式 - 优化悬停效果 */
.verify_refresh{width: 30px;height: 30px;cursor: pointer;background-image: url(../../vscode/imaegs/icon_verify.png);background-position: 0 -680px;border: none;background-repeat: no-repeat;margin: 0;padding: 0;transition: transform 0.2s; /* 轻微缩放动画 */background-color: rgba(255, 255, 255, 0.2); /* 悬停时显示轻微背景 */
}
/* 关闭按钮样式 - 优化悬停效果 */
.verify_colse{width: 30px;height: 30px;cursor: pointer;background-image: url(../../vscode/imaegs/icon_verify.png);background-position:4px -526px;;border: none;background-repeat: no-repeat;margin: 0;padding: 0;transition: transform 0.2s; /* 轻微缩放动画 */background-color: rgba(255, 255, 255, 0.2); /* 悬停时显示轻微背景 */
}
/* 按钮悬停效果 */
.verify_refresh:hover, .verify_colse:hover {transform: scale(1.1); /* 悬停时轻微放大 */background-color: rgba(255, 255, 255, 0.2); /* 悬停时显示轻微背景 */border-radius: 3px; /* 圆角效果 */
}
.text-point{border:2px solid #FF0000;position:absolute;width: 30px;height: 30px;opacity: 0.7;border-radius:6px;box-shadow: 0 0 3px rgba(255, 82, 82, 0.2);z-index:1000;
}