From ebe6e23e0a2365a81010a33e206cdba0517f6f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E7=88=B1=E6=9C=89=E6=83=85?= Date: Fri, 22 Apr 2022 17:44:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=8B=E8=BD=AC=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 8 +- .../slider/common/util/CaptchaImageUtils.java | 39 +++++ .../AbstractImageCaptchaGenerator.java | 77 +++++++++ ...erator.java => ImageCaptchaGenerator.java} | 32 ++-- .../common/constant/CaptchaTypeConstant.java | 14 ++ .../common/model/dto/GenerateParam.java | 2 + .../common/model/dto/ImageCaptchaInfo.java | 66 ++++++++ .../model/dto/RotateImageCaptchaInfo.java | 43 +++++ .../common/model/dto/SliderCaptchaInfo.java | 66 -------- .../model/dto/SliderImageCaptchaInfo.java | 43 +++++ ...r.java => CacheImageCaptchaGenerator.java} | 39 ++--- ...ava => StandardImageCaptchaGenerator.java} | 125 ++++----------- .../impl/StandardRotateCaptchaGenerator.java | 147 ++++++++++++++++++ .../slider/resource/ResourceStore.java | 108 ++++++------- .../SliderCaptchaResourceManager.java | 4 +- .../resource/impl/DefaultResourceStore.java | 134 +++++++++------- .../DefaultSliderCaptchaResourceManager.java | 29 ++-- .../validator/SliderCaptchaValidator.java | 6 +- .../impl/SimpleSliderCaptchaValidator.java | 10 +- .../META-INF/cut-image/template/3/active.png | Bin 0 -> 13900 bytes .../META-INF/cut-image/template/3/fixed.png | Bin 0 -> 3171 bytes .../StandardRotateCaptchaGeneratorTest.java | 37 +++++ 22 files changed, 676 insertions(+), 353 deletions(-) create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/generator/AbstractImageCaptchaGenerator.java rename src/main/java/cloud/tianai/captcha/template/slider/generator/{SliderCaptchaGenerator.java => ImageCaptchaGenerator.java} (57%) create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/generator/common/constant/CaptchaTypeConstant.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/ImageCaptchaInfo.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/RotateImageCaptchaInfo.java delete mode 100644 src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderCaptchaInfo.java create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderImageCaptchaInfo.java rename src/main/java/cloud/tianai/captcha/template/slider/generator/impl/{CacheSliderCaptchaGenerator.java => CacheImageCaptchaGenerator.java} (74%) rename src/main/java/cloud/tianai/captcha/template/slider/generator/impl/{StandardSliderCaptchaGenerator.java => StandardImageCaptchaGenerator.java} (64%) create mode 100644 src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardRotateCaptchaGenerator.java create mode 100644 src/main/resources/META-INF/cut-image/template/3/active.png create mode 100644 src/main/resources/META-INF/cut-image/template/3/fixed.png create mode 100644 src/main/test/java/example/StandardRotateCaptchaGeneratorTest.java diff --git a/readme.md b/readme.md index 3d1389e..1be5526 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,7 @@ ### 2. 使用 `SliderCaptchaGenerator`生成器生成滑块验证码 ```java +import cloud.tianai.captcha.template.slider.generator.impl.StandardImageCaptchaGenerator; import cloud.tianai.captcha.template.slider.generator.impl.StandardSliderCaptchaGenerator; import cloud.tianai.captcha.template.slider.resource.SliderCaptchaResourceManager; import cloud.tianai.captcha.template.slider.resource.impl.DefaultSliderCaptchaResourceManager; @@ -41,9 +42,9 @@ import cloud.tianai.captcha.template.slider.resource.impl.DefaultSliderCaptchaRe public class Test { public static void main(String[] args) throws InterruptedException { SliderCaptchaResourceManager sliderCaptchaResourceManager = new DefaultSliderCaptchaResourceManager(); - StandardSliderCaptchaGenerator sliderCaptchaGenerator = new StandardSliderCaptchaGenerator(sliderCaptchaResourceManager, true); + StandardImageCaptchaGenerator sliderCaptchaGenerator = new StandardImageCaptchaGenerator(sliderCaptchaResourceManager, true); // 生成滑块图片 - SliderCaptchaInfo slideImageInfo = sliderCaptchaGenerator.generateSlideImageInfo(); + SliderCaptchaInfo slideImageInfo = sliderCaptchaGenerator.generateCaptchaImage(); System.out.println(slideImageInfo); // 负责计算一些数据存到缓存中,用于校验使用 @@ -58,11 +59,12 @@ public class Test { ### 3. 使用`SliderCaptchaValidator`校验器 验证 ```java +import cloud.tianai.captcha.template.slider.generator.ImageCaptchaGenerator; import cloud.tianai.captcha.template.slider.generator.SliderCaptchaGenerator; public class Test2 { public static void main(String[] args) { - SliderCaptchaGenerator sliderCaptchaValidator = new BasicCaptchaTrackValidator(); + ImageCaptchaGenerator sliderCaptchaValidator = new BasicCaptchaTrackValidator(); // 用户传来的行为轨迹和进行校验 // - sliderCaptchaTrack为前端传来的滑动轨迹数据 diff --git a/src/main/java/cloud/tianai/captcha/template/slider/common/util/CaptchaImageUtils.java b/src/main/java/cloud/tianai/captcha/template/slider/common/util/CaptchaImageUtils.java index 1063893..dc3cc95 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/common/util/CaptchaImageUtils.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/common/util/CaptchaImageUtils.java @@ -176,4 +176,43 @@ public class CaptchaImageUtils { } +// /** +// * 旋转图片 +// * +// * @param bufferedImage 图片 +// * @param degree 旋转xx度 +// */ +// public static void rotateImage(BufferedImage bufferedImage, int degree) { +// // 创建Graphics2D对象,用在底图对象上绘图 +// Graphics2D g2d = bufferedImage.createGraphics(); +// // 绘制 +// g2d.rotate(Math.toRadians(degree), bufferedImage.getWidth() / 2, bufferedImage.getHeight()/2); +// g2d.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), null); +// // 释放图形上下文使用的系统资源 +// g2d.dispose(); +// } + + public static BufferedImage rotateImage(final BufferedImage bufferedimage, + final double degree) { + // 得到图片宽度。 + int w = bufferedimage.getWidth(); + // 得到图片高度。 + int h = bufferedimage.getHeight(); + // 得到图片透明度。 + int type = bufferedimage.getColorModel().getTransparency(); + BufferedImage img;// 空的图片。 + Graphics2D graphics2d;// 空的画笔。 + (graphics2d = (img = new BufferedImage(w, h, type)) + .createGraphics()).setRenderingHint( + RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + // 旋转,degree是整型,度数,比如垂直90度。 + graphics2d.rotate(Math.toRadians(degree), w / 2, h / 2); + // 从bufferedimagecopy图片至img,0,0是img的坐标。 + graphics2d.drawImage(bufferedimage, 0, 0, null); + graphics2d.dispose(); + // 返回复制好的图片,原图片依然没有变,没有旋转,下次还可以使用。 + return img; + } + } diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/AbstractImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/AbstractImageCaptchaGenerator.java new file mode 100644 index 0000000..9e69a35 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/AbstractImageCaptchaGenerator.java @@ -0,0 +1,77 @@ +package cloud.tianai.captcha.template.slider.generator; + +import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +/** + * @Author: 天爱有情 + * @date 2022/4/22 16:30 + * @Description 抽象的验证码生成器 + */ +public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGenerator { + public static String DEFAULT_BG_IMAGE_TYPE = "jpeg"; + public static String DEFAULT_SLIDER_IMAGE_TYPE = "png"; + + @Getter + @Setter + /** 默认背景图片类型. */ + public String defaultBgImageType = DEFAULT_BG_IMAGE_TYPE; + @Getter + @Setter + /** 默认滑块图片类型. */ + public String defaultSliderImageType = DEFAULT_SLIDER_IMAGE_TYPE; + + @Override + public ImageCaptchaInfo generateCaptchaImage(String type) { + return generateCaptchaImage(type, defaultBgImageType, defaultSliderImageType); + } + + @SneakyThrows + @Override + public ImageCaptchaInfo generateCaptchaImage(String type, String backgroundFormatName, String sliderFormatName) { + return generateCaptchaImage(GenerateParam.builder() + .type(type) + .backgroundFormatName(backgroundFormatName) + .sliderFormatName(sliderFormatName) + .obfuscate(false) + .build()); + } + + /** + * 将图片转换成字符串格式 + * + * @param bufferedImage 图片 + * @param formatType 格式化类型 + * @return String + */ + public String transform(BufferedImage bufferedImage, String formatType) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ImageIO.write(bufferedImage, formatType, byteArrayOutputStream); + //转换成字节码 + byte[] data = byteArrayOutputStream.toByteArray(); + String base64 = Base64.getEncoder().encodeToString(data); + return "data:image/" + formatType + ";base64,".concat(base64); + } + + protected InputStream getTemplateFile(Map templateImages, String imageName) { + Resource resource = templateImages.get(imageName); + if (resource == null) { + throw new IllegalArgumentException("查找模板异常, 该模板下未找到 ".concat(imageName)); + } + return getSlideImageResourceManager().getResourceInputStream(resource); + } + +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/SliderCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/ImageCaptchaGenerator.java similarity index 57% rename from src/main/java/cloud/tianai/captcha/template/slider/generator/SliderCaptchaGenerator.java rename to src/main/java/cloud/tianai/captcha/template/slider/generator/ImageCaptchaGenerator.java index 2ab4ef2..d3489f3 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/generator/SliderCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/ImageCaptchaGenerator.java @@ -1,33 +1,35 @@ package cloud.tianai.captcha.template.slider.generator; +import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant; import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam; -import cloud.tianai.captcha.template.slider.generator.common.model.dto.SliderCaptchaInfo; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.template.slider.resource.SliderCaptchaResourceManager; import cloud.tianai.captcha.template.slider.validator.SliderCaptchaValidator; /** * @Author: 天爱有情 * @date 2020/10/19 18:37 - * @Description 滑块验证码模板 + * @Description 图片验证码生成器 */ -public interface SliderCaptchaGenerator { +public interface ImageCaptchaGenerator { + /** - * 获取滑块验证码 - * + * 生成验证码图片 + * @param type 类型 {@link CaptchaTypeConstant} * @return SliderCaptchaInfo */ - SliderCaptchaInfo generateSlideImageInfo(); + ImageCaptchaInfo generateCaptchaImage(String type); /** * 生成滑块验证码 - * + * @param type type {@link CaptchaTypeConstant} * @param targetFormatName jpeg或者webp格式 * @param matrixFormatName png或者webp格式 * @return SliderCaptchaInfo */ - SliderCaptchaInfo generateSlideImageInfo(String targetFormatName, String matrixFormatName); + ImageCaptchaInfo generateCaptchaImage(String type, String targetFormatName, String matrixFormatName); /** * 生成滑块验证码 @@ -35,20 +37,8 @@ public interface SliderCaptchaGenerator { * @param param 生成参数 * @return SliderCaptchaInfo */ - SliderCaptchaInfo generateSlideImageInfo(GenerateParam param); + ImageCaptchaInfo generateCaptchaImage(GenerateParam param); - /** - * 百分比对比 - * - * @param newPercentage 用户百分比 - * @param oriPercentage 原百分比 - * @return true 成功 false 失败 - *

