把之前项目中写的一个Java通用验证码程序整理了一下,与大家分享,希望能抛砖引玉。
一、主要功能:
1、支持纯数字、大写字母、小写字母,及两两混合或三者混合类型验证码;
2、支持自定义特殊字符排除(如0oOi1jI);
3、支持图片及文字两种类型验证码;
4、支持自定义验证码图片大小;
5、支持自定义干扰线条数;
6、支持自定义及随机定义图片、文字、干扰线颜色;
其他:示例页面提供了<iframe>和<img>两种页面显示验证码的方式。
a、仅阿拉伯数字
b、仅字母(大小写混合)
c、仅小写字母
d、仅大写字母
d、数字与小写字母混合
e、数字与大写字母混合
f、数字与大小写字母混合
g、自定义字符颜色(或随机)
h、自定义图片颜色(或随机)
i、自定义干扰线的条数及颜色(或随机)
j、自定义验证码图片大小
k、自定义需排除的特殊字符(如0o等难分辨字符)
二、代码说明:
1、pubMsg.jsp 发表留言页面,实现<iframe>和<img>两种显示验证码方式,及ajax提交功能
<%
String path = request.getContextPath();
String basePath = request.getScheme() + " :// " + request.getServerName() + " : " + request.getServerPort() + path + " / " ;
%>
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" >
< html >
< head >
< title > <% = basePath %> </ title >
</ head >
< script type ="text/javascript" >
var req;
// 提交留言
function pubMsg(){
msgTitle = document.msgForm.msgTitle.value;
msgContent = document.msgForm.msgContent.value;
verifyCode = document.msgForm.verifyCode.value;
// 检查输入内容格式
if (msgTitle.length < 5 || msgContent.length < 5 )
{
alert( " 标题和内容不能少于5个字符 " );
return ;
}
if (verifyCode.length == 0 ){
alert( " 请输入验证码 " );
return ;
}
// 获得XMLHttpRequest对象
if (window.XMLHttpRequest)
req = new XMLHttpRequest();
else if (window.ActiveXObject)
req = new ActiveXObject( " Microsoft.XMLHTTP " );
// 提交请求
if (req){
req.onreadystatechange = callBack; // 指定服务器响应结果处理函数(注意仅函数名无括号)
url = " <%=path%>/servlet/PubMsgServlet?msgTitle= " + msgTitle + " &msgContent= " + msgContent + " &verifyCode= " + verifyCode;
req.open( " GET " ,url, false );
req.send();
}
}
// 服务器响应结果处理函数
function callBack(){
if (req.readyState == 4 ){
if (req.status == 200 ){
next = req.responseText; // 获得服务器处理结果
if (next == " this " ){
alert( " 验证码不正确,请重新输入 " );
verifyCodeFrame.location.reload(); // 刷新验证码
}
else {
document.msgForm.msgTitle.value = "" ;
document.msgForm.msgContent.value = "" ;
window.location = next; // 跳转页面
}
} else
{
alert(req.status + " : " + req.statusText);
}
}
document.msgForm.verifyCode.value = "" ;
}
// 更换验证码
function changeVerifyCode(){
// 1、如果用<iframe>实现,则重新加载<iframe>的内容
// verifyCodeFrame.location.reload();
// 2、如果用<img>实现,则修改<img src=url>的url
// 这里有一个小技巧,如果给url赋相同的值,浏览器不会重新发出请求,因此用js生成一个即时毫秒数做url中的参数
t = new Date().getTime();
document.msgForm.verifyCodeImg.src = " <%=path%>/servlet/VerifyCodeServlet?t= " + t;
}
</ script >
< body >
< form name ="msgForm" action ="" method ="post" >
< table border =0 >
< tr >< td > 标题: </ td >< td >< input name ="msgTitle" size ="65" /></ td ></ tr >
< tr >< td > 内容: </ td >< td >< textarea name ="msgContent" rows ="10" cols ="64" ></ textarea ></ td ></ tr >
< tr >< td > 验证码: </ td >< td >< input name ="verifyCode" style ="height:29px;width=70px;font-size:25px" />
<!-- 以下:两种显示验证码的方式 -->
<!-- 1、采用<iframe>实现 -->
<!--
<iframe name="verifyCodeFrame" src="<%=path+"/servlet/VerifyCodeServlet"%>" width="100" height="30" frameborder=0 align="top" marginheight=0 marginwidth=0 scrolling=no></iframe>
-->
<!-- 2、采用<img>实现 -->
< img name ="verifyCodeImg" src ="<%=path%>/servlet/VerifyCodeServlet" style ="cursor:hand" align ="top" onClick ="changeVerifyCode()" />
<!-- 以上:两种显示验证码的方式 -->
< font style ="color:blue;font-size=14" >< a href ="javascript:changeVerifyCode()" > 看不清? </ a ></ font >
< input type ="button" value =" 发表留言 " onClick ="pubMsg()" /></ td ></ tr >
</ table >
</ form >
</ body >
</ html >
2、ok.jsp 留言发布成功页面(示意)
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" >
< html >
< body >
您的留言已提交! < input type ="button" value =" OK " onClick ="window.history.go(-1)" />< br >
</ body >
</ html >
3、org.javachina.util.VerifyCode.java 验证码生成器类
import java.util.Random;
import java.awt.image.BufferedImage;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.Color;
/**
* 验证码生成器类,可生成数字、大写、小写字母及三者混合类型的验证码。
* 支持自定义验证码字符数量;
* 支持自定义验证码图片的大小;
* 支持自定义需排除的特殊字符;
* 支持自定义干扰线的数量;
* 支持自定义验证码图文颜色
* @author org.javachina
* @version 1.01
*/
public class VerifyCode {
/**
* 验证码类型为仅数字 0~9
*/
public static final int TYPE_NUM_ONLY = 0 ;
/**
* 验证码类型为仅字母,即大写、小写字母混合
*/
public static final int TYPE_LETTER_ONLY = 1 ;
/**
* 验证码类型为数字、大写字母、小写字母混合
*/
public static final int TYPE_ALL_MIXED = 2 ;
/**
* 验证码类型为数字、大写字母混合
*/
public static final int TYPE_NUM_UPPER = 3 ;
/**
* 验证码类型为数字、小写字母混合
*/
public static final int TYPE_NUM_LOWER = 4 ;
/**
* 验证码类型为仅大写字母
*/
public static final int TYPE_UPPER_ONLY = 5 ;
/**
* 验证码类型为仅小写字母
*/
public static final int TYPE_LOWER_ONLY = 6 ;
private VerifyCode(){}
/**
* 生成验证码字符串
* @param type 验证码类型,参见本类的静态属性
* @param length 验证码长度,大于0的整数
* @param exChars 需排除的特殊字符(仅对数字、字母混合型验证码有效,无需排除则为null)
* @return 验证码字符串
*/
public static String generateTextCode( int type, int length,String exChars){
if (length <= 0 ) return "" ;
StringBuffer code = new StringBuffer();
int i = 0 ;
Random r = new Random();
switch (type)
{
// 仅数字
case TYPE_NUM_ONLY:
while (i < length){
int t = r.nextInt( 10 );
if (exChars == null || exChars.indexOf(t + "" ) < 0 ){ // 排除特殊字符
code.append(t);
i ++ ;
}
}
break ;
// 仅字母(即大写字母、小写字母混合)
case TYPE_LETTER_ONLY:
while (i < length){
int t = r.nextInt( 123 );
if ((t >= 97 || (t >= 65 && t <= 90 )) && (exChars == null || exChars.indexOf(( char )t) < 0 )){
code.append(( char )t);
i ++ ;
}
}
break ;
// 数字、大写字母、小写字母混合
case TYPE_ALL_MIXED:
while (i < length){
int t = r.nextInt( 123 );
if ((t >= 97 || (t >= 65 && t <= 90 ) || (t >= 48 && t <= 57 )) && (exChars == null || exChars.indexOf(( char )t) < 0 )){
code.append(( char )t);
i ++ ;
}
}
break ;
// 数字、大写字母混合
case TYPE_NUM_UPPER:
while (i < length){
int t = r.nextInt( 91 );
if ((t >= 65 || (t >= 48 && t <= 57 )) && (exChars == null || exChars.indexOf(( char )t) < 0 )){
code.append(( char )t);
i ++ ;
}
}
break ;
// 数字、小写字母混合
case TYPE_NUM_LOWER:
while (i < length){
int t = r.nextInt( 123 );
if ((t >= 97 || (t >= 48 && t <= 57 )) && (exChars == null || exChars.indexOf(( char )t) < 0 )){
code.append(( char )t);
i ++ ;
}
}
break ;
// 仅大写字母
case TYPE_UPPER_ONLY:
while (i < length){
int t = r.nextInt( 91 );
if ((t >= 65 ) && (exChars == null || exChars.indexOf(( char )t) < 0 )){
code.append(( char )t);
i ++ ;
}
}
break ;
// 仅小写字母
case TYPE_LOWER_ONLY:
while (i < length){
int t = r.nextInt( 123 );
if ((t >= 97 ) && (exChars == null || exChars.indexOf(( char )t) < 0 )){
code.append(( char )t);
i ++ ;
}
}
break ;
}
return code.toString();
}
/**
* 已有验证码,生成验证码图片
* @param textCode 文本验证码
* @param width 图片宽度
* @param height 图片高度
* @param interLine 图片中干扰线的条数
* @param randomLocation 每个字符的高低位置是否随机
* @param backColor 图片颜色,若为null,则采用随机颜色
* @param foreColor 字体颜色,若为null,则采用随机颜色
* @param lineColor 干扰线颜色,若为null,则采用随机颜色
* @return 图片缓存对象
*/
public static BufferedImage generateImageCode(String textCode, int width, int height, int interLine, boolean randomLocation,Color backColor,Color foreColor,Color lineColor){
BufferedImage bim = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics g = bim.getGraphics();
// 画背景图
g.setColor(backColor == null ? getRandomColor():backColor);
g.fillRect( 0 , 0 ,width,height);
// 画干扰线
Random r = new Random();
if (interLine > 0 ){
int x = 0 ,y = 0 ,x1 = width,y1 = 0 ;
for ( int i = 0 ;i < interLine;i ++ ){
g.setColor(lineColor == null ? getRandomColor():lineColor);
y = r.nextInt(height);
y1 = r.nextInt(height);
g.drawLine(x,y,x1,y1);
}
}
// 写验证码
// 字体大小为图片高度的80%
int fsize = ( int )(height * 0.8 );
int fx = 0 ;
int fy = fsize;
g.setFont( new Font(Font.DIALOG,Font.PLAIN,fsize));
// 写验证码字符
for ( int i = 0 ;i < textCode.length();i ++ ){
fy = randomLocation ? ( int )((Math.random() * 0.3 + 0.6 ) * height):fy; // 每个字符高低是否随机
g.setColor(foreColor == null ? getRandomColor():foreColor);
g.drawString(textCode.charAt(i) + "" ,fx,fy);
fx+=(width / textCode.length()) * (Math.random() * 0.3 + 0.8); //星风兄的高见,依据宽度浮动
}
g.dispose();
return bim;
}
/**
* 产生随机颜色
* @return
*/
private static Color getRandomColor(){
Random r = new Random();
Color c = new Color(r.nextInt( 256 ),r.nextInt( 256 ),r.nextInt( 256 ));
return c;
}
}
4、org.javachina.servlet.VerifyCodeServlet.java 接受客户端请求,向客户端输出验证码
throws ServletException, IOException {
// 设置浏览器不缓存本页
response.setHeader( " Cache-Control " , " no-cache " );
// 生成验证码,写入用户session
String verifyCode = VerifyCode.generateTextCode(VerifyCode.TYPE_UPPER_ONLY, 4 , null );
request.getSession().setAttribute( " verifyCode " ,verifyCode);
// 输出验证码给客户端
response.setContentType( " image/jpeg " );
BufferedImage bim = VerifyCode.generateImageCode(verifyCode, 90 , 30 , 3 , true ,Color.WHITE,Color.BLACK, null );
ImageIO.write(bim, " JPEG " ,response.getOutputStream());
}
5、org.javachina.servlet.PubMsgServlet.java 保存留言信息(比对验证码)
throws ServletException, IOException {
// 得到留言标题、内容及用户输入的验证码
String msgTitle = request.getParameter( " msgTitle " );
String msgContent = request.getParameter( " msgContent " );
String verifyCode = request.getParameter( " verifyCode " );
// 取session中的正确验证码
String legalCode = null ;
if (request.getSession().getAttribute( " verifyCode " ) != null )
legalCode = (String)(request.getSession().getAttribute( " verifyCode " ));
String next;
// 比较session中的验证码与用户输入是否一致(这里忽略了大小写)
if (verifyCode != null && verifyCode.equalsIgnoreCase(legalCode)){
/*
* 保存留言内容(省略)
*/
// 指定下一个URL
next = request.getContextPath() + " /ok.jsp " ;
} else
next = " this " ;
response.setContentType( " text/plain " );
PrintWriter out = response.getWriter();
out.print(next);
out.flush();
out.close();
}
一些小瑕疵:
1.生成图片时,传入的宽度没用上导致宽度过小时字符超出图片宽度。
// 写验证码字符
...
// fx += fsize * 0.9; // 原代码
// 根据传入宽度调整字距并随机±20%浮动
fx += (width / textCode.length()) * (Math.random() * 0.4 + 0.8);
...
2.VerifyCode.generateImageCode()的第二个重载无法拿到随机生成的字符串,没有任何意义。可以增加一个方法getLastTextCode()获取但又必须保证线程安全,还不如删掉。
3.随机颜色里
Color c = new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255));
应该是256
4.生成验证码字符串的那一大段swich完全可以简化,这样太繁冗了。
可以这样写(只是一个思路,没有测试):
// 添加一个静态字段
static char[] chars = {数字,大写字母,小写字母};
...
// 方法内替换switch
int begin = 0;
int end = 0;
switch (type) {
case TYPE_NUM_ONLY:
begin = 0;
end = 10; // 第10个不是数字
break;
// 其他的case...
}
while (i < length) {
int t = r.nextInt(end - begin); // 如果end设为9,这里要+1
code.append(chars[t + begin]);
i++;
}
...