zoukankan      html  css  js  c++  java
  • 仿12306的图片验证码

    由于要做一个新项目,所以打算做一个简单的图片验证码。

    先说说思路吧:在服务端,从一个文件夹里面找出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>
            <div id="base">
                <img src="<%=request.getContextPath()%>/identify" style=" 300px; height: 150px;" onclick="clickImg(event)" id="bigPicture">
            </div>
            
        </div>

    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();
    

     验证码整体代码完成了,还有有一些细节要处理。由于图片容易被百度识图,要对生成的图片做模糊处理,暂时还没想到什么好的办法~

  • 相关阅读:
    漫谈C语言结构体
    如何理解c和c++的复杂类型声明
    STM32 时钟系统
    STM32 系统架构
    运放参数的详细解释和分析-part1,输入偏置电流和输入失调电流【转】
    ROM、RAM、DRAM、SRAM、FLASH的区别?
    FATFS模块应用笔记
    关于I2C和SPI总线协议【转】
    USB编程概念
    Ubuntu手机识别
  • 原文地址:https://www.cnblogs.com/qldhlbs/p/5386679.html
Copyright © 2011-2022 走看看