文章目录
  1. 1. 具体代码:
  2. 2. 接口实现:
  3. 3. 测试代码:

  验证码生成模块,配置信息基本和前面的模块一样。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目录下看到生成的验证码图片。

文章目录
  1. 1. 具体代码:
  2. 2. 接口实现:
  3. 3. 测试代码: