Java仿12306图片验证码
作者:青狼的华丽变身 发布时间:2022-09-29 05:36:08
由于要做一个新项目,所以打算做一个简单的图片验证码。
先说说思路吧:在服务端,从一个文件夹里面找出8张图片,再把8张图片合并成一张大图,在8个小图里面随机生成一个要用户验证的图片分类,如小狗、啤酒等。在前端,访问这个页面时,把图片加载上去,用户在图片上选择提示所需要的图片,当用户点登陆时,根据用户选择的所有坐标判断所选的图片是不是实际上的验证图片。
先放两张效果图:
为了让文件查找比较简单,在图片文件结构上可以这样:
这样方便生成用户要选择的Key图片,和取出8张小图合并成大图。
上代码:这是选择8张图片,并且在每张图片选取时用递归保证选取的图片不会重复。
//选取8个图片
public static List<Object> getEightImages() {
//保存取到的每一个图片的path,保证图片不会重复
List<String> paths = new ArrayList<String>();
File[] finalImages = new File[8];
List<Object> object = new ArrayList<Object>();
//保存tips
String[] tips = new String[8];
for (int i = 0; i < 8; i++) {
//获取随机的二级目录
int dirIndex = getRandom(secondaryDirNumbers);
File secondaryDir = getFiles()[dirIndex];
//随机到的文件夹名称保存到tips中
tips[i] = secondaryDir.getName();
//获取二级图片目录下的文件
File[] images = secondaryDir.listFiles();
int imageIndex = getRandom(imageRandomIndex);
File image = images[imageIndex];
//图片去重
image = dropSameImage(image, paths, tips, i);
paths.add(image.getPath());
finalImages[i] = image;
}
object.add(finalImages);
object.add(tips);
return object;
}
在生成这8张图片中,用一个数组保存所有的文件分类。在这个分类里面可以用随机数选取一个分类做为Key分类,就是用户要选择的所有图片。由于数组是有序的,可以遍历数组中的元素,获取每个key分类图片的位置,方便在用户验证时,进行匹配。
//获取位置,返回的是第几个图片,而不是下标,从1开始,集合第一个元素为tip
public static List<Object> getLocation(String[] tips) {
List<Object> locations = new ArrayList<Object>();
//获取Key分类
String tip = getTip(tips);
locations.add(tip);
int length = tips.length;
for (int i = 0; i < length; i++) {
if (tip.equals(tips[i])) {
locations.add(i+1);
}
}
return locations;
}
选取了8张图片后,接下来就是合并图片。合并图片可以用到BufferedImage这个类的方法:setRGB()这个方法如果不明白可以看下api文档,上面有详细的说明。
public static void mergeImage(File[] finalImages, HttpServletResponse response) throws IOException {
//读取图片
BufferedImage mergeImage = new BufferedImage(800, 400, BufferedImage.TYPE_INT_BGR);
for (int i = 0; i < 8; i++) {
File image = finalImages[i];
BufferedImage bufferedImage = ImageIO.read(image);
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
//从图片中读取RGB
int[] imageBytes = new int[width*height];
imageBytes = bufferedImage.getRGB(0, 0, width, height, imageBytes, 0, width);
if ( i < 4) {
mergeImage.setRGB(i*200, 0, width, height, imageBytes, 0, width);
} else {
mergeImage.setRGB((i -4 )*200, 200, width, height, imageBytes, 0, width);
}
}
ImageIO.write(mergeImage, "jpg", response.getOutputStream());
//ImageIO.write(mergeImage, "jpg", destImage);
}
在controller层中,先把key分类保存到session中,为用户选择图片分类做提示和图片验证做判断。然后把图片流输出到response中,就可以生成验证图片了。
response.setContentType("image/jpeg");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
List<Object> object = ImageSelectedHelper.getEightImages();
File[] finalImages = (File[]) object.get(0);
String[] tips = (String[]) object.get(1);
//所有key的图片位置,即用户必须要选的图片
List<Object> locations = ImageSelectedHelper.getLocation(tips);
String tip = locations.get(0).toString();
System.out.println(tip);
session.setAttribute("tip", tip);
locations.remove(0);
int length = locations.size();
for (int i = 0; i < length; i++) {
System.out.println("实际Key图片位置:" + locations.get(i));
}
session.setAttribute("locations", locations);
ImageMerge.mergeImage(finalImages, response);
在jsp中,为用户的点击生成小图片标记。当用户点图片击时,在父div上添加一个子div标签,并且把他定位为relative, 并且设置zIndex,然后再这个div上添加一个img标签,定位为absolute。在用户的点击时,可以获取点击事件,根据点击事件获取点击坐标,然后减去父div的坐标,就可以获取相对坐标。可以根据自己的喜好定坐标原点,这里的坐标原点是第8个图片的右下角。
<div><br> <div id="base"><br> <img src="<%=request.getContextPath()%>/identify" style="width: 300px; height: 150px;" onclick="clickImg(event)" id="bigPicture"><br> </div><br> <br> </div><br><br>function clickImg(e) {
var baseDiv = document.getElementById("base");
var topValue = 0;
var leftValue = 0;
var obj = baseDiv;
while (obj) {
leftValue += obj.offsetLeft;
topValue +=obj.offsetTop;
obj = obj.offsetParent;
}
//解决firefox获取不到点击事件的问题
var clickEvent = e ? e : (window.event ? window.event : null);
var clickLeft = clickEvent.clientX + document.body.scrollLeft - document.body.clientLeft - 10;
var clickTop = clickEvent.clientY + document.body.scrollTop - document.body.clientTop - 10;
var divId = "img_" + index++;
var divEle = document.createElement("div");
divEle.setAttribute("id", divId);
divEle.style.position = "relative";
divEle.style.zIndex = index;
divEle.style.width = "20px";
divEle.style.height = "20px";
divEle.style.display = "inline";
divEle.style.top = clickTop - topValue - 150 + 10 + "px";
divEle.style.left = clickLeft - leftValue - 300 + "px";
divEle.setAttribute("onclick", "remove('" + divId + "')");
baseDiv.appendChild(divEle);
var imgEle = document.createElement("img");
imgEle.src = "<%=request.getContextPath()%>/resources/timo.png";
imgEle.style.width = "20px";
imgEle.style.height = "20px";
imgEle.style.top = "0px";
imgEle.style.left = "0px";
imgEle.style.position = "absolute";
imgEle.style.zIndex = index;
divEle.appendChild(imgEle);
}
用户选择登录后,服务器端根据用户的选择坐标进行判断
public List<Integer> isPass(String result) {
String[] xyLocations = result.split(",");
//保存用户选择的坐标落在哪些图片上
List<Integer> list = new ArrayList<Integer>();
//每一组坐标
System.out.println("用户选择图片数:"+xyLocations.length);
for (String xyLocation : xyLocations) {
String[] xy = xyLocation.split("\\|\\|");
int x = Integer.parseInt(xy[0]);
int y = Integer.parseInt(xy[1]);
//8,4图片区间
if ( x > -75 && x <= 0) {
if ( y > -75 && y <= 0) { //8号
list.add(8);
} else if ( y >= -150 && y <= -75 ) { //4号
list.add(4);
}
} else if ( x > -150 && x <= -75) { //7,3图片区间
if ( y > -75 && y <= 0) { //7号
list.add(7);
} else if ( y >= -150 && y <= -75 ) { //3号
list.add(3);
}
} else if ( x > -225 && x <= -150) { //6,2图片区间
if ( y > -75 && y <= 0) { //6号
list.add(6);
} else if ( y >= -150 && y <= -75 ) { //2号
list.add(2);
}
} else if ( x >= -300 && x <= -225) { //5,1图片区间
if ( y > -75 && y <= 0) { //5号
list.add(5);
} else if ( y >= -150 && y <= -75 ) { //1号
list.add(1);
}
} else {
return null;
}
}
return list;
}
刷新生成新的图片,由于ajax不支持二进制流,可以自己用原生的xmlHttpRequest对象加html5的blob来完成。
function refresh() {
var url = "<%=request.getContextPath()%>/identify";
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = "blob";
xhr.onload = function() {
if (this.status == 200) {
var blob = this.response;
//加载成功后释放blob
bigPicture.onload = function(e) {
window.URL.revokeObjectURL(bigPicture.src);
};
bigPicture.src = window.URL.createObjectURL(blob);
}
}
xhr.send();
验证码整体代码完成了,还有有一些细节要处理。
猜你喜欢
- 双向循环链表定义相比于单链表,有两个指针,next指针指向下一个结点,prior指针指向上一个结点,最后一个结点的next指针指向头结点,头
- 实际上,按一定速度读取摄像头视频图像后,便可以对图像进行各种处理了。那么获取主要用到的是VideoCapture类,一个demo如下://如
- java API中提供了一个基于指针操作实现对文件随机访问操作的类,该类就是RandomAccessFile类,该类不同于其他很多基于流方式
- 本文主要实现在自定义的ListView布局中加入CheckBox控件,通过判断用户是否选中CheckBox来对ListView的选中项进行相
- 一、面向对象的描述面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的
- 在很多仿真和游戏应用中都需要大规模地形,这样会使3D环境似乎“无限大”,增加用户的真实感,比如飞行模拟游戏。那么在Unity中如何实现大规模
- 这几天在弄后端管理系统向指定的Android
- 1. 用indexof的方法:public class Test11 {private static int counter = 0;/**
- 本文实例总结了C#配置文件Section节点处理方法。分享给大家供大家参考。具体如下:很多时候在项目开发中,我们都需要用配置文件来存储一些关
- 主要使用的类:java.text.DecimalFormat1。实例化对象,可以用如下两种方法:DecimalFormat df=(Deci
- 这里记录Java中从控制台读入信息的几种方式,已备后查!(1)JDK 1.4(JDK 1.5和JDK 1.6也都兼容这种方法)public
- C++中,对于任意一个类,都会为我们提供4个默认的成员函数(如果我们不显示的去声明)——构造函数、析
- 1.前言热修复一直是这几年来很热门的话题,主流方案大致有两种,一种是微信Tinker的dex文件替换,另一种是阿里的Native层的方法替换
- 先上效果图,如果大家感觉不错,请参考实现代码。 重要的是如何实现自定义的
- 复习了下数据结构,用Java的数组实现一下循环队列。队列的类//循环队列class CirQueue{ private int QueueS
- 本文为大家分享了使用entrySet方法获取Map集合中元素的具体代码,供大家参考,具体内容如下/*--------------------
- 前言我们都知道memberwiseclone 会将浅克隆。什么是浅克隆?如何深克隆呢?正文public class good{
- 默认情况下,如果应用以 Android Q 为目标平台,则在访问外部存储设备中的文件时会进入过滤视图。应用可以使用 Context.getE
- Example官方介绍Query by Example (QBE) is a user-friendly querying techniqu
- 目前html5发展非常迅速,很多native app都会嵌入到网页中,以此来适用多变的市场需求。但是android的webview默认支持的