重构生成器逻辑,优化图片验证码生成效率

This commit is contained in:
天爱有情
2023-05-04 11:15:43 +08:00
parent 317ce72db9
commit 82dd58587d
22 changed files with 625 additions and 271 deletions
@@ -2,6 +2,8 @@ package cloud.tianai.captcha.generator;
import cloud.tianai.captcha.common.exception.ImageCaptchaException;
import cloud.tianai.captcha.common.util.CollectionUtils;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaTransferData;
import cloud.tianai.captcha.generator.common.model.dto.CustomData;
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;
@@ -22,6 +24,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import static cloud.tianai.captcha.generator.impl.StaticCaptchaPostProcessorManager.*;
/**
* @Author: 天爱有情
* @date 2022/4/22 16:30
@@ -97,9 +101,24 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener
@Override
public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) {
assertInit();
return doGenerateCaptchaImage(param);
CustomData data = new CustomData();
CaptchaTransferData transferData = CaptchaTransferData.create(data, param);
ImageCaptchaInfo imageCaptchaInfo = applyPostProcessorBeforeGenerate(transferData, this);
if (imageCaptchaInfo != null) {
return imageCaptchaInfo;
}
doGenerateCaptchaImage(transferData);
applyPostProcessorBeforeWrapImageCaptchaInfo(transferData, this);
imageCaptchaInfo = wrapImageCaptchaInfo(transferData);
applyPostProcessorAfterGenerateCaptchaImage(transferData, imageCaptchaInfo, this);
return imageCaptchaInfo;
}
public ImageCaptchaInfo wrapImageCaptchaInfo(CaptchaTransferData transferData) {
ImageCaptchaInfo imageCaptchaInfo = doWrapImageCaptchaInfo(transferData);
imageCaptchaInfo.setData(transferData.getCustomData());
return imageCaptchaInfo;
}
protected ResourceMap requiredRandomGetTemplate(String type, String tag) {
ResourceMap templateMap = imageCaptchaResourceManager.randomGetTemplate(type, tag);
@@ -144,6 +163,7 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener
protected int randomInt(int origin, int bound) {
return ThreadLocalRandom.current().nextInt(origin, bound);
}
protected boolean randomBoolean() {
return ThreadLocalRandom.current().nextBoolean();
}
@@ -205,10 +225,12 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener
/**
* 生成验证码方法
*
* @param param param
* @param transferData transferData
* @return ImageCaptchaInfo
*/
protected abstract ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param);
protected abstract void doGenerateCaptchaImage(CaptchaTransferData transferData);
protected abstract ImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaTransferData transferData);
@Override
public ImageCaptchaResourceManager getImageResourceManager() {
@@ -0,0 +1,25 @@
package cloud.tianai.captcha.generator;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaTransferData;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
/**
* @Author: 天爱有情
* @date 2023/4/24 15:11
* @Description 图片验证码后处理器
*/
public interface ImageCaptchaPostProcessor {
default ImageCaptchaInfo beforeGenerateCaptchaImage(CaptchaTransferData transferData, ImageCaptchaGenerator context) {
return null;
}
default void beforeWrapImageCaptchaInfo(CaptchaTransferData transferData, ImageCaptchaGenerator context) {
}
default void afterGenerateCaptchaImage(CaptchaTransferData transferData, ImageCaptchaInfo imageCaptchaInfo, ImageCaptchaGenerator context) {
}
}
@@ -1,5 +1,6 @@
package cloud.tianai.captcha.generator;
import cloud.tianai.captcha.generator.common.model.dto.CustomData;
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;
@@ -21,8 +22,8 @@ public interface ImageTransform {
* @param backgroundResource 背景资源对象
* @return ImageTransformData
*/
default ImageTransformData transform(GenerateParam param, BufferedImage backgroundImage, Resource backgroundResource) {
return transform(param, backgroundImage, null, backgroundResource, null);
default ImageTransformData transform(GenerateParam param, BufferedImage backgroundImage, Resource backgroundResource,CustomData data) {
return transform(param, backgroundImage, null, backgroundResource, null,data);
}
/**
@@ -39,5 +40,6 @@ public interface ImageTransform {
BufferedImage backgroundImage,
BufferedImage templateImage,
Object backgroundResource,
Object templateResource);
Object templateResource,
CustomData data);
}
@@ -11,6 +11,8 @@ public interface SliderCaptchaConstant {
String TEMPLATE_ACTIVE_IMAGE_NAME = "active.png";
/** 模板凹槽固定名称. */
String TEMPLATE_FIXED_IMAGE_NAME = "fixed.png";
/** 模板背景固定名称. */
String TEMPLATE_MATRIX_IMAGE_NAME = "matrix.png";
/** 模板蒙版. */
String TEMPLATE_MASK_IMAGE_NAME = "mask.png";
/** 混淆的凹槽.*/
String OBFUSCATE_TEMPLATE_FIXED_IMAGE_NAME = "obfuscate_" + TEMPLATE_FIXED_IMAGE_NAME;
}
@@ -0,0 +1,35 @@
package cloud.tianai.captcha.generator.common.model.dto;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
import lombok.Data;
import java.awt.image.BufferedImage;
/**
* @Author: 天爱有情
* @date 2023/4/24 15:02
* @Description 传输用
*/
@Data
public class CaptchaTransferData {
private ResourceMap templateResource;
private Resource resourceImage;
private BufferedImage backgroundImage;
private BufferedImage templateImage;
private CustomData customData;
private GenerateParam param;
private Object transferData;
public static CaptchaTransferData create(CustomData customData, GenerateParam param) {
CaptchaTransferData captchaTransferData = new CaptchaTransferData();
captchaTransferData.setCustomData(customData);
captchaTransferData.setParam(param);
return captchaTransferData;
}
public static CaptchaTransferData create(GenerateParam param) {
return create(new CustomData(), param);
}
}
@@ -0,0 +1,38 @@
package cloud.tianai.captcha.generator.common.model.dto;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: 天爱有情
* @date 2023/4/24 10:27
* @Description 自定义扩展数据
*/
@Data
public class CustomData {
/** 透传字段,用于传给前端. */
private Map<String, Object> viewData;
/** 内部使用的字段数据. */
private Map<String, Object> data;
/**
* 扩展字段
*/
public Object expand;
public void putViewData(String key, Object data) {
if (this.viewData == null) {
this.viewData = new HashMap<>();
}
this.viewData.put(key, data);
}
public void putData(String key, Object data) {
if (this.data == null) {
this.data = new HashMap<>();
}
this.data.put(key, data);
}
}
@@ -37,11 +37,7 @@ public class ImageCaptchaInfo {
/** 验证码类型. */
private String type;
/** 透传字段,用于传给前端. */
private Object data;
/**
* 扩展字段
*/
public Object expand;
private CustomData data;
public ImageCaptchaInfo(String backgroundImage,
String templateImage,
@@ -44,6 +44,11 @@ public class CaptchaImageUtils {
return ImageIO.read(resource);
}
public static BufferedImage createTransparentImage(int width, int height) {
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
return bufferedImage;
}
/**
* 图片覆盖(覆盖图压缩到width*height大小,覆盖到底图上)
@@ -63,6 +68,7 @@ public class CaptchaImageUtils {
g2d.dispose();
}
/**
* 将Image图像中的透明/不透明部分转换为Shape图形
*
@@ -135,20 +141,44 @@ public class CaptchaImageUtils {
/**
* 通过模板图片抠图(不透明部分)
*
* @param origin 源图片
* @param template 模板图片
* @param x 坐标轴x
* @param y 坐标轴y
* @param oriImage 源图片
* @param templateImage 模板图片
* @param xPos 坐标轴x
* @param yPos 坐标轴y
* @return BufferedImage
*/
@SneakyThrows
public static BufferedImage cutImage(BufferedImage origin, BufferedImage template, int x, int y) {
public static BufferedImage cutImage(BufferedImage oriImage, BufferedImage templateImage, int xPos, int yPos) {
// 模板图像矩阵
int bw = templateImage.getWidth(null);
int bh = templateImage.getHeight(null);
BufferedImage targetImage = new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB);
// 透明色
for (int y = 0; y < bh; y++) {
for (int x = 0; x < bw; x++) {
int rgb = templateImage.getRGB(x, y);
int alpha = (rgb >> 24) & 0xff;
// 透明度大于100才处理,过滤一下边缘过于透明的像素点
if (alpha > 100) {
int bgRgb = oriImage.getRGB(xPos + x, yPos + y);
targetImage.setRGB(x, y, bgRgb);
}
}
}
return targetImage;
}
@SneakyThrows
public static BufferedImage cutImage_bak(BufferedImage origin, BufferedImage template, int x, int y) {
int bw = template.getWidth(null);
int bh = template.getHeight(null);
int lw = origin.getWidth(null);
int lh = origin.getHeight(null);
//得到透明的区域(人物轮廓)
Shape imageShape = getImageShape(template, false);
long end = System.currentTimeMillis();
//合成后的图片
BufferedImage image = new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
@@ -305,12 +335,6 @@ public class CaptchaImageUtils {
return newImage;
}
public static void main(String[] args) {
char randomChar = getRandomChar();
System.out.println(randomChar);
}
public static char getRandomChar() {
return (char) (0x4e00 + (int) (Math.random() * (0x9fa5 - 0x4e00 + 1)));
}
@@ -426,7 +450,7 @@ public class CaptchaImageUtils {
char[] chars = data.toCharArray();
for (int i = 0; i < chars.length; i++) {
g.setColor(getRandomColor(random));
g.setColor(Color.gray);
g.drawString(String.valueOf(chars[i]), startX + i * font.getSize(), startY);
}
// 干扰点
@@ -441,6 +465,24 @@ public class CaptchaImageUtils {
return bufferedImage;
}
public static void drawInterfere(Graphics2D g, int width,
int height,
int interferenceLineNum,
int interferencePointNum) {
ThreadLocalRandom random = ThreadLocalRandom.current();
// 干扰点
if (interferencePointNum > 0) {
drawOval(interferencePointNum, null, g, width, height, random);
}
if (interferencePointNum > 0) {
g.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
// 干扰线
drawBesselLine(interferenceLineNum, null, g, width, height, random);
}
}
/**
* 随机获取颜色
*
@@ -522,6 +564,30 @@ public class CaptchaImageUtils {
return bimage;
}
/**
* 灰度处理,把原图传进去,传出来为修改后的图
*
* @param b b
* @return BufferedImage
*/
public static BufferedImage gray(BufferedImage b) {
int width = b.getWidth();
int height = b.getHeight();
// 下面这个别忘了定义,不然会出错
BufferedImage bufferedImageEnd = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
// 双层循环更改图片的RGB值,把得到的灰度值存到bufferedImage_end中,然后返回bufferedImage_end
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// 获取到(x,y)此像素点的Colo,转化为灰度
Color color = new Color(b.getRGB(x, y));
int gray = (int) (color.getRed() * 0.299 + color.getGreen() * 0.587 + color.getBlue() * 0.114);
Color color_end = new Color(gray, gray, gray);
bufferedImageEnd.setRGB(x, y, color_end.getRGB());
}
}
return bufferedImageEnd;
}
/**
* 创建画板
*
@@ -539,6 +605,21 @@ public class CaptchaImageUtils {
return g;
}
public static BufferedImage toBufferedImage(Image img) {
if (img instanceof BufferedImage) {
return (BufferedImage) img;
}
// Create a buffered image with transparency
BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
// Draw the image on to the buffered image
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(img, 0, 0, null);
bGr.dispose();
// Return the buffered image
return bimage;
}
/**
* 后缀是否是jpg
*
@@ -1,6 +1,7 @@
package cloud.tianai.captcha.generator.impl;
import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaTransferData;
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;
@@ -34,7 +35,8 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
@SneakyThrows
@Override
public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) {
public void doGenerateCaptchaImage(CaptchaTransferData transferData) {
GenerateParam param = transferData.getParam();
// 文字点选验证码不需要模板 只需要背景图
Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
@@ -73,8 +75,15 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
clickImageCheckDefinitionList.add(clickImageCheckDefinition);
}
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList = filterAndSortClickImageCheckDefinition(clickImageCheckDefinitionList);
// wrap
return wrapClickImageCaptchaInfo(param, bgImage, checkClickImageCheckDefinitionList, resourceImage);
transferData.setBackgroundImage(bgImage);
transferData.setTransferData(checkClickImageCheckDefinitionList);
transferData.setResourceImage(resourceImage);
// // wrap
// ImageCaptchaInfo imageCaptchaInfo = wrapClickImageCaptchaInfo(param, bgImage, checkClickImageCheckDefinitionList, resourceImage, data);
// imageCaptchaInfo.setData(data);
// return imageCaptchaInfo;
}
@@ -101,18 +110,6 @@ public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCa
*/
public abstract ImgWrapper getClickImg(Resource tip);
/**
* 包装 ImageCaptchaInfo
*
* @param param param
* @param bgImage bgImage
* @param checkClickImageCheckDefinitionList checkClickImageCheckDefinitionList
* @param resourceImage 随机读取到的图片资源
* @return ImageCaptchaInfo
*/
public abstract ImageCaptchaInfo wrapClickImageCaptchaInfo(GenerateParam param, BufferedImage bgImage,
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList, Resource resourceImage);
/**
* @Author: 天爱有情
* @date 2022/4/28 14:26
@@ -6,10 +6,11 @@ import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaTransferData;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.generator.impl.provider.StandardConcatImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.generator.impl.provider.StandardRandomWordClickImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.generator.impl.provider.StandardWordClickImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.generator.impl.provider.StandardRotateImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.generator.impl.provider.StandardSliderImageImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
@@ -55,7 +56,7 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
// 拼接验证码
addImageCaptchaGeneratorProvider(new StandardConcatImageCaptchaGeneratorProvider());
// 点选文字验证码
addImageCaptchaGeneratorProvider(new StandardRandomWordClickImageCaptchaGeneratorProvider());
addImageCaptchaGeneratorProvider(new StandardWordClickImageCaptchaGeneratorProvider());
}
public void addImageCaptchaGeneratorProvider(ImageCaptchaGeneratorProvider provider) {
@@ -83,7 +84,7 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
}
@Override
public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) {
public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) {
String type = param.getType();
if (ObjectUtils.isEmpty(type)) {
param.setType(defaultCaptcha);
@@ -93,6 +94,17 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
return imageCaptchaGenerator.generateCaptchaImage(param);
}
@Override
protected void doGenerateCaptchaImage(CaptchaTransferData transferData) {
}
@Override
protected ImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaTransferData transferData) {
return null;
}
public ImageCaptchaGenerator requireGetCaptchaGenerator(String type) {
ImageCaptchaGenerator imageCaptchaGenerator = imageCaptchaGeneratorMap.computeIfAbsent(type, t -> {
ImageCaptchaGeneratorProvider provider = imageCaptchaGeneratorProviderMap.get(t);
@@ -101,7 +113,6 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
}
return provider.get(getImageResourceManager(), getImageTransform()).init(initDefaultResource);
});
return imageCaptchaGenerator;
}
@@ -3,9 +3,7 @@ package cloud.tianai.captcha.generator.impl;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
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.generator.common.model.dto.*;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
@@ -19,6 +17,7 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.ThreadLocalRandom;
import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_TAG;
import static cloud.tianai.captcha.generator.common.util.CaptchaImageUtils.*;
import static cloud.tianai.captcha.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH;
@@ -48,23 +47,20 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen
public void initDefaultResource() {
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加一些系统的资源文件
resourceStore.addResource(CaptchaTypeConstant.CONCAT, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
resourceStore.addResource(CaptchaTypeConstant.CONCAT, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"), DEFAULT_TAG));
}
@Override
public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) {
public void doGenerateCaptchaImage(CaptchaTransferData transferData) {
GenerateParam param = transferData.getParam();
// 拼接验证码不需要模板 只需要背景图
Collection<InputStream> inputStreams = new LinkedList<>();
try {
Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage);
inputStreams.add(resourceInputStream);
BufferedImage bgImage = wrapFile2BufferedImage(resourceInputStream);
BufferedImage bgImage = getResourceImage(resourceImage);
int spacingY = bgImage.getHeight() / 4;
int randomY = ThreadLocalRandom.current().nextInt(spacingY, bgImage.getHeight() - spacingY);
int randomY = randomInt(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);
int randomX = randomInt(spacingX, bgImage.getWidth() - bgImage.getWidth() / 5);
BufferedImage[] bgImageTopSplit = splitImage(randomX, false, bgImageSplit[0]);
BufferedImage sliderImage = concatImage(true,
@@ -72,24 +68,29 @@ 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, resourceImage);
} finally {
// 使用完后关闭流
for (InputStream inputStream : inputStreams) {
try {
inputStream.close();
} catch (IOException e) {
// ignore
}
}
Data data = new Data();
data.x = randomX;
data.y = randomY;
transferData.setTransferData(data);
transferData.setBackgroundImage(bgImage);
transferData.setResourceImage(resourceImage);
}
public static class Data {
int x;
int y;
}
@SneakyThrows
private ImageCaptchaInfo wrapConcatCaptchaInfo(int randomX, int randomY, BufferedImage bgImage, GenerateParam param, Resource resourceImage) {
ImageTransformData transform = getImageTransform().transform(param, bgImage, resourceImage);
@Override
public ImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaTransferData transferData) {
GenerateParam param = transferData.getParam();
BufferedImage bgImage = transferData.getBackgroundImage();
Resource resourceImage = transferData.getResourceImage();
CustomData customData = transferData.getCustomData();
ImageTransformData transform = getImageTransform().transform(param, bgImage, resourceImage, customData);
Data data = (Data) transferData.getTransferData();
ImageCaptchaInfo imageCaptchaInfo = ImageCaptchaInfo.of(transform.getBackgroundImageUrl(),
null,
resourceImage.getTag(),
@@ -98,9 +99,9 @@ public class StandardConcatImageCaptchaGenerator extends AbstractImageCaptchaGen
bgImage.getHeight(),
null,
null,
randomX,
data.x,
CaptchaTypeConstant.CONCAT);
imageCaptchaInfo.setData(randomY);
customData.putViewData("randomY", data.y);
imageCaptchaInfo.setTolerant(0.05F);
return imageCaptchaInfo;
}
@@ -1,13 +1,11 @@
package cloud.tianai.captcha.generator.impl;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.constant.CommonConstant;
import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
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.model.dto.*;
import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
@@ -19,13 +17,12 @@ import lombok.SneakyThrows;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_TAG;
import static cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant.*;
import static cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant.TEMPLATE_MASK_IMAGE_NAME;
/**
* @Author: 天爱有情
@@ -53,71 +50,98 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen
public void initDefaultResource() {
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加一些系统的资源文件
resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"), DEFAULT_TAG));
// 添加一些系统的 模板文件
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")));
template1.put(TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/active.png")));
template1.put(TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/3/fixed.png")));
resourceStore.addTemplate(CaptchaTypeConstant.ROTATE, template1);
ResourceMap template2 = new ResourceMap(CommonConstant.DEFAULT_TAG + "_obfuscate", 4);
template2.put(TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/4/active.png")));
template2.put(TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/4/fixed.png")));
template2.put(TEMPLATE_MASK_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/4/mask.png")));
resourceStore.addTemplate(CaptchaTypeConstant.ROTATE, template2);
}
@Override
public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) {
// 旋转验证码没有混淆
ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Collection<InputStream> inputStreams = new LinkedList<>();
try {
public void doGenerateCaptchaImage(CaptchaTransferData transferData) {
GenerateParam param = transferData.getParam();
CustomData data = new CustomData();
ResourceMap templateResource = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage);
inputStreams.add(resourceInputStream);
BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream);
// 拷贝一份图片
BufferedImage targetBackground = CaptchaImageUtils.copyImage(cutBackground, cutBackground.getType());
BufferedImage background = getResourceImage(resourceImage);
InputStream fixedTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME);
inputStreams.add(fixedTemplateInput);
BufferedImage fixedTemplate = CaptchaImageUtils.wrapFile2BufferedImage(fixedTemplateInput);
InputStream activeTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME);
inputStreams.add(activeTemplateInput);
BufferedImage activeTemplate = CaptchaImageUtils.wrapFile2BufferedImage(activeTemplateInput);
InputStream matrixTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME);
inputStreams.add(matrixTemplateInput);
BufferedImage matrixTemplate = CaptchaImageUtils.wrapFile2BufferedImage(matrixTemplateInput);
BufferedImage fixedTemplate = getTemplateImage(templateResource, TEMPLATE_FIXED_IMAGE_NAME);
BufferedImage activeTemplate = getTemplateImage(templateResource, TEMPLATE_ACTIVE_IMAGE_NAME);
BufferedImage maskTemplate = fixedTemplate;
Optional<BufferedImage> maskTemplateOptional = getTemplateImageOfOptional(templateResource, TEMPLATE_MASK_IMAGE_NAME);
if (maskTemplateOptional.isPresent()) {
maskTemplate = maskTemplateOptional.get();
}
// 算出居中的x和y
int x = targetBackground.getWidth() / 2 - fixedTemplate.getWidth() / 2;
int y = targetBackground.getHeight() / 2 - fixedTemplate.getHeight() / 2;
CaptchaImageUtils.overlayImage(targetBackground, fixedTemplate, x, y);
int x = background.getWidth() / 2 - fixedTemplate.getWidth() / 2;
int y = background.getHeight() / 2 - fixedTemplate.getHeight() / 2;
// 抠图部分
BufferedImage cutImage = CaptchaImageUtils.cutImage(cutBackground, fixedTemplate, x, y);
CaptchaImageUtils.overlayImage(cutImage, activeTemplate, 0, 0);
BufferedImage cutImage = CaptchaImageUtils.cutImage(background, maskTemplate, x, y);
BufferedImage rotateFixed = fixedTemplate;
BufferedImage rotateActive = activeTemplate;
if (param.getObfuscate()) {
int randomDegree = randomInt(10, 350);
rotateFixed = CaptchaImageUtils.rotateImage(fixedTemplate, randomDegree);
randomDegree = randomInt(10, 350);
rotateActive = CaptchaImageUtils.rotateImage(activeTemplate, randomDegree);
}
CaptchaImageUtils.overlayImage(background, rotateFixed, x, y);
CaptchaImageUtils.overlayImage(cutImage, rotateActive, 0, 0);
// 随机旋转抠图部分
// 随机x 转换为角度
int randomX = ThreadLocalRandom.current().nextInt(fixedTemplate.getWidth() + 10, targetBackground.getWidth() - 10);
double degree = 360d - randomX / ((targetBackground.getWidth()) / 360d);
int randomX = randomInt(fixedTemplate.getWidth() + 10, background.getWidth() - 10);
double degree = 360d - randomX / ((background.getWidth()) / 360d);
// 旋转的透明图片是一张正方形的
BufferedImage matrixTemplate = CaptchaImageUtils.createTransparentImage(background.getHeight(), background.getHeight());
CaptchaImageUtils.centerOverlayAndRotateImage(matrixTemplate, cutImage, degree);
return wrapRotateCaptchaInfo(degree, randomX, targetBackground, matrixTemplate, param, templateImages, resourceImage);
} finally {
// 使用完后关闭流
for (InputStream inputStream : inputStreams) {
try {
inputStream.close();
} catch (IOException e) {
// ignore
RotateData rotateData = new RotateData();
rotateData.degree = degree;
rotateData.randomX = randomX;
transferData.setTransferData(rotateData);
transferData.setBackgroundImage(background);
transferData.setTemplateImage(matrixTemplate);
transferData.setTemplateResource(templateResource);
transferData.setResourceImage(resourceImage);
// return wrapRotateCaptchaInfo(degree, randomX, background, matrixTemplate, param, templateResource, resourceImage, data);
}
public static class RotateData {
double degree;
int randomX;
}
private String getObfuscateTag(String templateTag) {
if (templateTag == null) {
return "obfuscate";
}
return templateTag + "_" + "obfuscate";
}
@SneakyThrows
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,
@Override
public ImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaTransferData transferData) {
GenerateParam param = transferData.getParam();
BufferedImage backgroundImage = transferData.getBackgroundImage();
BufferedImage sliderImage = transferData.getTemplateImage();
Resource resourceImage = transferData.getResourceImage();
ResourceMap templateResource = transferData.getTemplateResource();
CustomData data = transferData.getCustomData();
RotateData rotateData = (RotateData) transferData.getTransferData();
ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource, data);
RotateImageCaptchaInfo imageCaptchaInfo = RotateImageCaptchaInfo.of(rotateData.degree,
rotateData.randomX,
transform.getBackgroundImageUrl(),
transform.getTemplateImageUrl(),
resourceImage.getTag(),
@@ -125,6 +149,8 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen
backgroundImage.getWidth(), backgroundImage.getHeight(),
sliderImage.getWidth(), sliderImage.getHeight()
);
imageCaptchaInfo.setData(data);
return imageCaptchaInfo;
}
}
@@ -4,10 +4,7 @@ import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.AbstractImageCaptchaGenerator;
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.model.dto.*;
import cloud.tianai.captcha.generator.common.util.CaptchaImageUtils;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.resource.ResourceStore;
@@ -17,16 +14,16 @@ import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_TAG;
import static cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant.*;
import static cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME;
/**
* @Author: 天爱有情
@@ -63,80 +60,91 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen
@SneakyThrows
@Override
public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) {
public void doGenerateCaptchaImage(CaptchaTransferData transferData) {
GenerateParam param = transferData.getParam();
Boolean obfuscate = param.getObfuscate();
ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Collection<InputStream> inputStreams = new LinkedList<>();
try {
ResourceMap templateResource = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage);
inputStreams.add(resourceInputStream);
BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream);
// 拷贝一份图片
BufferedImage targetBackground = CaptchaImageUtils.copyImage(cutBackground, cutBackground.getType());
InputStream fixedTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME);
inputStreams.add(fixedTemplateInput);
BufferedImage fixedTemplate = CaptchaImageUtils.wrapFile2BufferedImage(fixedTemplateInput);
InputStream activeTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME);
inputStreams.add(activeTemplateInput);
BufferedImage activeTemplate = CaptchaImageUtils.wrapFile2BufferedImage(activeTemplateInput);
InputStream matrixTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME);
inputStreams.add(matrixTemplateInput);
BufferedImage matrixTemplate = CaptchaImageUtils.wrapFile2BufferedImage(matrixTemplateInput);
// BufferedImage cutTemplate = warpFile2BufferedImage(getTemplateFile(templateImages, CUT_IMAGE_NAME));
BufferedImage background = getResourceImage(resourceImage);
BufferedImage fixedTemplate = getTemplateImage(templateResource, TEMPLATE_FIXED_IMAGE_NAME);
BufferedImage activeTemplate = getTemplateImage(templateResource, TEMPLATE_ACTIVE_IMAGE_NAME);
BufferedImage maskTemplate = fixedTemplate;
Optional<BufferedImage> maskTemplateOptional = getTemplateImageOfOptional(templateResource, TEMPLATE_MASK_IMAGE_NAME);
if (maskTemplateOptional.isPresent()) {
maskTemplate = maskTemplateOptional.get();
}
// 获取随机的 x 和 y 轴
int randomX = ThreadLocalRandom.current().nextInt(fixedTemplate.getWidth() + 5, targetBackground.getWidth() - fixedTemplate.getWidth() - 10);
int randomY = ThreadLocalRandom.current().nextInt(targetBackground.getHeight() - fixedTemplate.getHeight());
int randomX = randomInt(fixedTemplate.getWidth() + 5, background.getWidth() - fixedTemplate.getWidth() - 10);
int randomY = randomInt(background.getHeight() - fixedTemplate.getHeight());
CaptchaImageUtils.overlayImage(targetBackground, fixedTemplate, randomX, randomY);
BufferedImage cutImage = CaptchaImageUtils.cutImage(background, maskTemplate, randomX, randomY);
CaptchaImageUtils.overlayImage(background, fixedTemplate, randomX, randomY);
if (obfuscate) {
// 加入混淆滑块
int obfuscateX = randomObfuscateX(randomX, fixedTemplate.getWidth(), targetBackground.getWidth());
CaptchaImageUtils.overlayImage(targetBackground, fixedTemplate, obfuscateX, randomY);
Optional<BufferedImage> obfuscateFixedTemplate = getTemplateImageOfOptional(templateResource, OBFUSCATE_TEMPLATE_FIXED_IMAGE_NAME);
BufferedImage obfuscateImage = obfuscateFixedTemplate.orElseGet(() -> createObfuscate(fixedTemplate));
int obfuscateX = randomObfuscateX(randomX, fixedTemplate.getWidth(), background.getWidth());
CaptchaImageUtils.overlayImage(background, obfuscateImage, obfuscateX, randomY);
}
BufferedImage cutImage = CaptchaImageUtils.cutImage(cutBackground, fixedTemplate, randomX, randomY);
CaptchaImageUtils.overlayImage(cutImage, activeTemplate, 0, 0);
// 这里创建一张png透明图片
BufferedImage matrixTemplate = CaptchaImageUtils.createTransparentImage(activeTemplate.getWidth(), background.getHeight());
CaptchaImageUtils.overlayImage(matrixTemplate, cutImage, 0, randomY);
return wrapSliderCaptchaInfo(randomX, randomY, targetBackground, matrixTemplate, param, templateImages, resourceImage);
} finally {
// 使用完后关闭流
for (InputStream inputStream : inputStreams) {
try {
inputStream.close();
} catch (IOException e) {
// ignore
}
}
}
XandY xandY = new XandY();
xandY.x = randomX;
xandY.y = randomY;
transferData.setBackgroundImage(background);
transferData.setTemplateImage(matrixTemplate);
transferData.setTemplateResource(templateResource);
transferData.setResourceImage(resourceImage);
transferData.setTransferData(xandY);
// 后处理
// applyPostProcessorBeforeWrapImageCaptchaInfo(transferData, this);
// imageCaptchaInfo = wrapSliderCaptchaInfo(randomX, randomY, transferData);
// applyPostProcessorAfterGenerateCaptchaImage(transferData, imageCaptchaInfo, this);
// return imageCaptchaInfo;
}
protected BufferedImage createObfuscate(BufferedImage fixedImage) {
// 随机拉伸或缩放宽高, 每次只拉伸高或者宽
int width = fixedImage.getWidth();
int height = fixedImage.getHeight();
int window = randomInt(-3, 4);
if (randomBoolean()) {
height = height + window * 5;
} else {
width = width + window * 5;
}
int type = fixedImage.getColorModel().getTransparency();
BufferedImage image = new BufferedImage(width, height, type);
Graphics2D graphics = image.createGraphics();
// 透明度
double alpha = ThreadLocalRandom.current().nextDouble(0.5, 0.8);
AlphaComposite alphaComposite = AlphaComposite.Src.derive((float) alpha);
graphics.setComposite(alphaComposite);
graphics.drawImage(fixedImage, 0, 0, width, height, null);
return image;
}
public static class XandY {
int x;
int y;
}
/**
* 包装成 SliderCaptchaInfo
*
* @param randomX 随机生成的 x轴
* @param randomY 随机生成的 y轴
* @param backgroundImage 背景图片
* @param sliderImage 滑块图片
* @param param 接口传入参数
* @return SliderCaptchaInfo
*/
@SneakyThrows
public SliderImageCaptchaInfo wrapSliderCaptchaInfo(int randomX,
int randomY,
BufferedImage backgroundImage,
BufferedImage sliderImage,
GenerateParam param,
ResourceMap templateResource,
Resource resourceImage) {
ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource);
@Override
public SliderImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaTransferData transferData) {
GenerateParam param = transferData.getParam();
BufferedImage backgroundImage = transferData.getBackgroundImage();
BufferedImage sliderImage = transferData.getTemplateImage();
Resource resourceImage = transferData.getResourceImage();
ResourceMap templateResource = transferData.getTemplateResource();
CustomData customData = transferData.getCustomData();
XandY data = (XandY) transferData.getTransferData();
ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource, customData);
return SliderImageCaptchaInfo.of(randomX, randomY,
SliderImageCaptchaInfo imageCaptchaInfo = SliderImageCaptchaInfo.of(data.x, data.y,
transform.getBackgroundImageUrl(),
transform.getTemplateImageUrl(),
resourceImage.getTag(),
@@ -144,15 +152,17 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen
backgroundImage.getWidth(), backgroundImage.getHeight(),
sliderImage.getWidth(), sliderImage.getHeight()
);
imageCaptchaInfo.setData(customData);
return imageCaptchaInfo;
}
protected int randomObfuscateX(int sliderX, int slWidth, int bgWidth) {
if (bgWidth / 2 > (sliderX + (slWidth / 2))) {
// 右边混淆
return ThreadLocalRandom.current().nextInt(sliderX + slWidth, bgWidth - slWidth);
return randomInt(sliderX + slWidth, bgWidth - slWidth);
}
// 左边混淆
return ThreadLocalRandom.current().nextInt(slWidth, sliderX - slWidth);
return randomInt(slWidth, sliderX - slWidth);
}
/**
@@ -161,20 +171,17 @@ public class StandardSliderImageCaptchaGenerator extends AbstractImageCaptchaGen
public void initDefaultResource() {
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加一些系统的资源文件
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"), DEFAULT_TAG));
// 添加一些系统的 模板文件
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")));
template1.put(TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png")));
template1.put(TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png")));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template1);
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")));
template2.put(TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png")));
template2.put(TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png")));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template2);
}
}
@@ -1,20 +1,16 @@
package cloud.tianai.captcha.generator.impl;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.util.CollectionUtils;
import cloud.tianai.captcha.common.util.FontUtils;
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.model.dto.*;
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.impl.provider.ClassPathResourceProvider;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.*;
import java.awt.*;
import java.awt.image.BufferedImage;
@@ -26,23 +22,25 @@ import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_TAG;
/**
* @Author: 天爱有情
* @date 2022/4/27 11:46
* @Description 点选验证码
*/
public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickImageCaptchaGenerator {
public class StandardWordClickImageCaptchaGenerator extends AbstractClickImageCaptchaGenerator {
/** 字体包. */
@Getter
@Setter
protected Font font;
protected List<FontWrapper> fonts = new ArrayList<>();
@Getter
@Setter
protected Integer clickImgWidth = 80;
protected Integer clickImgWidth = 100;
@Getter
@Setter
protected Integer clickImgHeight = 80;
protected Integer clickImgHeight = 100;
@Getter
@Setter
protected int tipImageInterferenceLineNum = 2;
@@ -63,19 +61,25 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
* 方案: 通过 线性回归模型 计算出 intercept和coef 用于计算 容错值
* 训练数据为 宋体 字体大小为 30~150 随机选择7组数据进行训练 训练后r2结果为 0.9967106324620846
*/
protected float intercept = 0.39583333f;
protected float coef = 0.14645833f;
protected float currentFontTopCoef = 0.0f;
public StandardRandomWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager) {
// protected float intercept = 0.39583333f;
// protected float coef = 0.14645833f;
//
// protected float currentFontTopCoef = 0.0f;
public StandardWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager) {
super(imageCaptchaResourceManager);
}
public StandardRandomWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform) {
public StandardWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
}
public StandardWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, ImageTransform imageTransform, List<FontWrapper> fonts) {
super(imageCaptchaResourceManager);
setImageTransform(imageTransform);
this.fonts = fonts;
}
@Override
protected List<Resource> randomGetClickImgTips(GenerateParam param) {
int tipSize = interferenceCount + checkClickCount;
@@ -92,32 +96,31 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
@Override
@SneakyThrows({IOException.class, FontFormatException.class})
protected void doInit(boolean initDefaultResource) {
if (this.font == null) {
if (CollectionUtils.isEmpty(fonts)) {
// 使用默认字体
Resource fontResource = new Resource(null, "META-INF/fonts/SIMSUN.TTC");
InputStream inputStream = new ClassPathResourceProvider().doGetResourceInputStream(fontResource);
Font font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
this.font = font.deriveFont(Font.BOLD, 70);
font = font.deriveFont(Font.BOLD, 70);
float currentFontTopCoef = 0.14645833f * font.getSize() + 0.39583333f;
fonts.add(new FontWrapper(font, currentFontTopCoef));
}
// 计算容错
currentFontTopCoef = coef * font.getSize() + intercept;
if (initDefaultResource) {
initDefaultResource();
}
}
public StandardRandomWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, Font font) {
super(imageCaptchaResourceManager);
this.font = font;
}
public void initDefaultResource() {
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加一些系统的资源文件
resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"), DEFAULT_TAG));
}
public ImgWrapper genTipImage(List<ClickImageCheckDefinition> imageCheckDefinitions) {
FontWrapper fontWrapper = fonts.get(randomInt(fonts.size()));
Font font = fontWrapper.getFont();
float currentFontTopCoef = fontWrapper.getCurrentFontTopCoef();
String tips = imageCheckDefinitions.stream().map(c -> c.getTip().getData()).collect(Collectors.joining());
// 生成随机颜色
int fontWidth = tips.length() * font.getSize();
@@ -137,6 +140,9 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
Color randomColor = CaptchaImageUtils.getRandomColor(random);
// 随机角度
int randomDeg = randomInt(0, 85);
FontWrapper fontWrapper = fonts.get(randomInt(fonts.size()));
Font font = fontWrapper.getFont();
float currentFontTopCoef = fontWrapper.getCurrentFontTopCoef();
BufferedImage fontImage = CaptchaImageUtils.drawWordImg(randomColor,
tip.getData(),
font,
@@ -161,11 +167,15 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
}
@Override
public ImageCaptchaInfo wrapClickImageCaptchaInfo(GenerateParam param, BufferedImage bgImage,
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList, Resource resourceImage) {
public ImageCaptchaInfo doWrapImageCaptchaInfo(CaptchaTransferData transferData) {
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList = (List<ClickImageCheckDefinition>) transferData.getTransferData();
BufferedImage bgImage = transferData.getBackgroundImage();
GenerateParam param = transferData.getParam();
Resource resourceImage = transferData.getResourceImage();
CustomData data = transferData.getCustomData();
// 提示图片
BufferedImage tipImage = genTipImage(checkClickImageCheckDefinitionList).getImage();
ImageTransformData transform = getImageTransform().transform(param, bgImage, tipImage, resourceImage, checkClickImageCheckDefinitionList);
ImageTransformData transform = getImageTransform().transform(param, bgImage, tipImage, resourceImage, checkClickImageCheckDefinitionList, data);
ImageCaptchaInfo clickImageCaptchaInfo = new ImageCaptchaInfo();
clickImageCaptchaInfo.setBackgroundImage(transform.getBackgroundImageUrl());
clickImageCaptchaInfo.setBackgroundImageTag(resourceImage.getTag());
@@ -177,8 +187,16 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI
clickImageCaptchaInfo.setRandomX(null);
clickImageCaptchaInfo.setTolerant(null);
clickImageCaptchaInfo.setType(CaptchaTypeConstant.WORD_IMAGE_CLICK);
clickImageCaptchaInfo.setExpand(checkClickImageCheckDefinitionList);
data.setExpand(checkClickImageCheckDefinitionList);
return clickImageCaptchaInfo;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class FontWrapper {
private Font font;
private float currentFontTopCoef;
}
}
@@ -0,0 +1,87 @@
package cloud.tianai.captcha.generator.impl;
import cloud.tianai.captcha.common.exception.ImageCaptchaException;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageCaptchaPostProcessor;
import cloud.tianai.captcha.generator.common.model.dto.CaptchaTransferData;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import lombok.Getter;
import java.util.LinkedList;
import java.util.List;
/**
* @Author: 天爱有情
* @date 2023/4/24 15:23
* @Description 验证码后处理器管理
*/
public class StaticCaptchaPostProcessorManager {
@Getter
private static LinkedList<ImageCaptchaPostProcessor> processors = new LinkedList<>();
static {
}
public static void add(ImageCaptchaPostProcessor processor) {
processors.add(processor);
}
public static void add(Integer index, ImageCaptchaPostProcessor processor) {
processors.add(index, processor);
}
public static void addFirst(ImageCaptchaPostProcessor processor) {
processors.addFirst(processor);
}
public static void addLast(ImageCaptchaPostProcessor processor) {
processors.addLast(processor);
}
public static void clear() {
processors.clear();
}
public static void add(List<ImageCaptchaPostProcessor> addPostProcessors) {
processors.addAll(addPostProcessors);
}
public static ImageCaptchaInfo applyPostProcessorBeforeGenerate(CaptchaTransferData transferData, ImageCaptchaGenerator context) {
for (ImageCaptchaPostProcessor processor : processors) {
try {
ImageCaptchaInfo imageCaptchaInfo = processor.beforeGenerateCaptchaImage(transferData, context);
if (imageCaptchaInfo != null) {
return imageCaptchaInfo;
}
} catch (Exception e) {
throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.beforeGenerateCaptchaImage error, [" + processor.getClass() + "]", e);
}
}
return null;
}
public static void applyPostProcessorBeforeWrapImageCaptchaInfo(CaptchaTransferData transferData, ImageCaptchaGenerator context) {
for (ImageCaptchaPostProcessor processor : processors) {
try {
processor.beforeWrapImageCaptchaInfo(transferData, context);
} catch (Exception e) {
throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.beforeWrapImageCaptchaInfo error, [" + processor.getClass() + "]", e);
}
}
}
public static void applyPostProcessorAfterGenerateCaptchaImage(CaptchaTransferData transferData, ImageCaptchaInfo imageCaptchaInfo, ImageCaptchaGenerator context) {
for (ImageCaptchaPostProcessor processor : processors) {
try {
processor.afterGenerateCaptchaImage(transferData, imageCaptchaInfo, context);
} catch (Exception e) {
throw new ImageCaptchaException("apply ImageCaptchaPostProcessor.afterGenerateCaptchaImage error, [" + processor.getClass() + "]", e);
}
}
}
}
@@ -4,13 +4,13 @@ import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.ImageCaptchaGeneratorProvider;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.impl.StandardRandomWordClickImageCaptchaGenerator;
import cloud.tianai.captcha.generator.impl.StandardWordClickImageCaptchaGenerator;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
public class StandardRandomWordClickImageCaptchaGeneratorProvider implements ImageCaptchaGeneratorProvider {
public class StandardWordClickImageCaptchaGeneratorProvider implements ImageCaptchaGeneratorProvider {
@Override
public ImageCaptchaGenerator get(ImageCaptchaResourceManager resourceManager, ImageTransform imageTransform) {
return new StandardRandomWordClickImageCaptchaGenerator(resourceManager, imageTransform);
return new StandardWordClickImageCaptchaGenerator(resourceManager, imageTransform);
}
@Override
@@ -1,6 +1,7 @@
package cloud.tianai.captcha.generator.impl.transform;
import cloud.tianai.captcha.generator.ImageTransform;
import cloud.tianai.captcha.generator.common.model.dto.CustomData;
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;
@@ -59,7 +60,12 @@ public class Base64ImageTransform implements ImageTransform {
}
@Override
public ImageTransformData transform(GenerateParam param, BufferedImage backgroundImage, BufferedImage templateImage, Object backgroundResource, Object templateResource) {
public ImageTransformData transform(GenerateParam param,
BufferedImage backgroundImage,
BufferedImage templateImage,
Object backgroundResource,
Object templateResource,
CustomData data) {
ImageTransformData imageTransformData = new ImageTransformData();
if (backgroundImage != null) {
imageTransformData.setBackgroundImageUrl(transform(backgroundImage, param.getBackgroundFormatName()));
@@ -102,12 +102,12 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
ImageCaptchaInfo imageCaptchaInfo) {
// type
String type = (String) map.getOrDefault(TYPE_KEY, CaptchaTypeConstant.SLIDER);
Object expand = imageCaptchaInfo.getData() == null ? null : imageCaptchaInfo.getData().getExpand();
if (CaptchaUtils.isSliderCaptcha(type)) {
// 滑动验证码
addPercentage(imageCaptchaInfo, map);
} else if (CaptchaUtils.isClickCaptcha(type)) {
// 图片点选验证码
Object expand = imageCaptchaInfo.getExpand();
if (expand == null) {
throw new IllegalArgumentException("点选验证码扩展数据转换为 List<ClickImageCheckDefinition> 失败, info=" + imageCaptchaInfo);
}
+1 -1
View File
@@ -19,7 +19,7 @@ public class Test3 {
// 生成 具有混淆的 滑块验证码 (目前只有滑块验证码支持混淆滑块, 旋转验证,滑动还原,点选验证 均不支持混淆功能)
ImageCaptchaInfo imageCaptchaInfo = imageCaptchaGenerator.generateCaptchaImage(GenerateParam.builder()
.type(CaptchaTypeConstant.SLIDER)
.sliderFormatName("jpeg")
.templateFormatName("jpeg")
.backgroundFormatName("png")
// 是否添加混淆滑块
.obfuscate(true)
+1 -1
View File
@@ -20,7 +20,7 @@ public class Test4 {
// 参考 https://bitbucket.org/luciad/webp-imageio
ImageCaptchaInfo slideImageInfo = imageCaptchaGenerator.generateCaptchaImage(GenerateParam.builder()
.type(CaptchaTypeConstant.ROTATE)
.sliderFormatName("webp")
.templateFormatName("webp")
.backgroundFormatName("webp")
.build());
System.out.println(slideImageInfo);
+1 -1
View File
@@ -24,7 +24,7 @@ public class Test8 {
// 获取背景图片的base64
String backgroundImage = slideImageInfo.getBackgroundImage();
// 获取滑块图片
String sliderImage = slideImageInfo.getSliderImage();
String sliderImage = slideImageInfo.getTemplateImage();
System.out.println(slideImageInfo);
}
}