- * 废除 , 建议使用 - * @see SliderCaptchaValidator 进行校验 - */ - @Deprecated - boolean percentageContrast(Float newPercentage, Float oriPercentage); /** * 获取滑块验证码资源管理器 diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/constant/CaptchaTypeConstant.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/constant/CaptchaTypeConstant.java new file mode 100644 index 0000000..c50e1ce --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/constant/CaptchaTypeConstant.java @@ -0,0 +1,14 @@ +package cloud.tianai.captcha.template.slider.generator.common.constant; + +/** + * @Author: 天爱有情 + * @date 2021/8/7 17:14 + * @Description 滑块类型 + */ +public interface CaptchaTypeConstant { + + /** 滑块. */ + String SLIDER = "SLIDER"; + /** 旋转. */ + String ROTATE = "ROTATE"; +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/GenerateParam.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/GenerateParam.java index 2382658..255bac7 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/GenerateParam.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/GenerateParam.java @@ -21,4 +21,6 @@ public class GenerateParam { private String sliderFormatName = "png"; /** 是否混淆.*/ private Boolean obfuscate = false; + /** 类型.*/ + private String type; } diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/ImageCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/ImageCaptchaInfo.java new file mode 100644 index 0000000..71711f6 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/ImageCaptchaInfo.java @@ -0,0 +1,66 @@ +package cloud.tianai.captcha.template.slider.generator.common.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author: 天爱有情 + * @Date 2020/5/29 8:04 + * @Description 滑块验证码 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ImageCaptchaInfo { + + /** + * 背景图 + */ + private String backgroundImage; + /** + * 移动图 + */ + private String sliderImage; + /** 背景图片宽度. */ + private Integer bgImageWidth; + /** 背景图片高度. */ + private Integer bgImageHeight; + /** 滑块图片宽度. */ + private Integer sliderImageWidth; + /** 滑块图片高度. */ + private Integer sliderImageHeight; + /** 随机值.*/ + private Integer randomX; + /** + * 扩展字段 + */ + public Object expand; + + public ImageCaptchaInfo(String backgroundImage, + String sliderImage, + Integer bgImageWidth, + Integer bgImageHeight, + Integer sliderImageWidth, + Integer sliderImageHeight, + Integer randomX) { + this.backgroundImage = backgroundImage; + this.sliderImage = sliderImage; + this.bgImageWidth = bgImageWidth; + this.bgImageHeight = bgImageHeight; + this.sliderImageWidth = sliderImageWidth; + this.sliderImageHeight = sliderImageHeight; + this.randomX = randomX; + } + + public static ImageCaptchaInfo of(String backgroundImage, + String sliderImage, + Integer bgImageWidth, + Integer bgImageHeight, + Integer sliderImageWidth, + Integer sliderImageHeight, + Integer randomKey) { + return new ImageCaptchaInfo( backgroundImage, sliderImage, bgImageWidth, bgImageHeight, sliderImageWidth, sliderImageHeight, randomKey); + } + +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/RotateImageCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/RotateImageCaptchaInfo.java new file mode 100644 index 0000000..a2ef472 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/RotateImageCaptchaInfo.java @@ -0,0 +1,43 @@ +package cloud.tianai.captcha.template.slider.generator.common.model.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * @Author: 天爱有情 + * @date 2022/4/22 15:49 + * @Description 旋转图片 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class RotateImageCaptchaInfo extends ImageCaptchaInfo { + /** + * 旋转多少度 + */ + private Double degree; + + + + public static RotateImageCaptchaInfo of(Double degree, + Integer randomX, + String backgroundImage, + String sliderImage, + Integer bgImageWidth, + Integer bgImageHeight, + Integer sliderImageWidth, + Integer sliderImageHeight) { + RotateImageCaptchaInfo rotateImageCaptchaInfo = new RotateImageCaptchaInfo(); + rotateImageCaptchaInfo.setDegree(degree); + rotateImageCaptchaInfo.setRandomX(randomX); + rotateImageCaptchaInfo.setBackgroundImage(backgroundImage); + rotateImageCaptchaInfo.setSliderImage(sliderImage); + rotateImageCaptchaInfo.setBgImageWidth(bgImageWidth); + rotateImageCaptchaInfo.setBgImageHeight(bgImageHeight); + rotateImageCaptchaInfo.setSliderImageWidth(sliderImageWidth); + rotateImageCaptchaInfo.setSliderImageHeight(sliderImageHeight); + return rotateImageCaptchaInfo; + } + +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderCaptchaInfo.java deleted file mode 100644 index 639a232..0000000 --- a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderCaptchaInfo.java +++ /dev/null @@ -1,66 +0,0 @@ -package cloud.tianai.captcha.template.slider.generator.common.model.dto; - -import lombok.AllArgsConstructor; -import lombok.Data; - -/** - * @Author: 天爱有情 - * @Date 2020/5/29 8:04 - * @Description 滑块验证码 - */ -@Data -@AllArgsConstructor -public class SliderCaptchaInfo { - - /** - * x轴 - */ - private Integer x; - /** - * y轴 - */ - private Integer y; - /** - * 背景图 - */ - private String backgroundImage; - /** - * 移动图 - */ - private String sliderImage; - /** 背景图片宽度. */ - private Integer bgImageWidth; - /** 背景图片高度. */ - private Integer bgImageHeight; - /** 滑块图片宽度. */ - private Integer sliderImageWidth; - /** 滑块图片高度. */ - private Integer sliderImageHeight; - /** - * 扩展字段 - */ - public Object expand; - - public SliderCaptchaInfo(Integer x, Integer y, String backgroundImage, String sliderImage, Integer bgImageWidth, Integer bgImageHeight, Integer sliderImageWidth, Integer sliderImageHeight) { - this.x = x; - this.y = y; - this.backgroundImage = backgroundImage; - this.sliderImage = sliderImage; - this.bgImageWidth = bgImageWidth; - this.bgImageHeight = bgImageHeight; - this.sliderImageWidth = sliderImageWidth; - this.sliderImageHeight = sliderImageHeight; - } - - public static SliderCaptchaInfo of(Integer x, - Integer y, - String backgroundImage, - String sliderImage, - Integer bgImageWidth, - Integer bgImageHeight, - Integer sliderImageWidth, - Integer sliderImageHeight) { - return new SliderCaptchaInfo(x, y, backgroundImage, sliderImage, bgImageWidth, bgImageHeight, sliderImageWidth, sliderImageHeight); - } - -} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderImageCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderImageCaptchaInfo.java new file mode 100644 index 0000000..563cffd --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/common/model/dto/SliderImageCaptchaInfo.java @@ -0,0 +1,43 @@ +package cloud.tianai.captcha.template.slider.generator.common.model.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SliderImageCaptchaInfo extends ImageCaptchaInfo { + /** + * x轴 + */ + private Integer x; + /** + * y轴 + */ + private Integer y; + + + public static SliderImageCaptchaInfo of(Integer x, + Integer y, + String backgroundImage, + String sliderImage, + Integer bgImageWidth, + Integer bgImageHeight, + Integer sliderImageWidth, + Integer sliderImageHeight) { + SliderImageCaptchaInfo sliderImageCaptchaInfo = new SliderImageCaptchaInfo(); + sliderImageCaptchaInfo.setX(x); + sliderImageCaptchaInfo.setY(y); + sliderImageCaptchaInfo.setRandomX(x); + sliderImageCaptchaInfo.setBackgroundImage(backgroundImage); + sliderImageCaptchaInfo.setSliderImage(sliderImage); + sliderImageCaptchaInfo.setBgImageWidth(bgImageWidth); + sliderImageCaptchaInfo.setBgImageHeight(bgImageHeight); + sliderImageCaptchaInfo.setSliderImageWidth(sliderImageWidth); + sliderImageCaptchaInfo.setSliderImageHeight(sliderImageHeight); + + return sliderImageCaptchaInfo; + } + +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/CacheSliderCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/CacheImageCaptchaGenerator.java similarity index 74% rename from src/main/java/cloud/tianai/captcha/template/slider/generator/impl/CacheSliderCaptchaGenerator.java rename to src/main/java/cloud/tianai/captcha/template/slider/generator/impl/CacheImageCaptchaGenerator.java index 787ea01..9651a27 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/CacheSliderCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/CacheImageCaptchaGenerator.java @@ -1,8 +1,8 @@ package cloud.tianai.captcha.template.slider.generator.impl; +import cloud.tianai.captcha.template.slider.generator.ImageCaptchaGenerator; import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam; -import cloud.tianai.captcha.template.slider.generator.SliderCaptchaGenerator; -import cloud.tianai.captcha.template.slider.generator.common.model.dto.SliderCaptchaInfo; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.template.slider.common.util.NamedThreadFactory; import cloud.tianai.captcha.template.slider.resource.SliderCaptchaResourceManager; import lombok.Getter; @@ -22,12 +22,12 @@ import java.util.concurrent.atomic.AtomicInteger; * @Description 滑块验证码缓冲器 */ @Slf4j -public class CacheSliderCaptchaGenerator implements SliderCaptchaGenerator { +public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { protected final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("slider-captcha-queue")); - protected ConcurrentLinkedQueue queue; + protected ConcurrentLinkedQueue queue; protected AtomicInteger pos = new AtomicInteger(0); - protected SliderCaptchaGenerator target; + protected ImageCaptchaGenerator target; protected int size; /** 等待时间,一般报错或者拉取为空时会休眠一段时间再试. */ protected int waitTime = 1000; @@ -39,13 +39,13 @@ public class CacheSliderCaptchaGenerator implements SliderCaptchaGenerator { @Setter protected boolean requiredGetCaptcha = true; - public CacheSliderCaptchaGenerator(SliderCaptchaGenerator target, GenerateParam generateParam, int size) { + public CacheImageCaptchaGenerator(ImageCaptchaGenerator target, GenerateParam generateParam, int size) { this.target = target; this.generateParam = generateParam; this.size = size; } - public CacheSliderCaptchaGenerator(SliderCaptchaGenerator target, GenerateParam generateParam, int size, int waitTime, int period) { + public CacheImageCaptchaGenerator(ImageCaptchaGenerator target, GenerateParam generateParam, int size, int waitTime, int period) { this.target = target; this.generateParam = generateParam; this.size = size; @@ -71,7 +71,7 @@ public class CacheSliderCaptchaGenerator implements SliderCaptchaGenerator { if (pos.get() >= size) { return; } - SliderCaptchaInfo slideImageInfo = target.generateSlideImageInfo(generateParam); + ImageCaptchaInfo slideImageInfo = target.generateCaptchaImage(generateParam); if (slideImageInfo != null) { boolean addStatus = queue.offer(slideImageInfo); if (addStatus) { @@ -101,17 +101,17 @@ public class CacheSliderCaptchaGenerator implements SliderCaptchaGenerator { @SneakyThrows @Override - public SliderCaptchaInfo generateSlideImageInfo() { - return generateSlideImageInfo(this.requiredGetCaptcha); + public ImageCaptchaInfo generateCaptchaImage(String type) { + return generateCaptchaImage(this.requiredGetCaptcha); } @SneakyThrows - public SliderCaptchaInfo generateSlideImageInfo(boolean requiredGetCaptcha) { - SliderCaptchaInfo poll = queue.poll(); + public ImageCaptchaInfo generateCaptchaImage(boolean requiredGetCaptcha) { + ImageCaptchaInfo poll = queue.poll(); if (poll == null && requiredGetCaptcha) { log.warn("滑块验证码缓存不足, genParam:{}", generateParam); // 如果池内没数据, 则直接生成 - return target.generateSlideImageInfo(generateParam); + return target.generateCaptchaImage(generateParam); } // 减1 pos.decrementAndGet(); @@ -119,18 +119,13 @@ public class CacheSliderCaptchaGenerator implements SliderCaptchaGenerator { } @Override - public SliderCaptchaInfo generateSlideImageInfo(String targetFormatName, String matrixFormatName) { - return target.generateSlideImageInfo(targetFormatName, matrixFormatName); + public ImageCaptchaInfo generateCaptchaImage(String type, String targetFormatName, String matrixFormatName) { + return target.generateCaptchaImage(type,targetFormatName, matrixFormatName); } @Override - public SliderCaptchaInfo generateSlideImageInfo(GenerateParam param) { - return target.generateSlideImageInfo(param); - } - - @Override - public boolean percentageContrast(Float newPercentage, Float oriPercentage) { - return target.percentageContrast(newPercentage, oriPercentage); + public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) { + return target.generateCaptchaImage(param); } @Override diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardSliderCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardImageCaptchaGenerator.java similarity index 64% rename from src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardSliderCaptchaGenerator.java rename to src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardImageCaptchaGenerator.java index ee0540d..547633e 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardSliderCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardImageCaptchaGenerator.java @@ -1,24 +1,25 @@ package cloud.tianai.captcha.template.slider.generator.impl; -import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam; +import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator; +import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant; import cloud.tianai.captcha.template.slider.generator.common.constant.SliderCaptchaConstant; -import cloud.tianai.captcha.template.slider.generator.SliderCaptchaGenerator; -import cloud.tianai.captcha.template.slider.generator.common.model.dto.SliderCaptchaInfo; -import cloud.tianai.captcha.template.slider.resource.impl.provider.ClassPathResourceProvider; -import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.SliderImageCaptchaInfo; import cloud.tianai.captcha.template.slider.resource.ResourceStore; import cloud.tianai.captcha.template.slider.resource.SliderCaptchaResourceManager; -import lombok.Getter; -import lombok.Setter; +import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource; +import cloud.tianai.captcha.template.slider.resource.impl.provider.ClassPathResourceProvider; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils.*; @@ -29,7 +30,7 @@ import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils * @Description 滑块验证码模板 */ @Slf4j -public class StandardSliderCaptchaGenerator implements SliderCaptchaGenerator { +public class StandardImageCaptchaGenerator extends AbstractImageCaptchaGenerator { /** * 默认的resource资源文件路径. @@ -40,59 +41,28 @@ public class StandardSliderCaptchaGenerator implements SliderCaptchaGenerator { */ public static final String DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH = "META-INF/cut-image/template"; - public static String DEFAULT_BG_IMAGE_TYPE = "jpeg"; - public static String DEFAULT_SLIDER_IMAGE_TYPE = "png"; - public static float DEFAULT_TOLERANT = 0.02f; - protected final SliderCaptchaResourceManager sliderCaptchaResourceManager; - @Getter - @Setter - /** 容错值. */ - public float tolerant = DEFAULT_TOLERANT; - @Getter - @Setter - /** 默认背景图片类型. */ - public String defaultBgImageType = DEFAULT_BG_IMAGE_TYPE; - @Getter - @Setter - /** 默认滑块图片类型. */ - public String defaultSliderImageType = DEFAULT_SLIDER_IMAGE_TYPE; - public StandardSliderCaptchaGenerator(SliderCaptchaResourceManager sliderCaptchaResourceManager, - boolean initDefaultResource) { + public StandardImageCaptchaGenerator(SliderCaptchaResourceManager sliderCaptchaResourceManager, + boolean initDefaultResource) { this.sliderCaptchaResourceManager = sliderCaptchaResourceManager; if (initDefaultResource) { initDefaultResource(); } } - @Override - public SliderCaptchaInfo generateSlideImageInfo() { - return generateSlideImageInfo(defaultBgImageType, defaultSliderImageType); - } - @SneakyThrows @Override - public SliderCaptchaInfo generateSlideImageInfo(String backgroundFormatName, String sliderFormatName) { - return generateSlideImageInfo(GenerateParam.builder() - .backgroundFormatName(backgroundFormatName) - .sliderFormatName(sliderFormatName) - .obfuscate(false) - .build()); - } - - @SneakyThrows - @Override - public SliderCaptchaInfo generateSlideImageInfo(GenerateParam param) { + public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) { Boolean obfuscate = param.getObfuscate(); - Map templateImages = sliderCaptchaResourceManager.randomGetTemplate(); + Map templateImages = sliderCaptchaResourceManager.randomGetTemplate(param.getType()); if (templateImages == null || templateImages.isEmpty()) { return null; } Collection inputStreams = new LinkedList<>(); try { - Resource resourceImage = sliderCaptchaResourceManager.randomGetResource(); + Resource resourceImage = sliderCaptchaResourceManager.randomGetResource(param.getType()); InputStream resourceInputStream = sliderCaptchaResourceManager.getResourceInputStream(resourceImage); inputStreams.add(resourceInputStream); BufferedImage cutBackground = wrapFile2BufferedImage(resourceInputStream); @@ -151,16 +121,16 @@ public class StandardSliderCaptchaGenerator implements SliderCaptchaGenerator { * @return SliderCaptchaInfo */ @SneakyThrows - public SliderCaptchaInfo wrapSliderCaptchaInfo(int randomX, - int randomY, - BufferedImage backgroundImage, - BufferedImage sliderImage, - GenerateParam param) { + public SliderImageCaptchaInfo wrapSliderCaptchaInfo(int randomX, + int randomY, + BufferedImage backgroundImage, + BufferedImage sliderImage, + GenerateParam param) { String backgroundFormatName = param.getBackgroundFormatName(); String sliderFormatName = param.getSliderFormatName(); String backGroundImageBase64 = transform(backgroundImage, backgroundFormatName); String sliderImageBase64 = transform(sliderImage, sliderFormatName); - return SliderCaptchaInfo.of(randomX, randomY, + return SliderImageCaptchaInfo.of(randomX, randomY, backGroundImageBase64, sliderImageBase64, backgroundImage.getWidth(), backgroundImage.getHeight(), @@ -168,41 +138,6 @@ public class StandardSliderCaptchaGenerator implements SliderCaptchaGenerator { ); } - /** - * 将图片转换成字符串格式 - * - * @param bufferedImage 图片 - * @param formatType 格式化类型 - * @return String - */ - public String transform(BufferedImage bufferedImage, String formatType) throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - ImageIO.write(bufferedImage, formatType, byteArrayOutputStream); - //转换成字节码 - byte[] data = byteArrayOutputStream.toByteArray(); - String base64 = Base64.getEncoder().encodeToString(data); - return "data:image/" + formatType + ";base64,".concat(base64); - } - - /** - * 百分比对比 - * - * @param newPercentage 用户百分比 - * @param oriPercentage 原百分比 - * @return true 成功 false 失败 - */ - @Override - public boolean percentageContrast(Float newPercentage, Float oriPercentage) { - if (newPercentage == null || Float.isNaN(newPercentage) || Float.isInfinite(newPercentage) - || oriPercentage == null || Float.isNaN(oriPercentage) || Float.isInfinite(oriPercentage)) { - return false; - } - // 容错值 - float maxTolerant = oriPercentage + tolerant; - float minTolerant = oriPercentage - tolerant; - return newPercentage >= minTolerant && newPercentage <= maxTolerant; - } - @Override public SliderCaptchaResourceManager getSlideImageResourceManager() { return sliderCaptchaResourceManager; @@ -217,34 +152,26 @@ public class StandardSliderCaptchaGenerator implements SliderCaptchaGenerator { return ThreadLocalRandom.current().nextInt(slWidth, sliderX - slWidth); } - protected InputStream getTemplateFile(Map templateImages, String imageName) { - Resource resource = templateImages.get(imageName); - if (resource == null) { - throw new IllegalArgumentException("查找模板异常, 该模板下未找到 ".concat(imageName)); - } - return sliderCaptchaResourceManager.getResourceInputStream(resource); - } - /** * 初始化默认资源 */ public void initDefaultResource() { ResourceStore resourceStore = sliderCaptchaResourceManager.getResourceStore(); // 添加一些系统的资源文件 - resourceStore.addResource(new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); + resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); // 添加一些系统的 模板文件 Map template1 = new HashMap<>(4); template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png"))); template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png"))); template1.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/matrix.png"))); - resourceStore.addTemplate(template1); + resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template1); Map template2 = new HashMap<>(4); template2.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png"))); template2.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png"))); template2.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/matrix.png"))); - resourceStore.addTemplate(template2); + resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template2); } } diff --git a/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardRotateCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardRotateCaptchaGenerator.java new file mode 100644 index 0000000..12654b3 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/template/slider/generator/impl/StandardRotateCaptchaGenerator.java @@ -0,0 +1,147 @@ +package cloud.tianai.captcha.template.slider.generator.impl; + +import cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils; +import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator; +import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant; +import cloud.tianai.captcha.template.slider.generator.common.constant.SliderCaptchaConstant; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.RotateImageCaptchaInfo; +import cloud.tianai.captcha.template.slider.resource.ResourceStore; +import cloud.tianai.captcha.template.slider.resource.SliderCaptchaResourceManager; +import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource; +import cloud.tianai.captcha.template.slider.resource.impl.provider.ClassPathResourceProvider; +import lombok.SneakyThrows; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils.*; +import static cloud.tianai.captcha.template.slider.generator.impl.StandardImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH; +import static cloud.tianai.captcha.template.slider.generator.impl.StandardImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH; + +/** + * @Author: 天爱有情 + * @date 2022/4/22 16:43 + * @Description 旋转图片验证码生成器 + */ +public class StandardRotateCaptchaGenerator extends AbstractImageCaptchaGenerator { + + protected final SliderCaptchaResourceManager sliderCaptchaResourceManager; + + public StandardRotateCaptchaGenerator(SliderCaptchaResourceManager sliderCaptchaResourceManager, boolean initDefaultResource) { + this.sliderCaptchaResourceManager = sliderCaptchaResourceManager; + if (initDefaultResource) { + initDefaultResource(); + } + } + + public void initDefaultResource() { + ResourceStore resourceStore = sliderCaptchaResourceManager.getResourceStore(); + // 添加一些系统的资源文件 + resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); + + // 添加一些系统的 模板文件 + Map template1 = new HashMap<>(4); + template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/active.png"))); + template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/fixed.png"))); + resourceStore.addTemplate(CaptchaTypeConstant.ROTATE, template1); + } + + @Override + public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) { + // 旋转验证码没有混淆 + Boolean obfuscate = param.getObfuscate(); + Map templateImages = sliderCaptchaResourceManager.randomGetTemplate(param.getType()); + if (templateImages == null || templateImages.isEmpty()) { + return null; + } + Collection inputStreams = new LinkedList<>(); + try { + Resource resourceImage = sliderCaptchaResourceManager.randomGetResource(param.getType()); + InputStream resourceInputStream = sliderCaptchaResourceManager.getResourceInputStream(resourceImage); + inputStreams.add(resourceInputStream); + BufferedImage cutBackground = wrapFile2BufferedImage(resourceInputStream); + // 拷贝一份图片 + BufferedImage targetBackground = deepCopyBufferedImage(cutBackground); + + InputStream fixedTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME); + inputStreams.add(fixedTemplateInput); + BufferedImage fixedTemplate = wrapFile2BufferedImage(fixedTemplateInput); + + InputStream activeTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME); + inputStreams.add(activeTemplateInput); + BufferedImage activeTemplate = wrapFile2BufferedImage(activeTemplateInput); + // 算出居中的x和y + int x = targetBackground.getWidth() / 2 - fixedTemplate.getWidth() / 2; + int y = targetBackground.getHeight() / 2 - fixedTemplate.getHeight() / 2; + overlayImage(targetBackground, fixedTemplate, x, y); + // 抠图部分 + BufferedImage cutImage = cutImage(cutBackground, fixedTemplate, x, y); + overlayImage(cutImage, activeTemplate, 0, 0); + // 随机旋转抠图部分 + // 随机x, 转换为角度 + int randomX = ThreadLocalRandom.current().nextInt(fixedTemplate.getWidth() + 10, targetBackground.getWidth() - 10); + double degree = randomX / ((targetBackground.getWidth()) / 360d); +// int degree = ThreadLocalRandom.current().nextInt(10, 350); + cutImage = rotateImage(cutImage, degree); + return wrapRotateCaptchaInfo(degree, randomX, targetBackground, cutImage, param); + } finally { + // 使用完后关闭流 + for (InputStream inputStream : inputStreams) { + try { + inputStream.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + @SneakyThrows + private ImageCaptchaInfo wrapRotateCaptchaInfo(double degree, int randomX, BufferedImage backgroundImage, BufferedImage sliderImage, GenerateParam param) { + String backgroundFormatName = param.getBackgroundFormatName(); + String sliderFormatName = param.getSliderFormatName(); + String backGroundImageBase64 = transform(backgroundImage, backgroundFormatName); + String sliderImageBase64 = transform(sliderImage, sliderFormatName); + return RotateImageCaptchaInfo.of(degree, + randomX, + backGroundImageBase64, + sliderImageBase64, + backgroundImage.getWidth(), backgroundImage.getHeight(), + sliderImage.getWidth(), sliderImage.getHeight() + ); + } + + @Override + public SliderCaptchaResourceManager getSlideImageResourceManager() { + return sliderCaptchaResourceManager; + } + + public static void main(String[] args) throws IOException { + BufferedImage bgImage = CaptchaImageUtils.wrapFile2BufferedImage( + new FileInputStream("E:\\projects\\tianai-captcha\\src\\main\\resources\\META-INF\\cut-image\\resource\\1.jpg")); + BufferedImage image1 = CaptchaImageUtils.wrapFile2BufferedImage(new FileInputStream("C:\\Users\\Thinkpad\\Desktop\\a.png")); + BufferedImage image2 = CaptchaImageUtils.wrapFile2BufferedImage(new FileInputStream("C:\\Users\\Thinkpad\\Desktop\\b.png")); + + BufferedImage cutImage = CaptchaImageUtils.cutImage(bgImage, image1, bgImage.getWidth() / 2 - 100, bgImage.getHeight() / 2 - 100); + CaptchaImageUtils.overlayImage(cutImage, image2, 0, 0); + cutImage = CaptchaImageUtils.rotateImage(cutImage, 180); + CaptchaImageUtils.overlayImage(bgImage, image1, bgImage.getWidth() / 2 - 100, bgImage.getHeight() / 2 - 100); + FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\a1.jpg"); + FileOutputStream file2OutputStream = new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\a2.png"); + ImageIO.write(bgImage, "jpeg", fileOutputStream); + ImageIO.write(cutImage, "png", file2OutputStream); + fileOutputStream.close(); + file2OutputStream.close(); + } +} diff --git a/src/main/java/cloud/tianai/captcha/template/slider/resource/ResourceStore.java b/src/main/java/cloud/tianai/captcha/template/slider/resource/ResourceStore.java index ad7918d..2f03b3e 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/resource/ResourceStore.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/resource/ResourceStore.java @@ -12,97 +12,97 @@ public interface ResourceStore { * * @param resource 资源 */ - void addResource(Resource resource); + void addResource(String type, Resource resource); /** - * 设置资源 + * 清除某个类型下的所有资源 * - * @param resources resources + * @param type type */ - void setResource(List resources); - - /** - * 删除资源 - * - * @param resource resource - */ - boolean deleteResource(Resource resource); + void clearResources(String type); /** * 清除所有资源 */ - void clearResources(); - - /** - * 添加模板 - * - * @param template template - */ - void addTemplate(Map template); - - - /** - * 设置模板 - * - * @param templateResource templateResource - */ - void setTemplates(List> templateResource); - - /** - * 删除模板 - * - * @param template template - */ - void deleteTemplate(Map template); - - /** - * 清除所有模板 - */ - void clearTemplates(); - + void clearAllResources(); /** * 获取所有资源对象 * * @return List */ - List listResources(); + Map> listAllResources(); /** - * 获取所有模板 + * 获取某个type下的所有资源对象 * - * @return List> + * @param type type + * @return List */ - List> listTemplates(); + List listResourcesByType(String type); + + /** + * 随机获取某个资源 + * + * @param type type + * @return Resource + */ + Resource randomGetResource(String type); /** * 获取资源总数 * * @return int */ - int getResourceCount(); + int getAllResourceCount(); /** - * 获取模板count + * 获取某个type下的资源总数 * + * @param type type * @return int */ - int getTemplateCount(); + int getResourceCount(String type); /** - * 获取资源通过index + * 添加模板 * - * @param index index - * @return Resource + * @param template template */ - Resource getResourceByIndex(int index); + void addTemplate(String type, Map template); /** - * 获取模板通过indx + * 清除所有模板 + */ + void clearAllTemplates(); + + /** + * 清除某个type下的所有模板 * - * @param index index + * @param type type + */ + void clearTemplates(String type); + + /** + * 获取所有模板通过type + * + * @return List> + */ + List> listTemplatesByType(String type); + + /** + * 获取所有模板 + * + * @return Map>> + */ + Map>> listAllTemplates(); + + /** + * 随机获取某个模板通过type + * + * @param type type * @return Map */ - Map getTemplateByIndex(int index); + Map randomGetTemplateByType(String type); } diff --git a/src/main/java/cloud/tianai/captcha/template/slider/resource/SliderCaptchaResourceManager.java b/src/main/java/cloud/tianai/captcha/template/slider/resource/SliderCaptchaResourceManager.java index 22eace4..2cc6fee 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/resource/SliderCaptchaResourceManager.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/resource/SliderCaptchaResourceManager.java @@ -18,14 +18,14 @@ public interface SliderCaptchaResourceManager { * * @return Map */ - Map randomGetTemplate(); + Map randomGetTemplate(String type); /** * 随机获取某个资源对象 * * @return Resource */ - Resource randomGetResource(); + Resource randomGetResource(String type); /** * 获取真正的资源流通过资源对象 diff --git a/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultResourceStore.java b/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultResourceStore.java index 2f8eae1..1cc7eeb 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultResourceStore.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultResourceStore.java @@ -1,12 +1,11 @@ package cloud.tianai.captcha.template.slider.resource.impl; +import cloud.tianai.captcha.template.slider.common.util.CollectionUtils; import cloud.tianai.captcha.template.slider.resource.ResourceStore; import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; /** * @Author: 天爱有情 @@ -17,86 +16,103 @@ public class DefaultResourceStore implements ResourceStore { /** * 模板资源. */ - private List> templateResourceList = new ArrayList<>(2); + private Map>> templateResourceMap = new HashMap<>(2); /** * resource. */ - private List resourceList = new ArrayList<>(20); + private Map> resourceMap = new HashMap<>(2); @Override - public void addResource(Resource resource) { - resourceList.add(resource); + public void addResource(String type, Resource resource) { + resourceMap.computeIfAbsent(type, k -> new ArrayList<>(20)).add(resource); } @Override - public void setResource(List resources) { - resourceList = new ArrayList<>(resources); + public void clearResources(String type) { + resourceMap.remove(type); } @Override - public boolean deleteResource(Resource resource) { - return resourceList.remove(resource); + public void clearAllResources() { + resourceMap.clear(); } @Override - public void clearResources() { - resourceList.clear(); + public Map> listAllResources() { + return resourceMap; } @Override - public void addTemplate(Map template) { - templateResourceList.add(template); + public List listResourcesByType(String type) { + return resourceMap.getOrDefault(type, Collections.emptyList()); } @Override - public void setTemplates(List> templateResource) { - templateResourceList = new ArrayList<>(templateResource); - } - - @Override - public void deleteTemplate(Map template) { - templateResourceList.remove(template); - } - - @Override - public void clearTemplates() { - templateResourceList.clear(); - } - - @Override - public List listResources() { - return Collections.unmodifiableList(resourceList); - } - - @Override - public List> listTemplates() { - return Collections.unmodifiableList(templateResourceList); - } - - @Override - public int getResourceCount() { - return resourceList.size(); - } - - @Override - public int getTemplateCount() { - return templateResourceList.size(); - } - - @Override - public Resource getResourceByIndex(int index) { - if (index < 0 || index > resourceList.size() - 1) { - throw new IllegalArgumentException("错误的index"); + public Resource randomGetResource(String type) { + List resources = resourceMap.get(type); + if (CollectionUtils.isEmpty(resources)) { + throw new IllegalStateException("随机获取资源错误,store中资源为空, type:" + type); } - return resourceList.get(index); + if (resources.size() == 1) { + return resources.get(0); + } + int randomIndex = ThreadLocalRandom.current().nextInt(resources.size()); + return resources.get(randomIndex); } @Override - public Map getTemplateByIndex(int index) { - if (index < 0 || index > templateResourceList.size() - 1) { - throw new IllegalArgumentException("错误的index"); + public int getAllResourceCount() { + int count = 0; + for (List value : resourceMap.values()) { + count += value.size(); } - return templateResourceList.get(index); + return count; } + + @Override + public int getResourceCount(String type) { + return resourceMap.getOrDefault(type, Collections.emptyList()).size(); + } + + + @Override + public void addTemplate(String type, Map template) { + templateResourceMap.computeIfAbsent(type, k -> new ArrayList<>(2)).add(template); + } + + @Override + public void clearAllTemplates() { + templateResourceMap.clear(); + } + + @Override + public void clearTemplates(String type) { + templateResourceMap.remove(type); + } + + @Override + public List> listTemplatesByType(String type) { + return templateResourceMap.getOrDefault(type, Collections.emptyList()); + } + + @Override + public Map>> listAllTemplates() { + return templateResourceMap; + } + + @Override + public Map randomGetTemplateByType(String type) { + List> templateList = templateResourceMap.get(type); + if (CollectionUtils.isEmpty(templateList)) { + throw new IllegalStateException("随机获取模板错误,store中模板为空, type:" + type); + } + + if (templateList.size() == 1) { + return templateList.get(0); + } + int randomIndex = ThreadLocalRandom.current().nextInt(templateList.size()); + return templateList.get(randomIndex); + } + } diff --git a/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultSliderCaptchaResourceManager.java b/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultSliderCaptchaResourceManager.java index 234c002..1e213a1 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultSliderCaptchaResourceManager.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/resource/impl/DefaultSliderCaptchaResourceManager.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; /** * @Author: 天爱有情 @@ -47,29 +46,21 @@ public class DefaultSliderCaptchaResourceManager implements SliderCaptchaResourc } @Override - public Map randomGetTemplate() { - int count = resourceStore.getTemplateCount(); - if (count < 1) { - throw new IllegalStateException("随机获取模板错误,store中模板为空"); + public Map randomGetTemplate(String type) { + Map resourceMap = resourceStore.randomGetTemplateByType(type); + if (resourceMap == null) { + throw new IllegalStateException("随机获取模板错误,store中模板为空, type:" + type); } - if (count == 1) { - return resourceStore.getTemplateByIndex(0); - } - int randomIndex = ThreadLocalRandom.current().nextInt(count); - return resourceStore.getTemplateByIndex(randomIndex); + return resourceMap; } @Override - public Resource randomGetResource() { - int count = resourceStore.getResourceCount(); - if (count < 1) { - throw new IllegalStateException("随机获取资源错误,store中资源为空"); + public Resource randomGetResource(String type) { + Resource resource = resourceStore.randomGetResource(type); + if (resource == null) { + throw new IllegalStateException("随机获取资源错误,store中资源为空, type:" + type); } - if (count == 1) { - return resourceStore.getResourceByIndex(0); - } - int randomIndex = ThreadLocalRandom.current().nextInt(count); - return resourceStore.getResourceByIndex(randomIndex); + return resource; } @Override diff --git a/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java b/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java index 6e40446..76e1e8e 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/validator/SliderCaptchaValidator.java @@ -1,6 +1,6 @@ package cloud.tianai.captcha.template.slider.validator; -import cloud.tianai.captcha.template.slider.generator.common.model.dto.SliderCaptchaInfo; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.template.slider.validator.common.model.dto.SliderCaptchaTrack; import java.util.Map; @@ -43,10 +43,10 @@ public interface SliderCaptchaValidator { /** * 用于生成验证码校验时需要的回传参数 * - * @param sliderCaptchaInfo 生成的验证码数据 + * @param imageCaptchaInfo 生成的验证码数据 * @return Map */ - Map generateSliderCaptchaValidData(SliderCaptchaInfo sliderCaptchaInfo); + Map generateSliderCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo); /** * 校验用户滑动滑块是否正确 diff --git a/src/main/java/cloud/tianai/captcha/template/slider/validator/impl/SimpleSliderCaptchaValidator.java b/src/main/java/cloud/tianai/captcha/template/slider/validator/impl/SimpleSliderCaptchaValidator.java index c7bf66d..21f502b 100644 --- a/src/main/java/cloud/tianai/captcha/template/slider/validator/impl/SimpleSliderCaptchaValidator.java +++ b/src/main/java/cloud/tianai/captcha/template/slider/validator/impl/SimpleSliderCaptchaValidator.java @@ -1,7 +1,7 @@ package cloud.tianai.captcha.template.slider.validator.impl; -import cloud.tianai.captcha.template.slider.generator.common.model.dto.SliderCaptchaInfo; import cloud.tianai.captcha.template.slider.common.util.CollectionUtils; +import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.template.slider.validator.SliderCaptchaValidator; import cloud.tianai.captcha.template.slider.validator.common.model.dto.SliderCaptchaTrack; import lombok.Getter; @@ -56,9 +56,9 @@ public class SimpleSliderCaptchaValidator implements SliderCaptchaValidator { } @Override - public Map generateSliderCaptchaValidData(SliderCaptchaInfo sliderCaptchaInfo) { + public Map generateSliderCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) { Map map = new HashMap<>(8); - addPercentage(sliderCaptchaInfo, map); + addPercentage(imageCaptchaInfo, map); return map; } @@ -106,8 +106,8 @@ public class SimpleSliderCaptchaValidator implements SliderCaptchaValidator { return null; } - protected void addPercentage(SliderCaptchaInfo sliderCaptchaInfo, Map sliderCaptchaValidData) { - float percentage = calcPercentage(sliderCaptchaInfo.getX(), sliderCaptchaInfo.getBgImageWidth()); + protected void addPercentage(ImageCaptchaInfo imageCaptchaInfo, Map sliderCaptchaValidData) { + float percentage = calcPercentage(imageCaptchaInfo.getRandomX(), imageCaptchaInfo.getBgImageWidth()); sliderCaptchaValidData.put("percentage", percentage); } } diff --git a/src/main/resources/META-INF/cut-image/template/3/active.png b/src/main/resources/META-INF/cut-image/template/3/active.png new file mode 100644 index 0000000000000000000000000000000000000000..924d57068efd8b2ba1aeef0edbacea9dd754bffb GIT binary patch literal 13900 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 z template = new HashMap<>(); +// template.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource("file", "C:\\Users\\Thinkpad\\Desktop\\a.png")); +// template.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource("file", "C:\\Users\\Thinkpad\\Desktop\\b.png")); + +// resourceStore.addTemplate(CaptchaTypeConstant.ROTATE, template); +// resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource("file", "E:\\projects\\tianai-captcha\\src\\main\\resources\\META-INF\\cut-image\\resource\\1.jpg")); + + SliderCaptchaResourceManager sliderCaptchaResourceManager = new DefaultSliderCaptchaResourceManager(resourceStore); + + StandardRotateCaptchaGenerator standardRotateCaptchaGenerator = new StandardRotateCaptchaGenerator(sliderCaptchaResourceManager, true); + ImageCaptchaInfo imageCaptchaInfo = standardRotateCaptchaGenerator.generateCaptchaImage(CaptchaTypeConstant.ROTATE); + System.out.println("backgroundImage:" + imageCaptchaInfo.getBackgroundImage()); + System.out.println("sliderImage:" + imageCaptchaInfo.getSliderImage()); + System.out.println(((RotateImageCaptchaInfo)imageCaptchaInfo).getDegree()); + + } +}