Java仿12306圖片驗(yàn)證碼
由于要做一個(gè)新項(xiàng)目,所以打算做一個(gè)簡(jiǎn)單的圖片驗(yàn)證碼。
先說(shuō)說(shuō)思路吧:在服務(wù)端,從一個(gè)文件夾里面找出8張圖片,再把8張圖片合并成一張大圖,在8個(gè)小圖里面隨機(jī)生成一個(gè)要用戶驗(yàn)證的圖片分類,如小狗、啤酒等。在前端,訪問(wèn)這個(gè)頁(yè)面時(shí),把圖片加載上去,用戶在圖片上選擇提示所需要的圖片,當(dāng)用戶點(diǎn)登陸時(shí),根據(jù)用戶選擇的所有坐標(biāo)判斷所選的圖片是不是實(shí)際上的驗(yàn)證圖片。
先放兩張效果圖:

為了讓文件查找比較簡(jiǎn)單,在圖片文件結(jié)構(gòu)上可以這樣:

這樣方便生成用戶要選擇的Key圖片,和取出8張小圖合并成大圖。
上代碼:這是選擇8張圖片,并且在每張圖片選取時(shí)用遞歸保證選取的圖片不會(huì)重復(fù)。
//選取8個(gè)圖片
public static List<Object> getEightImages() {
//保存取到的每一個(gè)圖片的path,保證圖片不會(huì)重復(fù)
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++) {
//獲取隨機(jī)的二級(jí)目錄
int dirIndex = getRandom(secondaryDirNumbers);
File secondaryDir = getFiles()[dirIndex];
//隨機(jī)到的文件夾名稱保存到tips中
tips[i] = secondaryDir.getName();
//獲取二級(jí)圖片目錄下的文件
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張圖片中,用一個(gè)數(shù)組保存所有的文件分類。在這個(gè)分類里面可以用隨機(jī)數(shù)選取一個(gè)分類做為Key分類,就是用戶要選擇的所有圖片。由于數(shù)組是有序的,可以遍歷數(shù)組中的元素,獲取每個(gè)key分類圖片的位置,方便在用戶驗(yàn)證時(shí),進(jìn)行匹配。
//獲取位置,返回的是第幾個(gè)圖片,而不是下標(biāo),從1開(kāi)始,集合第一個(gè)元素為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張圖片后,接下來(lái)就是合并圖片。合并圖片可以用到BufferedImage這個(gè)類的方法:setRGB()這個(gè)方法如果不明白可以看下api文檔,上面有詳細(xì)的說(shuō)明。
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中,為用戶選擇圖片分類做提示和圖片驗(yàn)證做判斷。然后把圖片流輸出到response中,就可以生成驗(yàn)證圖片了。
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("實(shí)際Key圖片位置:" + locations.get(i));
}
session.setAttribute("locations", locations);
ImageMerge.mergeImage(finalImages, response);
在jsp中,為用戶的點(diǎn)擊生成小圖片標(biāo)記。當(dāng)用戶點(diǎn)圖片擊時(shí),在父div上添加一個(gè)子div標(biāo)簽,并且把他定位為relative, 并且設(shè)置zIndex,然后再這個(gè)div上添加一個(gè)img標(biāo)簽,定位為absolute。在用戶的點(diǎn)擊時(shí),可以獲取點(diǎn)擊事件,根據(jù)點(diǎn)擊事件獲取點(diǎn)擊坐標(biāo),然后減去父div的坐標(biāo),就可以獲取相對(duì)坐標(biāo)??梢愿鶕?jù)自己的喜好定坐標(biāo)原點(diǎn),這里的坐標(biāo)原點(diǎn)是第8個(gè)圖片的右下角?! ?/p>
<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獲取不到點(diǎn)擊事件的問(wèn)題
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);
}
用戶選擇登錄后,服務(wù)器端根據(jù)用戶的選擇坐標(biāo)進(jìn)行判斷
public List<Integer> isPass(String result) {
String[] xyLocations = result.split(",");
//保存用戶選擇的坐標(biāo)落在哪些圖片上
List<Integer> list = new ArrayList<Integer>();
//每一組坐標(biāo)
System.out.println("用戶選擇圖片數(shù):"+xyLocations.length);
for (String xyLocation : xyLocations) {
String[] xy = xyLocation.split("\\|\\|");
int x = Integer.parseInt(xy[0]);
int y = Integer.parseInt(xy[1]);
//8,4圖片區(qū)間
if ( x > -75 && x <= 0) {
if ( y > -75 && y <= 0) { //8號(hào)
list.add(8);
} else if ( y >= -150 && y <= -75 ) { //4號(hào)
list.add(4);
}
} else if ( x > -150 && x <= -75) { //7,3圖片區(qū)間
if ( y > -75 && y <= 0) { //7號(hào)
list.add(7);
} else if ( y >= -150 && y <= -75 ) { //3號(hào)
list.add(3);
}
} else if ( x > -225 && x <= -150) { //6,2圖片區(qū)間
if ( y > -75 && y <= 0) { //6號(hào)
list.add(6);
} else if ( y >= -150 && y <= -75 ) { //2號(hào)
list.add(2);
}
} else if ( x >= -300 && x <= -225) { //5,1圖片區(qū)間
if ( y > -75 && y <= 0) { //5號(hào)
list.add(5);
} else if ( y >= -150 && y <= -75 ) { //1號(hào)
list.add(1);
}
} else {
return null;
}
}
return list;
}
刷新生成新的圖片,由于ajax不支持二進(jìn)制流,可以自己用原生的xmlHttpRequest對(duì)象加html5的blob來(lái)完成。
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();
驗(yàn)證碼整體代碼完成了,還有有一些細(xì)節(jié)要處理。
更多關(guān)于驗(yàn)證碼的文章請(qǐng)點(diǎn)擊查看:《java驗(yàn)證碼》
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
- java 圖片驗(yàn)證碼的實(shí)現(xiàn)代碼
- java圖片驗(yàn)證碼生成教程詳解
- java生成圖片驗(yàn)證碼示例程序
- java生成圖片驗(yàn)證碼實(shí)例代碼
- Java實(shí)現(xiàn)圖片驗(yàn)證碼具體代碼
- Java實(shí)現(xiàn)驗(yàn)證碼具體代碼(圖片、漢字)
- Javaweb開(kāi)發(fā)中通過(guò)Servlet生成驗(yàn)證碼圖片
- java web中圖片驗(yàn)證碼功能的簡(jiǎn)單實(shí)現(xiàn)方法
- java生成圖片驗(yàn)證碼功能
- Java實(shí)現(xiàn)圖片驗(yàn)證碼功能
相關(guān)文章
java虛擬機(jī)鉤子關(guān)閉函數(shù)addShutdownHook的操作
這篇文章主要介紹了java虛擬機(jī)鉤子關(guān)閉函數(shù)addShutdownHook的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES
這篇文章主要介紹了IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Java實(shí)現(xiàn)把文件及文件夾壓縮成zip
這篇文章主要介紹了Java實(shí)現(xiàn)把文件及文件夾壓縮成zip,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
java.lang.NoSuchMethodException: com.sun.proxy.$Proxy58.list
這篇文章主要介紹了java.lang.NoSuchMethodException: com.sun.proxy.$Proxy58.list錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12
SpringBoot 如何實(shí)現(xiàn)異步編程
在SpringBoot的日常開(kāi)發(fā)中,一般都是同步調(diào)用的,但實(shí)際中有很多場(chǎng)景非常適合使用異步來(lái)處理,本文就詳細(xì)的介紹一下SpringBoot 如何實(shí)現(xiàn)異步編程 ,具有一定的參考價(jià)值,感興趣的可以了解一下2021-12-12
Spring Boot 安全 API 構(gòu)建之加密解密功能的實(shí)踐記錄
本文詳述了如何在SpringBoot3.3環(huán)境中實(shí)施API加密的最佳實(shí)踐,包括選擇合適的加密算法,密鑰管理,數(shù)據(jù)加密,防止加密漏洞,安全日志記錄,測(cè)試和監(jiān)控等方面,同時(shí),文章也對(duì)RSA非對(duì)稱加密和AES對(duì)稱加密的實(shí)現(xiàn)步驟進(jìn)行了詳細(xì)的解析2024-10-10
SpringBoot+RabbitMq具體使用的幾種姿勢(shì)
這篇文章主要介紹了SpringBoot+RabbitMq具體使用的幾種姿勢(shì),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
學(xué)習(xí)Java之IO流的基礎(chǔ)概念詳解
這篇文章主要給大家介紹了Java中的IO流,我們首先要搞清楚一件事,就是為什么需要IO流這個(gè)東西,但在正式學(xué)習(xí)IO流的使用之前,小編有必要帶大家先了解一下IO流的基本概念,需要的朋友可以參考下2023-09-09

