重构 resource 模块,支持自定义tag标签

重构 transform 模块
修复以及优化各验证码生成器,
This commit is contained in:
天爱有情
2023-02-13 14:29:11 +08:00
parent ebc39efb02
commit 37d88b5ec2
25 changed files with 518 additions and 346 deletions
@@ -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<String, Resource> requiredRandomGetTemplate(String type) {
Map<String, Resource> 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<String, Resource> 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<InputStream> inputStreams) {
InputStream stream = getImageResourceManager().getResourceInputStream(resource);
if (stream != null && inputStreams != null) {
inputStreams.add(stream);
}
return stream;
}
protected Optional<BufferedImage> getTemplateImageOfOptional(Map<String, Resource> templateImages, String imageName) {
Optional<InputStream> 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<InputStream> getTemplateFileOfOptional(Map<String, Resource> 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) {
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
@@ -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;
}
@@ -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<InputStream> 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<ClickImageCheckDefinition> clickImageCheckDefinitionList = new ArrayList<>(interferenceCount);
int allImages = interferenceCount + checkClickCount;
int avg = bgImage.getWidth() / allImages;
List<String> 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<ClickImageCheckDefinition> 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<Resource> imgTips = randomGetClickImgTips(param);
int allImages = imgTips.size();
List<ClickImageCheckDefinition> 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<ClickImageCheckDefinition> checkClickImageCheckDefinitionList = filterAndSortClickImageCheckDefinition(clickImageCheckDefinitionList);
// wrap
return wrapClickImageCaptchaInfo(param, bgImage, checkClickImageCheckDefinitionList, resourceImage);
}
/**
* 从总的图片中 去除要校验的图片数据 以及顺序
* 过滤并排序校验的图片点选顺序
*
* @param allCheckDefinitionList 总的点选图片
* @param checkClickCount 参与校验的数据
* @return List<ClickImageCheckDefinition>
*/
protected List<ClickImageCheckDefinition> getCheckClickImageCheckDefinitionList(List<ClickImageCheckDefinition> allCheckDefinitionList,
Integer checkClickCount) {
// 打乱
Collections.shuffle(allCheckDefinitionList);
// 拿出参与校验的数据
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList = new ArrayList<>(checkClickCount);
for (int i = 0; i < checkClickCount; i++) {
ClickImageCheckDefinition clickImageCheckDefinition = allCheckDefinitionList.get(i);
checkClickImageCheckDefinitionList.add(clickImageCheckDefinition);
}
return checkClickImageCheckDefinitionList;
}
protected abstract List<ClickImageCheckDefinition> filterAndSortClickImageCheckDefinition(List<ClickImageCheckDefinition> allCheckDefinitionList);
/**
* 随机获取一组数据用于生成随机图
*
* @param tipSize tipSize
* @return List<String>
*/
protected abstract List<String> randomGetClickImgTips(int tipSize);
protected abstract List<Resource> 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<ClickImageCheckDefinition> checkClickImageCheckDefinitionList);
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList, Resource resourceImage);
/**
* @Author: 天爱有情
@@ -157,6 +125,6 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
/** 图片. */
private BufferedImage image;
/** 提示. */
private String tip;
private Resource tip;
}
}
@@ -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
@@ -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<InputStream> 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(),
@@ -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<String> randomGetClickImgTips(int tipSize) {
protected List<Resource> randomGetClickImgTips(GenerateParam param) {
int tipSize = interferenceCount + checkClickCount;
ThreadLocalRandom random = ThreadLocalRandom.current();
List<String> tipList = new ArrayList<>(tipSize);
List<Resource> 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<ClickImageCheckDefinition> 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<ClickImageCheckDefinition> filterAndSortClickImageCheckDefinition(List<ClickImageCheckDefinition> allCheckDefinitionList) {
// 打乱
Collections.shuffle(allCheckDefinitionList);
// 拿出参与校验的数据
List<ClickImageCheckDefinition> 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<ClickImageCheckDefinition> checkClickImageCheckDefinitionList) {
List<ClickImageCheckDefinition> 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);
@@ -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<String, Resource> 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<String, Resource> templateImages = requiredRandomGetTemplate(param.getType());
ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Collection<InputStream> 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()
);
@@ -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<String, Resource> templateImages = requiredRandomGetTemplate(param.getType());
ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Collection<InputStream> 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<String, Resource> 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<String, Resource> 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")));
@@ -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;
}
}