From 993470a9cff4981712e87af6e3390f0629c1a491 Mon Sep 17 00:00:00 2001 From: xielei Date: Tue, 1 Dec 2020 16:24:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=BE=E7=89=87=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/captcha/CaptchaUtil.java | 154 ++++++------ .../java/cn/hutool/captcha/GifCaptcha.java | 221 ++++++++++++++++++ .../java/cn/hutool/captcha/CaptchaTest.java | 172 +++++++------- 3 files changed, 391 insertions(+), 156 deletions(-) create mode 100644 hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/CaptchaUtil.java b/hutool-captcha/src/main/java/cn/hutool/captcha/CaptchaUtil.java index eff5aac6a..7c814e490 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/CaptchaUtil.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/CaptchaUtil.java @@ -2,85 +2,93 @@ package cn.hutool.captcha; /** * 图形验证码工具 - * + * * @author looly * @since 3.1.2 */ public class CaptchaUtil { - /** - * 创建线干扰的验证码,默认5位验证码,150条干扰线 - * - * @param width 图片宽 - * @param height 图片高 - * @return {@link LineCaptcha} - */ - public static LineCaptcha createLineCaptcha(int width, int height) { - return new LineCaptcha(width, height); - } + /** + * 创建线干扰的验证码,默认5位验证码,150条干扰线 + * + * @param width 图片宽 + * @param height 图片高 + * @return {@link LineCaptcha} + */ + public static LineCaptcha createLineCaptcha(int width, int height) { + return new LineCaptcha(width, height); + } - /** - * 创建线干扰的验证码 - * - * @param width 图片宽 - * @param height 图片高 - * @param codeCount 字符个数 - * @param lineCount 干扰线条数 - * @return {@link LineCaptcha} - */ - public static LineCaptcha createLineCaptcha(int width, int height, int codeCount, int lineCount) { - return new LineCaptcha(width, height, codeCount, lineCount); - } + /** + * 创建线干扰的验证码 + * + * @param width 图片宽 + * @param height 图片高 + * @param codeCount 字符个数 + * @param lineCount 干扰线条数 + * @return {@link LineCaptcha} + */ + public static LineCaptcha createLineCaptcha(int width, int height, int codeCount, int lineCount) { + return new LineCaptcha(width, height, codeCount, lineCount); + } - /** - * 创建圆圈干扰的验证码,默认5位验证码,15个干扰圈 - * - * @param width 图片宽 - * @param height 图片高 - * @return {@link CircleCaptcha} - * @since 3.2.3 - */ - public static CircleCaptcha createCircleCaptcha(int width, int height) { - return new CircleCaptcha(width, height); - } + /** + * 创建圆圈干扰的验证码,默认5位验证码,15个干扰圈 + * + * @param width 图片宽 + * @param height 图片高 + * @return {@link CircleCaptcha} + * @since 3.2.3 + */ + public static CircleCaptcha createCircleCaptcha(int width, int height) { + return new CircleCaptcha(width, height); + } - /** - * 创建圆圈干扰的验证码 - * - * @param width 图片宽 - * @param height 图片高 - * @param codeCount 字符个数 - * @param circleCount 干扰圆圈条数 - * @return {@link CircleCaptcha} - * @since 3.2.3 - */ - public static CircleCaptcha createCircleCaptcha(int width, int height, int codeCount, int circleCount) { - return new CircleCaptcha(width, height, codeCount, circleCount); - } - - /** - * 创建扭曲干扰的验证码,默认5位验证码 - * - * @param width 图片宽 - * @param height 图片高 - * @return {@link ShearCaptcha} - * @since 3.2.3 - */ - public static ShearCaptcha createShearCaptcha(int width, int height) { - return new ShearCaptcha(width, height); - } - - /** - * 创建扭曲干扰的验证码,默认5位验证码 - * - * @param width 图片宽 - * @param height 图片高 - * @param codeCount 字符个数 - * @param thickness 干扰线宽度 - * @return {@link ShearCaptcha} - * @since 3.3.0 - */ - public static ShearCaptcha createShearCaptcha(int width, int height, int codeCount, int thickness) { - return new ShearCaptcha(width, height, codeCount, thickness); - } + /** + * 创建圆圈干扰的验证码 + * + * @param width 图片宽 + * @param height 图片高 + * @param codeCount 字符个数 + * @param circleCount 干扰圆圈条数 + * @return {@link CircleCaptcha} + * @since 3.2.3 + */ + public static CircleCaptcha createCircleCaptcha(int width, int height, int codeCount, int circleCount) { + return new CircleCaptcha(width, height, codeCount, circleCount); + } + + /** + * 创建扭曲干扰的验证码,默认5位验证码 + * + * @param width 图片宽 + * @param height 图片高 + * @return {@link ShearCaptcha} + * @since 3.2.3 + */ + public static ShearCaptcha createShearCaptcha(int width, int height) { + return new ShearCaptcha(width, height); + } + + /** + * 创建扭曲干扰的验证码,默认5位验证码 + * + * @param width 图片宽 + * @param height 图片高 + * @param codeCount 字符个数 + * @param thickness 干扰线宽度 + * @return {@link ShearCaptcha} + * @since 3.3.0 + */ + public static ShearCaptcha createShearCaptcha(int width, int height, int codeCount, int thickness) { + return new ShearCaptcha(width, height, codeCount, thickness); + } + + public static GifCaptcha createGifCaptcha(int width, int height) { + return new GifCaptcha(width, height); + } + + public static GifCaptcha createGifCaptcha(int width, int height, int delay) { + return new GifCaptcha(width, height, delay); + } } diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java new file mode 100644 index 000000000..1112105c9 --- /dev/null +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java @@ -0,0 +1,221 @@ +package cn.hutool.captcha; + + +import cn.hutool.core.img.gif.AnimatedGifEncoder; +import cn.hutool.core.util.ObjectUtil; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.util.Random; + +/** + * Gif验证码类 + */ +public class GifCaptcha extends AbstractCaptcha { + private static final long serialVersionUID = 7091627304326538464L; + + private final Random RANDOM = new Random(); + // 帧延迟 (默认100) + private int delay = 100; + //量化器取样间隔 - 默认是10ms + private int quality = 10; + // 帧循环次数 + private int repeat = 0; + //设置随机颜色时,最小的取色范围 + private int minColor = 0; + //设置随机颜色时,最大的取色范围 + private int maxColor = 255; + + + /** + * 可以设置验证码宽度,高度的构造函数 + * + * @param width -验证码宽度 + * @param height -验证码高度 + */ + public GifCaptcha(int width, int height) { + this(width, height, 100); + } + + /** + * @param width -验证码宽度 + * @param height -验证码高度 + * @param codeCount -验证码个数 + */ + public GifCaptcha(int width, int height, int codeCount) { + super(width, height, codeCount, 10); + } + + /** + * 设置图像的颜色量化(转换质量 由GIF规范允许的最大256种颜色)。 + * 低的值(最小值= 1)产生更好的颜色,但处理显著缓慢。 + * 10是默认,并产生良好的颜色而且有以合理的速度。 + * 值更大(大于20)不产生显著的改善速度 + * + * @param quality 大于1 + */ + public void setQuality(int quality) { + if (quality < 1) { + quality = 1; + } + this.quality = quality; + } + + /** + * 设置GIF帧应该播放的次数。 + * 默认是 0; 0意味着无限循环。 + * 必须在添加的第一个图像之前被调用。 + * + * @param repeat 必须大于等于0 + */ + public void setRepeat(int repeat) { + if (repeat >= 0) { + this.repeat = repeat; + } + } + + /** + * 设置验证码字符颜色 + * + * @param maxColor 颜色 + */ + public void setMaxColor(int maxColor) { + this.maxColor = maxColor; + } + + /** + * 设置验证码字符颜色 + * + * @param minColor 颜色 + */ + public void setMinColor(int minColor) { + this.minColor = minColor; + } + + @Override + public void createCode() { + generateCode(); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder();// gif编码类 + //生成字符 + gifEncoder.start(out); + gifEncoder.setQuality(quality);//设置量化器取样间隔 + gifEncoder.setDelay(delay);//设置帧延迟 + gifEncoder.setRepeat(repeat);//帧循环次数 + BufferedImage frame; + char[] chars = code.toCharArray(); + Color[] fontColor = new Color[chars.length]; + for (int i = 0; i < chars.length; i++) { + fontColor[i] = getRandomColor(minColor, maxColor); + frame = graphicsImage(chars, fontColor, chars, i); + gifEncoder.addFrame(frame); + frame.flush(); + } + gifEncoder.finish(); + this.imageBytes = out.toByteArray(); + } + + @Override + protected Image createImage(String code) { + return null; + } + + /** + * 画随机码图 + * + * @param fontColor 随机字体颜色 + * @param words 字符数组 + * @param flag 透明度使用 + * @return BufferedImage + */ + private BufferedImage graphicsImage(char[] chars, Color[] fontColor, char[] words, int flag) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + //或得图形上下文 + Graphics2D g2d = image.createGraphics(); + //利用指定颜色填充背景 + g2d.setColor(ObjectUtil.defaultIfNull(this.background, Color.WHITE)); + g2d.fillRect(0, 0, width, height); + AlphaComposite ac; + // 字符的y坐标 + float y = (height >> 1) + (font.getSize() >> 1); + float m = 1.0f * (width - (chars.length * font.getSize())) / chars.length; + //字符的x坐标 + float x = Math.max(m / 2.0f, 2); + g2d.setFont(font); + // 指定透明度 + if (null != this.textAlpha) { + g2d.setComposite(this.textAlpha); + } + for (int i = 0; i < chars.length; i++) { + ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getPellucidity(chars.length, flag, i)); + g2d.setComposite(ac); + g2d.setColor(fontColor[i]); + g2d.drawOval(num(width), num(height), num(5, 30), 5 + num(5, 30));//绘制椭圆边框 + g2d.drawString(words[i] + "", x + (font.getSize() + m) * i, y); + } + g2d.dispose(); + return image; + } + + /** + * 获取透明度,从0到1,自动计算步长 + * + * @return float 透明度 + */ + private float getPellucidity(int v, int i, int j) { + int num = i + j; + float r = (float) 1 / v; + float s = (v + 1) * r; + return num > v ? (num * r - s) : num * r; + } + + /** + * 通过给定范围获得随机的颜色 + * + * @return Color 获得随机的颜色 + */ + private Color getRandomColor(int min, int max) { + if (min > 255) { + min = 255; + } + if (max > 255) { + max = 255; + } + if (min < 0) { + min = 0; + } + if (max < 0) { + max = 0; + } + if (min > max) { + min = 0; + max = 255; + } + return new Color(min + num(max - min), min + num(max - min), min + num(max - min)); + } + + + /** + * 产生两个数之间的随机数 + * + * @param min 小数 + * @param max 比min大的数 + * @return int 随机数字 + */ + private int num(int min, int max) { + return min + RANDOM.nextInt(max - min); + } + + /** + * 产生0--num的随机数,不包括num + * + * @param num 数字 + * @return int 随机数字 + */ + private int num(int num) { + return RANDOM.nextInt(num); + } + +} diff --git a/hutool-captcha/src/test/java/cn/hutool/captcha/CaptchaTest.java b/hutool-captcha/src/test/java/cn/hutool/captcha/CaptchaTest.java index 6b9d1cb31..d6708e66a 100644 --- a/hutool-captcha/src/test/java/cn/hutool/captcha/CaptchaTest.java +++ b/hutool-captcha/src/test/java/cn/hutool/captcha/CaptchaTest.java @@ -1,105 +1,111 @@ package cn.hutool.captcha; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.captcha.generator.MathGenerator; import cn.hutool.core.lang.Console; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; import java.awt.*; /** * 直线干扰验证码单元测试 - * - * @author looly * + * @author looly */ public class CaptchaTest { - @Test - public void lineCaptchaTest1() { - // 定义图形验证码的长和宽 - LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); - Assert.assertNotNull(lineCaptcha.getCode()); - Assert.assertTrue(lineCaptcha.verify(lineCaptcha.getCode())); - } + @Test + public void lineCaptchaTest1() { + // 定义图形验证码的长和宽 + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); + Assert.assertNotNull(lineCaptcha.getCode()); + Assert.assertTrue(lineCaptcha.verify(lineCaptcha.getCode())); + } - @Test - @Ignore - public void lineCaptchaTest3() { - // 定义图形验证码的长和宽 - LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 70, 4, 15); - lineCaptcha.setBackground(Color.yellow); - lineCaptcha.write("f:/test/captcha/tellow.png"); - } - - @Test - @Ignore - public void lineCaptchaWithMathTest() { - // 定义图形验证码的长和宽 - LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80); - lineCaptcha.setGenerator(new MathGenerator()); - lineCaptcha.setTextAlpha(0.8f); - lineCaptcha.write("f:/captcha/math.png"); - } + @Test + @Ignore + public void lineCaptchaTest3() { + // 定义图形验证码的长和宽 + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 70, 4, 15); + lineCaptcha.setBackground(Color.yellow); + lineCaptcha.write("f:/test/captcha/tellow.png"); + } - @Test - @Ignore - public void lineCaptchaTest2() { + @Test + @Ignore + public void lineCaptchaWithMathTest() { + // 定义图形验证码的长和宽 + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80); + lineCaptcha.setGenerator(new MathGenerator()); + lineCaptcha.setTextAlpha(0.8f); + lineCaptcha.write("f:/captcha/math.png"); + } - // 定义图形验证码的长和宽 - LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); - // LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150); - // 图形验证码写出,可以写出到文件,也可以写出到流 - lineCaptcha.write("f:/captcha/line.png"); - Console.log(lineCaptcha.getCode()); - // 验证图形验证码的有效性,返回boolean值 - lineCaptcha.verify("1234"); + @Test + @Ignore + public void lineCaptchaTest2() { - lineCaptcha.createCode(); - lineCaptcha.write("f:/captcha/line2.png"); - Console.log(lineCaptcha.getCode()); - // 验证图形验证码的有效性,返回boolean值 - lineCaptcha.verify("1234"); - } + // 定义图形验证码的长和宽 + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100); + // LineCaptcha lineCaptcha = new LineCaptcha(200, 100, 4, 150); + // 图形验证码写出,可以写出到文件,也可以写出到流 + lineCaptcha.write("f:/captcha/line.png"); + Console.log(lineCaptcha.getCode()); + // 验证图形验证码的有效性,返回boolean值 + lineCaptcha.verify("1234"); - @Test - @Ignore - public void circleCaptchaTest() { + lineCaptcha.createCode(); + lineCaptcha.write("f:/captcha/line2.png"); + Console.log(lineCaptcha.getCode()); + // 验证图形验证码的有效性,返回boolean值 + lineCaptcha.verify("1234"); + } - // 定义图形验证码的长和宽 - CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20); - // CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20); - // 图形验证码写出,可以写出到文件,也可以写出到流 - captcha.write("f:/captcha/circle.png"); - // 验证图形验证码的有效性,返回boolean值 - captcha.verify("1234"); - } + @Test + @Ignore + public void circleCaptchaTest() { - @Test - @Ignore - public void ShearCaptchaTest() { + // 定义图形验证码的长和宽 + CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20); + // CircleCaptcha captcha = new CircleCaptcha(200, 100, 4, 20); + // 图形验证码写出,可以写出到文件,也可以写出到流 + captcha.write("f:/captcha/circle.png"); + // 验证图形验证码的有效性,返回boolean值 + captcha.verify("1234"); + } - // 定义图形验证码的长和宽 - ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(203, 100, 4, 4); - // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); - // 图形验证码写出,可以写出到文件,也可以写出到流 - captcha.write("f:/captcha/shear.png"); - // 验证图形验证码的有效性,返回boolean值 - captcha.verify("1234"); - } - - @Test - @Ignore - public void ShearCaptchaWithMathTest() { - // 定义图形验证码的长和宽 - ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4); - captcha.setGenerator(new MathGenerator()); - // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); - // 图形验证码写出,可以写出到文件,也可以写出到流 - captcha.write("f:/captcha/shear_math.png"); - // 验证图形验证码的有效性,返回boolean值 - captcha.verify("1234"); - } + @Test + @Ignore + public void ShearCaptchaTest() { + + // 定义图形验证码的长和宽 + ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(203, 100, 4, 4); + // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); + // 图形验证码写出,可以写出到文件,也可以写出到流 + captcha.write("f:/captcha/shear.png"); + // 验证图形验证码的有效性,返回boolean值 + captcha.verify("1234"); + } + + @Test + @Ignore + public void ShearCaptchaWithMathTest() { + // 定义图形验证码的长和宽 + ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4); + captcha.setGenerator(new MathGenerator()); + // ShearCaptcha captcha = new ShearCaptcha(200, 100, 4, 4); + // 图形验证码写出,可以写出到文件,也可以写出到流 + captcha.write("f:/captcha/shear_math.png"); + // 验证图形验证码的有效性,返回boolean值 + captcha.verify("1234"); + } + + @Test + @Ignore + public void GifCaptchaTest() { + GifCaptcha captcha = CaptchaUtil.createGifCaptcha(200, 100, 4); + captcha.write("f:/gif_captcha.gif"); + assert captcha.verify(captcha.getCode()); + } }