一个Maven实现的验证码模块

下面是Maven构建的实现账户注册服务的account-captcha模块,该模块负责处理账户注册时key生成、图片生成以及验证等。

  • POM部分配置

//account-captcha的pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  
  <parent>
  	<groupId>com.juvenxu.mvnbook.account</groupId>
  	<artifactId>account-parent</artifactId>
  	<version>1.0.0-SNAPSHOT</version>
  </parent>
  
  <artifactId>account-captcha</artifactId>
  <name>Account Captcha</name>
  
  <properties>
  	<kaptcha.version>2.3.2</kaptcha.version>
  </properties>
  
  <dependencies>
  	<dependency>
  		<groupId>com.google.code.kaptcha</groupId>
  		<artifactId>kaptcha</artifactId>
  		<version>${kaptcha.version}</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-core</artifactId>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-beans</artifactId>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-context</artifactId>
  	</dependency>
  </dependencies>
  <build/>
</project>

这里的kaptcha依赖通过 默认repo没有找到,所以只有通过手动添加。jar下载包地址:https://code.google.com/p/kaptcha/downloads/list

另外手动jar包到本地仓库的教程地址:http://www.cnblogs.com/jerome-rong/archive/2012/12/08/2808947.html

简单来说这里的kaptcha包下载jar包之后执行命令:mvn install:install-file -Dfile=${下载路径}\kaptcha-2.3.2.jar -DgroupId=com.google.code.kaptcha -DartifactId=kaptcha -Dversion=2.3.2 -Dpackaging=jar  即可添加到本地仓库。

这段POM配置中首先是父模块声明,之后是项目本身的artifactId和name,groupId和version继承自父模块。然后声明了一个Maven属性kaptcha.version,用于依赖声明。依赖除了SpringFramework和junit之外,还包含一个com.google.code.kaptcha:kaptcha。kaptcha是一个用来生成验证码的开源类库。

