论坛首页 Java企业应用论坛

基于Struts2+Spring+iBatis的web应用最佳实践系列之六(验证码篇)

浏览 2442 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-02-05   最后修改:2010-02-08

本篇主要讨论下如何使用Struts2实现一个通用的验证码(checkcode)功能。

 

首先我们要有一个CheckCodeSession类,用来保存我们生成好的checkcode。这个类很简单,主要就是保存了验证码本身,创建时间以及是否有效的标志。

 

public class CheckCodeSession {
	private String checkCode;//验证码
	private long createTime;//创建时间
	private boolean validate = false;//是否有效(默认无效)
	public String getCheckCode() {
		return checkCode;
	}
	public void setCheckCode(String checkCode) {
		this.checkCode = checkCode;
	}
	public long getCreateTime() {
		return createTime;
	}
	public void setCreateTime(long createTime) {
		this.createTime = createTime;
	}
	public boolean isValidate() {
		return validate;
	}
	public void setValidate(boolean validate) {
		this.validate = validate;
	}
}

 

我们再来看一下checkcode aciton。它从0-9十个数字中随机选取4个作为一个checkcode,即验证码本身,并把生成的验证码赋值给checkCodeSession,并设置成有效。同时还实现了CheckCodeSessionAware 这个接口。

public class CheckCodeAction implements Action, CheckCodeSessionAware {

	private static final long serialVersionUID = 1L;
	private String checkCode;
	private CheckCodeSession checkCodeSession;
	private int codeLenght = 4;
	private static char[] codes={'0','1','2','3','4','5','6','7','8','9'};
	public String execute() throws Exception {
		Random random = new Random();
		char[] checkCodeChararray = new char[codeLenght];
		for(int i=0;i<codeLenght;i++){
			checkCodeChararray[i] = codes[random.nextInt(codes.length)];
		}
		checkCode = String.valueOf(checkCodeChararray);
		checkCodeSession.setCheckCode(checkCode);
		
		checkCodeSession.setCreateTime(System.currentTimeMillis());
		checkCodeSession.setValidate(true);
		return SUCCESS;
	}

	public String getCheckCode() {
		return checkCode;
	}

	public void setCheckCodeSession(CheckCodeSession checkCodeSession) {
		this.checkCodeSession = checkCodeSession;
	}

	public void setCodeLenght(int codeLenght) {
		this.codeLenght = codeLenght;
	}

	public CheckCodeSession getCheckCodeSession() {
		return checkCodeSession;
	}
	
	
}

 

CheckCodeSessionAware接口,这个接口很简单,只有两个getter、setter方法。

public interface CheckCodeSessionAware {
	public void setCheckCodeSession(CheckCodeSession checkCodeSession);
	public CheckCodeSession getCheckCodeSession();
}

 

现在我们已经有了checkcode和生成checkcode的aciton,那么具体如何实现验证功能呢?这就要涉及到Struts2的验证框架,我们还是来看代码,结合CheckCodeSessionAware接口,CheckCodeValidator实现对checkcode的验证功能。

public class CheckCodeValidator extends FieldValidatorSupport {
	private static Logger logger = Logger.getLogger(CheckCodeValidator.class);
	
	public void validate(Object object) throws ValidationException {
		Object obj = getFieldValue(getFieldName(), object);
		CheckCodeSession checkCodeSession = null;
		
		if(object instanceof CheckCodeSessionAware){
			checkCodeSession = ((CheckCodeSessionAware)object).getCheckCodeSession();
			if(checkCodeSession == null){
				logger.error("action: "+ object +" not implements CheckCodeSessionAware.class");
			}
		}
		
		String checkCode = (String) obj;
		if(StringUtil.isEmpty(checkCode) || checkCodeSession == null || !checkCodeSession.isValidate()){
			this.addFieldError(getFieldName(), object);
		}else{
			if(checkCode.equalsIgnoreCase(checkCodeSession.getCheckCode())){
				checkCodeSession.setValidate(false);
			}else{
				this.addFieldError(getFieldName(), object);
			}
		}
	}

 

Struts2框架设计的非常灵活,验证本身就可以成为一个框架,我们的CheckCodeValidator只要简单的继承FieldValidatorSupport就能成为一个validator,在应用中只需要简单的配置就可以使用。

 

下面我们再来看一下如何在页面上显示验证码,在这里又是对框架的扩展,我们的CheckCodeResultType继承了StrutsResultSupport从而变成了一个新的result type。在这里,我们使用java 2d的api根据checkcode的值生成图片后直接写入到httpResponse中。值得注意的是我们在http header中设置了No-cache的方式,这是为了不让浏览器缓存checkcode图片。

 

public class CheckCodeResultType extends StrutsResultSupport{
    protected String contentType = "image/jpeg";
    protected int contentLength;
    private String checkCode;
    
    
    /**
     * @return Returns the contentType.
     */
    public String getContentType() {
        return (contentType);
    }

