hutool的验证码已经够用,但有些地方需要自定义(画布超界、去除一些字符),所以对此进行了扩展。
依赖
cn.hutool
hutool-all
4.5.8
生成器
package com.rz.util;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.img.GraphicsUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.concurrent.ThreadLocalRandom;
/**
* 自定义验证码生成器(因原生工具经常出现字符超出画布边界,去除一些0和o等字母,特此自定义一个)
*
* @author sunziwen
* @version 1.0
* @date 2019/9/2 14:24
**/
public class BuilderCaptcha extends AbstractCaptcha {
/**
* 用于随机选的数字
*/
private static final String BASE_NUMBER = "123456789";
/**
* 用于随机选的字符
*/
private static final String BASE_CHAR_LOWER = "abcdefghjkmnpqrstuvwxyz";
private static final String BASE_CHAR_UP = "ABCDEFGHJKMNPQRSTUVWXYZ";
/**
* 用于随机选的字符和数字
*/
private static final String BASE_CHAR_NUMBER = BASE_NUMBER + BASE_CHAR_LOWER + BASE_CHAR_UP;
/**
* 构造,使用随机验证码生成器生成验证码
*
* @param width 图片宽
* @param height 图片高
* @param codeCount 字符个数
* @param interfereCount 验证码干扰元素个数
*/
public BuilderCaptcha(int width, int height, int codeCount, int interfereCount) {
super(width, height, new RandomGenerator(BASE_CHAR_NUMBER, codeCount), interfereCount);
}
/**
* 根据生成的code创建验证码图片
*
* @param code 验证码
*/
@Override
protected Image createImage(String code) {
// 图像buffer
final BufferedImage image = new BufferedImage(width + 100, height + 50, BufferedImage.TYPE_INT_RGB);
final Graphics2D g = GraphicsUtil.createGraphics(image, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
// 干扰线
drawInterfere(g);
// 字符串
drawString(g, code);
return image;
}
/**
* 绘制字符串
*
* @param g {@link Graphics2D}画笔
* @param code 验证码
*/
private void drawString(Graphics2D g, String code) {
// 抗锯齿
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 创建字体
g.setFont(this.font);
// 文字
int minY = GraphicsUtil.getMinY(g);
if (minY < 0) {
minY = this.height - 1;
}
final int len = this.generator.getLength();
int charWidth = width / len;
for (int i = 0; i < len; i++) {
// 产生随机的颜色值,让输出的每个字符的颜色值都将不同。
g.setColor(ImgUtil.randomColor());
g.drawString(String.valueOf(code.charAt(i)), i * charWidth + 50, RandomUtil.randomInt(minY, this.height));
}
}
/**
* 绘制干扰线
*
* @param g {@link Graphics2D}画笔
*/
private void drawInterfere(Graphics2D g) {
final ThreadLocalRandom random = RandomUtil.getRandom();
// 干扰线
for (int i = 0; i < this.interfereCount; i++) {
int xs = random.nextInt(width + 200);
int ys = random.nextInt(height + 100);
int xe = xs + random.nextInt(width / 8);
int ye = ys + random.nextInt(height / 8);
g.setColor(ImgUtil.randomColor(random));
g.drawLine(xs, ys, xe, ye);
}
}
}
附上一个对应的工具类:
package com.rz.util;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.rz.config.Config;
import com.rz.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
/**
* 图形验证码工具类
*
* @author sunziwen
* @version 1.0
* @date 2019/6/10 10:44
**/
@Component
public class RzCaptchaUtil {
private RedisUtil redisUtil;
private Config config;
@Autowired
public RzCaptchaUtil(RedisUtil redisUtil, Config config) {
this.redisUtil = redisUtil;
this.config = config;
}
/**
* 获取图形验证码
*
* @return
*/
public HashMap getCaptchaBase64() {
BuilderCaptcha lineCaptcha = new BuilderCaptcha(config.getCaptchWidth(), config.getCaptchHeight(), 4, 150);
String imageBase64 = lineCaptcha.getImageBase64();
String code = lineCaptcha.getCode();
HashMap hashMap = new HashMap<>(2);
hashMap.put("captcha", "data:image/png;base64," + imageBase64);
//存储在redis中的key
String key = IdUtil.randomUUID() + System.currentTimeMillis();
hashMap.put("verifyStr", key);
redisUtil.set(key, code, config.getCaptchExpireTime());
return hashMap;
}
/**
* 校验图形验证码
*
* @param code
* @param verify
* @return
*/
public boolean verifyCaptcha(String code, String verify) {
String redisCode = redisUtil.get(verify) + "";
if (StrUtil.isNotBlank(code) && StrUtil.equalsIgnoreCase(redisCode, code)) {
redisUtil.remove(verify);
return true;
}
return false;
}
}
使用示例:
@PostMapping(value = "/getCaptcha", name = "获取图形验证码")
@ApiOperation("获取图形验证码")
public AppResDto getCaptcha() {
HashMap hashMap = rzCaptchaUtil.getCaptchaBase64();
return AppResDto.success(hashMap);
}
@ApiOperation("登录")
@PostMapping("/login")
@Limit(program = "rz-xxxx", prefix = "users-login", count = 10, period = 30, limitType = LimitType.ANNOTATION)
public AppResDto login(@RequestParam("phone") @LimitParam String phone,
@RequestParam("password") String password,
@RequestParam("captcha") String captcha,
@RequestParam("verifyStr") String verifyStr) {
//图形验证码正确
AssertUtil.isTrue(ResEnums.verify_error, rzCaptchaUtil.verifyCaptcha(captcha, verifyStr));
return userController.login(phone, password);
}