注意不要忘记把account-captcha加入到聚合模块中,即修改account-parent的POM文件:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.juvenxu.mvnbook.account</groupId>
	<artifactId>account-parent</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<packaging>pom</packaging>
	<name>Account Parent</name>

	<modules>
		<module>../account-email</module>
		<module>../account-persist</module>
		<module>../account-captcha</module>
	</modules>

	<properties>
		<springframework.version>4.1.7.RELEASE</springframework.version>
		<junit.version>4.12</junit.version>
	</properties>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-core</artifactId>
				<version>${springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-beans</artifactId>
				<version>${springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-context</artifactId>
				<version>${springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-context-support</artifactId>
				<version>${springframework.version}</version>
			</dependency>
			<dependency>
				<groupId>junit</groupId>
				<artifactId>junit</artifactId>
				<version>${junit.version}</version>
				<scope>test</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<configuration>
						<source>1.7</source>
						<target>1.7</target>
					</configuration>
				</plugin>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-resources-plugin</artifactId>
					<configuration>
						<encoding>UTF-8</encoding>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>

  • 主代码部分
account-captcha需要提供的服务是生成随机的验证码主键,然后用户可以使用这个主键要求服务生成一个验证码图片,这个图片对应的值应该是随机的,最后用户读取图片值并交给服务验证。这一服务的接口定义如下:
//AccountCaptchaService.java
package com.juvenxu.mvnbook.account.captcha;

import java.util.List;

public interface AccountCaptchaService {
	/**
	 * 生成随机的验证码主键
	 * 
	 * @return 验证码主键
	 * @throws AccountCaptchaException
	 */
	String generateCaptchaKey() throws AccountCaptchaException;

	/**
	 * 生成验证码图片
	 * 
	 * @param 验证码主键
	 * @return 验证码图片
	 * @throws AccountCaptchaException
	 */
	byte[] generateCaptchaImage(String captchaKey)
			throws AccountCaptchaException;

	/**
	 * 验证用户反馈的主键和值
	 * 
	 * @param captchaKey
	 *            验证码主键
	 * @param captchaValue
	 *            验证码的值
	 * @return 是否验证成功
	 * @throws AccountCaptchaException
	 */
	boolean validateCaptcha(String captchaKey, String captchaValue)
			throws AccountCaptchaException;

	/**
	 * 预获取验证码内容
	 * 
	 * @return 验证码
	 */
	List<String> getPreDefinedTexts();

	/**
	 * 预定义验证码图片的内容
	 * 
	 * @param 验证码
	 */
	void setPreDefinedTexts(List<String> preDefinedTexts);
}

这里引入了一个异常类,定义如下:
package com.juvenxu.mvnbook.account.captcha;

@SuppressWarnings("serial")
public class AccountCaptchaException extends Exception {
	/**
	 * 带一个参数的构造函数
	 * 
	 * @param message
	 *            错误信息
	 */
	public AccountCaptchaException(String message) {
		super(message);
	}

	/**
	 * 带两个参数的构造参数
	 * 
	 * @param message
	 *            错误信息
	 * @param throwable
	 *            是否可抛出
	 */
	public AccountCaptchaException(String message, Throwable throwable) {
		super(message, throwable);
	}
}
这里的服务接口之所以要定义额外的getPreDefinedTexts()和setPreDefinedTexts()方法,主要是为了提高可测试性,方便获取验证码及设置方便测试。
另外为了能够生成随机的验证码主键,引入一个RandomGenerator类
//RandomGenerator.java
package com.juvenxu.mvnbook.account.captcha;

import java.util.Random;

public class RandomGenerator {
	// 验证码字符范围
	private static String range = "0123456789abcdefghijklmnopqrstuvwxyz";

	/**
	 * 静态且安全地获取一个长度为8的随机字符串
	 * 
	 * @return 随机字符串
	 */
	public static synchronized String getRandomString() {
		Random random = new Random();
		StringBuffer result = new StringBuffer();
		for (int i = 0; i < 8; i++) {
			result.append(range.charAt(random.nextInt(range.length())));
		}
		return result.toString();
	}
}
接下来就是模块的重要部分,服务的实现
package com.juvenxu.mvnbook.account.captcha;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.imageio.ImageIO;

import org.springframework.beans.factory.InitializingBean;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;

public class AccountCaptchaServiceImpl implements AccountCaptchaService,
		InitializingBean {
	private DefaultKaptcha producer; // 验证码生成器
	private Map<String, String> captchaMap = new HashMap<String, String>(); // 验证键值对
	private List<String> preDefinedTexts; // 预定义验证码字符串
	private int textCount = 0; // 验证码计数器

	public void afterPropertiesSet() throws Exception {
		producer = new DefaultKaptcha(); // 初始化验证码生成器
		producer.setConfig(new Config(new Properties())); // 为producer提供默认配置
	}

	public String generateCaptchaKey() {
		String key = RandomGenerator.getRandomString(); // 生成随机的验证码主键
		String value = getCaptchaText();
		captchaMap.put(key, value); // 存储主键到captchaMap
		return key;
	}

	public List<String> getPreDefinedTexts() {
		return preDefinedTexts;
	}

	public void setPreDefinedTexts(List<String> preDefinedTexts) {
		this.preDefinedTexts = preDefinedTexts;
	}

	private String getCaptchaText() {
		if (preDefinedTexts != null && !preDefinedTexts.isEmpty()) {
			String text = preDefinedTexts.get(textCount);
			textCount = (textCount + 1) % preDefinedTexts.size();
			return text;
		} else {
			return producer.createText();
		}
	}

	public byte[] generateCaptchaImage(String captchaKey)
			throws AccountCaptchaException {
		String text = captchaMap.get(captchaKey);
		if (text == null) {
			throw new AccountCaptchaException("Captch key '" + captchaKey
					+ "' not found!");
		}
		// 通过producer生成一个BufferImage
		BufferedImage image = producer.createImage(text);
		// 将图片对象转换为jpg格式的字节数组并返回
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			ImageIO.write(image, "jpg", out);
		} catch (IOException e) {
			throw new AccountCaptchaException(
					"Failed to write captcha stream!", e);
		}
		return out.toByteArray();
	}

	public boolean validateCaptcha(String captchaKey, String captchaValue)
			throws AccountCaptchaException {
		String text = captchaMap.get(captchaKey); // 通过主键找到正确的验证码值
		if (text == null) {
			throw new AccountCaptchaException("Captch key '" + captchaKey
					+ "' not found!");
		}
		if (text.equals(captchaValue)) { // 将验证码的值与用户输入值进行比较
			captchaMap.remove(captchaKey);
			return true;
		} else {
			return false;
		}
	}
}
另外还需要SpringFramework的配置文件,放在src/main/resources/目录下
//account-captcha.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">
	<bean id="accountCaptchaService"
		class="com.juvenxu.mvnbook.account.captcha.AccountCaptchaServiceImpl">
	</bean>
</beans>
  • 测试代码
首先是测试随机数生成:
//RandomGeneratorTest.java
package com.juvenxu.mvnbook.account.captcha;

import static org.junit.Assert.assertFalse;

import java.util.HashSet;
import java.util.Set;

import org.junit.Test;

public class RandomGeneratorTest {
	@Test
	public void testGetRandomString() throws Exception {
		Set<String> randoms = new HashSet<String>(100);	//创建初始容量为100的集合
		for (int i = 0; i < 100; i++) {
			String random = RandomGenerator.getRandomString();
			assertFalse(randoms.contains(random));		//检查新生成的随机数是否包含在集合中
			randoms.add(random);
		}
	}
}
然后是服务模块的测试:
//AccountCaptchaServiceTest.java
package com.juvenxu.mvnbook.account.captcha;

import static org.junit.Assert.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AccountCaptchaServiceTest {
	private AccountCaptchaService service;

	@Before
	/**
	 * 运行在测试方法前,初始化AccountCaptchaService的bean
	 * @throws Exception
	 */
	public void prepare() throws Exception {
		@SuppressWarnings("resource")
		ApplicationContext ctx = new ClassPathXmlApplicationContext(
				"account-captcha.xml");
		service = (AccountCaptchaService) ctx.getBean("accountCaptchaService");
	}
	
	@Test
	/**
	 * 测试验证码图片生成
	 * @throws Exception
	 */
	public void testGenerateCaptcha() throws Exception {
		String captchaKey = service.generateCaptchaKey();
		assertNotNull(captchaKey);

		byte[] captchaImage = service.generateCaptchaImage(captchaKey);
		assertTrue(captchaImage.length > 0);
		//在项目的target目录下创建一个名为主键的jpg格式文件
		File image = new File("target/" + captchaKey + ".jpg");	
		OutputStream output = null;
		try {
			//将验证码图片字节数组内容写入到jpg文件中
			output = new FileOutputStream(image);
			output.write(captchaImage);
		} finally {
			if (output != null) {
				output.close();
			}
		}
		//检查文件存在且包含实际内容
		assertTrue(image.exists() && image.length() > 0);
	}

	@Test
	/**
	 * 测试验证流程正确性
	 * @throws Exception
	 */
	public void testValidateCaptchaCorrect() throws Exception {
		List<String> preDefinedTexts = new ArrayList<String>();
		preDefinedTexts.add("12345");
		preDefinedTexts.add("abcde");
		service.setPreDefinedTexts(preDefinedTexts);

		String captchaKey = service.generateCaptchaKey();
		service.generateCaptchaImage(captchaKey);
		assertTrue(service.validateCaptcha(captchaKey, "12345"));

		captchaKey = service.generateCaptchaKey();
		service.generateCaptchaImage(captchaKey);
		assertTrue(service.validateCaptcha(captchaKey, "abcde"));
	}

	@Test
	/**
	 * 测试用户反馈Captcha错误时发生情况
	 * @throws Exception
	 */
	public void testValidateCaptchaIncorrect() throws Exception {
		List<String> preDefinedTexts = new ArrayList<String>();
		preDefinedTexts.add("12345");
		service.setPreDefinedTexts(preDefinedTexts);

		String captchaKey = service.generateCaptchaKey();
		service.generateCaptchaImage(captchaKey);
		assertFalse(service.validateCaptcha(captchaKey, "67890"));
	}
}

  • 测试结果
执行mvn test,构建成功,结果如下:
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13.555 s
[INFO] Finished at: 2015-07-24T23:06:14+08:00
[INFO] Final Memory: 12M/127M
[INFO] ------------------------------------------------------------------------


参考书籍:《Maven实战》第10章——徐晓斌著





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值