    /**
     * @param contentType The contentType to set.
     */
    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    /**
     * @return Returns the contentLength.
     */
    public int getContentLength() {
        return contentLength;
    }

    /**
     * @param contentLength The contentLength to set.
     */
    public void setContentLength(int contentLength) {
        this.contentLength = contentLength;
    }



    /**
     * @see com.opensymphony.xwork.Result#execute(com.opensymphony.xwork.ActionInvocation)
     */
    protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
        try {
            // Find the Response in context
            HttpServletResponse oResponse = (HttpServletResponse) invocation.getInvocationContext().get(HTTP_RESPONSE);
            oResponse.setHeader("Pragma","No-cache");
            oResponse.setHeader("Cache-Control","no-cache");
            oResponse.setDateHeader("Expires", 0);

            // Set the content type
            oResponse.setContentType(conditionalParse(contentType, invocation));

            // Set the content length
            if (contentLength != 0) {
                 oResponse.setContentLength(contentLength);
            }
            // Get the outputstream
            OutputStream oOutput = oResponse.getOutputStream();
            
            //取随机产生的认证码(4位数字)
            String rand=conditionalParse(checkCode, invocation);
            
//          在内存中创建图象
            int width=60, height=24;
            if(rand!= null){
            	width = 17 * rand.length();
            }
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

//             获取图形上下文
            Graphics g = image.getGraphics();

//            生成随机类
            Random random = new Random();

//             设定背景色
            g.setColor(getRandColor(200,250));
            g.fillRect(0, 0, width, height);

//            设定字体
            Font font = new Font("Times New Roman",Font.BOLD,20);
            g.setFont(font);

//            画边框
            g.setColor(getRandColor(20,50));
            g.drawRect(0,0,width-1,height-1);


//             随机产生155条干扰线,使图象中的认证码不易被其它程序探测到
            g.setColor(getRandColor(160,200));
            for (int i=0;i<155;i++)
            {
             int x = random.nextInt(width);
             int y = random.nextInt(height);
                    int xl = random.nextInt(12);
                    int yl = random.nextInt(12);
             g.drawLine(x,y,x+xl,y+yl);
            }
            
            if(rand != null){
	            for (int i=0;i<rand.length();i++){
	                // 将认证码显示到图象中
	                g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));//调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
	                g.drawString(String.valueOf(rand.charAt(i)),15 * i + 7,17);
	            }
            }

//             图象生效
            g.dispose();

//             输出图象到页面
            ImageIO.write(image, "JPEG", oResponse.getOutputStream());
            
            // Flush
            oOutput.flush();
        }
        finally {
        	
        }
    }
   private Color getRandColor(int fc,int bc){//给定范围获得随机颜色
        Random random = new Random();
        if(fc>255) fc=255;
        if(bc>255) bc=255;
        int r=fc+random.nextInt(bc-fc);
        int g=fc+random.nextInt(bc-fc);
        int b=fc+random.nextInt(bc-fc);
        return new Color(r,g,b);
        }

	public String getCheckCode() {
		return checkCode;
	}
	
	public void setCheckCode(String checkCode) {
		this.checkCode = checkCode;
	}
}

 

 现在,我们所需要的代码全部都有了,那么,在具体的应用中应该如何使用呢?

