前段時間上級要我弄一個登陸時候的圖片拖動驗證。相似於嗶哩嗶哩登陸的那種。在網上找了一波,發現有的要錢,有的是直接前臺的拖動驗證操做的。可是不清楚究竟是在前端驗證仍是後端驗證安全點。想來想去我以爲得在後端,原諒我原本是後端的被趕鴨子上架寫前端潛意識以爲後端更安全。如今記錄一下。javascript
平常不變的SSM框架。css
思路:後端圖片裁剪轉爲base64(裁剪的X軸起點存在session,原本是想存redis)-->前端展現(獲取前端拖動的距離ajax進後臺與session中X軸對比)html
就在兩步。前端
首先是圖片裁剪類(在網上找的一位大佬的本身改了一下):java
package com.image.yanzhen; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Date; import java.util.Iterator; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import sun.misc.BASE64Encoder; public class ImageCut { /** * 源圖片路徑名稱如:c:\1.jpg */ private String srcpath = "e:/poool.jpg"; /** * 剪切圖片存放路徑名稱.如:c:\2.jpg */ private String subpath = "e:/pool_end"; /** * jpg圖片格式 */ private static final String IMAGE_FORM_OF_JPG = "jpg"; /** * png圖片格式 */ private static final String IMAGE_FORM_OF_PNG = "png"; /** * 剪切點x座標 */ private int x; /** * 剪切點y座標 */ private int y; /** * 剪切點寬度 */ private int width; /** * 剪切點高度 */ private int height; public ImageCut() { } public ImageCut(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } public static void main(String[] args) throws Exception { ImageCut imageCut = new ImageCut(134, 0, 366, 366); imageCut.cut(imageCut.getSrcpath(), imageCut.getSubpath()); } /** * 返回包含全部當前已註冊 ImageReader 的 Iterator,這些 ImageReader 聲稱可以解碼指定格式。 * 參數:formatName - 包含非正式格式名稱 .(例如 "jpeg" 或 "tiff")等 。 * * @param postFix * 文件的後綴名 * @return */ public Iterator<ImageReader> getImageReadersByFormatName(String postFix) { switch (postFix) { case IMAGE_FORM_OF_JPG: return ImageIO.getImageReadersByFormatName(IMAGE_FORM_OF_JPG); case IMAGE_FORM_OF_PNG: return ImageIO.getImageReadersByFormatName(IMAGE_FORM_OF_PNG); default: return ImageIO.getImageReadersByFormatName(IMAGE_FORM_OF_JPG); } } /** * 對圖片裁剪,並把裁剪完蛋新圖片保存 。 * @param srcpath 源圖片路徑 * @param subpath 剪切圖片存放路徑 * @throws IOException */ public String cut(String srcpath, String subpath) throws IOException { FileInputStream is = null; ImageInputStream iis = null; try { // 讀取圖片文件 is = new FileInputStream(srcpath); // 獲取文件的後綴名 String postFix = getPostfix(srcpath); System.out.println("圖片格式爲:" + postFix); /* * 返回包含全部當前已註冊 ImageReader 的 Iterator,這些 ImageReader 聲稱可以解碼指定格式。 * 參數:formatName - 包含非正式格式名稱 .(例如 "jpeg" 或 "tiff")等 。 */ Iterator<ImageReader> it = getImageReadersByFormatName(postFix); ImageReader reader = it.next(); // 獲取圖片流 iis = ImageIO.createImageInputStream(is); /* * <p>iis:讀取源.true:只向前搜索 </p>.將它標記爲 ‘只向前搜索’。 * 此設置意味着包含在輸入源中的圖像將只按順序讀取,可能容許 reader 避免緩存包含與之前已經讀取的圖像關聯的數據的那些輸入部分。 */ reader.setInput(iis, true); /* * <p>描述如何對流進行解碼的類<p>.用於指定如何在輸入時從 Java Image I/O * 框架的上下文中的流轉換一幅圖像或一組圖像。用於特定圖像格式的插件 將從其 ImageReader 實現的 * getDefaultReadParam 方法中返回 ImageReadParam 的實例。 */ ImageReadParam param = reader.getDefaultReadParam(); /* * 圖片裁剪區域。Rectangle 指定了座標空間中的一個區域,經過 Rectangle 對象 * 的左上頂點的座標(x,y)、寬度和高度能夠定義這個區域。 */ Rectangle rect = new Rectangle(x, y, width, height); // 提供一個 BufferedImage,將其用做解碼像素數據的目標。 param.setSourceRegion(rect); /* * 使用所提供的 ImageReadParam 讀取經過索引 imageIndex 指定的對象,並將 它做爲一個完整的 * BufferedImage 返回。 */ BufferedImage bi = reader.read(0, param); ByteArrayOutputStream CatBaos = new ByteArrayOutputStream();//io流 ImageIO.write(bi, "jpg", CatBaos);//寫入流中 byte[] CutBytes = CatBaos.toByteArray();//轉換成字節 BASE64Encoder encoder = new BASE64Encoder(); String CutPng_base64 = encoder.encodeBuffer(CutBytes).trim();//轉換成base64串 CutPng_base64 = CutPng_base64.replaceAll("\n", "").replaceAll("\r", "");//刪除 \r\n // String path = subpath + "_" + new Date().getTime() + "." + postFix; // // File file = new File(path); // File fileParent = file.getParentFile(); // if(!fileParent.exists()){ // System.out.println("自動建立文件"); // fileParent.mkdirs(); // } // file.createNewFile(); // // // 保存新圖片 // ImageIO.write(bi, postFix, new File(path)); return CutPng_base64; } finally { if (is != null) is.close(); if (iis != null) iis.close(); } } /** * 獲取inputFilePath的後綴名,如:"e:/test.pptx"的後綴名爲:"pptx"<br> * * @param inputFilePath * @return */ public String getPostfix(String inputFilePath) { return inputFilePath.substring(inputFilePath.lastIndexOf(".") + 1); } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public String getSrcpath() { return srcpath; } public void setSrcpath(String srcpath) { this.srcpath = srcpath; } public String getSubpath() { return subpath; } public void setSubpath(String subpath) { this.subpath = subpath; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }
再下來就是裁剪了(代碼辣雞,你們看看就好)jquery
package com.image.yanzhen; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Random; import javax.imageio.ImageIO; import com.attendance.utils.PathUtil; import sun.misc.BASE64Encoder; public class ImgCutTest { public Map<Object,Object> getBase() throws FileNotFoundException, IOException { Map<Object,Object> map = new HashMap<Object,Object>(); Random rand = new Random(); int ImgIndex = rand.nextInt(6); //生成0-6之內的隨機數 if(ImgIndex==0){ ImgIndex = 1; } String path = PathUtil.getClasspath()+"baseImg/"+ImgIndex+".jpg"; System.out.println("圖片路徑-->"+path); File picture = new File(path); BufferedImage sourceImg = ImageIO.read(new FileInputStream(picture)); /*************************裁剪圖片得到base64*************************/ int CJX = rand.nextInt(700); //生成0-700之內的隨機數 int CJY = rand.nextInt(480); //生成0-480之內的隨機數 System.out.println("隨機x起點-->"+CJX); System.out.println("隨機y起點-->"+CJY); if(CJX<200){ System.out.println("太少了"); CJX = CJX + 200; System.out.println("增長後-->"+CJX); } int CutX1 = CJX; //裁剪X軸起點 int CutY1 = CJY; //裁剪Y軸起點 int CutW1 = 200; //裁剪寬度 int CutH1 = 120; //裁剪高度 System.out.println(CutX1+"、"+CutY1+"、"+CutW1+"、"+CutH1); ImageCut imageCut1 = new ImageCut(CutX1, CutY1, CutW1, CutH1); String CutPng_base64 = imageCut1.cut(path, null); /********************************************************/ /*************************生成數組*************************/ int[][] data = new int[sourceImg.getWidth()][sourceImg.getHeight()]; for (int i=0;i<sourceImg.getWidth();i++){//1280 for(int j=0;j<sourceImg.getHeight();j++){//720 if(i<CJX+200&&i>=CJX&&j<CJY+120&&j>CJY){ data[i][j]=1; }else { data[i][j]=0; } } } /*********************************************************/ /************************圖片局部變黑************************/ for (int i = 0; i < sourceImg.getWidth(); i++) { for (int j = 0; j < sourceImg.getHeight(); j++) { int rgb = data[i][j]; // 原圖中對應位置變色處理 int rgb_ori = sourceImg.getRGB(i, j); if (rgb == 1) { //顏色處理 int r = (0XFF000000 & rgb_ori); int g = (0XFF000000 & (rgb_ori >> 8)); int b = (0XFF000000 & (rgb_ori >> 16)); int Gray = (r*2 + g*5 + b*1) >> 3; //原圖對應位置顏色變化 sourceImg.setRGB( i, j, Gray); } } } /**********************************************************/ /************************陰影圖片轉base64************************/ BASE64Encoder encoder = new BASE64Encoder(); ByteArrayOutputStream YYbaos = new ByteArrayOutputStream();//io流 ImageIO.write(sourceImg, "jpg", YYbaos);//寫入流中 byte[] bytes = YYbaos.toByteArray();//轉換成字節 String YYPng_base64 = encoder.encodeBuffer(bytes).trim();//轉換成base64串 YYPng_base64 = YYPng_base64.replaceAll("\n", "").replaceAll("\r", "");//刪除 \r\n /**********************************************************/ map.put("CJX", CJX); //裁剪開始X座標 map.put("CJY", CJY); //裁剪開始Y座標 map.put("YYPng_base64", YYPng_base64); //陰影圖片base map.put("CutPng_base64", CutPng_base64);//裁剪圖片base return map; } }
最重要的就是上面ajax
這個兩個了。redis
而後是controller調用圖片裁剪並返回前臺json
/** * 去圖片驗證頁面 * @param session * @return * @throws IOException */ @RequestMapping(value="/GoUploadImg.do",method = RequestMethod.GET) @ResponseBody public Object IndexGoLogin(HttpSession session) throws IOException{ System.out.println("進入圖片上傳頁面"); Map<Object,Object> map = new HashMap<Object,Object>(); map = new ImgCutTest().getBase(); String uuid = UuidUtil.get32UUID(); //前臺圖片展現爲原圖的一半 int CJX = (int) map.get("CJX")/2; session.setAttribute(uuid, CJX); map.put("uuid", uuid); return JSONArray.toJSONString(map); }
接下來就是前臺的一些展現,和拖動的一些東西。後端
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!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"> <title>Insert title here</title> </head> <script type="text/javascript" charset="utf-8" src="statics/jquery-3.2.1.min.js"></script> <link rel="stylesheet" href="statics/layui/css/layui.css" type="text/css"/> <script type="text/javascript" src="statics/layui/layui.js"></script> <script> function getYZ(){ $.ajax({ type:"GET", url:"GoUploadImg.do", data:{}, dataType:"json", success:function(data){ console.log(data); $("#YZUUID").val(data.uuid); $("#JQX").html(data.CJX); $("#JQY").html(data.CJY); $("#JQIMG").html("<img src=\"data:image/jpeg;base64,"+data.CutPng_base64+"\"/>"); $("#YYIMG").html("<img src=\"data:image/jpeg;base64,"+data.YYPng_base64+"\"/>"); $("#YZDIVIMG").html("<img style=\"max-width:100%;max-height:100%;\" src=\"data:image/jpeg;base64,"+data.YYPng_base64+"\"/>"); $("#CJDIVIMG").html("<img style=\"max-width:100%;max-height:100%;\" src=\"data:image/jpeg;base64,"+data.CutPng_base64+"\"/>"); //移動上下位置 var MarTop = data.CJY/2+20+"px"; $("#CJDIVIMG").css('margin-top',MarTop); },error:function(data){//當訪問是,404,500,等非200錯誤狀態碼 alert("親的網絡忽然出錯了呢!請稍後刷新再操做!"); } }); } $(document).ready(function(){ var w = 450; var PL_Size = 100; //缺失拼圖的大小 var padding = 0; //缺失拼圖與邊框的距離 // 滑塊拖動 var moveStart = '';//定義一個鼠標按下的X軸值 //鼠標按下 $(".slider-btn").mousedown(function(e){ e = e || window.event; // 鼠標在滑塊按下切換滑塊背景 $(this).css({ "background-position":"0 -216px" }); moveStart = e.pageX;//記錄鼠標按下時的座標 X軸值 }); //鼠標拖動(這裏使用全局監聽鼠標移動的事件) onmousemove = function(e) { e = e || window.event; var moveX = e.pageX;//監聽鼠標的位置 var d = moveX-moveStart; //鼠標按住後在X軸上移動的距離 if(moveStart == '') { // console.log('未拖動滑塊'); } else { if(d<0 || d>(w-padding-PL_Size)) { // console.log('超過範圍'); } else { var OtherD = d+20; $(".slider-btn").css({ "left":d + 'px', "transition":"inherit" }); $("#CJDIVIMG").css({ "left":OtherD + 'px', "transition":"inherit" }); } } }; //鼠標鬆開 (這裏使用全局監聽鼠標鬆開的事件) onmouseup = function (e) { e = e || window.event; var moveEnd_X = e.pageX - moveStart;//鬆開鼠標後滑塊移動的距離 if(moveStart == '') { } else { var uuid = $("#YZUUID").val(); $.ajax({ type:"POST", url:"YanZhenX.do", data:{uuid:uuid,moveEnd_X:moveEnd_X}, dataType:"json", success:function(data){ console.log(data); if(data.YZ=="yes"){ $("#YZDExpress").html("<i class=\"layui-icon layui-icon-ok-circle\" style=\"font-size: 18px; color: green;\"><i style=\"font-size:15px;\">驗證經過 </i></i><font style=\"font-size:15px;\">你的速度飛快,超過絕大多數人</font>"); }else{ $("#YZDExpress").html("<i class=\"layui-icon layui-icon-close-fill\" style=\"font-size: 18px; color: red;\"><i style=\"font-size:15px;\">驗證失敗 </i></i><font style=\"font-size:15px;\">拖動滑塊將懸浮圖像正確拼接</font>"); } },error:function(data){//當訪問是,404,500,等非200錯誤狀態碼 alert("親的網絡忽然出錯了呢!請稍後刷新再操做!"); } }); } setTimeout(function () { $(".slider-btn").css({ "left":'0', "transition":"left 0.5s" }); $("#CJDIVIMG").css({ "left":'20px', "transition":"left 0.5s" }); $("#YZDExpress").html(""); },1000); $(".slider-btn").css({ "background-position":"0 -84px" }); moveStart = '';// 清空上一次鼠標按下時的座標X軸值; } }); </script> <style> .YZDIV{ width: 490px; height:360px; border: solid #E7E3DA thin; background-color: #F4ECE3; border-radius: 20px; text-align: center; } .YYIMG{ width: 450px; height:300px; border: none; background-color: #ECE4DD; margin:0 auto; margin-top:20px; } .CJIMG{ width: 100px; height:60px; border: #D3D664 solid thin; background-color: #ECE4DD; z-index: 200; position: absolute; left:20px; } .slider-btn { position:absolute; width:44px; height:44px; left:0; top:-7px; z-index:12; cursor:pointer; background-image:url("statics/image/sprite.3.2.0.png"); background-position:0 -84px; transition:inherit; } .layui-icon-refresh-3:HOVER { color: green; } </style> <body onload="getYZ()"> <!-- <button onclick="getYZ()">驗證素材</button> --> <input type="hidden" id="YZUUID"/> <div class="YZDIV"> <div class="CJIMG" id="CJDIVIMG"></div> <div class="YYIMG" id="YZDIVIMG"></div> <i onclick="getYZ()" class="layui-icon layui-icon-refresh-3" style="font-size: 18px;float: left;line-height: 40px;margin-left: 12px;cursor: pointer;" title="刷新驗證"></i> <i id="YZDExpress" style="line-height: 40px;"></i> </div> <br/> <div style="position:relative;width:490px;"> <div style="border:1px solid #c3c3c3;border-radius:24px;background:#ece4dd;box-shadow:0 1px 1px rgba(12,10,10,0.2) inset;"> <p style="-moz-user-select: none; -khtml-user-select: none; user-select: none;font-size:12px;color: #486c80;line-height:28px;margin:0;text-align:right;padding-right:22px;text-align: center;">按住左邊滑塊,拖動完成上方拼圖</p> </div> <div class="slider-btn" id="ANNIU"></div> </div> <br/><br/><br/><br/> 截取X起點:<div id="JQX"></div><br/> 截取Y起點:<div id="JQY"></div><br/> 截取圖片:<div id="JQIMG"></div><br/> 陰影圖片:<div id="YYIMG"></div><br/> </body> </html>
這邊拖動在鼠標鬆開的時候會回後臺進行驗證
/** * 滑動驗證 * @param uuid 標識符 * @param moveEnd_X 滑動距離 * @param session * @return * @throws IOException */ @RequestMapping(value="/YanZhenX.do",method = RequestMethod.POST) @ResponseBody public Object YanZhenX(@RequestParam String uuid,@RequestParam int moveEnd_X,HttpSession session) throws IOException{ System.out.println("進行驗證"); Map<Object,Object> map = new HashMap<Object,Object>(); int CJX = (int) session.getAttribute(uuid); System.out.println("uuid-->"+uuid); System.out.println("滑動x距離-->"+moveEnd_X); System.out.println("裁剪距離-->"+CJX); if(moveEnd_X>CJX-3&&moveEnd_X<CJX+3){ //誤差在3之類 System.out.println("拼接成功"); map.put("YZ", "yes"); }else{ System.out.println("誤差過大"); map.put("YZ", "no"); } return JSONArray.toJSONString(map); }
大體的就這麼個樣子。
放幾張效果圖
好了,就這些了。有問題請多多指點。對了那些圖片我都是直接選的900*600的
源碼也放一下下。你們想看的能夠下載,雖然基本的代碼都在上面了