diff --git a/pom.xml b/pom.xml index ca982a8..fc3576c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 cloud.tianai.captcha tianai-captcha - 1.3.3 + 1.3.3.1 tianai-captcha 行为验证码 diff --git a/src/main/java/cloud/tianai/captcha/common/constant/CommonConstant.java b/src/main/java/cloud/tianai/captcha/common/constant/CommonConstant.java new file mode 100644 index 0000000..c6ac1d7 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/common/constant/CommonConstant.java @@ -0,0 +1,8 @@ +package cloud.tianai.captcha.common.constant; + +public interface CommonConstant { + + String DEFAULT_TAG = "default"; + /** 图标点选资源存储类型.*/ + String IMAGE_CLICK_ICON = "ICON"; +} diff --git a/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java index e6dd37b..5bef908 100644 --- a/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java @@ -4,16 +4,23 @@ import cloud.tianai.captcha.common.exception.ImageCaptchaException; import cloud.tianai.captcha.common.util.CollectionUtils; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils; import cloud.tianai.captcha.generator.impl.transform.Base64ImageTransform; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; import cloud.tianai.captcha.resource.common.model.dto.Resource; +import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; /** * @Author: 天爱有情 @@ -78,11 +85,11 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener @SneakyThrows @Override - public ImageCaptchaInfo generateCaptchaImage(String type, String backgroundFormatName, String sliderFormatName) { + public ImageCaptchaInfo generateCaptchaImage(String type, String backgroundFormatName, String templateFormatName) { return generateCaptchaImage(GenerateParam.builder() .type(type) .backgroundFormatName(backgroundFormatName) - .sliderFormatName(sliderFormatName) + .templateFormatName(templateFormatName) .obfuscate(false) .build()); } @@ -94,18 +101,18 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener } - protected Map requiredRandomGetTemplate(String type) { - Map templateMap = imageCaptchaResourceManager.randomGetTemplate(type); + protected ResourceMap requiredRandomGetTemplate(String type, String tag) { + ResourceMap templateMap = imageCaptchaResourceManager.randomGetTemplate(type, tag); if (CollectionUtils.isEmpty(templateMap)) { - throw new ImageCaptchaException("随机获取模板资源失败, 获取到的资源为空, type=" + type); + throw new ImageCaptchaException("随机获取模板资源失败, 获取到的资源为空, type=" + type + ",tag=" + tag); } return templateMap; } - protected Resource requiredRandomGetResource(String type) { - Resource resource = imageCaptchaResourceManager.randomGetResource(type); + protected Resource requiredRandomGetResource(String type, String tag) { + Resource resource = imageCaptchaResourceManager.randomGetResource(type, tag); if (resource == null) { - throw new ImageCaptchaException("随机获取资源失败, 获取到的资源为空, type=" + type); + throw new ImageCaptchaException("随机获取资源失败, 获取到的资源为空, type=" + type + ",tag=" + tag); } return resource; } @@ -116,9 +123,71 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener if (resource == null) { throw new IllegalArgumentException("查找模板异常, 该模板下未找到 ".concat(imageName)); } - return getImageResourceManager().getResourceInputStream(resource); + return getResourceInputStream(resource, null); } + protected BufferedImage getTemplateImage(Map templateImages, String imageName) { + InputStream stream = getTemplateFile(templateImages, imageName); + BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(stream); + closeStream(stream); + return bufferedImage; + } + + + protected BufferedImage getResourceImage(Resource resource) { + InputStream stream = getResourceInputStream(resource, null); + BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(stream); + closeStream(stream); + return bufferedImage; + } + + protected int randomInt(int origin, int bound) { + return ThreadLocalRandom.current().nextInt(origin, bound); + } + protected boolean randomBoolean() { + return ThreadLocalRandom.current().nextBoolean(); + } + + protected int randomInt(int bound) { + return ThreadLocalRandom.current().nextInt(bound); + } + + public void closeStream(InputStream stream) { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // ignore + } + } + } + + protected InputStream getResourceInputStream(Resource resource, Collection inputStreams) { + InputStream stream = getImageResourceManager().getResourceInputStream(resource); + if (stream != null && inputStreams != null) { + inputStreams.add(stream); + } + return stream; + } + + protected Optional getTemplateImageOfOptional(Map templateImages, String imageName) { + Optional optional = getTemplateFileOfOptional(templateImages, imageName); + if (optional.isPresent()) { + InputStream inputStream = optional.get(); + BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(inputStream); + closeStream(inputStream); + return Optional.ofNullable(bufferedImage); + } + return Optional.empty(); + } + + protected Optional getTemplateFileOfOptional(Map templateImages, String imageName) { + Resource resource = templateImages.get(imageName); + if (resource == null) { + return Optional.empty(); + } + return Optional.ofNullable(getResourceInputStream(resource, null)); + } protected void assertInit() { if (!init) { diff --git a/src/main/java/cloud/tianai/captcha/generator/ImageTransform.java b/src/main/java/cloud/tianai/captcha/generator/ImageTransform.java index eb50b71..e9176a3 100644 --- a/src/main/java/cloud/tianai/captcha/generator/ImageTransform.java +++ b/src/main/java/cloud/tianai/captcha/generator/ImageTransform.java @@ -1,5 +1,9 @@ package cloud.tianai.captcha.generator; +import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; +import cloud.tianai.captcha.generator.common.model.dto.ImageTransformData; +import cloud.tianai.captcha.resource.common.model.dto.Resource; + import java.awt.image.BufferedImage; /** @@ -12,9 +16,28 @@ public interface ImageTransform { /** * 转换 * - * @param bufferedImage 图片 - * @param transformType 转换类型 + * @param backgroundImage 背景图片 + * @param param 参数 + * @param backgroundResource 背景资源对象 + * @return ImageTransformData + */ + default ImageTransformData transform(GenerateParam param, BufferedImage backgroundImage, Resource backgroundResource) { + return transform(param, backgroundImage, null, backgroundResource, null); + } + + /** + * 转换 + * + * @param backgroundImage 背景图片 + * @param templateImage 模板图片(可能为空) + * @param param 参数 + * @param backgroundResource 背景资源对象 + * @param templateResource 模板资源对象(可能为空) * @return String */ - String transform(BufferedImage bufferedImage, String transformType); + ImageTransformData transform(GenerateParam param, + BufferedImage backgroundImage, + BufferedImage templateImage, + Object backgroundResource, + Object templateResource); } diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ClickImageCheckDefinition.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ClickImageCheckDefinition.java index 8679266..772e461 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ClickImageCheckDefinition.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ClickImageCheckDefinition.java @@ -1,5 +1,6 @@ package cloud.tianai.captcha.generator.common.model.dto; +import cloud.tianai.captcha.resource.common.model.dto.Resource; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -14,7 +15,7 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class ClickImageCheckDefinition { /** 提示.*/ - private String tip; + private Resource tip; /** x.*/ private Integer x; /** y.*/ @@ -24,4 +25,4 @@ public class ClickImageCheckDefinition { /** 高.*/ private Integer height; -} \ No newline at end of file +} diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java index de44c50..76eb763 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/GenerateParam.java @@ -1,7 +1,7 @@ package cloud.tianai.captcha.generator.common.model.dto; import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; -import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; +import cloud.tianai.captcha.common.constant.CommonConstant; import lombok.*; /** @@ -15,12 +15,18 @@ import lombok.*; @AllArgsConstructor @EqualsAndHashCode public class GenerateParam { - /** 背景格式化名称.*/ + /** 背景格式化类型. */ private String backgroundFormatName = "jpeg"; - /** 滑块格式化名称.*/ - private String sliderFormatName = "png"; - /** 是否混淆.*/ + /** 模板图片格式化类型. */ + private String templateFormatName = "png"; + /** 是否混淆. */ private Boolean obfuscate = false; - /** 类型.*/ + /** 类型. */ private String type = CaptchaTypeConstant.SLIDER; + /** 背景图片标签, 用户二级过滤背景图片,或指定某背景图片. */ + private String backgroundImageTag = CommonConstant.DEFAULT_TAG; + /** 滑动图片标签,用户二级过滤模板图片,或指定某模板图片.. */ + private String templateImageTag = CommonConstant.DEFAULT_TAG; + /** 扩展参数.*/ + private Object param; } diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ImageCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ImageCaptchaInfo.java index e2590a3..04cff2d 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ImageCaptchaInfo.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ImageCaptchaInfo.java @@ -14,29 +14,29 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class ImageCaptchaInfo { - /** - * 背景图 - */ + /** 背景图. */ private String backgroundImage; - /** - * 移动图 - */ - private String sliderImage; + /** 模板图. */ + private String templateImage; + /** 背景图片所属标签. */ + private String backgroundImageTag; + /** 模板图片所属标签. */ + private String templateImageTag; /** 背景图片宽度. */ - private Integer bgImageWidth; + private Integer backgroundImageWidth; /** 背景图片高度. */ - private Integer bgImageHeight; + private Integer backgroundImageHeight; /** 滑块图片宽度. */ - private Integer sliderImageWidth; + private Integer templateImageWidth; /** 滑块图片高度. */ - private Integer sliderImageHeight; + private Integer templateImageHeight; /** 随机值. */ private Integer randomX; /** 容错值, 可以为空 默认 0.02容错,校验的时候用. */ private Float tolerant; /** 验证码类型. */ private String type; - /** 透传字段,用于传给前端.*/ + /** 透传字段,用于传给前端. */ private Object data; /** * 扩展字段 @@ -44,32 +44,46 @@ public class ImageCaptchaInfo { public Object expand; public ImageCaptchaInfo(String backgroundImage, - String sliderImage, - Integer bgImageWidth, - Integer bgImageHeight, - Integer sliderImageWidth, - Integer sliderImageHeight, + String templateImage, + String backgroundImageTag, + String templateImageTag, + Integer backgroundImageWidth, + Integer backgroundImageHeight, + Integer templateImageWidth, + Integer templateImageHeight, Integer randomX, String type) { this.backgroundImage = backgroundImage; - this.sliderImage = sliderImage; - this.bgImageWidth = bgImageWidth; - this.bgImageHeight = bgImageHeight; - this.sliderImageWidth = sliderImageWidth; - this.sliderImageHeight = sliderImageHeight; + this.templateImage = templateImage; + this.backgroundImageTag = backgroundImageTag; + this.templateImageTag = templateImageTag; + this.backgroundImageWidth = backgroundImageWidth; + this.backgroundImageHeight = backgroundImageHeight; + this.templateImageWidth = templateImageWidth; + this.templateImageHeight = templateImageHeight; this.randomX = randomX; this.type = type; } public static ImageCaptchaInfo of(String backgroundImage, - String sliderImage, - Integer bgImageWidth, - Integer bgImageHeight, - Integer sliderImageWidth, - Integer sliderImageHeight, - Integer randomKey, + String templateImage, + String backgroundImageTag, + String templateImageTag, + Integer backgroundImageWidth, + Integer backgroundImageHeight, + Integer templateImageWidth, + Integer templateImageHeight, + Integer randomX, String type) { - return new ImageCaptchaInfo(backgroundImage, sliderImage, bgImageWidth, bgImageHeight, sliderImageWidth, sliderImageHeight, randomKey, type); + return new ImageCaptchaInfo(backgroundImage, + templateImage, + backgroundImageTag, + templateImageTag, + backgroundImageWidth, + backgroundImageHeight, + templateImageWidth, + templateImageHeight, + randomX, type); } } diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ImageTransformData.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ImageTransformData.java new file mode 100644 index 0000000..0dcc2c5 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/ImageTransformData.java @@ -0,0 +1,25 @@ +package cloud.tianai.captcha.generator.common.model.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author: 天爱有情 + * @date 2023/1/5 11:39 + * @Description 图片转换成url后的对象 + */ +@Data +@NoArgsConstructor +public class ImageTransformData { + /** 背景图. */ + private String backgroundImageUrl; + /** 模板图. */ + private String templateImageUrl; + /** 留一个扩展数据. */ + private Object data; + + public ImageTransformData(String backgroundImageUrl, String templateImageUrl) { + this.backgroundImageUrl = backgroundImageUrl; + this.templateImageUrl = templateImageUrl; + } +} diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/RotateImageCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/RotateImageCaptchaInfo.java index a1f7abe..b5b465d 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/RotateImageCaptchaInfo.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/RotateImageCaptchaInfo.java @@ -1,6 +1,5 @@ package cloud.tianai.captcha.generator.common.model.dto; -import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; import lombok.Data; import lombok.EqualsAndHashCode; @@ -25,21 +24,25 @@ public class RotateImageCaptchaInfo extends ImageCaptchaInfo { public static RotateImageCaptchaInfo of(Double degree, Integer randomX, String backgroundImage, - String sliderImage, + String templateImage, + String backgroundImageTag, + String templateImageTag, Integer bgImageWidth, Integer bgImageHeight, - Integer sliderImageWidth, - Integer sliderImageHeight) { + Integer templateImageWidth, + Integer templateImageHeight) { RotateImageCaptchaInfo rotateImageCaptchaInfo = new RotateImageCaptchaInfo(); rotateImageCaptchaInfo.setDegree(degree); rotateImageCaptchaInfo.setRandomX(randomX); rotateImageCaptchaInfo.setBackgroundImage(backgroundImage); + rotateImageCaptchaInfo.setBackgroundImageTag(backgroundImageTag); + rotateImageCaptchaInfo.setTemplateImage(templateImageTag); rotateImageCaptchaInfo.setTolerant(DEFAULT_TOLERANT); - rotateImageCaptchaInfo.setSliderImage(sliderImage); - rotateImageCaptchaInfo.setBgImageWidth(bgImageWidth); - rotateImageCaptchaInfo.setBgImageHeight(bgImageHeight); - rotateImageCaptchaInfo.setSliderImageWidth(sliderImageWidth); - rotateImageCaptchaInfo.setSliderImageHeight(sliderImageHeight); + rotateImageCaptchaInfo.setTemplateImage(templateImage); + rotateImageCaptchaInfo.setBackgroundImageWidth(bgImageWidth); + rotateImageCaptchaInfo.setBackgroundImageHeight(bgImageHeight); + rotateImageCaptchaInfo.setTemplateImageWidth(templateImageWidth); + rotateImageCaptchaInfo.setTemplateImageHeight(templateImageHeight); // 类型为旋转图片验证码 rotateImageCaptchaInfo.setType(CaptchaTypeConstant.ROTATE); return rotateImageCaptchaInfo; diff --git a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/SliderImageCaptchaInfo.java b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/SliderImageCaptchaInfo.java index 8ece69f..7455128 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/model/dto/SliderImageCaptchaInfo.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/model/dto/SliderImageCaptchaInfo.java @@ -1,6 +1,5 @@ package cloud.tianai.captcha.generator.common.model.dto; -import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; import lombok.Data; import lombok.EqualsAndHashCode; @@ -23,7 +22,9 @@ public class SliderImageCaptchaInfo extends ImageCaptchaInfo { public static SliderImageCaptchaInfo of(Integer x, Integer y, String backgroundImage, - String sliderImage, + String templateImage, + String backgroundImageTag, + String templateImageTag, Integer bgImageWidth, Integer bgImageHeight, Integer sliderImageWidth, @@ -33,11 +34,13 @@ public class SliderImageCaptchaInfo extends ImageCaptchaInfo { sliderImageCaptchaInfo.setY(y); sliderImageCaptchaInfo.setRandomX(x); sliderImageCaptchaInfo.setBackgroundImage(backgroundImage); - sliderImageCaptchaInfo.setSliderImage(sliderImage); - sliderImageCaptchaInfo.setBgImageWidth(bgImageWidth); - sliderImageCaptchaInfo.setBgImageHeight(bgImageHeight); - sliderImageCaptchaInfo.setSliderImageWidth(sliderImageWidth); - sliderImageCaptchaInfo.setSliderImageHeight(sliderImageHeight); + sliderImageCaptchaInfo.setTemplateImage(templateImage); + sliderImageCaptchaInfo.setBackgroundImageTag(backgroundImageTag); + sliderImageCaptchaInfo.setTemplateImageTag(templateImageTag); + sliderImageCaptchaInfo.setBackgroundImageWidth(bgImageWidth); + sliderImageCaptchaInfo.setBackgroundImageHeight(bgImageHeight); + sliderImageCaptchaInfo.setTemplateImageWidth(sliderImageWidth); + sliderImageCaptchaInfo.setTemplateImageHeight(sliderImageHeight); sliderImageCaptchaInfo.setType(CaptchaTypeConstant.SLIDER); return sliderImageCaptchaInfo; } diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/AbstractClickImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/AbstractClickImageCaptchaGenerator.java index 6301c51..cf1fb17 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/AbstractClickImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/AbstractClickImageCaptchaGenerator.java @@ -7,12 +7,14 @@ import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; import cloud.tianai.captcha.resource.common.model.dto.Resource; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; /** @@ -22,14 +24,6 @@ import java.util.concurrent.ThreadLocalRandom; */ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCaptchaGenerator { - /** 参与校验的数量. */ - @Getter - @Setter - protected Integer checkClickCount = 4; - /** 干扰数量. */ - @Getter - @Setter - protected Integer interferenceCount = 2; public AbstractClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager) { super(imageCaptchaResourceManager); @@ -42,89 +36,62 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa @Override public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) { // 文字点选验证码不需要模板 只需要背景图 - Collection inputStreams = new LinkedList<>(); - try { - Resource resourceImage = requiredRandomGetResource(param.getType()); - InputStream resourceInputStream = getImageResourceManager().getResourceInputStream(resourceImage); - inputStreams.add(resourceInputStream); - BufferedImage bgImage = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream); + Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag()); - List clickImageCheckDefinitionList = new ArrayList<>(interferenceCount); - int allImages = interferenceCount + checkClickCount; - int avg = bgImage.getWidth() / allImages; - List imgTips = randomGetClickImgTips(allImages); - if (allImages < imgTips.size()) { - throw new IllegalStateException("随机生成点击图片小于请求数量, 请求生成数量=" + allImages + ",实际生成数量=" + imgTips.size()); - } - for (int i = 0; i < allImages; i++) { - // 随机获取点击图片 - ImgWrapper imgWrapper = getClickImg(imgTips.get(i)); - BufferedImage image = imgWrapper.getImage(); - int clickImgWidth = image.getWidth(); - int clickImgHeight = image.getHeight(); - // 随机x - int randomX; - if (i == 0) { - randomX = 1; - } else { - randomX = avg * i; - } - // 随机y - int randomY = ThreadLocalRandom.current().nextInt(10, bgImage.getHeight() - clickImgHeight); - // 通过随机x和y 进行覆盖图片 - CaptchaImageUtils.overlayImage(bgImage, imgWrapper.getImage(), randomX, randomY); - ClickImageCheckDefinition clickImageCheckDefinition = new ClickImageCheckDefinition(); - clickImageCheckDefinition.setTip(imgWrapper.getTip()); - clickImageCheckDefinition.setX(randomX + clickImgWidth / 2); - clickImageCheckDefinition.setY(randomY + clickImgHeight / 2); - clickImageCheckDefinition.setWidth(clickImgWidth); - clickImageCheckDefinition.setHeight(clickImgHeight); - clickImageCheckDefinitionList.add(clickImageCheckDefinition); - } - List checkClickImageCheckDefinitionList = getCheckClickImageCheckDefinitionList(clickImageCheckDefinitionList, - checkClickCount); - // wrap - return wrapClickImageCaptchaInfo(param, bgImage, checkClickImageCheckDefinitionList); + BufferedImage bgImage = getResourceImage(resourceImage); - } finally { - // 使用完后关闭流 - for (InputStream inputStream : inputStreams) { - try { - inputStream.close(); - } catch (IOException e) { - // ignore - } - } + List imgTips = randomGetClickImgTips(param); + int allImages = imgTips.size(); + List clickImageCheckDefinitionList = new ArrayList<>(allImages); + int avg = bgImage.getWidth() / allImages; + if (allImages < imgTips.size()) { + throw new IllegalStateException("随机生成点击图片小于请求数量, 请求生成数量=" + allImages + ",实际生成数量=" + imgTips.size()); } + for (int i = 0; i < allImages; i++) { + // 随机获取点击图片 + ImgWrapper imgWrapper = getClickImg(imgTips.get(i)); + BufferedImage image = imgWrapper.getImage(); + int clickImgWidth = image.getWidth(); + int clickImgHeight = image.getHeight(); + // 随机x + int randomX; + if (i == 0) { + randomX = 1; + } else { + randomX = avg * i; + } + // 随机y + int randomY = randomInt(10, bgImage.getHeight() - clickImgHeight); + // 通过随机x和y 进行覆盖图片 + CaptchaImageUtils.overlayImage(bgImage, imgWrapper.getImage(), randomX, randomY); + ClickImageCheckDefinition clickImageCheckDefinition = new ClickImageCheckDefinition(); + clickImageCheckDefinition.setTip(imgWrapper.getTip()); + clickImageCheckDefinition.setX(randomX + clickImgWidth / 2); + clickImageCheckDefinition.setY(randomY + clickImgHeight / 2); + clickImageCheckDefinition.setWidth(clickImgWidth); + clickImageCheckDefinition.setHeight(clickImgHeight); + clickImageCheckDefinitionList.add(clickImageCheckDefinition); + } + List checkClickImageCheckDefinitionList = filterAndSortClickImageCheckDefinition(clickImageCheckDefinitionList); + // wrap + return wrapClickImageCaptchaInfo(param, bgImage, checkClickImageCheckDefinitionList, resourceImage); + } /** - * 从总的图片中 去除要校验的图片数据 以及顺序 + * 过滤并排序校验的图片点选顺序 * * @param allCheckDefinitionList 总的点选图片 - * @param checkClickCount 参与校验的数据 * @return List */ - protected List getCheckClickImageCheckDefinitionList(List allCheckDefinitionList, - Integer checkClickCount) { - // 打乱 - Collections.shuffle(allCheckDefinitionList); - // 拿出参与校验的数据 - List checkClickImageCheckDefinitionList = new ArrayList<>(checkClickCount); - for (int i = 0; i < checkClickCount; i++) { - ClickImageCheckDefinition clickImageCheckDefinition = allCheckDefinitionList.get(i); - checkClickImageCheckDefinitionList.add(clickImageCheckDefinition); - } - return checkClickImageCheckDefinitionList; - } + protected abstract List filterAndSortClickImageCheckDefinition(List allCheckDefinitionList); /** * 随机获取一组数据用于生成随机图 * - * @param tipSize tipSize * @return List */ - protected abstract List randomGetClickImgTips(int tipSize); + protected abstract List randomGetClickImgTips(GenerateParam param); /** * 随机获取点击的图片 @@ -132,7 +99,7 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa * @param tip 提示数据,根据改数据生成图片 * @return ImgWrapper */ - public abstract ImgWrapper getClickImg(String tip); + public abstract ImgWrapper getClickImg(Resource tip); /** * 包装 ImageCaptchaInfo @@ -140,10 +107,11 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa * @param param param * @param bgImage bgImage * @param checkClickImageCheckDefinitionList checkClickImageCheckDefinitionList + * @param resourceImage 随机读取到的图片资源 * @return ImageCaptchaInfo */ public abstract ImageCaptchaInfo wrapClickImageCaptchaInfo(GenerateParam param, BufferedImage bgImage, - List checkClickImageCheckDefinitionList); + List checkClickImageCheckDefinitionList, Resource resourceImage); /** * @Author: 天爱有情 @@ -157,6 +125,6 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa /** 图片. */ private BufferedImage image; /** 提示. */ - private String tip; + private Resource tip; } } diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java index de828fa..3d5ee13 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java @@ -171,7 +171,7 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { @Override public ImageCaptchaInfo generateCaptchaImage(String type, String targetFormatName, String matrixFormatName) { - return generateCaptchaImage(GenerateParam.builder().type(type).backgroundFormatName(targetFormatName).sliderFormatName(matrixFormatName).build(), true); + return generateCaptchaImage(GenerateParam.builder().type(type).backgroundFormatName(targetFormatName).templateFormatName(matrixFormatName).build(), true); } @Override diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardConcatImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardConcatImageCaptchaGenerator.java index d1387f1..9576b3e 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardConcatImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardConcatImageCaptchaGenerator.java @@ -5,6 +5,7 @@ import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator; import cloud.tianai.captcha.generator.ImageTransform; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.generator.common.model.dto.ImageTransformData; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; import cloud.tianai.captcha.resource.ResourceStore; import cloud.tianai.captcha.resource.common.model.dto.Resource; @@ -55,7 +56,7 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen // 拼接验证码不需要模板 只需要背景图 Collection inputStreams = new LinkedList<>(); try { - Resource resourceImage = requiredRandomGetResource(param.getType()); + Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag()); InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage); inputStreams.add(resourceInputStream); BufferedImage bgImage = wrapFile2BufferedImage(resourceInputStream); @@ -71,7 +72,7 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen + bgImageTopSplit[1].getWidth(), bgImageTopSplit[0].getHeight(), bgImageTopSplit[1], bgImageTopSplit[0]); bgImage = concatImage(false, bgImageSplit[1].getWidth(), sliderImage.getHeight() + bgImageSplit[1].getHeight(), sliderImage, bgImageSplit[1]); - return wrapConcatCaptchaInfo(randomX, randomY, bgImage, param); + return wrapConcatCaptchaInfo(randomX, randomY, bgImage, param, resourceImage); } finally { // 使用完后关闭流 for (InputStream inputStream : inputStreams) { @@ -85,9 +86,13 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen } @SneakyThrows - private ImageCaptchaInfo wrapConcatCaptchaInfo(int randomX, int randomY, BufferedImage bgImage, GenerateParam param) { - String backGroundImageBase64 = getImageTransform().transform(bgImage, param.getBackgroundFormatName()); - ImageCaptchaInfo imageCaptchaInfo = ImageCaptchaInfo.of(backGroundImageBase64, + private ImageCaptchaInfo wrapConcatCaptchaInfo(int randomX, int randomY, BufferedImage bgImage, GenerateParam param, Resource resourceImage) { + + ImageTransformData transform = getImageTransform().transform(param, bgImage, resourceImage); + + ImageCaptchaInfo imageCaptchaInfo = ImageCaptchaInfo.of(transform.getBackgroundImageUrl(), + null, + resourceImage.getTag(), null, bgImage.getWidth(), bgImage.getHeight(), diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java index 52e7b7b..5414355 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java @@ -6,6 +6,7 @@ import cloud.tianai.captcha.generator.ImageTransform; import cloud.tianai.captcha.generator.common.model.dto.ClickImageCheckDefinition; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.generator.common.model.dto.ImageTransformData; import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; import cloud.tianai.captcha.resource.ResourceStore; @@ -20,6 +21,7 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; @@ -47,6 +49,14 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI @Getter @Setter protected int tipImageInterferencePointNum = 5; + /** 参与校验的数量. */ + @Getter + @Setter + protected Integer checkClickCount = 4; + /** 干扰数量. */ + @Getter + @Setter + protected Integer interferenceCount = 2; /** * 因为在画文字图形的时候 y 值不能准确通过 除法计算得出, 字体大小不一致中间的容错值算不准确 @@ -66,14 +76,14 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI super(imageCaptchaResourceManager); setImageTransform(imageTransform); } - @Override - protected List randomGetClickImgTips(int tipSize) { + protected List randomGetClickImgTips(GenerateParam param) { + int tipSize = interferenceCount + checkClickCount; ThreadLocalRandom random = ThreadLocalRandom.current(); - List tipList = new ArrayList<>(tipSize); + List tipList = new ArrayList<>(tipSize); for (int i = 0; i < tipSize; i++) { String randomWord = FontUtils.getRandomChar(random); - tipList.add(randomWord); + tipList.add(new Resource(null, randomWord)); } // 随机文字 return tipList; @@ -108,7 +118,7 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI } public ImgWrapper genTipImage(List imageCheckDefinitions) { - String tips = imageCheckDefinitions.stream().map(ClickImageCheckDefinition::getTip).collect(Collectors.joining()); + String tips = imageCheckDefinitions.stream().map(c -> c.getTip().getData()).collect(Collectors.joining()); // 生成随机颜色 int fontWidth = tips.length() * font.getSize(); int width = fontWidth + 6; @@ -117,18 +127,18 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI float top = 6 / 2f + font.getSize() - currentFontTopCoef; BufferedImage bufferedImage = CaptchaImageUtils.genSimpleImgCaptcha(tips, font, width, height, left, top, tipImageInterferenceLineNum, tipImageInterferencePointNum); - return new ImgWrapper(bufferedImage, tips); + return new ImgWrapper(bufferedImage, new Resource(null, tips)); } @Override - public ImgWrapper getClickImg(String tip) { + public ImgWrapper getClickImg(Resource tip) { ThreadLocalRandom random = ThreadLocalRandom.current(); // 随机颜色 Color randomColor = CaptchaImageUtils.getRandomColor(random); // 随机角度 - int randomDeg = ThreadLocalRandom.current().nextInt(0, 85); + int randomDeg = randomInt(0, 85); BufferedImage fontImage = CaptchaImageUtils.drawWordImg(randomColor, - tip, + tip.getData(), font, currentFontTopCoef, clickImgWidth, @@ -137,19 +147,33 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI return new ImgWrapper(fontImage, tip); } + @Override + protected List filterAndSortClickImageCheckDefinition(List allCheckDefinitionList) { + // 打乱 + Collections.shuffle(allCheckDefinitionList); + // 拿出参与校验的数据 + List checkClickImageCheckDefinitionList = new ArrayList<>(checkClickCount); + for (int i = 0; i < checkClickCount; i++) { + ClickImageCheckDefinition clickImageCheckDefinition = allCheckDefinitionList.get(i); + checkClickImageCheckDefinitionList.add(clickImageCheckDefinition); + } + return checkClickImageCheckDefinitionList; + } @Override public ImageCaptchaInfo wrapClickImageCaptchaInfo(GenerateParam param, BufferedImage bgImage, - List checkClickImageCheckDefinitionList) { + List checkClickImageCheckDefinitionList, Resource resourceImage) { // 提示图片 BufferedImage tipImage = genTipImage(checkClickImageCheckDefinitionList).getImage(); + ImageTransformData transform = getImageTransform().transform(param, bgImage, tipImage, resourceImage, checkClickImageCheckDefinitionList); ImageCaptchaInfo clickImageCaptchaInfo = new ImageCaptchaInfo(); - clickImageCaptchaInfo.setBackgroundImage(getImageTransform().transform(bgImage, param.getBackgroundFormatName())); - clickImageCaptchaInfo.setSliderImage(getImageTransform().transform(tipImage, param.getSliderFormatName())); - clickImageCaptchaInfo.setBgImageWidth(bgImage.getWidth()); - clickImageCaptchaInfo.setBgImageHeight(bgImage.getHeight()); - clickImageCaptchaInfo.setSliderImageWidth(tipImage.getWidth()); - clickImageCaptchaInfo.setSliderImageHeight(tipImage.getHeight()); + clickImageCaptchaInfo.setBackgroundImage(transform.getBackgroundImageUrl()); + clickImageCaptchaInfo.setBackgroundImageTag(resourceImage.getTag()); + clickImageCaptchaInfo.setTemplateImage(transform.getTemplateImageUrl()); + clickImageCaptchaInfo.setBackgroundImageWidth(bgImage.getWidth()); + clickImageCaptchaInfo.setBackgroundImageHeight(bgImage.getHeight()); + clickImageCaptchaInfo.setTemplateImageWidth(tipImage.getWidth()); + clickImageCaptchaInfo.setTemplateImageHeight(tipImage.getHeight()); clickImageCaptchaInfo.setRandomX(null); clickImageCaptchaInfo.setTolerant(null); clickImageCaptchaInfo.setType(CaptchaTypeConstant.WORD_IMAGE_CLICK); diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java index f5288ec..b826770 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java @@ -6,11 +6,13 @@ import cloud.tianai.captcha.generator.ImageTransform; import cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.generator.common.model.dto.ImageTransformData; import cloud.tianai.captcha.generator.common.model.dto.RotateImageCaptchaInfo; import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; import cloud.tianai.captcha.resource.ResourceStore; import cloud.tianai.captcha.resource.common.model.dto.Resource; +import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider; import lombok.SneakyThrows; @@ -23,6 +25,8 @@ import java.util.LinkedList; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; +import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_TAG; + /** * @Author: 天爱有情 * @date 2022/4/22 16:43 @@ -52,7 +56,7 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); // 添加一些系统的 模板文件 - Map template1 = new HashMap<>(4); + ResourceMap template1 = new ResourceMap(DEFAULT_TAG, 4); template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/active.png"))); template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/fixed.png"))); template1.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/matrix.png"))); @@ -62,10 +66,10 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen @Override public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) { // 旋转验证码没有混淆 - Map templateImages = requiredRandomGetTemplate(param.getType()); + ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag()); Collection inputStreams = new LinkedList<>(); try { - Resource resourceImage = requiredRandomGetResource(param.getType()); + Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag()); InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage); inputStreams.add(resourceInputStream); BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream); @@ -96,7 +100,7 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen int randomX = ThreadLocalRandom.current().nextInt(fixedTemplate.getWidth() + 10, targetBackground.getWidth() - 10); double degree = 360d - randomX / ((targetBackground.getWidth()) / 360d); CaptchaImageUtils.centerOverlayAndRotateImage(matrixTemplate, cutImage, degree); - return wrapRotateCaptchaInfo(degree, randomX, targetBackground, matrixTemplate, param); + return wrapRotateCaptchaInfo(degree, randomX, targetBackground, matrixTemplate, param, templateImages, resourceImage); } finally { // 使用完后关闭流 for (InputStream inputStream : inputStreams) { @@ -110,15 +114,14 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen } @SneakyThrows - private ImageCaptchaInfo wrapRotateCaptchaInfo(double degree, int randomX, BufferedImage backgroundImage, BufferedImage sliderImage, GenerateParam param) { - String backgroundFormatName = param.getBackgroundFormatName(); - String sliderFormatName = param.getSliderFormatName(); - String backGroundImageBase64 = getImageTransform().transform(backgroundImage, backgroundFormatName); - String sliderImageBase64 = getImageTransform().transform(sliderImage, sliderFormatName); + private ImageCaptchaInfo wrapRotateCaptchaInfo(double degree, int randomX, BufferedImage backgroundImage, BufferedImage sliderImage, GenerateParam param, ResourceMap templateResource, Resource resourceImage) { + ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource); return RotateImageCaptchaInfo.of(degree, randomX, - backGroundImageBase64, - sliderImageBase64, + transform.getBackgroundImageUrl(), + transform.getTemplateImageUrl(), + resourceImage.getTag(), + templateResource.getTag(), backgroundImage.getWidth(), backgroundImage.getHeight(), sliderImage.getWidth(), sliderImage.getHeight() ); diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java index 92c23de..1ae1cf4 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardSliderImageCaptchaGenerator.java @@ -6,11 +6,13 @@ import cloud.tianai.captcha.generator.ImageTransform; import cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; +import cloud.tianai.captcha.generator.common.model.dto.ImageTransformData; import cloud.tianai.captcha.generator.common.model.dto.SliderImageCaptchaInfo; import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; import cloud.tianai.captcha.resource.ResourceStore; import cloud.tianai.captcha.resource.common.model.dto.Resource; +import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -24,6 +26,8 @@ import java.util.LinkedList; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; +import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_TAG; + /** * @Author: 天爱有情 * @Date 2020/5/29 8:06 @@ -61,10 +65,10 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen @Override public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) { Boolean obfuscate = param.getObfuscate(); - Map templateImages = requiredRandomGetTemplate(param.getType()); + ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag()); Collection inputStreams = new LinkedList<>(); try { - Resource resourceImage = requiredRandomGetResource(param.getType()); + Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag()); InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage); inputStreams.add(resourceInputStream); BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream); @@ -99,7 +103,7 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen BufferedImage cutImage = CaptchaImageUtils.cutImage(cutBackground, fixedTemplate, randomX, randomY); CaptchaImageUtils.overlayImage(cutImage, activeTemplate, 0, 0); CaptchaImageUtils.overlayImage(matrixTemplate, cutImage, 0, randomY); - return wrapSliderCaptchaInfo(randomX, randomY, targetBackground, matrixTemplate, param); + return wrapSliderCaptchaInfo(randomX, randomY, targetBackground, matrixTemplate, param, templateImages, resourceImage); } finally { // 使用完后关闭流 for (InputStream inputStream : inputStreams) { @@ -127,14 +131,16 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen int randomY, BufferedImage backgroundImage, BufferedImage sliderImage, - GenerateParam param) { - String backgroundFormatName = param.getBackgroundFormatName(); - String sliderFormatName = param.getSliderFormatName(); - String backGroundImageBase64 = getImageTransform().transform(backgroundImage, backgroundFormatName); - String sliderImageBase64 = getImageTransform().transform(sliderImage, sliderFormatName); + GenerateParam param, + ResourceMap templateResource, + Resource resourceImage) { + ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource); + return SliderImageCaptchaInfo.of(randomX, randomY, - backGroundImageBase64, - sliderImageBase64, + transform.getBackgroundImageUrl(), + transform.getTemplateImageUrl(), + resourceImage.getTag(), + templateResource.getTag(), backgroundImage.getWidth(), backgroundImage.getHeight(), sliderImage.getWidth(), sliderImage.getHeight() ); @@ -158,14 +164,14 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); // 添加一些系统的 模板文件 - Map template1 = new HashMap<>(4); + ResourceMap template1 = new ResourceMap(DEFAULT_TAG, 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(CaptchaTypeConstant.SLIDER, template1); - Map template2 = new HashMap<>(4); + ResourceMap template2 = new ResourceMap(DEFAULT_TAG, 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"))); diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/transform/Base64ImageTransform.java b/src/main/java/cloud/tianai/captcha/generator/impl/transform/Base64ImageTransform.java index 80c5f55..eb293c6 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/transform/Base64ImageTransform.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/transform/Base64ImageTransform.java @@ -1,8 +1,11 @@ package cloud.tianai.captcha.generator.impl.transform; import cloud.tianai.captcha.generator.ImageTransform; +import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; +import cloud.tianai.captcha.generator.common.model.dto.ImageTransformData; import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils; import cloud.tianai.captcha.generator.common.util.ImgWriter; +import cloud.tianai.captcha.resource.common.model.dto.Resource; import lombok.SneakyThrows; import javax.imageio.ImageIO; @@ -18,7 +21,6 @@ import java.util.Base64; */ public class Base64ImageTransform implements ImageTransform { - @Override @SneakyThrows(IOException.class) public String transform(BufferedImage bufferedImage, String transformType) { // 这里判断处理一下,加一些警告日志 @@ -55,4 +57,16 @@ public class Base64ImageTransform implements ImageTransform { // 其它的暂时不考虑 return null; } + + @Override + public ImageTransformData transform(GenerateParam param, BufferedImage backgroundImage, BufferedImage templateImage, Object backgroundResource, Object templateResource) { + ImageTransformData imageTransformData = new ImageTransformData(); + if (backgroundImage != null) { + imageTransformData.setBackgroundImageUrl(transform(backgroundImage, param.getBackgroundFormatName())); + } + if (templateImage != null) { + imageTransformData.setTemplateImageUrl(transform(templateImage, param.getTemplateFormatName())); + } + return imageTransformData; + } } diff --git a/src/main/java/cloud/tianai/captcha/resource/AbstractResourceProvider.java b/src/main/java/cloud/tianai/captcha/resource/AbstractResourceProvider.java index 4999dda..a2236d8 100644 --- a/src/main/java/cloud/tianai/captcha/resource/AbstractResourceProvider.java +++ b/src/main/java/cloud/tianai/captcha/resource/AbstractResourceProvider.java @@ -14,7 +14,7 @@ public abstract class AbstractResourceProvider implements ResourceProvider { public InputStream getResourceInputStream(Resource data) { InputStream resourceInputStream = doGetResourceInputStream(data); if (resourceInputStream == null) { - throw new IllegalArgumentException("滑块验证码无法读到指定的资源[" + getName() + "]" + data); + throw new IllegalArgumentException("无法读到指定的资源[" + getName() + "]" + data); } return resourceInputStream; } diff --git a/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java b/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java index f4625f7..6ff62d9 100644 --- a/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java +++ b/src/main/java/cloud/tianai/captcha/resource/ImageCaptchaResourceManager.java @@ -1,10 +1,10 @@ package cloud.tianai.captcha.resource; import cloud.tianai.captcha.resource.common.model.dto.Resource; +import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; import java.io.InputStream; import java.util.List; -import java.util.Map; /** * @Author: 天爱有情 @@ -17,17 +17,19 @@ public interface ImageCaptchaResourceManager { * 随机获取某个模板 * * @param type 验证码类型 + * @param tag 二级过滤,可以为空 * @return Map */ - Map randomGetTemplate(String type); + ResourceMap randomGetTemplate(String type, String tag); /** * 随机获取某个资源对象 * * @param type 验证码类型 + * @param tag 二级过滤,可以为空 * @return Resource */ - Resource randomGetResource(String type); + Resource randomGetResource(String type, String tag); /** * 获取真正的资源流通过资源对象 diff --git a/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java index 9727507..63c73f7 100644 --- a/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java +++ b/src/main/java/cloud/tianai/captcha/resource/ResourceStore.java @@ -1,9 +1,8 @@ package cloud.tianai.captcha.resource; import cloud.tianai.captcha.resource.common.model.dto.Resource; -import cloud.tianai.captcha.resource.common.model.dto.Resource; +import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; -import java.util.List; import java.util.Map; /** @@ -21,55 +20,6 @@ public interface ResourceStore { */ void addResource(String type, Resource resource); - /** - * 清除某个类型下的所有资源 - * - * @param type type - */ - void clearResources(String type); - - /** - * 清除所有资源 - */ - void clearAllResources(); - - /** - * 获取所有资源对象 - * - * @return List - */ - Map> listAllResources(); - - /** - * 获取某个type下的所有资源对象 - * - * @param type type - * @return List - */ - List listResourcesByType(String type); - - /** - * 随机获取某个资源 - * - * @param type type - * @return Resource - */ - Resource randomGetResource(String type); - - /** - * 获取资源总数 - * - * @return int - */ - int getAllResourceCount(); - - /** - * 获取某个type下的资源总数 - * - * @param type type - * @return int - */ - int getResourceCount(String type); /** * 添加模板 @@ -77,34 +27,15 @@ public interface ResourceStore { * @param type 验证码类型 * @param template 模板 */ - void addTemplate(String type, Map template); + void addTemplate(String type, ResourceMap template); /** - * 清除所有模板 - */ - void clearAllTemplates(); - - /** - * 清除某个type下的所有模板 + * 随机获取某个资源 * * @param type type + * @return Resource */ - void clearTemplates(String type); - - /** - * 获取所有模板通过type - * - * @param type 验证码类型 - * @return List> - */ - List> listTemplatesByType(String type); - - /** - * 获取所有模板 - * - * @return Map>> - */ - Map>> listAllTemplates(); + Resource randomGetResourceByTypeAndTag(String type, String tag); /** * 随机获取某个模板通过type @@ -112,6 +43,6 @@ public interface ResourceStore { * @param type type * @return Map */ - Map randomGetTemplateByType(String type); + ResourceMap randomGetTemplateByTypeAndTag(String type, String tag); } diff --git a/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java index 13b9f97..73724a6 100644 --- a/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java +++ b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/Resource.java @@ -10,10 +10,22 @@ import lombok.Data; * @Description 资源对象 */ @Data -@AllArgsConstructor public class Resource { /** 类型. */ private String type; /** 数据,传输给 {@link ResourceProvider} 的参数 */ public String data; + /** 标签.*/ + private String tag; + + public Resource(String type, String data) { + this.type = type; + this.data = data; + } + + public Resource(String type, String data, String tag) { + this.type = type; + this.data = data; + this.tag = tag; + } } diff --git a/src/main/java/cloud/tianai/captcha/resource/common/model/dto/ResourceMap.java b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/ResourceMap.java new file mode 100644 index 0000000..cb03db4 --- /dev/null +++ b/src/main/java/cloud/tianai/captcha/resource/common/model/dto/ResourceMap.java @@ -0,0 +1,35 @@ +package cloud.tianai.captcha.resource.common.model.dto; + +import cloud.tianai.captcha.resource.common.model.dto.Resource; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.HashMap; + +/** + * @Author: 天爱有情 + * @date 2022/12/30 9:23 + * @Description 存储一组Resource的Map, 增加tag标记 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ResourceMap extends HashMap { + + private String tag; + + public ResourceMap(String tag) { + this.tag = tag; + } + + public ResourceMap(String tag, int initialCapacity) { + super(initialCapacity); + this.tag = tag; + } + + public ResourceMap(int initialCapacity) { + super(initialCapacity); + } + + public ResourceMap() { + } +} diff --git a/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java b/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java index 5691750..2ff017d 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/DefaultImageCaptchaResourceManager.java @@ -1,6 +1,7 @@ package cloud.tianai.captcha.resource.impl; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; +import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; import cloud.tianai.captcha.resource.impl.provider.URLResourceProvider; import cloud.tianai.captcha.resource.ImageCaptchaResourceManager; import cloud.tianai.captcha.resource.ResourceProvider; @@ -49,8 +50,8 @@ public class DefaultImageCaptchaResourceManager implements ImageCaptchaResourceM } @Override - public Map randomGetTemplate(String type) { - Map resourceMap = resourceStore.randomGetTemplateByType(type); + public ResourceMap randomGetTemplate(String type, String tag) { + ResourceMap resourceMap = resourceStore.randomGetTemplateByTypeAndTag(type, tag); if (resourceMap == null) { throw new IllegalStateException("随机获取模板错误,store中模板为空, type:" + type); } @@ -58,8 +59,8 @@ public class DefaultImageCaptchaResourceManager implements ImageCaptchaResourceM } @Override - public Resource randomGetResource(String type) { - Resource resource = resourceStore.randomGetResource(type); + public Resource randomGetResource(String type, String tag) { + Resource resource = resourceStore.randomGetResourceByTypeAndTag(type, tag); if (resource == null) { throw new IllegalStateException("随机获取资源错误,store中资源为空, type:" + type); } diff --git a/src/main/java/cloud/tianai/captcha/resource/impl/DefaultResourceStore.java b/src/main/java/cloud/tianai/captcha/resource/impl/DefaultResourceStore.java index 1bb2854..5d7fb6b 100644 --- a/src/main/java/cloud/tianai/captcha/resource/impl/DefaultResourceStore.java +++ b/src/main/java/cloud/tianai/captcha/resource/impl/DefaultResourceStore.java @@ -1,10 +1,10 @@ package cloud.tianai.captcha.resource.impl; import cloud.tianai.captcha.common.util.CollectionUtils; -import cloud.tianai.captcha.resource.common.model.dto.Resource; -import cloud.tianai.captcha.common.util.CollectionUtils; +import cloud.tianai.captcha.common.util.ObjectUtils; import cloud.tianai.captcha.resource.ResourceStore; import cloud.tianai.captcha.resource.common.model.dto.Resource; +import cloud.tianai.captcha.resource.common.model.dto.ResourceMap; import java.util.*; import java.util.concurrent.ThreadLocalRandom; @@ -15,44 +15,47 @@ import java.util.concurrent.ThreadLocalRandom; * @Description 默认的资源存储 */ public class DefaultResourceStore implements ResourceStore { + private static final String TYPE_TAG_SPLIT_FLAG = "|"; /** * 模板资源. */ - private Map>> templateResourceMap = new HashMap<>(2); + private Map> templateResourceMap = new HashMap<>(2); /** * resource. */ private Map> resourceMap = new HashMap<>(2); + /** 用于检索 type和tag. */ + private Map> templateResourceTagMap = new HashMap<>(2); + private Map> resourceTagMap = new HashMap<>(2); + @Override public void addResource(String type, Resource resource) { resourceMap.computeIfAbsent(type, k -> new ArrayList<>(20)).add(resource); + // 添加tag标签字典 + if (!ObjectUtils.isEmpty(resource.getTag())) { + resourceTagMap.computeIfAbsent(mergeTypeAndTag(type, resource.getTag()), k -> new ArrayList<>(20)).add(resource); + } } @Override - public void clearResources(String type) { - resourceMap.remove(type); + public void addTemplate(String type, ResourceMap template) { + templateResourceMap.computeIfAbsent(type, k -> new ArrayList<>(2)).add(template); + // 添加tag标签字典 + if (!ObjectUtils.isEmpty(template.getTag())) { + templateResourceTagMap.computeIfAbsent(mergeTypeAndTag(type, template.getTag()), k -> new ArrayList<>(2)).add(template); + } } @Override - public void clearAllResources() { - resourceMap.clear(); - } - - @Override - public Map> listAllResources() { - return resourceMap; - } - - @Override - public List listResourcesByType(String type) { - return resourceMap.getOrDefault(type, Collections.emptyList()); - } - - @Override - public Resource randomGetResource(String type) { - List resources = resourceMap.get(type); + public Resource randomGetResourceByTypeAndTag(String type, String tag) { + List resources; + if (ObjectUtils.isEmpty(tag)) { + resources = resourceMap.get(type); + } else { + resources = resourceTagMap.get(mergeTypeAndTag(type, tag)); + } if (CollectionUtils.isEmpty(resources)) { throw new IllegalStateException("随机获取资源错误,store中资源为空, type:" + type); } @@ -64,48 +67,13 @@ public class DefaultResourceStore implements ResourceStore { } @Override - public int getAllResourceCount() { - int count = 0; - for (List value : resourceMap.values()) { - count += value.size(); + public ResourceMap randomGetTemplateByTypeAndTag(String type, String tag) { + List templateList; + if (ObjectUtils.isEmpty(tag)) { + templateList = templateResourceMap.get(type); + } else { + templateList = templateResourceTagMap.get(mergeTypeAndTag(type, tag)); } - 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); } @@ -117,4 +85,55 @@ public class DefaultResourceStore implements ResourceStore { return templateList.get(randomIndex); } + public String mergeTypeAndTag(String type, String tag) { + return type + TYPE_TAG_SPLIT_FLAG + tag; + } + + + public void clearResources(String type) { + resourceMap.remove(type); + } + + public void clearAllResources() { + resourceMap.clear(); + } + + public Map> listAllResources() { + return resourceMap; + } + + public List listResourcesByType(String type) { + return resourceMap.getOrDefault(type, Collections.emptyList()); + } + + public int getAllResourceCount() { + int count = 0; + for (List value : resourceMap.values()) { + count += value.size(); + } + return count; + } + + public int getResourceCount(String type) { + return resourceMap.getOrDefault(type, Collections.emptyList()).size(); + } + + + public void clearAllTemplates() { + templateResourceMap.clear(); + } + + public void clearTemplates(String type) { + templateResourceMap.remove(type); + } + + public List listTemplatesByType(String type) { + return templateResourceMap.getOrDefault(type, Collections.emptyList()); + } + + public Map> listAllTemplates() { + return templateResourceMap; + } + + } diff --git a/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java b/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java index 8929e5e..943c7f6 100644 --- a/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java +++ b/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java @@ -122,8 +122,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator { ClickImageCheckDefinition definition = clickImageCheckDefinitionList.get(i); Integer x = definition.getX(); Integer y = definition.getY(); - Integer width = imageCaptchaInfo.getBgImageWidth(); - Integer height = imageCaptchaInfo.getBgImageHeight(); + Integer width = imageCaptchaInfo.getBackgroundImageWidth(); + Integer height = imageCaptchaInfo.getBackgroundImageHeight(); float vx = calcPercentage(x, width); float vy = calcPercentage(y, height); sb.append(vx).append(",").append(vy).append(";"); @@ -327,8 +327,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator { return defaultData; } - protected void addPercentage(ImageCaptchaInfo imageCaptchaInfo, Map sliderCaptchaValidData) { - float percentage = calcPercentage(imageCaptchaInfo.getRandomX(), imageCaptchaInfo.getBgImageWidth()); - sliderCaptchaValidData.put(PERCENTAGE_KEY, percentage); + protected void addPercentage(ImageCaptchaInfo imageCaptchaInfo, Map imageCaptchaValidData) { + float percentage = calcPercentage(imageCaptchaInfo.getRandomX(), imageCaptchaInfo.getBackgroundImageWidth()); + imageCaptchaValidData.put(PERCENTAGE_KEY, percentage); } }