首先,在struts.xml中,配置好checkcode aciton。注意,这里的result type使用的是我们自己定义的checkCode类型。

 

		<result-types>
			<result-type name="checkCode"
				class="com.meidusa.toolkit.web.common.resulttype.CheckCodeResultType" />
		</result-types>
		<action name="checkCode"
			class="com.meidusa.toolkit.web.common.action.CheckCodeAction">
			<result name="success" type="checkCode">
				<param name="contentType">image/jpeg</param>
				<param name="checkCode">${checkCode}</param>
			</result>
		</action>

 

其次,在需要使用验证码验证的aciton中同样实现CheckCodeSessionAware接口,比如我们的login aciton

 

public class LoginAction extends ActionSupport implements	ClientCookieAware<Cookie>, ClientCookieNotCare, CheckCodeSessionAware{

	private Cookie cookie;
	private String loginId;
	private CheckCodeSession checkCodeSession;
	private String checkcode;
	
	public String getCheckcode() {
		return checkcode;
	}

	public void setCheckcode(String checkcode) {
		this.checkcode = checkcode;
	}

	public String getLoginId() {
		return loginId;
	}

	public void setLoginId(String loginId) {
		this.loginId = loginId;
	}

	public Cookie getCookie() {
		return cookie;
	}

	public void setClientCookie(Cookie cookie) {
		this.cookie = cookie;		
	}

	public String execute() throws Exception {
			cookie.setLoginId(loginId);
			cookie.save();
			return SUCCESS;

		}

	public CheckCodeSession getCheckCodeSession() {
		return checkCodeSession;
	}

	public void setCheckCodeSession(CheckCodeSession checkCodeSession) {
		this.checkCodeSession = checkCodeSession;
	}
}

 

 

这里,还有非常关键的一步。大家是否还记得在本文开头介绍的那个CheckCodeSession类和CheckCodeSessionAware接口呢?CheckCodeSessionAware接口的功能除了让struts2的validation框架在调用validate方法验证的时候可以从aciton中获得CheckCodeSession对象外,还有一个很重要的功能是让spring来注入这个CheckCodeSession对象。也就是说CheckCodeSession需要作为一个bean由spring来配置。注意,这里非常关键的一步是把scope设置成session,使得checkcode aciton中的CheckCodeSession与需要对checkcode验证的action(比如这里的login aciton)中的CheckCodeSession保持一致。如果不是设置成session的话,验证框架在验证的时候读取的checkcode值会与用户看到输入的值不同。

 

<bean id="checkCodeSession" class="com.meidusa.toolkit.web.common.components.CheckCodeSession" scope="session" />

 

这需要使用spring2.0的配置文件,老版本的spring配置文件不支持scope这个属性。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

 

 最后,我们还需要编写一个validators.xml,告诉struts2框架我们自己定义的额外的validator,并把这个文件放在与struts.xml相同的目录中。

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
        "-//OpenSymphony Group//XWork Validator Config 1.0//EN"
         "http://www.opensymphony.com/xwork/xwork-validator-config-1.0.dtd"> 
<validators>  
   <validator name="checkcode"  class="com.meidusa.toolkit.web.common.validators.CheckCodeValidator"/> 
</validators> 

 

在所有都配置好的情况下,我们再来看一下在页面上如何使用checkcode

<label>请输入验证码</label><input type="text" name="checkcode" /><img src="/demo/checkCode.action"/></br><s:fielderror/>

 

最后,再把我们的checkcode validator配置给我们的loginAction

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" 
       "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
       
<validators>
	<field name="checkcode">
		<field-validator type="requiredstring">
			<message>验证码必须填写</message>
		</field-validator>
		<field-validator type="checkcode">
			<message>验证码不正确!</message>
		</field-validator>
	</field>
</validators>

 

至此,对于验证码的实现就全部介绍完了,这已经是本系列的第六篇了,笔者会在下一篇中放出一个demo供大家下载体验,并结束整个系列。

   发表时间:2010-03-07  
最后面的一段代码是在您的LoginAction-validation中,是错误提示信息,当验证码输入出错时,这个文件是在怎样被加载的?
我也做了和您demo相似的工程,但是这里没能成功。
0 请登录后投票
   发表时间:2010-03-07  
这个文件是通过Struts2的validation框架自动加载的。
1 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics