maven小项目注册服务(二)--captcha模块

        验证码生成模块,配置信息基本和前面的模块一样。account-captcha需要提供的服务是生成随机的验证码主键,然后用户可以使用这个主键要求服务生成一个验证码图片,这个图片对应的值应该是随机的,最后用户用肉眼读取图片的值,并将验证码的主键与这个值交给服务进行验证。这一服务对应的接口可以定义如下:

具体代码:

public interface AccountCaptchaService {
	//生成主见
	String generateCaptchaKey() throws AccountCaptchaException;
	byte[] generateCaptchaImage(String captchaKey) throws AccountCaptchaException;
	//验证主键和值
	boolean validateCaptcha(String captchaKey, String captchaValue)throws AccountCaptchaException;
	List<String> getPreDefinedTexts();
	void setPreDefinedTexts(List<String> preDefinedTexts);
}

  额外定义的getPreDefinedText和set方法可以预定义验证码图片的值,提高程序的可预测性。

       为你了能够生成随机的验证码主键,定义一个类RandomGenerator如下:

public class RandomGenerator {

private static String rangeString = "0123456789qwertyuiopasdfghjklzxcvbnm";
public static synchronized String getRandomString(){
Random random = new Random();
StringBuffer result = new StringBuffer();
for (int i = 0; i < 8; i++) {
result.append(rangeString.charAt(random.nextInt(rangeString.length())));
}
return result.toString();
}
}

  该方法提供了一个线程安全且静态的方法,nextInt()会放回一个大于等于0且小于n的整数。

接口实现:

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())); } public String generateCaptchaKey() throws AccountCaptchaException { String key = RandomGenerator.getRandomString(); String value = getCaptchaText(); captchaMap.put(key, value); return key; } 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("captaha key"+captchaKey+"not found"); } BufferedImage image = producer.createImage(text); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { ImageIO.write(image,"jpg" , out); } catch (Exception e) { throw new AccountCaptchaException("failed to write image"); } return out.toByteArray(); } public boolean validateCaptcha(String captchaKey, String captchaValue) throws AccountCaptchaException { String text = captchaMap.get(captchaKey); if (text == null) { throw new AccountCaptchaException("captaha key"+captchaKey+"not found"); } if (text.equals(captchaValue)) { captchaMap.remove(captchaKey); return true; }else { return false; } } public List<String> getPreDefinedTexts() { return preDefinedTexts; } public void setPreDefinedTexts(List<String> preDefinedTexts) { this.preDefinedTexts = preDefinedTexts; } }

 afterPropertiesSet会在framework初始化的时候调用,这个方法初始化验证码生成器,并提供默认配置。

   generateCaptchaKey()首先生成一个随机的验证码主键,每个主键将和一个验证码字符串相关联,然后这组关联会被存储到中captchaMap以备将来验证。主键的目的仅仅是标识验证码图片,其本身没有实际的意义。getCaptchaText()用来生成验证码字符串,当preDefinedTexts存在或者为空的时候,就是用验证码图片生成producer创建一个随机的字符串。当preDefinedTexts,不为空的时候,就顺序地循环该字符串列表读取值。preDefinedTexts有其对应的一组get和stet方法,这样就能让用户预定义验证码字符串的值。generateCaptchaImage方法就能通过producer来生成一个Bufferedlmage ,随后的代码将这个图片对象转换成jpg格式的字节数组并返回。有了该字节数组,用户就能随意地将其保存成文件,或者在网页上显示。

    用户得到了验证码图片以及主键后。就会识别图片中所包含的字符串信息,然后将此验证码的值与主键一起反馈给 validateCaptcha方法以进行验证。

测试代码:

public class AccountCaptchaServiceTest {
	private AccountCaptchaService service;
	@Before
	public void prepare() throws Exception{
		ApplicationContext ctx = new ClassPathXmlApplicationContext("account_captcha.xml");
		service = (AccountCaptchaService) ctx.getBean("accountCaptchaService");
	}
	@Test
	public void testGenerateCaptcha() throws Exception{
		String captchaKey = service.generateCaptchaKey();
		assertNotNull(captchaKey);
		
		byte[] captchaImage = service.generateCaptchaImage(captchaKey);
		assertTrue(captchaImage.length>0);
		
		File image = new File("target"+captchaKey +".jpg");
		OutputStream output = null;
		try {
			output = new FileOutputStream(image);
			output.write(captchaImage);
		} finally {
			if (output != null) {
				output.close();
			}
		}
		assertTrue(image.exists() && image.length()>0);
	}
	@Test
	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
	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, "67809"));
	}
}

  

public class RandomGeneratorTest {
	@Test
	public void testGetRandomTest() throws Exception{
		Set<String> randoms = new HashSet<String>(100);
		for (int i = 0; i < 100; i++) {
			String random = RandomGenerator.getRandomString();
			assertFalse(randoms.contains(random));
			randoms.add(random);
		}
		
	}

}

  这个测试代码比较容易看懂。运行测试后可以在项目的target目录下看到生成的验证码图片。