This commit is contained in:
天爱有情
2022-04-29 18:07:33 +08:00
parent 19d06376a7
commit a4e0def76b
20 changed files with 285 additions and 163 deletions
@@ -15,4 +15,6 @@ public interface CaptchaTypeConstant {
String CONCAT = "CONCAT";
/** 图片点选.*/
String IMAGE_CLICK = "IMAGE_CLICK";
/** 文字图片点选.*/
String WORD_IMAGE_CLICK = "WORD_IMAGE_CLICK";
}
@@ -11,6 +11,7 @@ public class CaptchaUtils {
/**
* 是否是滑动验证码
*
* @param type 类型
* @return boolean
*/
@@ -22,11 +23,12 @@ public class CaptchaUtils {
/**
* 是否是点击验证码
*
* @param type type
* @return boolean
*/
public static boolean isClickCaptcha(String type) {
return CaptchaTypeConstant.IMAGE_CLICK.equals(type);
return CaptchaTypeConstant.WORD_IMAGE_CLICK.equals(type) || CaptchaTypeConstant.IMAGE_CLICK.equals(type);
}
}
@@ -15,6 +15,7 @@ public interface ImageCaptchaGenerator {
/**
* 生成验证码图片
*
* @param type 类型 {@link CaptchaTypeConstant}
* @return SliderCaptchaInfo
*/
@@ -23,7 +24,8 @@ public interface ImageCaptchaGenerator {
/**
* 生成滑块验证码
* @param type type {@link CaptchaTypeConstant}
*
* @param type type {@link CaptchaTypeConstant}
* @param targetFormatName jpeg或者webp格式
* @param matrixFormatName png或者webp格式
* @return SliderCaptchaInfo
@@ -34,8 +34,10 @@ public class ImageCaptchaInfo {
private Integer randomX;
/** 容错值, 可以为空 默认 0.02容错,校验的时候用. */
private Float tolerant;
/** 验证码类型.*/
/** 验证码类型. */
private String type;
/** 透传字段,用于传给前端.*/
private Object data;
/**
* 扩展字段
*/
@@ -406,14 +406,15 @@ public class CaptchaImageUtils {
/**
* 生成简单的验证码图片
* @param data 验证码内容
* @param font 字体包
* @param metrics FontDesignMetrics
* @param width 验证码宽度
* @param height 验证码
* @param startX 起始X
* @param startY 起始Y
* @param interferenceLineNum 干扰线数量
*
* @param data 验证码内容
* @param font 字体包
* @param metrics FontDesignMetrics
* @param width 验证码
* @param height 验证码高度
* @param startX 起始X
* @param startY 起始Y
* @param interferenceLineNum 干扰线数量
* @param interferencePointNum 干扰点数量
* @return BufferedImage
*/
@@ -450,6 +451,7 @@ public class CaptchaImageUtils {
/**
* 随机获取颜色
*
* @return Color
*/
public static Color getRandomColor(Random random) {
@@ -1,23 +1,16 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ClickImageCheckDefinition;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
@@ -28,12 +21,15 @@ import static cloud.tianai.captcha.template.slider.generator.common.util.Captcha
* @date 2022/4/27 11:46
* @Description 点选验证码 点选验证码分为点选文字和点选图标等
*/
@Data
public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
/** 参与校验的数量.*/
/** 参与校验的数量. */
@Getter
@Setter
protected Integer checkClickCount = 4;
/** 干扰数量.*/
/** 干扰数量. */
@Getter
@Setter
protected Integer interferenceCount = 2;
@SneakyThrows
@@ -49,6 +45,7 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
List<ClickImageCheckDefinition> clickImageCheckDefinitionList = new ArrayList<>(interferenceCount);
int allImages = interferenceCount + checkClickCount;
int avg = bgImage.getWidth() / allImages;
for (int i = 0; i < allImages; i++) {
// 随机获取点击图片
ImgWrapper imgWrapper = randomGetClickImg();
@@ -56,12 +53,16 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
int clickImgWidth = image.getWidth();
int clickImgHeight = image.getHeight();
// 随机x
int randomX = ThreadLocalRandom.current().nextInt(10, bgImage.getWidth() - clickImgWidth);
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);
ImageIO.write(imgWrapper.getImage(), "png", new FileOutputStream("C:\\Users\\tianai\\Desktop\\111\\" + i + ".png"));
ClickImageCheckDefinition clickImageCheckDefinition = new ClickImageCheckDefinition();
clickImageCheckDefinition.setTip(imgWrapper.getTip());
clickImageCheckDefinition.setX(randomX + clickImgWidth / 2);
@@ -70,14 +71,6 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
clickImageCheckDefinition.setHeight(clickImgHeight);
clickImageCheckDefinitionList.add(clickImageCheckDefinition);
}
// 背景图转换为字符串
// String bgImageStr = transform(bgImage, param.getBackgroundFormatName());
try {
ImageIO.write(bgImage, "jpeg", new FileOutputStream("C:\\Users\\tianai\\Desktop\\123.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
// 打乱
Collections.shuffle(clickImageCheckDefinitionList);
// 拿出参与校验的数据
@@ -88,12 +81,6 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
}
// 将校验的文字生成提示图片
ImgWrapper tipImage = genTipImage(checkClickImageCheckDefinitionList);
try {
ImageIO.write(tipImage.getImage(), "png", new FileOutputStream("C:\\Users\\tianai\\Desktop\\456.png"));
} catch (IOException e) {
e.printStackTrace();
}
return wrapClickImageCaptchaInfo(param, bgImage, tipImage.getImage(), checkClickImageCheckDefinitionList);
} finally {
@@ -110,26 +97,34 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
// return null;
}
protected ImageCaptchaInfo wrapClickImageCaptchaInfo(GenerateParam param, BufferedImage bgImage,
BufferedImage tipImage,
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList) {
ImageCaptchaInfo clickImageCaptchaInfo = new ImageCaptchaInfo();
clickImageCaptchaInfo.setBackgroundImage(transform(bgImage, param.getBackgroundFormatName()));
clickImageCaptchaInfo.setSliderImage(transform(bgImage, param.getSliderFormatName()));
clickImageCaptchaInfo.setBgImageWidth(bgImage.getWidth());
clickImageCaptchaInfo.setBgImageHeight(bgImage.getHeight());
clickImageCaptchaInfo.setSliderImageWidth(tipImage.getWidth());
clickImageCaptchaInfo.setSliderImageHeight(tipImage.getHeight());
clickImageCaptchaInfo.setRandomX(null);
clickImageCaptchaInfo.setTolerant(null);
clickImageCaptchaInfo.setType(CaptchaTypeConstant.IMAGE_CLICK);
clickImageCaptchaInfo.setExpand(checkClickImageCheckDefinitionList);
return clickImageCaptchaInfo;
}
protected abstract ImgWrapper genTipImage(List<ClickImageCheckDefinition> imageCheckDefinitions);
/**
* 随机获取点击的图片
*
* @return ImgWrapper
*/
public abstract ImgWrapper randomGetClickImg();
protected abstract ImgWrapper randomGetClickImg();
/**
* 生成 tip 图片
*
* @param imageCheckDefinitions imageCheckDefinitions
* @return ImgWrapper
*/
public abstract ImgWrapper genTipImage(List<ClickImageCheckDefinition> imageCheckDefinitions);
/**
* 包装 ImageCaptchaInfo
*
* @param param param
* @param bgImage bgImage
* @param tipImage tipImage
* @param checkClickImageCheckDefinitionList checkClickImageCheckDefinitionList
* @return ImageCaptchaInfo
*/
public abstract ImageCaptchaInfo wrapClickImageCaptchaInfo(GenerateParam param, BufferedImage bgImage,
BufferedImage tipImage,
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList);
/**
* @Author: 天爱有情
@@ -140,9 +135,9 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
@NoArgsConstructor
@AllArgsConstructor
public static class ImgWrapper {
/** 图片.*/
/** 图片. */
private BufferedImage image;
/** 提示.*/
/** 提示. */
private String tip;
}
}
@@ -136,21 +136,21 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator {
captchaInfo = queue.poll();
if (captchaInfo == null) {
log.warn("滑块验证码缓存不足, genParam:{}", generateParam);
}else {
} else {
AtomicInteger pos = posMap.get(generateParam);
if (pos != null) {
pos.decrementAndGet();
}
}
}else {
} else {
queueMap.putIfAbsent(generateParam, new ConcurrentLinkedQueue<>());
posMap.putIfAbsent(generateParam, new AtomicInteger(0));
}
if (captchaInfo == null && requiredGetCaptcha) {
// 直接生成 不走缓存
captchaInfo = target.generateCaptchaImage(generateParam);
captchaInfo = target.generateCaptchaImage(generateParam);
}
if (captchaInfo != null){
if (captchaInfo != null) {
// 记录最新时间
lastUpdateMap.put(generateParam, System.currentTimeMillis());
}
@@ -1,9 +1,9 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.template.slider.common.util.ObjectUtils;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
@@ -24,6 +24,7 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
private boolean initDefaultResource;
private String defaultCaptcha = CaptchaTypeConstant.SLIDER;
public MultiImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, boolean initDefaultResource) {
this.imageCaptchaResourceManager = imageCaptchaResourceManager;
this.initDefaultResource = initDefaultResource;
@@ -37,11 +38,14 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
addImageCaptchaGenerator(CaptchaTypeConstant.ROTATE, new StandardRotateImageCaptchaGenerator(imageCaptchaResourceManager, initDefaultResource));
// 拼接验证码
addImageCaptchaGenerator(CaptchaTypeConstant.CONCAT, new StandardConcatImageCaptchaGenerator(imageCaptchaResourceManager, initDefaultResource));
// 点选文字验证码
addImageCaptchaGenerator(CaptchaTypeConstant.WORD_IMAGE_CLICK, new StandardRandomWordClickImageCaptchaGenerator(imageCaptchaResourceManager, initDefaultResource));
}
public void addImageCaptchaGenerator(String key, ImageCaptchaGenerator captchaGenerator) {
imageCaptchaGeneratorMap.put(key, captchaGenerator);
}
public ImageCaptchaGenerator removeImageCaptchaGenerator(String key) {
return imageCaptchaGeneratorMap.remove(key);
}
@@ -49,7 +53,7 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
@Override
public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) {
String type = param.getType();
if (ObjectUtils.isEmpty(type)){
if (ObjectUtils.isEmpty(type)) {
param.setType(defaultCaptcha);
type = defaultCaptcha;
}
@@ -1,7 +1,7 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
@@ -51,18 +51,19 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen
InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage);
inputStreams.add(resourceInputStream);
BufferedImage bgImage = wrapFile2BufferedImage(resourceInputStream);
int spacing = bgImage.getHeight() / 4;
BufferedImage[] bgImageSplit = splitImage(ThreadLocalRandom.current().nextInt(spacing, bgImage.getHeight() - spacing), true, bgImage);
spacing = bgImage.getWidth() / 8;
int randomX = ThreadLocalRandom.current().nextInt(spacing, bgImage.getWidth() - spacing);
int spacingY = bgImage.getHeight() / 4;
int randomY = ThreadLocalRandom.current().nextInt(spacingY, bgImage.getHeight() - spacingY);
BufferedImage[] bgImageSplit = splitImage(randomY, true, bgImage);
int spacingX = bgImage.getWidth() / 8;
int randomX = ThreadLocalRandom.current().nextInt(spacingX, bgImage.getWidth() - bgImage.getWidth() / 5);
BufferedImage[] bgImageTopSplit = splitImage(randomX, false, bgImageSplit[0]);
BufferedImage sliderImage = concatImage(true,
bgImageTopSplit[0].getWidth()
+ bgImageTopSplit[1].getWidth()
+ bgImageSplit[0].getWidth(), bgImageTopSplit[1].getHeight(), bgImageSplit[0], bgImageTopSplit[0]);
return wrapConcatCaptchaInfo(randomX, bgImageSplit[1], sliderImage, param);
+ 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);
} finally {
// 使用完后关闭流
for (InputStream inputStream : inputStreams) {
@@ -76,17 +77,17 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen
}
@SneakyThrows
private ImageCaptchaInfo wrapConcatCaptchaInfo(int randomX, BufferedImage bgImage, BufferedImage sliderImage, GenerateParam param) {
private ImageCaptchaInfo wrapConcatCaptchaInfo(int randomX, int randomY, BufferedImage bgImage, GenerateParam param) {
String backGroundImageBase64 = transform(bgImage, param.getBackgroundFormatName());
String sliderImageBase64 = transform(sliderImage, param.getSliderFormatName());
ImageCaptchaInfo imageCaptchaInfo = ImageCaptchaInfo.of(backGroundImageBase64,
sliderImageBase64,
null,
bgImage.getWidth(),
bgImage.getHeight(),
sliderImage.getWidth(),
sliderImage.getHeight(),
null,
null,
randomX,
CaptchaTypeConstant.CONCAT);
imageCaptchaInfo.setData(randomY);
imageCaptchaInfo.setTolerant(0.05F);
return imageCaptchaInfo;
}
@@ -1,9 +1,11 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.template.slider.common.util.FontUtils;
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.template.slider.common.util.FontUtils;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ClickImageCheckDefinition;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.template.slider.resource.ResourceStore;
import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource;
@@ -30,11 +32,11 @@ import static cloud.tianai.captcha.template.slider.generator.impl.StandardSlider
public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickImageCaptchaGenerator {
protected ImageCaptchaResourceManager imageCaptchaResourceManager;
/** 字体包.*/
/** 字体包. */
protected Font font;
protected FontDesignMetrics metrics;
protected Integer clickImgWidth = 60;
protected Integer clickImgHeight = 60;
protected Integer clickImgWidth = 80;
protected Integer clickImgHeight = 80;
protected int tipImageInterferenceLineNum = 2;
protected int tipImageInterferencePointNum = 5;
@@ -48,11 +50,11 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
Resource fontResource = new Resource(null, "META-INF/fonts/SIMSUN.TTC");
InputStream inputStream = new ClassPathResourceProvider().doGetResourceInputStream(fontResource);
Font font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
font = font.deriveFont(Font.BOLD, 50);
font = font.deriveFont(Font.BOLD, 70);
this.metrics = FontDesignMetrics.getMetrics(font);
this.font = font;
setClickImgHeight(60);
setClickImgWidth(60);
setClickImgHeight(clickImgWidth);
setClickImgWidth(clickImgHeight);
}
public StandardRandomWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager,
@@ -71,11 +73,11 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
public void initDefaultResource() {
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加一些系统的资源文件
resourceStore.addResource(CaptchaTypeConstant.IMAGE_CLICK, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
}
@Override
protected ImgWrapper genTipImage(List<ClickImageCheckDefinition> imageCheckDefinitions) {
public ImgWrapper genTipImage(List<ClickImageCheckDefinition> imageCheckDefinitions) {
String tips = imageCheckDefinitions.stream().map(ClickImageCheckDefinition::getTip).collect(Collectors.joining());
// 生成随机颜色
int fontWidth = metrics.stringWidth(tips);
@@ -89,7 +91,7 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
}
@Override
protected ImgWrapper randomGetClickImg() {
public ImgWrapper randomGetClickImg() {
ThreadLocalRandom random = ThreadLocalRandom.current();
// 随机文字
String randomWord = FontUtils.getRandomChar(random);
@@ -107,6 +109,25 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
return new ImgWrapper(fontImage, randomWord);
}
@Override
public ImageCaptchaInfo wrapClickImageCaptchaInfo(GenerateParam param, BufferedImage bgImage,
BufferedImage tipImage,
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList) {
ImageCaptchaInfo clickImageCaptchaInfo = new ImageCaptchaInfo();
clickImageCaptchaInfo.setBackgroundImage(transform(bgImage, param.getBackgroundFormatName()));
clickImageCaptchaInfo.setSliderImage(transform(tipImage, param.getSliderFormatName()));
clickImageCaptchaInfo.setBgImageWidth(bgImage.getWidth());
clickImageCaptchaInfo.setBgImageHeight(bgImage.getHeight());
clickImageCaptchaInfo.setSliderImageWidth(tipImage.getWidth());
clickImageCaptchaInfo.setSliderImageHeight(tipImage.getHeight());
clickImageCaptchaInfo.setRandomX(null);
clickImageCaptchaInfo.setTolerant(null);
clickImageCaptchaInfo.setType(CaptchaTypeConstant.WORD_IMAGE_CLICK);
clickImageCaptchaInfo.setExpand(checkClickImageCheckDefinitionList);
return clickImageCaptchaInfo;
}
@Override
public ImageCaptchaResourceManager getImageResourceManager() {
return imageCaptchaResourceManager;
@@ -1,8 +1,7 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.generator.common.constant.SliderCaptchaConstant;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
@@ -13,10 +12,7 @@ import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource;
import cloud.tianai.captcha.template.slider.resource.impl.provider.ClassPathResourceProvider;
import lombok.SneakyThrows;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
@@ -61,7 +57,6 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen
@Override
public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) {
// 旋转验证码没有混淆
Boolean obfuscate = param.getObfuscate();
Map<String, Resource> templateImages = imageCaptchaResourceManager.randomGetTemplate(param.getType());
if (templateImages == null || templateImages.isEmpty()) {
return null;
@@ -128,36 +123,9 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen
);
}
// @Override
// public String transform(BufferedImage bufferedImage, String formatType) throws IOException {
// FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\aa" + formatType + "." + formatType);
// ImageIO.write(bufferedImage, formatType, fileOutputStream);
// fileOutputStream.close();
//// return super.transform(bufferedImage, formatType);
// return "";
// }
@Override
public ImageCaptchaResourceManager getImageResourceManager() {
return imageCaptchaResourceManager;
}
public static void main(String[] args) throws IOException {
BufferedImage bgImage = CaptchaImageUtils.wrapFile2BufferedImage(
new FileInputStream("E:\\projects\\tianai-captcha\\src\\main\\resources\\META-INF\\cut-image\\resource\\1.jpg"));
BufferedImage image1 = CaptchaImageUtils.wrapFile2BufferedImage(new FileInputStream("C:\\Users\\Thinkpad\\Desktop\\a.png"));
BufferedImage image2 = CaptchaImageUtils.wrapFile2BufferedImage(new FileInputStream("C:\\Users\\Thinkpad\\Desktop\\b.png"));
BufferedImage cutImage = CaptchaImageUtils.cutImage(bgImage, image1, bgImage.getWidth() / 2 - 100, bgImage.getHeight() / 2 - 100);
CaptchaImageUtils.overlayImage(cutImage, image2, 0, 0);
cutImage = CaptchaImageUtils.rotateImage(cutImage, 180);
CaptchaImageUtils.overlayImage(bgImage, image1, bgImage.getWidth() / 2 - 100, bgImage.getHeight() / 2 - 100);
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\a1.jpg");
FileOutputStream file2OutputStream = new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\a2.png");
ImageIO.write(bgImage, "jpeg", fileOutputStream);
ImageIO.write(cutImage, "png", file2OutputStream);
fileOutputStream.close();
file2OutputStream.close();
}
}
@@ -1,7 +1,7 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.generator.common.constant.SliderCaptchaConstant;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
@@ -1,6 +1,5 @@
package cloud.tianai.captcha.template.slider.resource;
import cloud.tianai.captcha.template.slider.resource.ResourceProvider;
import cloud.tianai.captcha.template.slider.resource.common.model.dto.Resource;
import java.io.InputStream;
@@ -31,7 +31,7 @@ public interface ResourceStore {
*
* @return List<Resource>
*/
Map<String,List<Resource>> listAllResources();
Map<String, List<Resource>> listAllResources();
/**
* 获取某个type下的所有资源对象
@@ -0,0 +1,18 @@
package cloud.tianai.captcha.template.slider.validator.common.constant;
/**
* @Author: 天爱有情
* @date 2022/4/29 8:33
* @Description 滑动轨迹类型
*/
public interface TrackTypeConstant {
/** 抬起.*/
String UP = "UP";
/** 按下.*/
String DOWN = "DOWN";
/** 移动.*/
String MOVE = "MOVE";
/** 点击.*/
String CLICK = "CLICK";
}
@@ -1,5 +1,6 @@
package cloud.tianai.captcha.template.slider.validator.common.model.dto;
import cloud.tianai.captcha.template.slider.validator.common.constant.TrackTypeConstant;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -35,8 +36,13 @@ public class SliderCaptchaTrack {
@NoArgsConstructor
@AllArgsConstructor
public static class Track {
/** x. */
private Integer x;
/** y. */
private Integer y;
/** 时间. */
private Integer t;
/** 类型. */
private String type = TrackTypeConstant.MOVE;
}
}
@@ -1,5 +1,6 @@
package cloud.tianai.captcha.template.slider.validator.impl;
import cloud.tianai.captcha.template.slider.common.util.CaptchaUtils;
import cloud.tianai.captcha.template.slider.common.util.CollectionUtils;
import cloud.tianai.captcha.template.slider.common.util.ObjectUtils;
import cloud.tianai.captcha.template.slider.validator.common.model.dto.SliderCaptchaTrack;
@@ -22,13 +23,17 @@ public class BasicCaptchaTrackValidator extends SimpleImageCaptchaValidator {
}
@Override
public boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData) {
public boolean beforeValid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData, Float tolerant, String type) {
// 校验参数
checkParam(sliderCaptchaTrack);
// 基础校验
boolean superValid = super.valid(sliderCaptchaTrack, sliderCaptchaValidData);
if (!superValid) {
return false;
return true;
}
@Override
public boolean afterValid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData, Float tolerant, String type) {
if (!CaptchaUtils.isSliderCaptcha(type)){
// 不是滑动验证码的话暂时跳过,点选验证码行为轨迹还没做
return true;
}
// 进行行为轨迹检测
long startSlidingTime = sliderCaptchaTrack.getStartSlidingTime().getTime();
@@ -93,7 +98,6 @@ public class BasicCaptchaTrackValidator extends SimpleImageCaptchaValidator {
return endAvgPosTime > startAvgPosTime;
}
public void checkParam(SliderCaptchaTrack sliderCaptchaTrack) {
if (ObjectUtils.isEmpty(sliderCaptchaTrack.getBgImageWidth())) {
throw new IllegalArgumentException("bgImageWidth must not be null");
@@ -120,8 +124,9 @@ public class BasicCaptchaTrackValidator extends SimpleImageCaptchaValidator {
Integer x = track.getX();
Integer y = track.getY();
Integer t = track.getT();
if (x == null || y == null || t == null) {
throw new IllegalArgumentException("track[x,y,t] must not be null");
String type = track.getType();
if (x == null || y == null || t == null || ObjectUtils.isEmpty(type)) {
throw new IllegalArgumentException("track[x,y,t,type] must not be null");
}
}
}
@@ -7,6 +7,7 @@ import cloud.tianai.captcha.template.slider.common.util.ObjectUtils;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ClickImageCheckDefinition;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.template.slider.validator.common.constant.TrackTypeConstant;
import cloud.tianai.captcha.template.slider.validator.common.model.dto.SliderCaptchaTrack;
import lombok.Getter;
import lombok.Setter;
@@ -15,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @Author: 天爱有情
@@ -25,15 +27,15 @@ import java.util.Map;
public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
public static float DEFAULT_TOLERANT = 0.02f;
public static final String PERCENTAGE_KEY = "percentage";
public static final String TOLERANT_KEY = "tolerant";
public static final String TYPE_KEY = "type";
/** 容错值. */
@Getter
@Setter
public float defaultTolerant = DEFAULT_TOLERANT;
public static final String PERCENTAGE_KEY = "percentage";
public static final String TOLERANT_KEY = "Tolerant";
public static final String TYPE_KEY = "type";
public SimpleImageCaptchaValidator() {
}
@@ -66,6 +68,14 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
@Override
public Map<String, Object> generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
Map<String, Object> map = new HashMap<>(8);
if(beforeGenerateImageCaptchaValidData(imageCaptchaInfo, map)) {
doGenerateImageCaptchaValidData(map, imageCaptchaInfo);
}
afterGenerateImageCaptchaValidData(imageCaptchaInfo, map);
return map;
}
public boolean beforeGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, Map<String, Object> map) {
// 容错值
Float tolerant = imageCaptchaInfo.getTolerant();
if (tolerant != null && tolerant > 0) {
@@ -77,13 +87,17 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
type = CaptchaTypeConstant.SLIDER;
}
map.put(TYPE_KEY, type);
return afterGenerateImageCaptchaValidData(map, imageCaptchaInfo, type);
return true;
}
public Map<String, Object> afterGenerateImageCaptchaValidData(Map<String, Object> map,
ImageCaptchaInfo imageCaptchaInfo,
String type) {
public void afterGenerateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo, Map<String, Object> map) {
}
public void doGenerateImageCaptchaValidData(Map<String, Object> map,
ImageCaptchaInfo imageCaptchaInfo) {
// type
String type = (String) map.getOrDefault(TYPE_KEY, CaptchaTypeConstant.SLIDER);
if (CaptchaUtils.isSliderCaptcha(type)) {
// 滑动验证码
addPercentage(imageCaptchaInfo, map);
@@ -93,7 +107,7 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
if (expand == null) {
throw new IllegalArgumentException("点选验证码扩展数据转换为 List<ClickImageCheckDefinition> 失败, info=" + imageCaptchaInfo);
}
List<ClickImageCheckDefinition> clickImageCheckDefinitionList = null;
List<ClickImageCheckDefinition> clickImageCheckDefinitionList;
try {
clickImageCheckDefinitionList = (List<ClickImageCheckDefinition>) expand;
} catch (Exception e) {
@@ -116,17 +130,15 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
map.put(TOLERANT_KEY, tolerant);
}
}
// 添加点选验证
// 添加点选验证数据
map.put(PERCENTAGE_KEY, sb.toString());
}
return map;
}
@Override
public boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData) {
// 读容错值
Float tolerantData = getFloatParam(TOLERANT_KEY, sliderCaptchaValidData, defaultTolerant);
Float tolerant = getFloatParam(TOLERANT_KEY, sliderCaptchaValidData, defaultTolerant);
// 读验证码类型
String type = getStringParam(TYPE_KEY, sliderCaptchaValidData, CaptchaTypeConstant.SLIDER);
Integer bgImageWidth = sliderCaptchaTrack.getBgImageWidth();
@@ -139,13 +151,49 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
// 没有滑动轨迹
return false;
}
return afterValid(sliderCaptchaTrack, sliderCaptchaValidData, tolerantData, type);
// 验证前
if (!beforeValid(sliderCaptchaTrack, sliderCaptchaValidData, tolerant, type)) {
return false;
}
// 验证
boolean valid = doValid(sliderCaptchaTrack, sliderCaptchaValidData, tolerant, type);
if (valid) {
// 验证后
valid = afterValid(sliderCaptchaTrack, sliderCaptchaValidData, tolerant, type);
}
return valid;
}
public boolean afterValid(SliderCaptchaTrack sliderCaptchaTrack,
Map<String, Object> sliderCaptchaValidData,
Float tolerant,
String type) {
/**
* 验证前
*
* @param sliderCaptchaTrack sliderCaptchaTrack
* @param sliderCaptchaValidData sliderCaptchaValidData
* @param tolerant tolerant
* @param type type
* @return boolean
*/
public boolean beforeValid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData, Float tolerant, String type) {
return true;
}
/**
* 验证后
*
* @param sliderCaptchaTrack sliderCaptchaTrack
* @param sliderCaptchaValidData sliderCaptchaValidData
* @param tolerant tolerant
* @param type type
* @return boolean
*/
public boolean afterValid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData, Float tolerant, String type) {
return true;
}
public boolean doValid(SliderCaptchaTrack sliderCaptchaTrack,
Map<String, Object> sliderCaptchaValidData,
Float tolerant,
String type) {
if (CaptchaUtils.isSliderCaptcha(type)) {
// 滑动类型验证码
return doValidSliderCaptcha(sliderCaptchaTrack, sliderCaptchaValidData, tolerant, type);
@@ -160,10 +208,11 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
/**
* 校验点选验证码
* @param sliderCaptchaTrack sliderCaptchaTrack
*
* @param sliderCaptchaTrack sliderCaptchaTrack
* @param sliderCaptchaValidData sliderCaptchaValidData
* @param tolerant tolerant
* @param type type
* @param tolerant tolerant
* @param type type
* @return boolean
*/
public boolean doValidClickCaptcha(SliderCaptchaTrack sliderCaptchaTrack,
@@ -176,18 +225,26 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
}
String[] splitArr = validStr.split(";");
List<SliderCaptchaTrack.Track> trackList = sliderCaptchaTrack.getTrackList();
if (trackList.size() != splitArr.length) {
if (trackList.size() < splitArr.length) {
return false;
}
// 取出点击事件的轨迹数据
List<SliderCaptchaTrack.Track> clickTrackList = trackList
.stream()
.filter(t -> TrackTypeConstant.CLICK.equalsIgnoreCase(t.getType()))
.collect(Collectors.toList());
if (clickTrackList.size() != splitArr.length) {
return false;
}
for (int i = 0; i < splitArr.length; i++) {
SliderCaptchaTrack.Track track = trackList.get(i);
SliderCaptchaTrack.Track track = clickTrackList.get(i);
String posStr = splitArr[i];
String[] posArr = posStr.split(",");
float xPercentage = Float.parseFloat(posArr[0]);
float yPercentage = Float.parseFloat(posArr[1]);
float calcXPercentage = calcPercentage(track.getX(), sliderCaptchaTrack.getBgImageWidth());
float calcYPercentage = calcPercentage(track.getY(), sliderCaptchaTrack.getBgImageWidth());
float calcYPercentage = calcPercentage(track.getY(), sliderCaptchaTrack.getBgImageHeight());
if (!checkPercentage(calcXPercentage, xPercentage, tolerant)
|| !checkPercentage(calcYPercentage, yPercentage, tolerant)) {
@@ -199,10 +256,11 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
/**
* 校验滑动验证码
* @param sliderCaptchaTrack sliderCaptchaTrack
*
* @param sliderCaptchaTrack sliderCaptchaTrack
* @param sliderCaptchaValidData sliderCaptchaValidData
* @param tolerant tolerant
* @param type type
* @param tolerant tolerant
* @param type type
* @return boolean
*/
public boolean doValidSliderCaptcha(SliderCaptchaTrack sliderCaptchaTrack,