NETCTOSS代码实现第七版:登录模块之验证码功能
前言
- 返回 NETCTOSS
登录模块之验证码功能
验证码功能需求分析与设计
- 那现在啊,咱们就是了解了cookie和session啊 ,反正也知道它们怎么用了,那下面我们再做一个案例,那这个案例呢,可能是用cookie,或者是用session来做,那到底用哪一个,我们再,具体再分析啊,再想,那你看啊,做哪个案例呢,就是做我们电信计费项目当中的一个案例,就一个场景啊,看一下,咱们这个项目的登录功能上,还有个东西没处理,验证码对吧,之前没处理了,是因为没有学cookie和session,处理不了,现在可以处理了,然后你想啊,我们想一下,这个验证码这个功能是怎么用的,首先呢,当我打开登录页面的时候,是不是就应该出现一张图片,然后呢,图片里写什么,我就照着写对吧,比如说8251,和它一致对吧,一登录,登录的那一刻,服务器得判断,你这个,你输入的验证码和图片的对不对,对吧,如果对了可以去,如果不对的话,还得重新输入对吧,是这样。
- 所以呢,关于验证码呢,在使用上,有两个这个细节,或者说有两个环节,第一个打开网页时要出现验证码这个图片,第二个输入以后,登录时要验证对吧,两个环节。那咱们一个环节一个环节来探讨啊,我们先不管验证,我们只管这个图片,那你看,现在这个图片是不符合要求的,它是死的对吧,它是固定的,那我们这个图片应该是动态的,应该是每次访问,或每点一下,它得变对吧,得有所变化,那大家再想一想,我想让这个图片变,那你说这个得怎么办,有没有什么思路去解决这个问题,有人说定义单击事件,那是后话,首先我们打开这个网页,这个图片就得生成对吧,就得有一个图片,就得是动态的,就得是,每次刷新是不是得变啊,每次访问都得不固定,得变啊,你看这个图片得怎么来呢,从何而来呢,就是网页上需要一个动态的图片,这个动态的图片,我们怎么办。
- 有人说画啊,画,用什么画,对象,用对象来画,什么对象啊,能具体点,是吧,哎,或者这么说吧,咱们先不谈讨这个图片呢,怎么画出来的,就是说你想啊,是,我要画图片,不管怎么样,我得动态生成一个图片,对吧,别管是怎么画,还是怎么招,我得动态生成一个图片,得动态产生一个图片,这个动态产生图片的这段逻辑,这段代码,我在哪里写,我在哪个地方写,我是在Servlet里写,是在jsp里写,还是在,还是写js,还是写什么,用什么写,在哪里写,你看这个界面啊,现在很多同学,一看这个界面,有点发蒙,这代码怎么写的,都忘了,是吧,你看啊,我们看一下之前的那个图啊,别乱。
- 你看,这是我们之前呢,所讲的登录功能的流程对吧,哎,我们敲一路径,打开了登录页面,这登录页面呢,也是动态的啊,然后呢,点登录啊,提交登录的请求
/login.do
啊,等等,你看在众多环节当中,是在MainServlet.toLogin()
里写,是在/WEB-INF/main/login.jsp
里写,还是在哪个地方写,你看啊,在jsp里写,jsp里怎么写呢,注意啊,这个大家可能还是对这个啊,浏览器访问服务器获得网页,以及对网页的加载过程,还是没有理解透,即便是以前讲过,那么你还是没有理解透啊,之前我们讲了,我说了什么呢,浏览器访问服务器,得到一个网页,得到的是什么,仅仅是html,我们在这个请求当中,有可能同时得到一个html,又得到一个图片,有可能么,不可能,浏览器得到网页,加载网页时才会去获取图片,是这样吧,就是说浏览器获取网页,获取图片,获取css,是分步的是吧,是分开的,是这意思么。 - 所以浏览器得到图片,是一个独立的行为,是和得到这个网页无关的行为,是这样吧,所以,你说在jsp这里写,你觉得合适么。
/toLogin.do
到/WEB-INF/main/login.jsp
,这个请求是为了生成html的是吧,那你还得有一个请求用来生成一个图片,是这样吧,是分开的。好像有人,还没明白,我能说什么呢,看一下吧,之前讲的那个话题啊,就是我们在讲完第一个功能,查询以后,就说过这个话题。
- 我说了啊,就是说浏览器访问服务器获得网页,以及加载网页的过程当中包含多次请求,我们在讲这个话题的时候,还看了Network对吧,看到了,确实是多个请求啊,那么当我们呢,调用
findCost.do
的时候,我们得到的是什么呢,仅仅是一个html,那这个过程中,是不可能同时再返回一个图片的,明白么,你只能,就说白了,服务器向浏览器输出东西的时候,它必须声明输出的格式,如果你声明的格式是html,就没有图片,明白么,如果你输出的格式是图片,就不是html,明白吧,它只能输出一种类型,不能两种一起输出,明白吧,不可以。所以呢,这个findcost.do--MainServlet.findCost()
请求当中呢,只能输出html,那html加载的时候呢,访问服务器,再得到一个网页,而这个时候可以,单独输出一个图片,就是说,我们想这个向浏览器输出一个图片,怎么说呢,应该是一个独立的行为,独立的请求,单独写一个。 - 当然了,我这么说的话,意思是说,这个图片应是服务器生成的,那可能也有人会想,能不能说我向浏览器输出了网页,在加载网页时,我在网页上
onload
里,我用js拼个图片,能不能行,你看,我得到网页了是吧,我不访问服务器,我不跟你服务器要图片,我在这个网页加载时,我html里写js,我用js,页面加载后,我拼一个图片,那样行不行,为啥不行呢,有人说到点子上了,这是安全性的问题,安全性,你想啊,这个图片,如果是在客户端生成的,客户端这个html也好,js也好,css也好,咱们是不是能看到啊,右键查看源代码或者是F12,看那个Source,这些(前端)代码都能看到对吧,那你的js的逻辑,暴露在别人的面前,你是怎么拼的,这个逻辑一目了然,他很容易破解的,明白吧,听明白了吧,这是很不安全的,是这样吧,不安全的。 - 那么,我们在服务器端,写这个代码,服务器端的代码,java代码的逻辑,客户端看不到明白么,不知道,它不知道你咋拼的,他只能猜啊,那他破解难度会大大的增加,会更安全,所以呢,这个拼图片,一定是服务器去拼的,我说拼的是这个验证码图片,服务器拼,为了这个更安全啊。那这个具体的开发流程,我再详细的再说一下啊,这是对着以前的案例啊,我们回顾一下,比如说,其实有一些关键点,以前我在讲其他的案例时,其实也涉及到了,可能是大家忘了,回顾一下,然后,我们再说这个案例,那有些事就好说了啊。
- 左侧这是浏览器啊,然后呢,右侧这是服务器,现在呢,浏览器要访问服务器,这是要打开登录的网页啊,那么打开登录的网页呢,我们在浏览器上是敲了一个地址,是
/toLogin.do
对吧,这个请求我们已经写了,但是呢,这个图片的生成和这个请求是有关联的,所以,我把它也画出来啊,当这个用户呢,敲了这个路径以后,访问了服务器,那服务器呢,我们是用Servlet处理了这个请求,那就是MainServlet当中的toLogin方法,MainServlet.toLogin()
,这个方法呢,它没干什么,它就是转发了对吧,它把请求转发给了这个login.jsp
,然后呢,由login.jsp
向浏览器呢,做出响应,那么在响应之后,浏览器就得到了,它输出的html,是这样吧,我们这个jsp上啊,那个指令里没有写什么呢,没有写contentType,那如果没有写的话,我说了,默认值为html对吧,所以本次响应输出的仅仅是html,没有图片啊,只是html。那么这是第一个请求啊,打开登录页面。 - 那浏览器得到这个网页以后呢,它要对网页加载,那加载的话呢,它是分步啊,先加载head,再加载body,那我就一步到位了,这个head,我不写了,就只写body,总之呢,浏览器啊,需要对网页进行加载,就是它得到的是一个文本,它需要把这个文本转换为对象对吧,并显示啊,这个过程我们叫加载,那在加载body的时候啊,那它发现了,body里有一个标签,那个标签呢,叫image,而且标签上是有路径对吧,它知道啊,是要找这个图片,于是呢,这个浏览器就向服务器发出一个请求,要求服务器返回路径相对的那个图片,那这个网页上啊,之前我们写的那个路径是什么呢,我要没记错的话是这个
valicode.jpg
啊,好像是这样,你可以看一下login那个页面,应该是这个。 - 然后呢,浏览器单独访问它
valicode.jpg
,才得到了那个图片,所以img,图片这个请求是第二个请求,是一个独立的请求,这个我们之前是没有管对吧,自动的,那现在呢,就有问题了,现在的问题是什么呢,就是这个请求,我们给它返回了一个静态的图片,这个不合适是吧,现在不能是静态的,是要动态的,所以我们需要呢,做的事情是把原先这个valicode.jpg
,这个静态的资源换成动态的,那你想啊,我把它换成动态的,我得写个啥,我得写什么呢,那你看,我们这个javaEE讲的就是什么呢,我们怎么向浏览器输出资源对吧,那么,输出动态资源两种方式,要么Servlet,要么jsp对吧,或者是结合起来,servlet+jsp
对吧,是吧,也可以,MVC模式,都行,那你看这里,我用谁呢,其实都行,但是呢,jsp里呢,更适合写标签,是吧,更适合写html,那我们现在输出的不是html,我输出的图片对吧,是一份二进制的数据,所以用什么来处理呢,Servlet,我们是要输出一个,显然是一个流对吧,字节流,图片么,对吧,肯定是字节流。 - 所以呢,换成动态资源的图片这里呢,我们得写,得用Servlet来代替这个jpg啊,那我们项目中呢,只有一个Servlet,就叫MainServlet,那我需要呢,给这个类中呢,加一个方法,单独处理一个请求,那么这个方法我叫什么呢,我叫createImg,行吧
MainServlet.createImg()
,叫创建图片可以吧,就叫这个名字吧,然后呢,由这个MainServlet.createImg()
,动态资源来代valicode.jpg
,这个静态资源,来代替它,那这个东西呢,这个createImg()
方法才是一个关键,在这里呢,我们需要呢,就是动态的拼一张图片,向浏览器呢,输出这张图片就可以了,那怎么动态拼一张图片呢,咱们其实以前也干过,学那个飞机大战的时候,不也画过么是吧,其实也画过,但是呢,我们画的时候,用的是那个javaswing当中的一些东西啊,那这个东西啊,其实java也可以做这个网页,java也可以做客户端的。 - 不过呢,java做客户端的话,用swing做的话,非常的繁琐,非常的麻烦,那么java呢,在这方面呢,没发展好,很少有企业用这种东西,不常用,一般不用啊,所以这个是次要的东西,我们这个4个月的课也没有以这个为主,所以不管是讲飞机大战也好,还是现在呢,我们要画一个,验证码图片也好,这个都是,我们把它作为一个次要的地位来对待,明白吧,因为我们主要的内容是网页,是javaEE啊。所以呢,生成图片的过程呢,时间原因,我把它提前封装好,咱们直接去调用,如果你感兴趣,自己看一下,那如果工作时呢,你要自己在工作中拼一个图片的话,你可以参考这个代码,明白吧,比如说我这拼的是,这个英文加数字,你工作时,想拼汉字,你可以改对吧,可以改啊,反正就是你可以参考。
- 那么,这个封装好的类啊,叫做ImageUtil,这个类啊,之前呢,都传给大家了啊,然后我们看一下,所以Servlet就调这个工具类,就行了,就调它就可以了,调它,它会给这个Servlet返回这个图片,然后呢,它把图片用流的方式输出给这个浏览器就行。当然了,这里面还有一些细节,这些细节我们开发时,遇到时再解决啊,这个别着急,总之呢,你看啊,整个过程是,我们敲这路径
/toLogin.do
,访问登录页面,服务器呢,会给我返回这个登录网页,在网页加载时,当加载到这个标签时,访问服务器,得到一个图片,之前图片是静态的,我们现在把它变成动态的,就要单独写个方法,明白吧,那之所以呢,没有把这段逻辑,写到这个Servlet和jsp里,是因为这件事跟他们是没什么关系,对吧,是分开的,是独立的,你再写到jsp这里面来了,就很奇怪了。 - 那然后呢,这个页面加载完成以后啊,那很显然,我们会在服务器上,就看到这个表单了,对吧,就能看到这个表单了,那表单里就会有这个,就会有验证码,那么验证码呢,是一个框,框的后面呢,还有这个图,你是参考图来输入这个数据,后面还有这个图,就这样,图里有字,当然我这画的没有字啊,了解一下。然后呢,用户呢,就可以输入这个内容,然后呢,点登录按钮,访问服务器啊,把这个数据提交给服务器,是这样,那么,当访问服务器的时候,把这个数据提交给服务器,那这个请求,也写过,就是那登录验证对吧,登录检查么,我们访问的是谁呢,是MainServlet里面的login方法,
MainServlet.login()
,那之前呢,这个方法当中呢,我们只是接收了账号密码,对账号密码做了判断,那现在再加一段逻辑,对验证码也做判断就可以了,做一段逻辑就行。那么你要判断,这个验证码是否正确的话,你首先得得到这个验证码,得到用户输入的对吧,那我们需要给这个框呢,取个名字,那比如说这个名字呢,那我叫什么呢,叫,叫code行吧,可以吧,咱们账号叫adminCode,验证码叫code,并不矛盾,对吧,不一样,就叫code。 - 然后呢,在
MainServlet.login()
这里面我得到了code,我code要和图片中的那个字去比,那图片中的字怎么得到的,那又出一个新的问题了,图片里的字怎么弄。想一想啊,思考一下这个字怎么得到,收藏,怎么收藏,就是说我在提交表单时,能不能把图片中的字也一起提交过去,可不可以,行不行,可以,怎么提交,给这个图片加个name,就提交过去了,你想的也太美了吧,咱们以前讲表单时啊,说表单有两要素,form,声明范围,目标对吧,控件,控件有12个,9个input,3个其他的对吧,能输入数据,数据呢,可以提交给服务器,我们讲过说,除了这12个之外的,其他的像段落,标题,能提交么,图片,超链接,能提交么,都不能,其他都是只读的,对吧,都没有办法提交,没有这个功能,它不具备这个能力啊。 - 那你看,那我现在这个图片中的字得不到,想想办法,怎么办,想想办法,就这个解决方案,才是我们当前这个关键点,关键之处啊,那怎么办,就说出你的想法啊,错了也没关系啊,错了怕什么啊,完了,越说越担心是吧,怎么弄啊,有人想到了,就是说,我们这得不到,就是第3个请求当中,没法得到对吧,但是你看,第二个请求,我们产生了验证码这个时候,我能把它记录下来,在第3个请求这再用,是不是可以啊,可以记录在这用,对吧,那你这注意了,这个第二个请求记录这数据(验证码),给第3个请求用,跨请求了,对不对,你得用什么来记,显然是cookie和session能解决问题,对吧,就别考虑什么request了,config和context对吧,别考虑了,就cookie和session,这才切入我们的正题,我们这段讲的就是cookie和session么。
- 那具体说,我们用cookie还是session呢,用哪一个呢,session,为啥用session,因为session存数据,存到了服务器里,为了安全,验证码是不是要保证安全呢,需不需要呢,肯定是需要的,对吧,如果你存到浏览器上,那人家一看就看到了对吧,懂技术的能看到,那就遭了,很容易就被破解了,所以呢,一定是存在session里啊,因此啊,我们在这里,需要呢,将这个imageUtil返回的那个验证码,存入session,就是imageUtil啊,给这个它,给
MainServlet.createImg()
,返回的是两个值啊,一个是图片,一个是这个验证码,验证码是个字符串,就是它返回的是两个值。 - 那我们得到这两个值以后啊,我们就需要呢,那个验证码存入哪呢,我们需要把验证码存入session,你在这
MainServlet.createImg()
,把验证码存入了session,那我们在这,第3个请求MainServlet.login()
,可以得到这个验证码对吧,就行了,但是这个验证码这个名字,就取个名字吧,就叫imgcode,这样行吧,就图片的code,我们就把它存入这个session,那么在第3个请求当中呢,我们这,MainServlet.login()
,能够接收到code,我们从session中取到imgcode,两者相比就可以,是这样吧,那验证码比较的时候,通常是区分不区分大小写,不区分大小写。 - 那我们怎么去比较,才能不区分大小写呢,这俩字符串,有人说,我把它都转成大写,或都转成小写对吧,一比较就可以了,可以这样,但其实呢,也没必要,这个String当中有一个方法,可能之前大家没有学过啊,那个方法叫什么呢,叫equals,还没完,IgnoreCase,学过没有啊,没有,没有没关系,这个不学了么,
code.equalsIgnoreCase(imgcode)
,这个方法比较长,叫equalsIgnoreCase,这个equals是等于对吧,ignore是忽略,case是大小写,就是连起来是啥意思,就是比较两个值是否想等,但是忽略大小写,明白吧,它底层自动转换成大写或小写了,所以这个就省点事了啊,用这个方法。
验证码功能代码分析
- 那后续啊,这个比较那就容易了,你只要知道这个方法,怎么比较很容易,这个案例的逻辑啊,关键点,其实主要呢,是在于这个地方,在于第2个请求,那我们再把这个逻辑呢,再看一下,别乱啊,首先说了,浏览器访问服务器得到登录页面,以及它加载登录页面,加载上面的图片,分开的对吧,不同请求,那么在它加载图片的时候,原来,我们给的是一个静态路径,现在,再把它换为动态的对吧,我们需要单独写一个Servlet处理,因为本次请求输出的是图片,是二进制的数据啊,用Servlet处理比较合适,jsp写标签不太合适,然后呢,生成图片的过程,我做了封装,封装成一个工具类,你都可以去调,可以去使,那它给我们返回的是图片和验证码,其中呢,验证码要存入session,好给第3个请求用对吧,在这个时候呢,将用户输入的验证码和图片中正式的验证码相比,比较时呢,忽略大小写,这是注意的地方。
- 那么要想开发这个功能啊,我们得一个一个来,那现在呢,整个流程当中,第一个请求是已经完成了对吧,不需要做了,而第2个请求正是需要做的对吧,这个
valicode.jpg
图片不需要了,我们需要写这个MainServlet.createImg()
,需要写ImageUtil
,那因为组件,就Servlet依赖于Util对吧,就我们先写Util,这ImageUtil呢,我之前上传了,找到它,能看一下,试一下啊,打开你的这个servlet-doc
,之前下载的那个目录啊,servlet-doc
。那servlet-doc之下有一个java文件,类啊,叫ImageUtil.java
,能找到么,找到它以后啊,你把它复制一下,然后呢,把它粘贴到我们的项目中来,那你看这个类,我们放到哪个包下比较合适啊,显然是util对吧,从名字就能看出来,看一下啊,放util包下。
- 放好以后啊,我们就打开这个类,咱们不写,但是我们大概过一遍,看看里面大概有什么啊,你工作时啊,如果想做这样的逻辑的话,你可以参考这个
ImageUtil.java
,照着改,那我这个做的比较简单,稍微简陋一点啊。首先呢,声明一堆常量,这个第一个常量呢,是这个char,那咱们生成验证码,那里面要写字对吧,这个字得有一个范围,多少范围呢,我声明的是,比较少,是0-9数字,还有这个A-Z,26个大写的字母,对吧,你还得加上小写的,那其实很多网站呢,会把这个什么去掉呢,会把这个L,会把这个I,因为这个,L和I,尤其是小写的,它和这1,不好区分明白吧,还有这个什么哦O,O和0对吧,也很难区分的,这个不好看,可以把它去掉,那我这个都没去掉,因为这就这样了,你可以自己再修饰一下啊,可以加一些小写的字母,甚至呢,可以加这个,一些汉字什么都可以啊。 - 然后呢,还有一些常量啊,就是验证码,我们拼的时候是拼几个字符,我这是4个,一般都是4个对吧,你也可以6个,可以几个啊,一般都是4个,可以多一点,然后你还要注意,咱们这个验证码图片啊,不是为了好看,是为了安全对吧,是为了防止用户,就懂编程的人的暴力破解,因为如果你没有验证码的话,没有一个人为的验证的话,他有可能是编一段程序,不断的瞎猜你的这个账号密码对吧,它不断地这个输入,随便拼任意的账号密码,万一给你破解了呢,那么,有了这个验证码以后的话,这个电脑啊,写程序,它再怎么聪明的话,它不好识别图片中的字明白吧,它不好识别。
- 所以为了增加呢,程序中识别文字的难度,我们需要对这个字,对这个图片还加以干扰,我们需要画几道干扰线明白吧,画几道线,当然这个验证码呢,这个对暴力破解的程序干扰,不同的网站,不同的做法,有的网站是干什么呢,它不是画干扰线,是画点,一大堆点,有的网站是画什么呢,把这个文字啊,把这个做出3D的,对吧,3D化,计算机不好识别,有的是把它扭曲了,有的是拉伸了对吧,反正还有的是,有的是出一道题,1+1等于几,你写那个2,那样的,更有甚者像那个12306对吧,问你下面哪个是樱桃,就这个,考你抽象能力的,电脑再聪明,它没有抽象能力对吧,它无法识别,所以说,这个更高级了啊。
- 总之啊,验证码是为安全,不是为了好看啊,我们这里呢,处理的方式是画几道干扰线,明白吧,画完线以后啊,那个图片看起来不好看了,但是呢,人呢,仔细一看,还能识别,人有抽象能力,计算机识别不了,这个线呢,画几条都行,你画的越多越不好识别,你画太多的话,自己都看不出来了,一般我们画个5条左右,差不多了,画太少也不行,你画一条线,计算机可能会识别,明白吧,还不能太少,5条 ,然后呢,图片多大啊,宽度是80,单位是像素默认啊,高是40,这个可以调,觉得小,可以调大,也可以调小,然后呢,图片上的字,字号多大,30像素啊,这都有设置啊,总之呢,这些是参数,是可调的,如果你想做的更好的话,也可以把它做到配置文件里,明白吧,读配置文件去改也行,这里就简单处理了啊。
- 然后呢,是提供了一个方法叫createImage,那这个方法呢,能够创建验证码图片,返回的数据是一个数组,因为之前我说了,我们这个工具啊,要给调用者返回的是图片和图片中的字对吧,是吧,因为调用者得到图片以后,它也不知道,图片里是啥字,你得告诉他,两个值,一个是图片,一个是字,所以呢,返回多个值,我是用数组来封装的,当然了,你不用数组的话,你用这个集合啊,Map啊,对吧,你写一个实体类啊,这都可以啊,我是用数组,然后呢,这个里面的逻辑啊,其实,咱们以前学飞机大战时呢,也是应该有所了解。
- 首先呢,我要画图片,用java画,我要创建一个空白图片
BufferedImage
,然后呢,创建时指定它的宽,高,和这个配置的方案,BufferedImage image = new BufferedImage(WIDTH, HEIGHT,BufferedImage.TYPE_INT_RGB);
,然后呢,我要在图片上画东西,我要获取画笔,啊,Graphics graphic = image.getGraphics();
,取到画笔graphic,然后呢,给画笔设置个颜色,这个是灰色LIGHT_GREY
,浅灰色,然后呢,我们再画这个验证码的时候,先给它画一个背景啊,所以,先画一个,就在这图片上啊,画一个方块,矩形的方块,graphic.fillRect(0, 0, WIDTH, HEIGHT);
,这个方块的背景色就是笔的颜色,灰色啊,那么画方块是从左上角画到右上角,你只要声明,左上角的坐标和右下角的坐标就可以了,左上角坐标为00,右下角坐标为宽高,这能理解吧,如果左上角为00的话,右下角不就是宽高么,对吧,这样。 - 然后呢,我们要在图片上,再画5个随机字符啊,这是循环5次,然后呢,每次画啊,那随机的话,没有用那个math.Random,用的是Random这个类,这个类啊是,java.util之下的类,它呢,也能生成随机数,它生成的随机数呢,是指定范围之内的整数,明白吧,就是指定范围之内的整数,就不用取整啊,就这个比那个math.Random还要,有的时候,还要简单一点啊,
Random ran = new Random();
,用这个,然后呢,每次循环我们要画一个字,写上一个字,那我就随机一下啊,范围是从0到chars.length
,取那(验证码字符集)数组的长度,得到一个随机的下标,下标对应的是那个字,int n = ran.nextInt(chars.length);
。 - 然后呢,再给画笔设置个随机颜色,
graphic.setColor(getRandomColor());
,每个字颜色都不一样,也是为了增加破解的难度啊,然后呢,设置这个字号啊,然后呢,就画这个字啊,把它画在不同的位置啊,graphic.setFont(new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE));
,比如第一次,我们把它画在这个,矩形的最左端,第2次,往右偏移点,第3次再往右偏移点,明白吧,这里有个简单的运算啊,乘以一个系数啊,可以自己详细看一看。 - 然后呢,画的这个字就是chars[n]啊,下标,
graphic.drawString(chars[n] + "", i * WIDTH / SIZE, HEIGHT / 2);
,字符画好以后呢,把这个字呢,记录下来,记到StringBuffer里,那么循环4回以后啊,这个验证码就画好了,图片就画好了,就是字就有了,有了以后呢,还得画这个干扰线,所以后面呢,再循环一遍,for (int i = 0; i <= LINES; i++)
,循环这个是5次,然后呢,每次也是啊,设置随机色,画一条线,画一条直线,你要指定这个线的起点和终点,是两个坐标啊,俩坐标是随机生成的,在矩形范围内,随机生成俩坐标,画条随机的线,不知道从哪画到哪,不一定,然后呢,最后返回的是验证码和图片,是一个数组啊。这是大概的流程啊。 - 然后呢,咱们在写这个方法的时候啊,有的地方设置这个随机色,这个随机色有单独的方法,封装了一下,那随机色的话,就是我们随机产生一个颜色,而一个颜色由3个值构成,红绿蓝对吧,然后呢,就随机生成这么一个范围内的值就可以了,这就了解下,然后,最后还有个main方法,对这个代码进行测试,咱们也可以测一下,看看行不行,这测试代码都写好了啊。
public static void main(String[] args) throws IOException {
Object[] objs = createImage();
BufferedImage image = (BufferedImage) objs[1];
// /home/soft01/1.png
OutputStream os = new FileOutputStream("d:/1.png");
ImageIO.write(image, "png", os);
System.out.println(objs[0]);
os.close();
}
-
首先,我们调createImage(),方法得到一个数组,数组中呢,第2个值是那个图片,我们想输出这个图片看一下,目前呢,没有浏览器啊,没有浏览器访问它,我们就输出到硬盘上,那我们就创建一个输出流,然后呢,这个输出的目标是d盘,名为
1.png
,这个java语言呢,输出图片的话,输出png,这个是更好的,它处理png的图片是更,怎么说呢,更这个细腻啊,更准确,效果更好,所以一般就是输出png,当然了,Linux呢,没有d盘,你得写这样的路径对吧,/home/soft01/1.png
,这样的,你得把它改成这样,改一下这个。 -
那么改完以后,然后呢,我们想输出图片,可以用这个工具啊,叫ImageIO, 然后呢write,write时呢,第一个参是输出的图片,第二个参呢,是图片的格式,第3个参呢,输出流,那么它会调用输出流,帮我们输出,明白吧,调这个工具就可以了,然后呢,最后把流关掉就行了,我试一下,我执行这个main方法,执行完以后呢,看一下d盘有没有图片,看一看,或者看的是soft01啊,我打开d盘,确实d盘之下,有了一个图片叫
1.png
,你看它的时间,它的时间是2017年5月24号11点07对吧,最新的,刚生成的,热乎的啊,然后你看,我把它打开看一下,对吧,就这样的。
-
有人说这太难看了,对,就是要难看啊,难看的目的是为了避免被破解对吧,对,不是为了好看,是为了避免被破解啊,这就行了,挺好了。那目前呢,已经把这个ImageUtil,把这类呢,拿过来,经过测试啊,可以了,那么有了这个类以后啊,下面呢,我们就需要写这个 Servlet,
MainServlet.createImage()
,然后在这个方法当中呢,下面呢,我们需要调用这个ImageUtil工具,然后呢,生成图片,那对于返回的结果,两个结果啊,一个是图片,一个是验证码,那对于图片呢,我们要把他输出给浏览器,而对于验证码,要存入session,那么,怎么把图片输出给浏览器呢,咱们刚才呢,在这个类当中,不看到那个main方法么,对吧,那个main方法是怎么去,把图片输出到d盘,我们就可以怎么把图片输出给浏览器,只不过目标不同,一个 是d盘,一个是浏览器而已,那有人说,那浏览器 谁,我不知道啊,你不知道没关系,服务器知道。 -
那么我们通过服务器呢,能够直接得到,我们要用的那个输出流,可以直接用。就是说,我们想把图片输出给d盘,我们得自己new一个,这个输出流,如果呢,是想把图片输出给浏览器,这个输出流,我们可以直接获得,你像以前,我们像浏览器啊,拼网页,咱们不是直接获得一个Writer么,是吧,字符流,其实我们还能通过response得到一个字节流啊,可以输出这个东西。那下面我们写一下这个啊,这个少点,忘点事啊,就是说,
MainServlet.createImg()
的访问路径是什么,给标一下啊,路径呢,和createImg(),这个方法是对应的啊,它的路径叫,/createImg.do
,和这个方法对应,这是访问路径啊。 -
那下面呢,我们就来处理这个请求啊 ,那我们打开MainServlet, 打开以后啊,我们在这个类里啊 ,找到那个service方法啊,然后呢,在service方法当中,对这个请求进行判断,那么在service里呢 ,我再加一个判断啊,
else if("/createImg.do".equals(path)){ createImg(req,res); }
,那么,如果呢,是这个路径,我们就调用对应的这个方法,那下面呢,我把这个方法呢,声明一下,那方法声明与其他方法一样,复制粘贴就行,那么我就直接啊,在service方法之后啊,再粘贴一个方法,然后呢,把它改名加createImg,然后呢,写注释啊,加以解释啊,这个方法的作用啊,是生成验证码。 -
那么这个方法之内,我们主要做的事情就是生成验证码,那我们可以调用那个工具啊,ImageUtil啊,我把这个代码呢,注释呢,写一下啊,就是生成验证码,以及图片啊。
Object[] objs = ImageUtil.createImage();
那么,我调的这个方法得到的是数组啊,数组中的第一个值呢,是验证码,第2个值呢,是图片,是这样么,我看一眼啊,对,第2个值是图片啊,第一个值呢,是那个验证码,那么对于这两个值呢,我们处理方式,不一样的啊,那么对于验证码,我是要存到session里去,就是将验证码存入session:HttpSession session = req.getSession();
session.setAttribute(“imgcode”, objs[0]); -
我就获取了session,然后往session中存值,那个名字呢,叫做imgcode,后面取的时候要用啊,这要记住,那值呢,就是数组中的第一个值,就是验证码,存了,然后呢,还有第二件事啊,我们需要呢,把得到的这个图片啊,将图片发送给浏览器,那我们在那个工具类的main方法里啊,我们是有一段测试代码,是把图片输出到d盘啊,那我们把图片发送给浏览器,差不多,但是呢,也有点这个区别,那我们想向浏览器呢,发送东西,首先得声明对吧,我得告诉你,我给你传的是什么东西,对吧,得写一句话,写什么呢,你得写
res.setContentType("");
,是这样么,是吧,你得写这句话,你看啊,以前呢,我们写这句话,这里面写的是text/html
对吧,那现在呢,你还能写text/html
么,能么,不能了,因为你现在输出的不是网页对吧,是图片了,那图片你说我怎么写呢,注意啊,这个不要乱猜,这个地方,这个字符串怎么写,是有明确规定的,哪里看规定呢,之前给你演示过,你去那个文档,rlc2616好像,那个是http协议的规定对吧,那里面有规定,得看那个文档,文章啊。
如何查询响应消息头contentType类型
- 不过那个文章呢,不好看啊,都是英文的,还很长啊,不好找,那我们还有一个办法。因为呢,tomcat,它遵守了这个协议,它实现了这个协议,那么tomcat里面有这个声明啊,tomcat里面有这个声明,我们可以看tomcat啊,这样方便,那tomcat这份声明在哪里写的呢,在配置文件里啊,但是你想啊,我们想看tomcat配置文件,怎么看,你得看那个Servers对吧,这个项目,所以呢,我们展开Servers这个项目,那么,这个项目之下啊,有若干配置文件,咱们之前用过哪一个呢,
server.xml
,这里面可以改那个端口,对吧,可以声明啊,get请求路径的编码对吧,现在呢,不是看这个啊,我们看的是什么呢,这个web.xml
,你看tomcat之内,也有一个web.xml
,看这个。 - 那我们打开这个
web.xml
,看一下,当然了,它这个文件代码可就长了,非常的长啊,然后呢,我们往后看一看,那某些内容,咱们还是能看得懂的,会有这个,怎么说呢,会后到一些启发的啊,那前面呢,都是注释啊,我需要看哪个地方呢,好像是500多行吧,我记得好像是500行以后了,有个地方,有段代码是我们所认识的,找到了,大家找到580行,看到了么session-config
,刚写过对吧,就为什么tomcat,它管理这个session,自动30分钟销毁对吧,因为这声明了,哎,那有同学想你这也能改,咱们在我们项目中的web.xml也能改这个东西对吧,那你说这两地方有没有什么联系呢,我改这和改那,有什么区别么,我改这也可以,但是,改这的话,所有项目受影响是吧,我们改我们项目之内,只有这一个项目受影响是吧,就这么个区别,但是呢,你看,我们在我们项目中,也是写这段代码,我们那个项目中也叫web.xml。
<session-config>
<session-timeout>30</session-timeout>
</session-config>
- 这俩有什么关系呢,其实,tomcat在启动时,它就会读它的这个配置文件,那我们项目中的web.xml是对tomcat这个配置文件的一个补充明白么,其实是一个补充,那如果说,我们项目中
web.xml
中,没有写这段话,那么它以它这段话为准,如果我们写了,以我们那个为准明白吧,我们在项目之内的web的配置文件里啊,可以把这里的这个东西覆盖掉,或者你也可这样认为啊,我们项目当中的web.xml,它继承于这个Servers当中的web.xml
这个配置文件,你可以这么理解,明白吧,可以这么理解,就总之啊,我们项目当中的配置文件,是对这个文件的补充,它们其实就是一个文件,就是一种文件,格式都是一样的。 - 然后呢,再看啊,从591行开始,有很多这样标签,叫
<mime-mapping>
啊,是一种映射关系,是一种对应关系,每一个mapping是声明了一种对应关系,当然,这里面有很多呢,内容,我们是看不懂的,有很多这个对应关系看不懂,我们也不用都看太多了啊,我们找一个能看得懂的,那我们搜一下啊,ctrl+F搜一下,搜什么呢,搜html啊,搜索html,搜一下啊,这个文件中呢,有好几个地方,有html啊,我是想,我想找哪一个呢,看一下啊,860行,不是我想要的,再往后搜啊,1037行也不是我想要的,再往后啊,哎,我找着了,1876行啊,你看,它声明的是什么呢
<mime-mapping>
<extension>htm</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<mime-mapping>
<extension>html</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
- 它声明了html这种格式的文件,我们在输出时,应该写这句话,
<mime-type>text/html</mime-type>
,就res.set时应改写这句话text/html
,听明白了么,就为什么,我们要想输出html时要写这句话text/html
,因为这的声明,它规定了以html为后缀的文件,格式就得这么写明白吧,听明白了吗,你差一个字母都不行,你差一个字母,浏览器无法识别,无法识别会怎么样呢,它认为这是一个特殊的文件,会让你保存明白吧,不会显示正确的内容。还有啊,咱们那个html啊,是不是可以省略l啊,可以叫htm对吧,那以这种后缀的文件,也是这格式,听明白了吗,要这样,那如果你想输出其他格式的内容怎么办呢,搜呗,对吧,搜那个后缀,我现在想输出什么呢,png对吧,哎,我们搜一下png啊,png搜一下,一搜呢,发现,在3075行对吧,
<mime-mapping>
<extension>png</extension>
<mime-type>image/png</mime-type>
</mime-mapping>
-
它规定了以png为后缀的文件,格式必须
image/png
这么写,明白吧,就不能有第二种写法,就必须这样写啊,当然了,如果你工作的时候,你不是输出png,你输出的是jpg,你就搜一下jpg,明白吧,你想输出什么,搜一下,写这句话啊,那现在呢,我们输出的是png啊,把image/png
这句话呢,复制一下,然后呢,我再回到,MainServlet啊,那么ContentType里面,就要写image/png
这段话,res.setContentType("image/png");
。 -
那格式声明完以后,我们要输出了,那我们要把内容输出给浏览器,我们得获得一个输出流对吧,这个流不能自己创建,因为我们不知道浏览器是谁对吧,但服务器知道,我们得跟服务器要,那以前呢,我们输出的都是网页,都是文本,我们要的是一个字符流,对吧,getWriter,现在输出的是字节,我们要一个字节流,也有啊,怎么做呢,response点get,你看哪个是,你看,它这里面有两个,两个流啊,get,一个是之前我们用的,这个getWriter对吧,能输出字符,现在想输出字节对吧,这个getOutputStream。
-
那
OutputStream os = res.getOutputStream();
,我们就得到了一个输出流啊,这写个注释吧,就是获得,获取啊,这个字节输出流啊,该流由服务器创建,那么其目标,就是当前访问的那个浏览器。就总之吧,我们在做的当中啊,你想输出字符,getWriter,你想输出字节,getOutputStream,明白吧,想输出什么,就获取对应的流啊,那得到这个流以后,怎么输出呢,输出的方式啊,和这个,咱们那个main方法中的测试代码是一样的,再看一下啊。BufferedImage img =(BufferedImage) objs[1];
ImageIO.write(img, “png”, os); -
那我们得到流以后啊,想输出东西,可以调这个ImageIO对吧,它可以帮我们输出,就调它啊,我们回到代码中来,调ImageIO啊,输出内容,ImageIO,点write啊,调那个有3个参的write,调这个3个参数的write,其中呢,第2个参,是格式,输出的就是后缀png啊,然后呢,第3个参,输出流os,那第一个参数是那个图片,那这个图片是谁呢,就是我们之前得到数组中的第2个值对吧,所以在此之前还得得到那个值,我忘了啊,写一下,在这写一下啊,objs,下标为1啊,它的类型呢,是BufferedImage啊,是这样一个类型啊,这个图片BufferedImage。
-
那咱们得到了输出流以后,我们从数组中呢,得到了第二个值,这个值呢是图片,把它转型一下,然后呢,
ImageIO.write()
时,传入图片,要输出的是这个内容,然后呢,输出的图片呢,后缀或者是格式啊,是png,然后用的流,是os,完了,最后呢,别忘了要怎么样呢,把流关闭,os.close()
,就可以了。那这个代码写完以后啊,咱们可以先测试一下,怎么测试呢,就是这个请求,谁访问它,/createImg.do
,它会把图片呢,输出给谁,我直接打开浏览器访问它,它就把图片输出给浏览器,明白吧,不是网页,是图片,那我在网页上,用img来访问它,/createImg.do
,它会把这个图片输出给img,明白吧,就总之呢,谁访问它,它会把这个图片输出给谁,先测一下。 -
那我们先把这个项目呢,重新部署一下,部属以后啊,启动tomcat,然后呢,我们地址栏敲个地址,直接访问这个方法,它会把这个图片直接输出给浏览器,我们看一下。叫
localhost:8080/netctoss/createImg.do
,经过测试发现,可以得到图片对吧,这回输出的不是网页,是图片,那么服务器啊,有能力输出一切内容,只要你能写出来,它就能输出,输出什么都行,那我们不能这样用啊,我们得在那个登录页面上去得到这个图片,所以呢,我们再回到eclipse当中,然后呢,我们打开这个项目之下的登录页面,login.jsp
,那么在login.jsp
之上,我们找到那个验证码图片所处的位置,大概27行,是这样吧,之前呢,图片的路径是valicode.jpg
,对吧,是固定的,现在我们把它改为动态的,那么改为谁呢,createImg.do
,即<td><img src="images/valicode.jpg" alt="验证码" title="点击更换" /></td>
,就是把这个图片呢,资源由静态的改为动态的。 -
那么处理完以后呢,我们打开浏览器,我们访问这个登录功能看一下,看行不行啊,看一下,那么登录页面的访问路径啊,是
toLogin.do
,即localhost:8080/netctoss/toLogin.do
,有么,有吧,行不行啊,可以。对,这个有的时候验证码看不清得能换对吧,是吧,点击更换,现在点击更换不了,为啥呢,因为我没写单击事件对吧,所以,肯定得写个单击事件,单击时把它换了,那我们回到程序当中,在这个img上,再写个单击事件,onclick=""
,27行啊,还是这个图片,在这事件里面呢,我直接写一句js,我要把这个图片换了,那大家想啊,我要换图片,实际上是要改什么才能让图片换,我得改点什么,就我通过改什么,能让图片有所变化,改样式,嗯,有人刷新网页,刷新网页就完了,因为啥呢,用户可能填了账号密码对吧,你把网页一刷新,账号密码没了,对吧,这就不太合适了,其实你应该能猜到,如果我把这个路径换了,它是不是就会换呢,是这意思吧,就是src路径,你换了,是不是就变了呢,是吧,你把这个值换了,就可以。 -
那我想写js啊,去修改这个src,如果修改src,我得怎么改,我用什么方法去改,location啊,那还是改地址么,那不又刷新了么,你看啊,我们改的是元素的东西对吧,是吧,那肯定是dom操作,是这样吧,dom操作呢,我们以前讲了,API讲了什么呢,读写,查询,增删对吧,那这里是什么呢,是读写中的写对吧,是写,那修改就是修改,修改的话,我们可以改什么呢,改这个元素的内容,可以改元素的值,可以改元素的属性,是吧,这里是属于哪一个呢,改属性,改属性有几个办法呢,第一种办法setAttribute,是这样吧,第2种办法,点属性名对吧,但点属性名,很多是不标准的对吧,不好用,最好是setAttribute,所以这里呢,用setAttribute去改这个src。那我要setAttribute的话,我得先得到这个图片对吧,得到图片的话,怎么获得呢,你得加个id,然后
document.getElementById
对吧,然后再setAttribute,可以,但还有一种方式啊,你可以写什么呢,this,你看,我在这个图片上写this,这个this代表这个图片本身对吧,是吧,我可以这样写么,都忘了啊,<td><img src="createImg.do" onclick="this.setAttribute('src','createImg.do');" alt="验证码" title="点击更换" /></td>
。 -
那this啊,得到这个图片,然后呢,改属性啊,setAttribute,属性名src,属性值,还是那句话,
createImg.do
,还是这句话。写完以后试一下,看行不行啊,我们看我这个,刷新一下,行吗,你们那个行,我这个真不行,这跟浏览器有关系啊,有些浏览器啊,它认为什么呢,你这属性是改了,你改完之后,这值和以前一样对吧,它认为一样,没变,它没去访问,有些浏览器就访问了,它没管啊,那这怎么办呢,我们得变通一下,让任何版本的浏览器都能变,怎么办呢,我,我善意的欺骗一下浏览器,我把这个路径伪装一下,让这浏览器以外它是变了,怎么伪装呢,我们这样做啊,你看一下,'createImg.do?x='+Math.random()
,你看,我在路径之后,人为的加了个参对吧,瞎传个参,对,你说的太对了,瞎传个参,这参数什么什么,叫什么,无所谓,参数值只要每次都变就行,那浏览器以为,这路径变了,我就可以访问了,明白这意思么,听明白了么,我随便加个参,这参数随机数,只是为了欺骗浏览器,让它以为路径变了,听懂了吧,这个意思。 -
你把它写上看一下,
<td><img src="createImg.do" onclick="this.setAttribute('src','createImg.do?x='+Math.random());"alt="验证码" title="点击更换" /></td>
,这样就万能了,什么浏览器都可以了,就是在路径之后啊,人为的拼个参数,参数名叫什么,没关系,等于一个随机数,每次都变就行,服务器是不需要这个参数的,对吧,这个参数只是为了欺骗浏览器的,没有别的作用啊,写完以后啊,试一下啊,你看我这个,刚才不好使,这回呢,这回就行了啊。那咱们第二个请求啊,加载个动态组件就搞定了,而第3个请求就容易了,坚持验证码对不对,判断就可以了啊。
-
那验证码的功能,在打开登录页面时,能够看到了,就是这个验证码好了,能看到了,但是还差一步啊,第3步,就是我们在登录时啊,还得对这个验证码进行检查,我们再把这个也写一下,把它完善了。那么要想检查验证码呢,首先呢,我们需要呢,在页面上,给这个框加上名字对吧,好让数据能传过去,然后呢,
MainServlet.login()
里,我们得到这个参数一比较就可以了啊,所以啊,首先我们打开那个login.jsp
,那么,26行,这个验证码的框,得有name,这个name呢,我给它取名叫code吧,<td class="width70"><input name="code" type="text" class="width70" /></td>
,取完名以后啊,我们再回到这个Servlet啊,打开这个MainServlet啊,找到login()方法。 -
那么login方法里头呢,之前咱们是对账号密码进行了检查,那现在呢,这个验证码也需要呢,进行处理,所以呢,在这个方法的一开始,我们再接收一个参数啊,再多接收一个参数啊,是code,即
String code = req.getParameter("code");
,然后呢,接收到code以后啊,我们需要呢,将这个code和session中的code相比,所以呢,下面我们需要呢,获取session中的code,那这个比较,我们在哪比呢,我们在这个账号密码的检查之前,去比就可以,因为如果验证码不对的话,我们就没有必要查询了,是这样么,这样的话,这效率能,就是,能节约点效率,能提高点效率啊,所以我们在那个,在验证之前啊,在这优先呢,这个检查验证码,在这验证之前写,那我们需要呢,将得到的验证码和session中的相比,所以在这呢,我们还得获取session啊,但是我记得啊,咱们之前取过session,在这个方法里,我们在后面,在else里,咱们之前不是获取过session么,是吧,取过一次,我们把这个往前提一下,是吧,就可以了,就没必要再写一遍。 -
因此呢,我把这个else啊,获取session这句话,把它剪切一下,啊,剪切完以后呢,把它往前提啊,那放到呢,这个方法的一开头这个地方,检查验证码这个位置,那提到这个位置以后呢,咱们从session中呢,得到这个正确的验证码啊,
String imgcode = (String) session.getAttribute("imgcode")
,我们将我们接收到的code啊,和这个session中的img相比,加以判断,那么如果呢,用户传入的code,是空的,或者呢,它不等于这个imgcode,它是错误的情况,需要给予提示,这个如果啊,code是空值,或者code不等于session的code,那么错误了,错误的处理方式和账号密码错误没什么区别,我们也是啊,要传一个这个错误的信息,要转发,所以呢,我们从这个账号,这个错误的地方呢,把这两句处理呢,复制一下,粘贴到上面去,然后稍微一修改就可以了。req.setAttribute(“error”, “账号错误”);
req.getRequestDispatcher(“WEB-INF/main/login.jsp”).forward(req, res); -
那复制完这两句话以后啊,粘贴到刚才的判断之内,那么,需要修改的呢,是这个错误信息啊,那如果说呢,这个验证码不对,那大家想一想,我们后面账号密码的检查,还用执行么,后面不用执行了,所以程序到此,就怎么样呢,就结束了,我们在这就return啊,这就处理完了,处理完以后呢,咱可以测试了啊,
//检查验证码
HttpSession session = req.getSession();
String imgcode = (String) session.getAttribute("imgcode");
if(code == null || !code.equalsIgnoreCase(imgcode)) {
req.setAttribute("error", "验证码错误");
req.getRequestDispatcher("WEB-INF/main/login.jsp").forward(req, res);
return;
}
- 我把这项目呢,再重新部署一下,然后呢,启动tomcat,然后呢,打开浏览器啊,我们再登录啊,重新登录,你看,我输入呢,正确的,账号,正确密码,然后呢,输入一个错误的验证码,那么,当我点登录的时候呢,它会给我提示啊,说验证码错误,那我填一个对的,填的时候呢,不用去看大小写啊,我这是fgnu啊,然后你看你自己的,然后呢,点登录,就成功了啊。那现在呢,我们就把这个验证码验证的功能说完了啊,那么,我们就把这个cookie啊,还有session,这两个对象,它们的应用讲完了,那么这个话题说我以后啊,我们再往下啊,说下一个话题啊。
验证码功能代码实现
1.src/main/java/util/ImageUtil.java
package util;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.imageio.ImageIO;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public final class ImageUtil {
// 验证码字符集
private static final char[] chars = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' };
// 字符数量
private static final int SIZE = 4;
// 干扰线数量
private static final int LINES = 5;
// 宽度
private static final int WIDTH = 80;
// 高度
private static final int HEIGHT = 40;
// 字体大小
private static final int FONT_SIZE = 30;
/**
* 生成随机验证码及图片
*/
// public static Map<String, BufferedImage> createImage() {
public static Object[] createImage() {
StringBuffer sb = new StringBuffer();
// 1.创建空白图片
BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_INT_RGB);
// 2.获取图片画笔
Graphics graphic = image.getGraphics();
// 3.设置画笔颜色
graphic.setColor(Color.LIGHT_GRAY);
// 4.绘制矩形背景
graphic.fillRect(0, 0, WIDTH, HEIGHT);
// 5.画随机字符
Random ran = new Random();
// for (int i = 1; i <= SIZE; i++) {
for (int i = 0; i < SIZE; i++) {
// 取随机字符索引
// int r = ran.nextInt(chars.length);
int n = ran.nextInt(chars.length);
// 设置随机颜色
graphic.setColor(getRandomColor());
// 设置字体大小
graphic.setFont(new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE));
// 画字符
// graphic.drawString(chars[r] + "", (i - 1) * WIDTH / SIZE, HEIGHT / 2);
graphic.drawString(chars[n] + "", i * WIDTH / SIZE, HEIGHT / 2);
// 记录字符
// sb.append(chars[r]);// 将字符保存,存入Session
sb.append(chars[n]);// 将字符保存,存入Session
}
// 6.画干扰线
// for (int i = 1; i <= LINES; i++) {
for (int i = 0; i < LINES; i++) {
// 设置随机颜色
graphic.setColor(getRandomColor());
// 随机画线
graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT),
ran.nextInt(WIDTH), ran.nextInt(HEIGHT));
}
// 7.返回验证码和图片
// Map<String, BufferedImage> map = new HashMap<String, BufferedImage>();
// map.put(sb.toString(), image);
// return map;
return new Object[] {sb.toString(), image};
}
/**
* 取随机色
*/
public static Color getRandomColor() {
Random ran = new Random();
Color color = new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256));
return color;
}
//测试代码
public static void main(String[] args) throws IOException {
Object[] objs = createImage();
BufferedImage image = (BufferedImage) objs[1];
// /home/soft01/1.png
OutputStream os = new FileOutputStream("d:/1.png");
ImageIO.write(image, "png", os);
System.out.println(objs[0]);
os.close();
}
public static InputStream getInputStream(BufferedImage image)
throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bos);
encoder.encode(image);
byte[] imageBts = bos.toByteArray();
InputStream in = new ByteArrayInputStream(imageBts);
return in;
}
}
2.src/main/java/web/MainServlet.java
package web;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import dao.AdminDao;
import dao.CostDao;
import entity.Admin;
import entity.Cost;
import util.ImageUtil;
public class MainServlet extends HttpServlet {
@Override
protected void service(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
//获取访问路径
String path = req.getServletPath();
//根据规范(图)处理路径
if("/findCost.do".equals(path)) {
findCost(req,res);
} else if("/toAddCost.do".equals(path)) {
toAddCost(req,res);
} else if("/addCost.do".equals(path)){
addCost(req,res);
} else if("/toLogin.do".equals(path)){
toLogin(req,res);
} else if("/toIndex.do".equals(path)){
toIndex(req,res);
} else if("/login.do".equals(path)){
login(req,res);
} else if("/createImg.do".equals(path)){
createImg(req,res);
} else {
throw new RuntimeException("没有这个页面");
}
}
//生成验证码
protected void createImg(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
//生成验证码及图片
Object[] objs = ImageUtil.createImage();
//将验证码存入session
HttpSession session = req.getSession();
session.setAttribute("imgcode", objs[0]);
//将图片发送给浏览器
res.setContentType("image/png");
//获取字节输出流,该流由服务器创建,
//其目标就是当前访问的那个浏览器。
OutputStream os = res.getOutputStream();
BufferedImage img =(BufferedImage) objs[1];
ImageIO.write(img, "png", os);
os.close();
}
//登录
protected void login(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
//接收参数
String adminCode = req.getParameter("adminCode");
String password = req.getParameter("password");
String code = req.getParameter("code");
System.out.println(code);
//检查验证码
HttpSession session = req.getSession();
String imgcode = (String) session.getAttribute("imgcode");
System.out.println(imgcode);
if(code == null || !code.equalsIgnoreCase(imgcode)) {
req.setAttribute("error", "验证码错误");
req.getRequestDispatcher("WEB-INF/main/login.jsp").forward(req, res);
return;
}
//验证
AdminDao dao = new AdminDao();
Admin a = dao.findByCode(adminCode);
if(a == null) {
//帐号错误
req.setAttribute("error", "账号错误");
req.getRequestDispatcher("WEB-INF/main/login.jsp").forward(req, res);
} else if(!a.getPassword().equals(password)) {
System.out.println(password);
System.out.println(adminCode);
//密码错误
req.setAttribute("error", "密码错误");
req.getRequestDispatcher("WEB-INF/main/login.jsp").forward(req, res);
} else {
//将账号存入cookie
Cookie cookie = new Cookie("user", adminCode);
res.addCookie(cookie);
//将账号存入session
// HttpSession session = req.getSession();
session.setAttribute("user", adminCode);
//验证通过
res.sendRedirect("toIndex.do");
}
}
//打开登录页面
protected void toLogin(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
req.getRequestDispatcher("WEB-INF/main/login.jsp").forward(req, res);
}
//打开主页
protected void toIndex(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
req.getRequestDispatcher("WEB-INF/main/index.jsp").forward(req, res);
}
//查询资费
protected void findCost(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
//查询所有的资费
CostDao dao = new CostDao();
List<Cost> list = dao.findAll();
//将请求转发到jsp
req.setAttribute("costs", list);
//当前:/netctoss/findCost.do
//目标:/netctoss/WEB-INF/cost/find.jsp
req.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);
}
//打开增加资费
protected void toAddCost(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
req.getRequestDispatcher("WEB-INF/cost/add.jsp").forward(req, res);
}
//增加资费数据
protected void addCost(
HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException {
//接收传入的参数
req.setCharacterEncoding("utf-8");
String name = req.getParameter("name");
String costType = req.getParameter("costType");
String baseDuration = req.getParameter("baseDuration");
String baseCost = req.getParameter("baseCost");
String unitCost = req.getParameter("unitCost");
String descr = req.getParameter("descr");
//保存该数据
Cost c = new Cost();
c.setName(name);
c.setCostType(costType);
if(baseDuration != null && baseDuration.length()>0){
c.setBaseDuration(Integer.valueOf(baseDuration));
}
if(baseCost != null && baseCost.length()>0) {
c.setBaseCost(Double.valueOf(baseCost));
}
if(unitCost != null && unitCost.length()>0) {
c.setUnitCost(Double.valueOf(unitCost));
}
c.setDescr(descr);
CostDao dao = new CostDao();
dao.save(c);
//重定向到查询
//当前:/netctoss/addCost.do
//目标:/netctoss/findCost.do
res.sendRedirect("findCost.do");
}
}
3.src/main/webapp/WEB-INF/main/login.jsp
<%@page pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>达内-NetCTOSS</title>
<link type="text/css" rel="stylesheet" media="all" href="styles/global.css" />
<link type="text/css" rel="stylesheet" media="all" href="styles/global_color.css" />
</head>
<body class="index">
<div class="login_box">
<form action="login.do" method="post">
<table>
<tr>
<td class="login_info">账号:</td>
<td colspan="2"><input name="adminCode" value="${param.adminCode }" type="text" class="width150" /></td>
<td class="login_error_info"><span class="required">30长度的字母、数字和下划线</span></td>
</tr>
<tr>
<td class="login_info">密码:</td>
<td colspan="2"><input name="password" value="${param.password }" type="password" class="width150" /></td>
<td><span class="required">30长度的字母、数字和下划线</span></td>
</tr>
<tr>
<td class="login_info">验证码:</td>
<td class="width70"><input name="code" type="text" class="width70" /></td>
<!-- <td><img src="images/valicode.jpg" alt="验证码" title="点击更换" /></td> -->
<td><img src="createImg.do" onclick="this.setAttribute('src','createImg.do?x='+Math.random());" alt="验证码" title="点击更换" /></td>
<td><span class="required">验证码错误</span></td>
</tr>
<tr>
<td></td>
<td class="login_button" colspan="2">
<!--
表单提交的方式:
1.点击submit按钮,触发表单的onsubmit事件
2.通过js调用表单的submit()
-->
<a href="javascript:document.forms[0].submit();"><img src="images/login_btn.png" /></a>
</td>
<!-- <td><span class="required">用户名或密码错误,请重试</span></td> -->
<td><span class="required">${error }</span></td>
</tr>
</table>
</form>
</div>
</body>
</html>