mirror of
https://github.com/dromara/tianai-captcha.git
synced 2026-08-31 02:46:10 +08:00
添加点选验证码 点选文字验证码完成
This commit is contained in:
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.common.constant;
|
package cloud.tianai.captcha.template.slider.common.constant;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 天爱有情
|
* @Author: 天爱有情
|
||||||
@@ -13,6 +13,6 @@ public interface CaptchaTypeConstant {
|
|||||||
String ROTATE = "ROTATE";
|
String ROTATE = "ROTATE";
|
||||||
/** 拼接.*/
|
/** 拼接.*/
|
||||||
String CONCAT = "CONCAT";
|
String CONCAT = "CONCAT";
|
||||||
/** 文字点选.*/
|
/** 图片点选.*/
|
||||||
String WORD_CLICK = "WORD_CLICK";
|
String IMAGE_CLICK = "IMAGE_CLICK";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package cloud.tianai.captcha.template.slider.common.util;
|
||||||
|
|
||||||
|
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: 天爱有情
|
||||||
|
* @date 2022/4/28 17:03
|
||||||
|
* @Description 验证码工具包
|
||||||
|
*/
|
||||||
|
public class CaptchaUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是滑动验证码
|
||||||
|
* @param type 类型
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static boolean isSliderCaptcha(String type) {
|
||||||
|
return CaptchaTypeConstant.SLIDER.equals(type)
|
||||||
|
|| CaptchaTypeConstant.ROTATE.equals(type)
|
||||||
|
|| CaptchaTypeConstant.CONCAT.equals(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是点击验证码
|
||||||
|
* @param type type
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static boolean isClickCaptcha(String type) {
|
||||||
|
return CaptchaTypeConstant.IMAGE_CLICK.equals(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
package cloud.tianai.captcha.template.slider.common.util;
|
package cloud.tianai.captcha.template.slider.common.util;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 天爱有情
|
* @Author: 天爱有情
|
||||||
* @date 2022/4/27 11:34
|
* @date 2022/4/27 11:34
|
||||||
@@ -7,11 +13,17 @@ package cloud.tianai.captcha.template.slider.common.util;
|
|||||||
*/
|
*/
|
||||||
public class FontUtils {
|
public class FontUtils {
|
||||||
|
|
||||||
public static char getRandomChar() {
|
@SneakyThrows
|
||||||
return (char)(0x4e00 + (int)(Math.random()*(0x9fa5 - 0x4e00 + 1)));
|
public static String getRandomChar(Random random) {
|
||||||
|
Integer hightPos, lowPos; // 定义高低位
|
||||||
|
hightPos = (176 + Math.abs(random.nextInt(39)));
|
||||||
|
lowPos = (161 + Math.abs(random.nextInt(93)));
|
||||||
|
byte[] bytes = new byte[2];
|
||||||
|
bytes[0] = hightPos.byteValue();
|
||||||
|
bytes[1] = lowPos.byteValue();
|
||||||
|
return new String(bytes, "GBK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator;
|
package cloud.tianai.captcha.template.slider.generator;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
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.GenerateParam;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: 天爱有情
|
||||||
|
* @date 2022/4/28 16:51
|
||||||
|
* @Description 点击图片校验描述
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ClickImageCheckDefinition {
|
||||||
|
/** 提示.*/
|
||||||
|
private String tip;
|
||||||
|
/** x.*/
|
||||||
|
private Integer x;
|
||||||
|
/** y.*/
|
||||||
|
private Integer y;
|
||||||
|
/** 宽.*/
|
||||||
|
private Integer width;
|
||||||
|
/** 高.*/
|
||||||
|
private Integer height;
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
@@ -19,7 +19,7 @@ public class RotateImageCaptchaInfo extends ImageCaptchaInfo {
|
|||||||
*/
|
*/
|
||||||
private Double degree;
|
private Double degree;
|
||||||
/** 旋转图片的容错值大一点. */
|
/** 旋转图片的容错值大一点. */
|
||||||
public static final Float DEFAULT_TOLERANT = 0.05F;
|
public static final Float DEFAULT_TOLERANT = 0.03F;
|
||||||
|
|
||||||
public static RotateImageCaptchaInfo of(Double degree,
|
public static RotateImageCaptchaInfo of(Double degree,
|
||||||
Integer randomX,
|
Integer randomX,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.common.model.dto;
|
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Author: 天爱有情
|
|
||||||
* @date 2022/4/27 15:33
|
|
||||||
* @Description 文字点选
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@ToString(callSuper = true)
|
|
||||||
public class WordClickImageCaptchaInfo extends ImageCaptchaInfo {
|
|
||||||
|
|
||||||
private List<WordDefinition> checkWords;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public static class WordDefinition {
|
|
||||||
private String word;
|
|
||||||
private Integer x;
|
|
||||||
private Integer y;
|
|
||||||
private Integer deg;
|
|
||||||
private Integer wordWidth;
|
|
||||||
private Integer wordHeight;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+146
-2
@@ -1,10 +1,13 @@
|
|||||||
package cloud.tianai.captcha.template.slider.common.util;
|
package cloud.tianai.captcha.template.slider.generator.common.util;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import sun.font.FontDesignMetrics;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.Area;
|
import java.awt.geom.Area;
|
||||||
|
import java.awt.geom.CubicCurve2D;
|
||||||
|
import java.awt.geom.QuadCurve2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.ColorModel;
|
import java.awt.image.ColorModel;
|
||||||
import java.awt.image.PixelGrabber;
|
import java.awt.image.PixelGrabber;
|
||||||
@@ -12,6 +15,8 @@ import java.awt.image.WritableRaster;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 天爱有情
|
* @Author: 天爱有情
|
||||||
@@ -314,7 +319,146 @@ public class CaptchaImageUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static char getRandomChar() {
|
public static char getRandomChar() {
|
||||||
return (char)(0x4e00 + (int)(Math.random()*(0x9fa5 - 0x4e00 + 1)));
|
return (char) (0x4e00 + (int) (Math.random() * (0x9fa5 - 0x4e00 + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static BufferedImage drawWordImg(Color fontColor,
|
||||||
|
String word,
|
||||||
|
Font font,
|
||||||
|
FontDesignMetrics metrics,
|
||||||
|
int imgWidth,
|
||||||
|
int imgHeight,
|
||||||
|
float deg) {
|
||||||
|
BufferedImage fillRect = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g = fillRect.createGraphics();
|
||||||
|
g.setColor(new Color(255, 255, 255, 0));
|
||||||
|
g.fillRect(0, 0, imgWidth, imgHeight);
|
||||||
|
g.setColor(fontColor);
|
||||||
|
g.setFont(font);
|
||||||
|
float left = (imgWidth - font.getSize()) / 2f;
|
||||||
|
float top = (imgHeight - font.getSize()) / 2f + metrics.getAscent() - 6;
|
||||||
|
g.rotate(Math.toRadians(deg), imgWidth / 2f, imgHeight / 2f);
|
||||||
|
g.drawString(word, left, top);
|
||||||
|
g.dispose();
|
||||||
|
return fillRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机画干扰圆
|
||||||
|
*
|
||||||
|
* @param num 数量
|
||||||
|
* @param color 颜色
|
||||||
|
* @param g Graphics2D
|
||||||
|
*/
|
||||||
|
public static void drawOval(int num,
|
||||||
|
Color color,
|
||||||
|
Graphics2D g,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
Random random) {
|
||||||
|
for (int i = 0; i < num; i++) {
|
||||||
|
g.setColor(color == null ? getRandomColor(random) : color);
|
||||||
|
int w = 5 + random.nextInt(10);
|
||||||
|
int x = random.nextInt(width - 25);
|
||||||
|
int y = random.nextInt(height - 25);
|
||||||
|
g.drawOval(x, y, w, w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机画贝塞尔曲线
|
||||||
|
*
|
||||||
|
* @param num 数量
|
||||||
|
* @param color 颜色
|
||||||
|
* @param g Graphics2D
|
||||||
|
*/
|
||||||
|
public static void drawBesselLine(int num, Color color,
|
||||||
|
Graphics2D g,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
ThreadLocalRandom random) {
|
||||||
|
for (int i = 0; i < num; i++) {
|
||||||
|
g.setColor(color == null ? getRandomColor(random) : color);
|
||||||
|
int x1 = 5, y1 = random.nextInt(5, height / 2);
|
||||||
|
int x2 = width - 5, y2 = random.nextInt(height / 2, height - 5);
|
||||||
|
int ctrlx = random.nextInt(width / 4, width / 4 * 3);
|
||||||
|
int ctrly = random.nextInt(5, height - 5);
|
||||||
|
if (random.nextInt(2) == 0) {
|
||||||
|
int ty = y1;
|
||||||
|
y1 = y2;
|
||||||
|
y2 = ty;
|
||||||
|
}
|
||||||
|
if (random.nextInt(2) == 0) { // 二阶贝塞尔曲线
|
||||||
|
QuadCurve2D shape = new QuadCurve2D.Double();
|
||||||
|
shape.setCurve(x1, y1, ctrlx, ctrly, x2, y2);
|
||||||
|
g.draw(shape);
|
||||||
|
} else { // 三阶贝塞尔曲线
|
||||||
|
int ctrlx1 = random.nextInt(width / 4, width / 4 * 3);
|
||||||
|
int ctrly1 = random.nextInt(5, height - 5);
|
||||||
|
CubicCurve2D shape = new CubicCurve2D.Double(x1, y1, ctrlx, ctrly, ctrlx1, ctrly1, x2, y2);
|
||||||
|
g.draw(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成简单的验证码图片
|
||||||
|
* @param data 验证码内容
|
||||||
|
* @param font 字体包
|
||||||
|
* @param metrics FontDesignMetrics
|
||||||
|
* @param width 验证码宽度
|
||||||
|
* @param height 验证码高度
|
||||||
|
* @param startX 起始X
|
||||||
|
* @param startY 起始Y
|
||||||
|
* @param interferenceLineNum 干扰线数量
|
||||||
|
* @param interferencePointNum 干扰点数量
|
||||||
|
* @return BufferedImage
|
||||||
|
*/
|
||||||
|
public static BufferedImage genSimpleImgCaptcha(String data,
|
||||||
|
Font font,
|
||||||
|
FontDesignMetrics metrics,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
float startX,
|
||||||
|
float startY,
|
||||||
|
int interferenceLineNum,
|
||||||
|
int interferencePointNum) {
|
||||||
|
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g = bufferedImage.createGraphics();
|
||||||
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
|
g.setFont(font);
|
||||||
|
char[] chars = data.toCharArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
g.setColor(getRandomColor(random));
|
||||||
|
g.drawString(String.valueOf(chars[i]), startX + i * font.getSize(), startY);
|
||||||
|
}
|
||||||
|
// 干扰点
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return bufferedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机获取颜色
|
||||||
|
* @return Color
|
||||||
|
*/
|
||||||
|
public static Color getRandomColor(Random random) {
|
||||||
|
return new Color(
|
||||||
|
random.nextInt(255),
|
||||||
|
random.nextInt(255),
|
||||||
|
random.nextInt(255));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
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.resource.common.model.dto.Resource;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import static cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils.wrapFile2BufferedImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: 天爱有情
|
||||||
|
* @date 2022/4/27 11:46
|
||||||
|
* @Description 点选验证码 点选验证码分为点选文字和点选图标等
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public abstract class AbstractClickImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
|
||||||
|
|
||||||
|
/** 参与校验的数量.*/
|
||||||
|
protected Integer checkClickCount = 4;
|
||||||
|
/** 干扰数量.*/
|
||||||
|
protected Integer interferenceCount = 2;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) {
|
||||||
|
// 文字点选验证码不需要模板 只需要背景图
|
||||||
|
Collection<InputStream> inputStreams = new LinkedList<>();
|
||||||
|
try {
|
||||||
|
Resource resourceImage = getImageResourceManager().randomGetResource(param.getType());
|
||||||
|
InputStream resourceInputStream = getImageResourceManager().getResourceInputStream(resourceImage);
|
||||||
|
inputStreams.add(resourceInputStream);
|
||||||
|
BufferedImage bgImage = wrapFile2BufferedImage(resourceInputStream);
|
||||||
|
|
||||||
|
List<ClickImageCheckDefinition> clickImageCheckDefinitionList = new ArrayList<>(interferenceCount);
|
||||||
|
int allImages = interferenceCount + checkClickCount;
|
||||||
|
for (int i = 0; i < allImages; i++) {
|
||||||
|
// 随机获取点击图片
|
||||||
|
ImgWrapper imgWrapper = randomGetClickImg();
|
||||||
|
BufferedImage image = imgWrapper.getImage();
|
||||||
|
int clickImgWidth = image.getWidth();
|
||||||
|
int clickImgHeight = image.getHeight();
|
||||||
|
// 随机x
|
||||||
|
int randomX = ThreadLocalRandom.current().nextInt(10, bgImage.getWidth() - clickImgWidth);
|
||||||
|
// 随机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);
|
||||||
|
clickImageCheckDefinition.setY(randomY + clickImgHeight / 2);
|
||||||
|
clickImageCheckDefinition.setWidth(clickImgWidth);
|
||||||
|
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);
|
||||||
|
// 拿出参与校验的数据
|
||||||
|
List<ClickImageCheckDefinition> checkClickImageCheckDefinitionList = new ArrayList<>(checkClickCount);
|
||||||
|
for (int i = 0; i < checkClickCount; i++) {
|
||||||
|
ClickImageCheckDefinition clickImageCheckDefinition = clickImageCheckDefinitionList.get(i);
|
||||||
|
checkClickImageCheckDefinitionList.add(clickImageCheckDefinition);
|
||||||
|
}
|
||||||
|
// 将校验的文字生成提示图片
|
||||||
|
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 {
|
||||||
|
// 使用完后关闭流
|
||||||
|
for (InputStream inputStream : inputStreams) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
protected abstract ImgWrapper randomGetClickImg();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: 天爱有情
|
||||||
|
* @date 2022/4/28 14:26
|
||||||
|
* @Description 点击图片包装
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class ImgWrapper {
|
||||||
|
/** 图片.*/
|
||||||
|
private BufferedImage image;
|
||||||
|
/** 提示.*/
|
||||||
|
private String tip;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -3,7 +3,7 @@ package cloud.tianai.captcha.template.slider.generator.impl;
|
|||||||
import cloud.tianai.captcha.template.slider.common.util.ObjectUtils;
|
import cloud.tianai.captcha.template.slider.common.util.ObjectUtils;
|
||||||
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
||||||
import cloud.tianai.captcha.template.slider.generator.ImageCaptchaGenerator;
|
import cloud.tianai.captcha.template.slider.generator.ImageCaptchaGenerator;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
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.GenerateParam;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
||||||
|
|||||||
-217
@@ -1,217 +0,0 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.impl;
|
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils;
|
|
||||||
import cloud.tianai.captcha.template.slider.common.util.FontUtils;
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.GenerateParam;
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.WordClickImageCaptchaInfo;
|
|
||||||
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;
|
|
||||||
import cloud.tianai.captcha.template.slider.resource.impl.provider.ClassPathResourceProvider;
|
|
||||||
import cloud.tianai.captcha.template.slider.resource.impl.provider.FileResourceProvider;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import sun.font.FontDesignMetrics;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
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;
|
|
||||||
|
|
||||||
import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils.wrapFile2BufferedImage;
|
|
||||||
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Author: 天爱有情
|
|
||||||
* @date 2022/4/27 11:46
|
|
||||||
* @Description 点选验证码
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class StandardClickImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
|
|
||||||
|
|
||||||
|
|
||||||
protected ImageCaptchaResourceManager imageCaptchaResourceManager;
|
|
||||||
|
|
||||||
protected Integer checkFontCount = 4;
|
|
||||||
protected Integer interferenceCount = checkFontCount + 2;
|
|
||||||
protected Font font;
|
|
||||||
protected List<Color> randomColors = Arrays.asList(Color.PINK, Color.BLUE, Color.GREEN, Color.BLACK);
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public StandardClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, boolean initDefaultResource) {
|
|
||||||
this.imageCaptchaResourceManager = imageCaptchaResourceManager;
|
|
||||||
if (initDefaultResource) {
|
|
||||||
initDefaultResource();
|
|
||||||
}
|
|
||||||
Resource fontResource = new Resource("", "META-INF/fonts/SIMSUN.TTC");
|
|
||||||
InputStream inputStream = new ClassPathResourceProvider().doGetResourceInputStream(fontResource);
|
|
||||||
this.font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
|
|
||||||
this.font = font.deriveFont(Font.BOLD, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StandardClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager,
|
|
||||||
boolean initDefaultResource,
|
|
||||||
Font font) {
|
|
||||||
this.imageCaptchaResourceManager = imageCaptchaResourceManager;
|
|
||||||
this.font = font;
|
|
||||||
if (initDefaultResource) {
|
|
||||||
initDefaultResource();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void initDefaultResource() {
|
|
||||||
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
|
|
||||||
// 添加一些系统的资源文件
|
|
||||||
resourceStore.addResource(CaptchaTypeConstant.WORD_CLICK, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImageCaptchaInfo generateCaptchaImage(GenerateParam param) {
|
|
||||||
|
|
||||||
// 文字点选验证码不需要模板 只需要背景图
|
|
||||||
Collection<InputStream> inputStreams = new LinkedList<>();
|
|
||||||
try {
|
|
||||||
Resource resourceImage = imageCaptchaResourceManager.randomGetResource(param.getType());
|
|
||||||
InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage);
|
|
||||||
inputStreams.add(resourceInputStream);
|
|
||||||
BufferedImage bgImage = wrapFile2BufferedImage(resourceInputStream);
|
|
||||||
Graphics2D graphics = (Graphics2D) bgImage.getGraphics();
|
|
||||||
|
|
||||||
List<WordClickImageCaptchaInfo.WordDefinition> wordDefinitionList = new ArrayList<>(interferenceCount);
|
|
||||||
for (int i = 0; i < interferenceCount; i++) {
|
|
||||||
// 随机角度
|
|
||||||
|
|
||||||
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
|
|
||||||
// 随机文字
|
|
||||||
String randomWord = String.valueOf(FontUtils.getRandomChar());
|
|
||||||
int wordWidth = metrics.stringWidth(randomWord);
|
|
||||||
int wordHeight = metrics.getHeight();
|
|
||||||
// 随机颜色
|
|
||||||
Color randomColor = randomColor();
|
|
||||||
// 随机x
|
|
||||||
int randomX = ThreadLocalRandom.current().nextInt(10, bgImage.getWidth() - wordWidth);
|
|
||||||
// 随机y
|
|
||||||
int randomY = ThreadLocalRandom.current().nextInt(10, bgImage.getHeight() - wordHeight);
|
|
||||||
int randomDeg = ThreadLocalRandom.current().nextInt(0, 85);
|
|
||||||
AffineTransform affineTransform = new AffineTransform();
|
|
||||||
affineTransform.rotate(Math.toRadians(randomDeg));
|
|
||||||
Font rotatedFont = font.deriveFont(affineTransform);
|
|
||||||
graphics.setFont(rotatedFont);
|
|
||||||
graphics.setColor(randomColor);
|
|
||||||
graphics.drawString(randomWord, randomX, randomY + metrics.getAscent());
|
|
||||||
|
|
||||||
WordClickImageCaptchaInfo.WordDefinition wordDefinition = new WordClickImageCaptchaInfo.WordDefinition();
|
|
||||||
wordDefinition.setWord(randomWord);
|
|
||||||
wordDefinition.setX(randomX + wordWidth / 2);
|
|
||||||
wordDefinition.setY(randomY + wordHeight / 2);
|
|
||||||
wordDefinition.setDeg(randomDeg);
|
|
||||||
wordDefinition.setWordWidth(wordWidth);
|
|
||||||
wordDefinition.setWordHeight(wordHeight);
|
|
||||||
wordDefinitionList.add(wordDefinition);
|
|
||||||
}
|
|
||||||
// 背景图转换为字符串
|
|
||||||
// String bgImageStr = transform(bgImage, param.getBackgroundFormatName());
|
|
||||||
try {
|
|
||||||
ImageIO.write(bgImage, "jpeg", new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\123.jpg"));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打乱 word
|
|
||||||
Collections.shuffle(wordDefinitionList);
|
|
||||||
// 拿出要校验的文字
|
|
||||||
List<WordClickImageCaptchaInfo.WordDefinition> checkWordDefinitionList = new ArrayList<>(checkFontCount);
|
|
||||||
for (int i = 0; i < checkFontCount; i++) {
|
|
||||||
checkWordDefinitionList.add(wordDefinitionList.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(checkWordDefinitionList);
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// 使用完后关闭流
|
|
||||||
for (InputStream inputStream : inputStreams) {
|
|
||||||
try {
|
|
||||||
inputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Color randomColor() {
|
|
||||||
if (randomColors.size() == 1) {
|
|
||||||
return randomColors.get(0);
|
|
||||||
}
|
|
||||||
return randomColors.get(ThreadLocalRandom.current().nextInt(0, randomColors.size() - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImageCaptchaResourceManager getImageResourceManager() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, FontFormatException {
|
|
||||||
Resource fontResource = new Resource("", "META-INF/fonts/SIMSUN.TTC");
|
|
||||||
InputStream inputStream = new ClassPathResourceProvider().doGetResourceInputStream(fontResource);
|
|
||||||
Font font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
|
|
||||||
font = font.deriveFont(Font.BOLD, 100);
|
|
||||||
|
|
||||||
|
|
||||||
for (int i = 40; i < 60; i+=1) {
|
|
||||||
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
|
|
||||||
|
|
||||||
Resource imageResource = new Resource("", "C:\\Users\\Thinkpad\\Desktop\\111.jpg");
|
|
||||||
inputStream = new FileResourceProvider().doGetResourceInputStream(imageResource);
|
|
||||||
BufferedImage bufferedImage = CaptchaImageUtils.wrapFile2BufferedImage(inputStream);
|
|
||||||
|
|
||||||
Graphics graphics = bufferedImage.getGraphics();
|
|
||||||
graphics.setColor(Color.PINK);
|
|
||||||
//设置角度
|
|
||||||
AffineTransform affineTransform = new AffineTransform();
|
|
||||||
affineTransform.rotate(Math.toRadians(180), i , -i);
|
|
||||||
Font rotatedFont = font.deriveFont(affineTransform);
|
|
||||||
graphics.setFont(rotatedFont);
|
|
||||||
char ch = FontUtils.getRandomChar();
|
|
||||||
// graphics.drawString(String.valueOf(ch), 10, 10+metrics.getAscent());
|
|
||||||
// graphics.drawString(String.valueOf(ch), 50, 50);
|
|
||||||
String randomWord = "张"/*String.valueOf(FontUtils.getRandomChar())*/;
|
|
||||||
int wordWidth = metrics.stringWidth(randomWord);
|
|
||||||
int wordHeight = metrics.getHeight();
|
|
||||||
// //左边位置
|
|
||||||
int left = (bufferedImage.getWidth()-wordWidth)/2;
|
|
||||||
// //顶边位置+上升距离(原本字体基线位置对准画布的y坐标导致字体偏上ascent距离,加上ascent后下移刚好顶边吻合)
|
|
||||||
int top = (bufferedImage.getHeight()-wordHeight)/2+metrics.getAscent();
|
|
||||||
|
|
||||||
// 随机生成6个字的居中的 x 和 y
|
|
||||||
// for (int i = 0; i < 6; i++) {
|
|
||||||
|
|
||||||
|
|
||||||
// int randomX = ThreadLocalRandom.current().nextInt(10, bufferedImage.getWidth() - wordWidth);
|
|
||||||
// int randomY = ThreadLocalRandom.current().nextInt(10, bufferedImage.getHeight() - wordHeight);
|
|
||||||
|
|
||||||
graphics.drawString(randomWord, left, top);
|
|
||||||
|
|
||||||
System.out.println(randomWord + "-->x:" + (left + wordWidth / 2) + ",y:" + (left + wordHeight / 2));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// graphics.drawString("居中文字",0,metrics.getAscent()); //基线对齐改为顶边对齐
|
|
||||||
ImageIO.write(bufferedImage, "jpeg", new FileOutputStream("C:\\Users\\Thinkpad\\Desktop\\111\\"+i+".jpg"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.impl;
|
package cloud.tianai.captcha.template.slider.generator.impl;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
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.GenerateParam;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
||||||
@@ -17,7 +17,7 @@ import java.util.Collection;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils.*;
|
import static cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils.*;
|
||||||
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH;
|
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
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.generator.common.model.dto.ClickImageCheckDefinition;
|
||||||
|
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;
|
||||||
|
import cloud.tianai.captcha.template.slider.resource.impl.provider.ClassPathResourceProvider;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import sun.font.FontDesignMetrics;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: 天爱有情
|
||||||
|
* @date 2022/4/27 11:46
|
||||||
|
* @Description 点选验证码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickImageCaptchaGenerator {
|
||||||
|
|
||||||
|
protected ImageCaptchaResourceManager imageCaptchaResourceManager;
|
||||||
|
/** 字体包.*/
|
||||||
|
protected Font font;
|
||||||
|
protected FontDesignMetrics metrics;
|
||||||
|
protected Integer clickImgWidth = 60;
|
||||||
|
protected Integer clickImgHeight = 60;
|
||||||
|
protected int tipImageInterferenceLineNum = 2;
|
||||||
|
protected int tipImageInterferencePointNum = 5;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public StandardRandomWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, boolean initDefaultResource) {
|
||||||
|
this.imageCaptchaResourceManager = imageCaptchaResourceManager;
|
||||||
|
if (initDefaultResource) {
|
||||||
|
initDefaultResource();
|
||||||
|
}
|
||||||
|
// 使用默认字体
|
||||||
|
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);
|
||||||
|
this.metrics = FontDesignMetrics.getMetrics(font);
|
||||||
|
this.font = font;
|
||||||
|
setClickImgHeight(60);
|
||||||
|
setClickImgWidth(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StandardRandomWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager,
|
||||||
|
boolean initDefaultResource,
|
||||||
|
Font font) {
|
||||||
|
this.imageCaptchaResourceManager = imageCaptchaResourceManager;
|
||||||
|
this.font = font;
|
||||||
|
this.metrics = FontDesignMetrics.getMetrics(font);
|
||||||
|
setClickImgWidth(font.getSize() + 10);
|
||||||
|
setClickImgHeight(font.getSize() + 10);
|
||||||
|
if (initDefaultResource) {
|
||||||
|
initDefaultResource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initDefaultResource() {
|
||||||
|
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
|
||||||
|
// 添加一些系统的资源文件
|
||||||
|
resourceStore.addResource(CaptchaTypeConstant.IMAGE_CLICK, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ImgWrapper genTipImage(List<ClickImageCheckDefinition> imageCheckDefinitions) {
|
||||||
|
String tips = imageCheckDefinitions.stream().map(ClickImageCheckDefinition::getTip).collect(Collectors.joining());
|
||||||
|
// 生成随机颜色
|
||||||
|
int fontWidth = metrics.stringWidth(tips);
|
||||||
|
int width = fontWidth + 5;
|
||||||
|
int height = metrics.getHeight() + 5;
|
||||||
|
float left = (width - fontWidth) / 2f;
|
||||||
|
float top = 5 / 2f + metrics.getAscent();
|
||||||
|
BufferedImage bufferedImage = CaptchaImageUtils.genSimpleImgCaptcha(tips,
|
||||||
|
font, metrics, width, height, left, top, tipImageInterferenceLineNum, tipImageInterferencePointNum);
|
||||||
|
return new ImgWrapper(bufferedImage, tips);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ImgWrapper randomGetClickImg() {
|
||||||
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
|
// 随机文字
|
||||||
|
String randomWord = FontUtils.getRandomChar(random);
|
||||||
|
// 随机颜色
|
||||||
|
Color randomColor = CaptchaImageUtils.getRandomColor(random);
|
||||||
|
// 随机角度
|
||||||
|
int randomDeg = ThreadLocalRandom.current().nextInt(0, 85);
|
||||||
|
BufferedImage fontImage = CaptchaImageUtils.drawWordImg(randomColor,
|
||||||
|
randomWord,
|
||||||
|
font,
|
||||||
|
this.metrics,
|
||||||
|
clickImgWidth,
|
||||||
|
clickImgHeight,
|
||||||
|
randomDeg);
|
||||||
|
return new ImgWrapper(fontImage, randomWord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageCaptchaResourceManager getImageResourceManager() {
|
||||||
|
return imageCaptchaResourceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.impl;
|
package cloud.tianai.captcha.template.slider.generator.impl;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils;
|
import cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils;
|
||||||
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.SliderCaptchaConstant;
|
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.GenerateParam;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
@@ -25,7 +25,7 @@ import java.util.LinkedList;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils.*;
|
import static cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils.*;
|
||||||
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH;
|
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH;
|
||||||
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;
|
import static cloud.tianai.captcha.template.slider.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
package cloud.tianai.captcha.template.slider.generator.impl;
|
package cloud.tianai.captcha.template.slider.generator.impl;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.SliderCaptchaConstant;
|
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.GenerateParam;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
@@ -22,7 +22,7 @@ import java.util.LinkedList;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils.*;
|
import static cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: 天爱有情
|
* @Author: 天爱有情
|
||||||
|
|||||||
+4
-4
@@ -15,11 +15,11 @@ public interface ImageCaptchaValidator {
|
|||||||
/**
|
/**
|
||||||
* 计算滑块要背景图的百分比,基本校验
|
* 计算滑块要背景图的百分比,基本校验
|
||||||
*
|
*
|
||||||
* @param x 凹槽的x轴
|
* @param pos 移动的位置
|
||||||
* @param bgImageWidth 背景图片的宽度
|
* @param maxPos 最大可移动的位置
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float calcPercentage(int x, int bgImageWidth);
|
float calcPercentage(Number pos, Number maxPos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验滑块百分比
|
* 校验滑块百分比
|
||||||
@@ -46,7 +46,7 @@ public interface ImageCaptchaValidator {
|
|||||||
* @param imageCaptchaInfo 生成的验证码数据
|
* @param imageCaptchaInfo 生成的验证码数据
|
||||||
* @return Map<String, Object>
|
* @return Map<String, Object>
|
||||||
*/
|
*/
|
||||||
Map<String, Object> generateSliderCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo);
|
Map<String, Object> generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验用户滑动滑块是否正确
|
* 校验用户滑动滑块是否正确
|
||||||
|
|||||||
+115
-17
@@ -1,8 +1,10 @@
|
|||||||
package cloud.tianai.captcha.template.slider.validator.impl;
|
package cloud.tianai.captcha.template.slider.validator.impl;
|
||||||
|
|
||||||
|
import cloud.tianai.captcha.template.slider.common.constant.CaptchaTypeConstant;
|
||||||
|
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.CollectionUtils;
|
||||||
import cloud.tianai.captcha.template.slider.common.util.ObjectUtils;
|
import cloud.tianai.captcha.template.slider.common.util.ObjectUtils;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
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.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
import cloud.tianai.captcha.template.slider.validator.ImageCaptchaValidator;
|
import cloud.tianai.captcha.template.slider.validator.ImageCaptchaValidator;
|
||||||
import cloud.tianai.captcha.template.slider.validator.common.model.dto.SliderCaptchaTrack;
|
import cloud.tianai.captcha.template.slider.validator.common.model.dto.SliderCaptchaTrack;
|
||||||
@@ -40,8 +42,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float calcPercentage(int x, int bgImageWidth) {
|
public float calcPercentage(Number pos, Number maxPos) {
|
||||||
return (float) x / bgImageWidth;
|
return pos.floatValue() / maxPos.floatValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,9 +64,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> generateSliderCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
|
public Map<String, Object> generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
|
||||||
Map<String, Object> map = new HashMap<>(8);
|
Map<String, Object> map = new HashMap<>(8);
|
||||||
addPercentage(imageCaptchaInfo, map);
|
|
||||||
// 容错值
|
// 容错值
|
||||||
Float tolerant = imageCaptchaInfo.getTolerant();
|
Float tolerant = imageCaptchaInfo.getTolerant();
|
||||||
if (tolerant != null && tolerant > 0) {
|
if (tolerant != null && tolerant > 0) {
|
||||||
@@ -76,17 +77,58 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
|
|||||||
type = CaptchaTypeConstant.SLIDER;
|
type = CaptchaTypeConstant.SLIDER;
|
||||||
}
|
}
|
||||||
map.put(TYPE_KEY, type);
|
map.put(TYPE_KEY, type);
|
||||||
|
|
||||||
|
return afterGenerateImageCaptchaValidData(map, imageCaptchaInfo, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> afterGenerateImageCaptchaValidData(Map<String, Object> map,
|
||||||
|
ImageCaptchaInfo imageCaptchaInfo,
|
||||||
|
String type) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
List<ClickImageCheckDefinition> clickImageCheckDefinitionList = null;
|
||||||
|
try {
|
||||||
|
clickImageCheckDefinitionList = (List<ClickImageCheckDefinition>) expand;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("点选验证码扩展数据转换为 List<ClickImageCheckDefinition> 失败, info=" + imageCaptchaInfo);
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < clickImageCheckDefinitionList.size(); i++) {
|
||||||
|
ClickImageCheckDefinition definition = clickImageCheckDefinitionList.get(i);
|
||||||
|
Integer x = definition.getX();
|
||||||
|
Integer y = definition.getY();
|
||||||
|
Integer width = imageCaptchaInfo.getBgImageWidth();
|
||||||
|
Integer height = imageCaptchaInfo.getBgImageHeight();
|
||||||
|
float vx = calcPercentage(x, width);
|
||||||
|
float vy = calcPercentage(y, height);
|
||||||
|
sb.append(vx).append(",").append(vy).append(";");
|
||||||
|
if (i == 0 && !map.containsKey(TOLERANT_KEY)) {
|
||||||
|
// 重新计算容错值
|
||||||
|
float minLeft = calcPercentage(x - definition.getWidth() / 2f, width);
|
||||||
|
float tolerant = vx - minLeft;
|
||||||
|
map.put(TOLERANT_KEY, tolerant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加点选验证
|
||||||
|
map.put(PERCENTAGE_KEY, sb.toString());
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData) {
|
public boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData) {
|
||||||
Float oriPercentage = getFloatParam(PERCENTAGE_KEY, sliderCaptchaValidData);
|
|
||||||
// 读容错值
|
// 读容错值
|
||||||
Float tolerantData = getFloatParam(TOLERANT_KEY, sliderCaptchaValidData, defaultTolerant);
|
Float tolerantData = getFloatParam(TOLERANT_KEY, sliderCaptchaValidData, defaultTolerant);
|
||||||
// 读验证码类型
|
// 读验证码类型
|
||||||
String type = getStringParam(TYPE_KEY, sliderCaptchaValidData, CaptchaTypeConstant.SLIDER);
|
String type = getStringParam(TYPE_KEY, sliderCaptchaValidData, CaptchaTypeConstant.SLIDER);
|
||||||
|
|
||||||
Integer bgImageWidth = sliderCaptchaTrack.getBgImageWidth();
|
Integer bgImageWidth = sliderCaptchaTrack.getBgImageWidth();
|
||||||
if (bgImageWidth == null || bgImageWidth < 1) {
|
if (bgImageWidth == null || bgImageWidth < 1) {
|
||||||
// 没有背景图片宽度
|
// 没有背景图片宽度
|
||||||
@@ -97,17 +139,77 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
|
|||||||
// 没有滑动轨迹
|
// 没有滑动轨迹
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return doValid(sliderCaptchaTrack, sliderCaptchaValidData, oriPercentage, tolerantData, type);
|
return afterValid(sliderCaptchaTrack, sliderCaptchaValidData, tolerantData, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doValid(SliderCaptchaTrack sliderCaptchaTrack,
|
public boolean afterValid(SliderCaptchaTrack sliderCaptchaTrack,
|
||||||
Map<String, Object> sliderCaptchaValidData,
|
Map<String, Object> sliderCaptchaValidData,
|
||||||
Float oriPercentage,
|
|
||||||
Float tolerant,
|
Float tolerant,
|
||||||
String type) {
|
String type) {
|
||||||
if (CaptchaTypeConstant.SLIDER.equals(type)
|
if (CaptchaUtils.isSliderCaptcha(type)) {
|
||||||
|| CaptchaTypeConstant.ROTATE.equals(type)
|
// 滑动类型验证码
|
||||||
|| CaptchaTypeConstant.CONCAT.equals(type)) {
|
return doValidSliderCaptcha(sliderCaptchaTrack, sliderCaptchaValidData, tolerant, type);
|
||||||
|
} else if (CaptchaUtils.isClickCaptcha(type)) {
|
||||||
|
// 点选类型验证码
|
||||||
|
return doValidClickCaptcha(sliderCaptchaTrack, sliderCaptchaValidData, tolerant, type);
|
||||||
|
}
|
||||||
|
// 不支持的类型
|
||||||
|
log.warn("校验验证码警告, 不支持的验证码类型:{}, 请手动扩展 cloud.tianai.captcha.template.slider.validator.impl.SimpleImageCaptchaValidator.doValid 进行校验扩展", type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验点选验证码
|
||||||
|
* @param sliderCaptchaTrack sliderCaptchaTrack
|
||||||
|
* @param sliderCaptchaValidData sliderCaptchaValidData
|
||||||
|
* @param tolerant tolerant
|
||||||
|
* @param type type
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public boolean doValidClickCaptcha(SliderCaptchaTrack sliderCaptchaTrack,
|
||||||
|
Map<String, Object> sliderCaptchaValidData,
|
||||||
|
Float tolerant,
|
||||||
|
String type) {
|
||||||
|
String validStr = getStringParam(PERCENTAGE_KEY, sliderCaptchaValidData, null);
|
||||||
|
if (ObjectUtils.isEmpty(validStr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String[] splitArr = validStr.split(";");
|
||||||
|
List<SliderCaptchaTrack.Track> trackList = sliderCaptchaTrack.getTrackList();
|
||||||
|
if (trackList.size() != splitArr.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < splitArr.length; i++) {
|
||||||
|
SliderCaptchaTrack.Track track = trackList.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());
|
||||||
|
|
||||||
|
if (!checkPercentage(calcXPercentage, xPercentage, tolerant)
|
||||||
|
|| !checkPercentage(calcYPercentage, yPercentage, tolerant)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验滑动验证码
|
||||||
|
* @param sliderCaptchaTrack sliderCaptchaTrack
|
||||||
|
* @param sliderCaptchaValidData sliderCaptchaValidData
|
||||||
|
* @param tolerant tolerant
|
||||||
|
* @param type type
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public boolean doValidSliderCaptcha(SliderCaptchaTrack sliderCaptchaTrack,
|
||||||
|
Map<String, Object> sliderCaptchaValidData,
|
||||||
|
Float tolerant,
|
||||||
|
String type) {
|
||||||
|
Float oriPercentage = getFloatParam(PERCENTAGE_KEY, sliderCaptchaValidData);
|
||||||
if (oriPercentage == null) {
|
if (oriPercentage == null) {
|
||||||
// 没读取到百分比
|
// 没读取到百分比
|
||||||
return false;
|
return false;
|
||||||
@@ -120,10 +222,6 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
|
|||||||
// 校验百分比
|
// 校验百分比
|
||||||
return checkPercentage(calcPercentage, oriPercentage, tolerant);
|
return checkPercentage(calcPercentage, oriPercentage, tolerant);
|
||||||
}
|
}
|
||||||
// 不支持的类型
|
|
||||||
log.warn("校验验证码警告, 不支持的验证码类型:{}, 请手动扩展 cloud.tianai.captcha.template.slider.validator.impl.SimpleImageCaptchaValidator.doValid 进行校验扩展", type);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Float getFloatParam(String key, Map<String, Object> sliderCaptchaValidData) {
|
protected Float getFloatParam(String key, Map<String, Object> sliderCaptchaValidData) {
|
||||||
return getFloatParam(key, sliderCaptchaValidData, null);
|
return getFloatParam(key, sliderCaptchaValidData, null);
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package example;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class AnchorPoint implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2L;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保持charImgWidth的最大值+1
|
||||||
|
*/
|
||||||
|
private Integer x_iv = 26;
|
||||||
|
/**
|
||||||
|
* 保持charImgHeight的最大值+1
|
||||||
|
*/
|
||||||
|
private Integer y_iv = 21;
|
||||||
|
|
||||||
|
private Integer x;
|
||||||
|
private Integer y;
|
||||||
|
|
||||||
|
public AnchorPoint(){
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public AnchorPoint(Integer x, Integer y){
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Integer getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setX(Integer x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setY(Integer y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
AnchorPoint that = (AnchorPoint) o;
|
||||||
|
|
||||||
|
//当X位置小于偏移量进行判段Y轴的偏移量
|
||||||
|
if(this.x+this.x_iv > that.getX() || this.x - this.x_iv < that.getX()){
|
||||||
|
//当Y轴的偏移量符合安全距离 判断锚点有效
|
||||||
|
if(this.y+this.y_iv < that.getY() || this.y-this.y_iv > that.getY()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
//两个对象的X点保持在偏移量之外无需比较Y轴位置,判定锚点有效
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DrawXInteger{" +
|
||||||
|
"x=" + x +
|
||||||
|
", y=" + y +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package example;
|
||||||
|
|
||||||
|
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 sun.font.FontDesignMetrics;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制验证码
|
||||||
|
*/
|
||||||
|
public class DrawCaptchaUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布宽度
|
||||||
|
*/
|
||||||
|
private static int canvasWidth = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布高度
|
||||||
|
*/
|
||||||
|
private static int canvasHeight = 150;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片宽度
|
||||||
|
*/
|
||||||
|
private static int imgWidth = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片高度
|
||||||
|
*/
|
||||||
|
private static int imgHeight = 150;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图中字体数量
|
||||||
|
*/
|
||||||
|
private static int charNumber = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字体大小
|
||||||
|
*/
|
||||||
|
private static int fontSize = 50;
|
||||||
|
/**
|
||||||
|
* 字体图片宽度
|
||||||
|
*/
|
||||||
|
private static int charImgWidth = 60;
|
||||||
|
/**
|
||||||
|
* 字体图片高度
|
||||||
|
*/
|
||||||
|
private static int charImgHeight = 60;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制验证码图片
|
||||||
|
* @param checkCode
|
||||||
|
* @param bkDirPath
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static BufferedImage drawImg(LinkedHashMap<String, AnchorPoint> checkCode, String bkDirPath) throws IOException {
|
||||||
|
|
||||||
|
BufferedImage imgCanvas = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_BGR);
|
||||||
|
Graphics graphics = imgCanvas.getGraphics();
|
||||||
|
|
||||||
|
|
||||||
|
//绘制图形
|
||||||
|
// graphics.setColor(new Color(0,0,0,0));
|
||||||
|
graphics.fillRect(0, 0, imgWidth, imgHeight);
|
||||||
|
|
||||||
|
File bkPath = randomBackGroundImage(bkDirPath);
|
||||||
|
|
||||||
|
BufferedImage read = ImageIO.read(Files.newInputStream(Paths.get(bkPath.getAbsolutePath())));
|
||||||
|
graphics.drawImage(read, 0, 0, null, null);
|
||||||
|
//绘制字体
|
||||||
|
// graphics.setColor(getRandomColor());
|
||||||
|
//随机在图片中生成5个绘制点每个绘制点半径能不能重叠
|
||||||
|
Set<AnchorPoint> point = new HashSet<>();
|
||||||
|
|
||||||
|
addPoint(point);
|
||||||
|
Object[] pointArr = point.toArray();
|
||||||
|
List<String> charList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < charNumber; i++) {
|
||||||
|
AnchorPoint p = (AnchorPoint) pointArr[i];
|
||||||
|
graphics.drawImage(getCharImg(getRandomColor(), charList), p.getX(), p.getY(), null, null);
|
||||||
|
|
||||||
|
}
|
||||||
|
//随机获取3个字符串作为校验对象
|
||||||
|
Object[] charArr = charList.toArray();
|
||||||
|
Random random = new Random();
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
int num = random.nextInt(pointArr.length - i);
|
||||||
|
String key = (String) charArr[num];
|
||||||
|
AnchorPoint value = (AnchorPoint) pointArr[num];
|
||||||
|
checkCode.put(key, value);
|
||||||
|
//置换位置
|
||||||
|
Object a = pointArr[num];
|
||||||
|
pointArr[num] = pointArr[pointArr.length - 1 - i];
|
||||||
|
pointArr[pointArr.length - 1 - i] = a;
|
||||||
|
|
||||||
|
a = charArr[num];
|
||||||
|
charArr[num] = charArr[charArr.length - 1 - i];
|
||||||
|
charArr[charArr.length - 1 - i] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
return imgCanvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在指定目录下随机获取背景图片
|
||||||
|
* @param dir
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static File randomBackGroundImage(String dir) {
|
||||||
|
File file = new File(dir);
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
File[] files = file.listFiles();
|
||||||
|
Random random = new Random();
|
||||||
|
int i = random.nextInt(files.length);
|
||||||
|
return files[i];
|
||||||
|
} else {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加坐标点
|
||||||
|
* @param point
|
||||||
|
*/
|
||||||
|
private static void addPoint(Set<AnchorPoint> point) {
|
||||||
|
Random random = new Random();
|
||||||
|
//避免因为坐标点过于特殊导致无法获取下一个坐标点进入死循环,到100置空重新获取
|
||||||
|
int clearNumber = 0;
|
||||||
|
//生成前2个点坐标
|
||||||
|
while (point.size() < 2) {
|
||||||
|
|
||||||
|
//添加X坐标点
|
||||||
|
point.add(new AnchorPoint(random.nextInt(canvasWidth / 3), random.nextInt(canvasHeight - charImgWidth)));
|
||||||
|
if (clearNumber == 100) {
|
||||||
|
point.clear();
|
||||||
|
clearNumber = 0;
|
||||||
|
}
|
||||||
|
clearNumber++;
|
||||||
|
|
||||||
|
}
|
||||||
|
clearNumber = 0;
|
||||||
|
//生成后3个点坐标
|
||||||
|
while (point.size() < charNumber) {
|
||||||
|
//添加X坐标点
|
||||||
|
point.add(new AnchorPoint(random.nextInt(canvasWidth - charImgWidth), random.nextInt(canvasHeight - charImgHeight)));
|
||||||
|
if (clearNumber == 100) {
|
||||||
|
point.clear();
|
||||||
|
clearNumber = 0;
|
||||||
|
addPoint(point);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
clearNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文字图片
|
||||||
|
* @param fontColor
|
||||||
|
* @param charList
|
||||||
|
* @return
|
||||||
|
* @throws UnsupportedEncodingException
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
private static BufferedImage getCharImg(Color fontColor, List<String> charList) throws IOException {
|
||||||
|
BufferedImage fillRect = new BufferedImage(charImgWidth, charImgHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g = fillRect.createGraphics();
|
||||||
|
g.setColor(new Color(0, 0, 0, 0));
|
||||||
|
g.fillRect(0, 0, charImgWidth, charImgHeight);
|
||||||
|
g.setColor(fontColor);
|
||||||
|
Resource fontResource = new Resource("", "META-INF/fonts/SIMSUN.TTC");
|
||||||
|
InputStream inputStream = new ClassPathResourceProvider().doGetResourceInputStream(fontResource);
|
||||||
|
Font font = Font.createFont(Font.TRUETYPE_FONT, inputStream);
|
||||||
|
font = font.deriveFont(Font.BOLD, fontSize);
|
||||||
|
// Font font = new Font("微软雅黑", Font.BOLD, fontSize);
|
||||||
|
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
|
||||||
|
// AffineTransform affineTransform = new AffineTransform();
|
||||||
|
// affineTransform.rotate(Math.toRadians(85), 0, 0);
|
||||||
|
// font.deriveFont(affineTransform);
|
||||||
|
g.setFont(font);
|
||||||
|
String charStr = getChineseCharacters();
|
||||||
|
charList.add(charStr);
|
||||||
|
|
||||||
|
float left = (60 - 50) / 2f;
|
||||||
|
// //顶边位置+上升距离(原本字体基线位置对准画布的y坐标导致字体偏上ascent距离,加上ascent后下移刚好顶边吻合)
|
||||||
|
float top = (60 - 50) / 2f + metrics.getAscent() - 10;
|
||||||
|
|
||||||
|
g.rotate(Math.toRadians(ThreadLocalRandom.current().nextInt(0, 85)), 30, 30);
|
||||||
|
g.drawString(charStr, left, top);
|
||||||
|
g.dispose();
|
||||||
|
return fillRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机获取颜色
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Color getRandomColor() {
|
||||||
|
Random random = new Random();
|
||||||
|
return new Color(
|
||||||
|
random.nextInt(255),
|
||||||
|
random.nextInt(255),
|
||||||
|
random.nextInt(255));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机获取中文字
|
||||||
|
* @return
|
||||||
|
* @throws UnsupportedEncodingException
|
||||||
|
*/
|
||||||
|
private static String getChineseCharacters() throws UnsupportedEncodingException {
|
||||||
|
Integer hightPos, lowPos; // 定义高低位
|
||||||
|
Random random = new Random();
|
||||||
|
hightPos = (176 + Math.abs(random.nextInt(39)));
|
||||||
|
lowPos = (161 + Math.abs(random.nextInt(93)));
|
||||||
|
byte[] bytes = new byte[2];
|
||||||
|
bytes[0] = hightPos.byteValue();
|
||||||
|
bytes[1] = lowPos.byteValue();
|
||||||
|
return new String(bytes, "GBK");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
BufferedImage charImg = getCharImg(getRandomColor(), new ArrayList<>());
|
||||||
|
ImageIO.write(charImg, "png", new FileOutputStream("C:\\Users\\tianai\\Desktop\\111\\" + i + ".png"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package example;
|
package example;
|
||||||
|
|
||||||
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.constant.CaptchaTypeConstant;
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.RotateImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.RotateImageCaptchaInfo;
|
||||||
import cloud.tianai.captcha.template.slider.generator.impl.CacheImageCaptchaGenerator;
|
import cloud.tianai.captcha.template.slider.generator.impl.CacheImageCaptchaGenerator;
|
||||||
|
|||||||
@@ -1,23 +1,33 @@
|
|||||||
package example;
|
package example;
|
||||||
|
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.constant.CaptchaTypeConstant;
|
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.GenerateParam;
|
||||||
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
import cloud.tianai.captcha.template.slider.generator.common.model.dto.ImageCaptchaInfo;
|
||||||
import cloud.tianai.captcha.template.slider.generator.impl.StandardClickImageCaptchaGenerator;
|
import cloud.tianai.captcha.template.slider.generator.impl.StandardRandomWordClickImageCaptchaGenerator;
|
||||||
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
|
||||||
import cloud.tianai.captcha.template.slider.resource.impl.DefaultImageCaptchaResourceManager;
|
import cloud.tianai.captcha.template.slider.resource.impl.DefaultImageCaptchaResourceManager;
|
||||||
|
import cloud.tianai.captcha.template.slider.validator.impl.BasicCaptchaTrackValidator;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class StandardWordClickImageCaptchaGeneratorTest {
|
public class StandardWordClickImageCaptchaGeneratorTest {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
ImageCaptchaResourceManager imageCaptchaResourceManager = new DefaultImageCaptchaResourceManager();
|
ImageCaptchaResourceManager imageCaptchaResourceManager = new DefaultImageCaptchaResourceManager();
|
||||||
|
|
||||||
StandardClickImageCaptchaGenerator defaultImageCaptchaResourceManager =
|
StandardRandomWordClickImageCaptchaGenerator defaultImageCaptchaResourceManager =
|
||||||
new StandardClickImageCaptchaGenerator(imageCaptchaResourceManager, true);
|
new StandardRandomWordClickImageCaptchaGenerator(imageCaptchaResourceManager, true);
|
||||||
|
|
||||||
GenerateParam generateParam = new GenerateParam();
|
GenerateParam generateParam = new GenerateParam();
|
||||||
generateParam.setType(CaptchaTypeConstant.WORD_CLICK);
|
generateParam.setType(CaptchaTypeConstant.IMAGE_CLICK);
|
||||||
|
generateParam.setSliderFormatName("png");
|
||||||
ImageCaptchaInfo imageCaptchaInfo = defaultImageCaptchaResourceManager.generateCaptchaImage(generateParam);
|
ImageCaptchaInfo imageCaptchaInfo = defaultImageCaptchaResourceManager.generateCaptchaImage(generateParam);
|
||||||
|
|
||||||
|
BasicCaptchaTrackValidator basicCaptchaTrackValidator = new BasicCaptchaTrackValidator();
|
||||||
|
Map<String, Object> stringObjectMap = basicCaptchaTrackValidator.generateImageCaptchaValidData(imageCaptchaInfo);
|
||||||
|
|
||||||
|
System.out.println(stringObjectMap);
|
||||||
|
|
||||||
|
System.out.println(imageCaptchaInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user