下面是Maven构建的实现账户注册服务的account-captcha模块,该模块负责处理账户注册时key生成、图片生成以及验证等。
- POM部分配置
//account-captcha的
<project xmlns="/POM/4.0.0" xmlns:xsi="http:///2001/XMLSchema-instance" xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId></groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>account-captcha</artifactId>
<name>Account Captcha</name>
<properties>
<>2.3.2</>
</properties>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>kaptcha</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
<build/>
</project>
这里的kaptcha依赖通过 默认repo没有找到,所以只有通过手动添加。jar下载包地址:/p/kaptcha/downloads/list
另外手动jar包到本地仓库的教程地址:/jerome-rong/archive/2012/12/08/
简单来说这里的kaptcha包下载jar包之后执行命令:mvn install:install-file -Dfile=${下载路径}\kaptcha-2.3. -DgroupId= -DartifactId=kaptcha -Dversion=2.3.2 -Dpackaging=jar 即可添加到本地仓库。
这段POM配置中首先是父模块声明,之后是项目本身的artifactId和name,groupId和version继承自父模块。然后声明了一个Maven属性,用于依赖声明。依赖除了SpringFramework和junit之外,还包含一个:kaptcha。kaptcha是一个用来生成验证码的开源类库。
注意不要忘记把account-captcha加入到聚合模块中,即修改account-parent的POM文件:
<project xmlns="/POM/4.0.0" xmlns:xsi="http:///2001/XMLSchema-instance"
xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.">
<modelVersion>4.0.0</modelVersion>
<groupId></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>
<>4.1.</>
<>4.12</>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>spring-core</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-beans</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-context</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-context-support</artifactId>
<version>${}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId></groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId></groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- 主代码部分
account-captcha需要提供的服务是生成随机的验证码主键,然后用户可以使用这个主键要求服务生成一个验证码图片,这个图片对应的值应该是随机的,最后用户读取图片值并交给服务验证。这一服务的接口定义如下:
//
package ;
import ;
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 ;
@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类
//
package ;
import ;
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++) {
(((())));
}
return ();
}
}
接下来就是模块的重要部分,服务的实现
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
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(); // 初始化验证码生成器
(new Config(new Properties())); // 为producer提供默认配置
}
public String generateCaptchaKey() {
String key = (); // 生成随机的验证码主键
String value = getCaptchaText();
(key, value); // 存储主键到captchaMap
return key;
}
public List<String> getPreDefinedTexts() {
return preDefinedTexts;
}
public void setPreDefinedTexts(List<String> preDefinedTexts) {
= preDefinedTexts;
}
private String getCaptchaText() {
if (preDefinedTexts != null && !()) {
String text = (textCount);
textCount = (textCount + 1) % ();
return text;
} else {
return ();
}
}
public byte[] generateCaptchaImage(String captchaKey)
throws AccountCaptchaException {
String text = (captchaKey);
if (text == null) {
throw new AccountCaptchaException("Captch key '" + captchaKey
+ "' not found!");
}
// 通过producer生成一个BufferImage
BufferedImage image = (text);
// 将图片对象转换为jpg格式的字节数组并返回
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
(image, "jpg", out);
} catch (IOException e) {
throw new AccountCaptchaException(
"Failed to write captcha stream!", e);
}
return ();
}
public boolean validateCaptcha(String captchaKey, String captchaValue)
throws AccountCaptchaException {
String text = (captchaKey); // 通过主键找到正确的验证码值
if (text == null) {
throw new AccountCaptchaException("Captch key '" + captchaKey
+ "' not found!");
}
if ((captchaValue)) { // 将验证码的值与用户输入值进行比较
(captchaKey);
return true;
} else {
return false;
}
}
}
另外还需要SpringFramework的配置文件,放在src/main/resources/目录下
//
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="/schema/beans"
xmlns:xsi="http:///2001/XMLSchema-instance"
xsi:schemaLocation="/schema/beans
/schema/beans/spring-beans-4.">
<bean
class="">
</bean>
</beans>
- 测试代码
首先是测试随机数生成:
//
package ;
import static ;
import ;
import ;
import ;
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 = ();
assertFalse((random)); //检查新生成的随机数是否包含在集合中
(random);
}
}
}
然后是服务模块的测试:
//
package ;
import static .*;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
public class AccountCaptchaServiceTest {
private AccountCaptchaService service;
@Before
/**
* 运行在测试方法前,初始化AccountCaptchaService的bean
* @throws Exception
*/
public void prepare() throws Exception {
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"");
service = (AccountCaptchaService) ("accountCaptchaService");
}
@Test
/**
* 测试验证码图片生成
* @throws Exception
*/
public void testGenerateCaptcha() throws Exception {
String captchaKey = ();
assertNotNull(captchaKey);
byte[] captchaImage = (captchaKey);
assertTrue( > 0);
//在项目的target目录下创建一个名为主键的jpg格式文件
File image = new File("target/" + captchaKey + ".jpg");
OutputStream output = null;
try {
//将验证码图片字节数组内容写入到jpg文件中
output = new FileOutputStream(image);
(captchaImage);
} finally {
if (output != null) {
();
}
}
//检查文件存在且包含实际内容
assertTrue(() && () > 0);
}
@Test
/**
* 测试验证流程正确性
* @throws Exception
*/
public void testValidateCaptchaCorrect() throws Exception {
List<String> preDefinedTexts = new ArrayList<String>();
("12345");
("abcde");
(preDefinedTexts);
String captchaKey = ();
(captchaKey);
assertTrue((captchaKey, "12345"));
captchaKey = ();
(captchaKey);
assertTrue((captchaKey, "abcde"));
}
@Test
/**
* 测试用户反馈Captcha错误时发生情况
* @throws Exception
*/
public void testValidateCaptchaIncorrect() throws Exception {
List<String> preDefinedTexts = new ArrayList<String>();
("12345");
(preDefinedTexts);
String captchaKey = ();
(captchaKey);
assertFalse((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] ------------------------------------------------------------------------
[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] ------------------------------------------------------------------------