仿12306的圖片驗證碼,仿12306圖片驗證碼
由於要做一個新項目,所以打算做一個簡單的圖片驗證碼。
先說說思路吧:在服務端,從一個檔案夾裡面找出8張圖片,再把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>();//儲存tipsString[] 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開始,集合第一個元素為tippublic 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>
<div id="base">
<img src="<%=request.getContextPath()%>/identify">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();
驗證碼整體程式碼完成了,還有有一些細節要處理。由於圖片容易被百度識圖,要對產生的圖片做模糊處理,暫時還沒想到什麼好的辦法~