下面是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>
- 主代码部分
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()方法,主要是为了提高可测试性,方便获取验证码及设置方便测试。
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/目录下
<?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>
- 测试代码
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);
}
}
}
然后是服务模块的测试:
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"));
}
}
- 测试结果
[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] ------------------------------------------------------------------------