验证码生成工具
本工具可以生成:
- 数字+字符
- 纯数字
- 纯字符
验证码样式:
- 字符串
- base64 字符图片验证码
主要方法:
- generateCaptchaImage:获取图片验证码
- generateCaptchaImageVerifyCode:获取图片验证码:(更多自定义参数)
- getVerifyCode:获取验证码
- getStringVerifyCode:获取默认长度验证码
- getNumberVerifyCode:获取默认长度纯数字验证码
- getStringAndNumberVerifyCode:获取默认长度,数字加字符
@Slf4j
public class VerifyCodeUtils {
// 加载字体并缓存
private static volatile Font cacheFont = null;
//默认宽度
private static final Integer imgWidth = 150;
//默认高度
private static final Integer imgHeight = 50;
//随机字符串和数字
private static final String STRING_NUMBER_VERIFY_CODE = "123456789ABCDEFGHJKLNPQRSTUVXYZ";
//随机字符串
private static final String STRING_VERIFY_CODE = "ABCDEFGHIJKLMNPOQRSTUVWXYZ";
//随机数
private static Random random = new Random();
//默认验证码长度
private static int DEFAULT_VERIFY_CODE_LENGTH = 4;
//验证码最大长度
private static int MAX_VERIFY_CODE_LENGTH = 6;
//默认干扰线系数
private static float DEFAULT_INTERFERE_RATE = 0.1f;
//默认噪声系数
private static float DEFAULT_NOISE_RATE = 0.3f;
/**
* 纯数字验证码
* @param length 4-6位,默认4位
* @return
*/
private static String generateNumberVerifyCode(int length) {
String code;
if (length == 6){
code = String.valueOf(ThreadLocalRandom.current().nextInt(0, 999999));
}else if (length == 5){
code = String.valueOf(ThreadLocalRandom.current().nextInt(0, 99999));
}else {
code = String.valueOf(ThreadLocalRandom.current().nextInt(0, 9999));
}
if (code.length() == length) {
return code;
} else {
StringBuilder str = new StringBuilder();
for(int i = 0; i < length - code.length(); ++i) {
str.append("0");
}
return str + code;
}
}
/**
* 生成随机验证码
* @param length
* @param sources
* @return
*/
private static String generateVerifyCode(int length, String sources) {
if (sources == null || sources.length() == 0) {
sources = STRING_NUMBER_VERIFY_CODE;
}
int codeLen = sources.length();
Random rand = new Random();
StringBuilder verifyCode = new StringBuilder(length);
for (int i = 0; i < length; i++) {
verifyCode.append(sources.charAt(rand.nextInt(codeLen - 1)));
}
return verifyCode.toString();
}
/**
* 默认长度4,数字加字符串
* @return
*/
public static String getStringAndNumberVerifyCode() {
return generateVerifyCode(DEFAULT_VERIFY_CODE_LENGTH, STRING_NUMBER_VERIFY_CODE);
}
/**
* 默认长度4,获取纯数字验证码
* @return
*/
public static String getNumberVerifyCode() {
return generateNumberVerifyCode(DEFAULT_VERIFY_CODE_LENGTH);
}
/**
* 默认长度4,字符串
* @return
*/
public static String getStringVerifyCode() {
return generateVerifyCode(DEFAULT_VERIFY_CODE_LENGTH, STRING_VERIFY_CODE);
}
/**
* 获取验证码
* @param length 验证码长度 最长不超过6位 ,默认4位
* @param type 验证码类型 0-数字+字符,1-纯数字,2-纯字符 默认0
* @return
*/
public static String getVerifyCode(int length, int type) {
if (length == 0){
length = DEFAULT_VERIFY_CODE_LENGTH;
}
if (length < DEFAULT_VERIFY_CODE_LENGTH){
throw new DescribeException("length不能小于"+DEFAULT_VERIFY_CODE_LENGTH, ServerCodeEnum.PARAM_ERROR.getCode());
}
if (length > MAX_VERIFY_CODE_LENGTH){
throw new DescribeException("length不能大于"+MAX_VERIFY_CODE_LENGTH,ServerCodeEnum.PARAM_ERROR.getCode());
}
if (type == 1){
return generateNumberVerifyCode(length);
}
if (type == 2){
return generateVerifyCode(length, STRING_VERIFY_CODE);
}
return generateVerifyCode(length, STRING_NUMBER_VERIFY_CODE);
}
/**
* 返回base64图片验证码
* @param code 验证码
* @param imgWidth 宽度
* @param imgHeight 高度
* @return
*/
public static String generateCaptchaImageVerifyCode(String code, Integer imgWidth, Integer imgHeight){
if (imgWidth == null){
imgWidth = VerifyCodeUtils.imgWidth;
}
if (imgHeight == null){
imgHeight = VerifyCodeUtils.imgHeight;
}
return generateCaptchaImage(imgWidth, imgHeight, code,DEFAULT_INTERFERE_RATE , DEFAULT_NOISE_RATE);
}
/**
* 返回base64图片验证码
* @param code 验证码
* @param imgWidth 宽度
* @param imgHeight 高度
* @param interfereRate 干扰性系数0.0-1.0
* @param noiseRate 噪声系数 0.0-1.0
* @return
*/
public static String generateCaptchaImage(String code, int imgWidth, int imgHeight,float interfereRate, float noiseRate){
return generateCaptchaImage(imgWidth, imgHeight, code, interfereRate, noiseRate);
}
/**
* 生成验证码图片
* @param imgWidth 宽度
* @param imgHeight 高度
* @param code 验证码
* @param interfereRate 干扰系数 0.1-1
* @param noiseRate 噪声系数 0.1-1
*/
private static String generateCaptchaImage(int imgWidth, int imgHeight, String code, float interfereRate, float noiseRate){
String base64Img = null;
try {
int length = code.length();
BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
Random rand = new Random();
Graphics2D graphics2D = image.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 设置边框色
graphics2D.setColor(Color.GRAY);
graphics2D.fillRect(0, 0, imgWidth, imgHeight);
// 设置背景色
Color c = new Color(235, 235, 235);
graphics2D.setColor(c);
graphics2D.fillRect(0, 1, imgWidth, imgHeight - 2);
//绘制干扰
interfere(image, graphics2D, imgWidth, imgHeight, interfereRate, noiseRate);
//使图片扭曲
shear(graphics2D, imgWidth, imgHeight, c);
//字体颜色
graphics2D.setColor(getRandColor(30, 100));
//字体大小
int fontSize = imgHeight >= 50 ? imgHeight - 8 : imgHeight >= 30 ? imgHeight - 6 : imgHeight - 4;
//字体 Fixedsys/Algerian
Font font = new Font("Fixedsys", Font.ITALIC, fontSize);
graphics2D.setFont(font);
char[] chars = code.toCharArray();
for (int i = 0; i < length; i++) {
//设置旋转
AffineTransform affine = new AffineTransform();
//控制在-0.3-0.3
double theta = ((Math.PI / 4 * rand.nextDouble())*0.3) * (rand.nextBoolean() ? 1 : -1);
affine.setToRotation(theta, (imgWidth / length) * i + fontSize / 2, imgHeight / 2);
graphics2D.setTransform(affine);
int x = ((imgWidth - 10) / length) * i + 4;
//文字居中
FontMetrics fm = graphics2D.getFontMetrics();
int stringAscent = fm.getAscent();
int stringDescent = fm.getDescent ();
int y = imgHeight / 2 + (stringAscent - stringDescent) / 2;
graphics2D.drawChars(chars, i, 1, x, y);
}
graphics2D.dispose();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", outputStream);
Base64.Encoder encoder = Base64.getEncoder();
base64Img = encoder.encodeToString(outputStream.toByteArray());
base64Img = base64Img.replaceAll("\n", "").replaceAll("\r", "");
}catch (Exception e){
log.error("生成图片验证码异常:", e);
}
return base64Img;
}
/**
* 绘制干扰线和噪点
* @param image 图像
* @param graphics2D 二维图
* @param imgWidth 宽度
* @param imgHeight 高度
* @param interfereRate 干扰线率 0.1-1
* @param noiseRate 噪声率 0.1-1
*/
private static void interfere(BufferedImage image,Graphics2D graphics2D, int imgWidth, int imgHeight, float interfereRate, float noiseRate){
Random random = new Random();
graphics2D.setColor(getRandColor(160, 200));
int line = (int) (interfereRate * 100);
for (int i = 0; i < line; i++) {
int x = random.nextInt(imgWidth - 1);
int y = random.nextInt(imgHeight - 1);
int xl = random.nextInt(6) + 1;
int yl = random.nextInt(12) + 1;
graphics2D.drawLine(x, y, x + xl + 40, y + yl + 20);
}
int area = (int) (noiseRate * imgWidth * imgHeight)/10;
for (int i = 0; i < area; i++) {
int x = random.nextInt(imgWidth);
int y = random.nextInt(imgHeight);
int rgb = getRandomIntColor();
image.setRGB(x, y, rgb);
}
}
/**
* 获取随机颜色
* @param fc
* @param bc
* @return
*/
private static Color getRandColor(int fc, int bc) {
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
/**
* 获取rgb
* @return
*/
private static int getRandomIntColor() {
int[] rgb = getRandomRgb();
int color = 0;
for (int c : rgb) {
color = color << 8;
color = color | c;
}
return color;
}
/**
* 获取随机rgb颜色
* @return
*/
private static int[] getRandomRgb() {
int[] rgb = new int[3];
for (int i = 0; i < 3; i++) {
rgb[i] = random.nextInt(255);
}
return rgb;
}
/**
* 使图片扭曲
* @param g
* @param w1
* @param h1
* @param color
*/
private static void shear(Graphics g, int w1, int h1, Color color) {
distortionX(g, w1, h1, color);
distortionY(g, w1, h1, color);
}
/**
* 使图片x轴扭曲
* @param g
* @param w1
* @param h1
* @param color
*/
private static void distortionX(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(4);
boolean borderGap = true;
int frames = 2;
int phase = random.nextInt(4);
for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
if (borderGap) {
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}
}
/**
* 使图片y轴扭曲
* @param g
* @param w1
* @param h1
* @param color
*/
private static void distortionY(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(20) + 10;
boolean borderGap = true;
int frames = 20;
int phase = 8;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
if (borderGap) {
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}
}
}
/**
* 创建字体
* @param style 字体样式
* @param size 字体大小
* @return 字体
*/
private static Font createFont(int style, int size) {
if (cacheFont != null) {
return cacheFont.deriveFont(style, size);
} else {
cacheFont = loadFont();
return cacheFont;
}
}
/**
* 加载字体,解决Centos 7.2 以后,使用OpenJDK11 生成不了验证码问题
* @return 生成验证码的字体
*/
private static Font loadFont() {
if (StringUtils.startsWithIgnoreCase(System.getProperty("os.name"), "windows")) {
// windows 操作系统直接返回即可
return new Font("Fixedsys", Font.BOLD, 0);
} else {
// linux 操作系统字体需要特殊处理
try {
InputStream inputStream = null;
Resource fontFile = new DefaultResourceLoader().getResource("classpath:ALGER.TTF");
try {
// 加载字体配置文件
Resource fontProperties = new DefaultResourceLoader().getResource("classpath:fontconfig.properties");
System.setProperty("sun.awt.fontconfig", fontProperties.getURL().getPath());
inputStream = fontFile.getInputStream();
return Font.createFont(Font.TRUETYPE_FONT, inputStream);
} catch (Exception e) {
log.error("Create Font Error", e);
} finally {
IOUtils.closeQuietly(inputStream);
}
} catch (Exception e) {
log.error("Load Font Error", e);
}
return null;
}
}
/**
* 生成验证码
* @param code
* @param imgWidth
* @param imgHeight
* @return
*/
private static String generateCaptchaImage(String code, Integer imgWidth, Integer imgHeight) {
if (StringUtils.isBlank(code)) {
return null;
}
imgWidth = imgWidth == null ? VerifyCodeUtils.imgWidth : imgWidth;
imgHeight = imgHeight == null ? VerifyCodeUtils.imgHeight: imgHeight;
Integer codeLength = code.length();
BufferedImage bufferedImg = null;
String base64Img = null;
try {
int x = 0;
// 每个字符的宽度
x = imgWidth / (codeLength + 1);
int fontHeight = 0;
// 字体的高度
fontHeight = imgHeight - 10;
float codeY;
codeY = (float) (imgHeight - 10.1);
// 生成一张图片
bufferedImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graph = bufferedImg.createGraphics();
// 填充图片颜色
graph.setColor(Color.WHITE);
graph.fillRect(0, 0, imgWidth, imgHeight);
// 创建字体
Font font = new Font("Fixedsys", Font.TRUETYPE_FONT, fontHeight);
graph.setFont(font);
Random random = new Random();
// 随机画干扰的圈圈
int red = 0;
int green = 0;
int blue = 0;
for (int i = 0; i < 10; i++) {
int xs = random.nextInt(imgWidth);
int ys = random.nextInt(imgHeight);
int xe = random.nextInt(imgWidth / 16);
int ye = random.nextInt(imgHeight / 16);
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);
graph.setColor(new Color(red, green, blue));
graph.drawOval(xs, ys, xe, ye);
}
// 画随机线
for (int i = 0; i < 20; i++) {
int xs = random.nextInt(imgWidth);
int ys = random.nextInt(imgHeight);
int xe = xs + random.nextInt(imgWidth / 16);
int ye = ys + random.nextInt(imgHeight / 16);
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);
graph.setColor(new Color(red, green, blue));
graph.drawLine(xs, ys, xe, ye);
}
// 产生随机的颜色值,让输出的每个字符的颜色值都将不同。
for (int i = 0; i < codeLength; i++) {
red = random.nextInt(225);
green = random.nextInt(225);
blue = random.nextInt(225);
graph.setColor(new Color(red, green, blue));
String c = String.valueOf(code.charAt(i));
graph.drawString(c, (float) (i + 0.5) * x, codeY);
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImg, "jpg", outputStream);
Base64.Encoder encoder = Base64.getEncoder();
base64Img = encoder.encodeToString(outputStream.toByteArray());
base64Img = base64Img.replaceAll("\n", "").replaceAll("\r", "");
} catch (Exception e) {
log.error("生成验证码异常:", e);
}
// return "data:image/jpg;base64," + base64Img;
return base64Img;
}
}