添加点选验证码 点选文字验证码完成

This commit is contained in:
天爱有情
2022-04-28 17:49:40 +08:00
parent 44f58f1c93
commit 19d06376a7
23 changed files with 979 additions and 307 deletions
@@ -1,4 +1,4 @@
package cloud.tianai.captcha.template.slider.generator.common.constant;
package cloud.tianai.captcha.template.slider.common.constant;
/**
* @Author: 天爱有情
@@ -13,6 +13,6 @@ public interface CaptchaTypeConstant {
String ROTATE = "ROTATE";
/** 拼接.*/
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;
import lombok.SneakyThrows;
import java.awt.*;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* @Author: 天爱有情
* @date 2022/4/27 11:34
@@ -7,11 +13,17 @@ package cloud.tianai.captcha.template.slider.common.util;
*/
public class FontUtils {
public static char getRandomChar() {
return (char)(0x4e00 + (int)(Math.random()*(0x9fa5 - 0x4e00 + 1)));
@SneakyThrows
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,6 +1,6 @@
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.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
@@ -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,6 +1,6 @@
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.*;
/**
@@ -1,6 +1,6 @@
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.EqualsAndHashCode;
import lombok.ToString;
@@ -19,7 +19,7 @@ public class RotateImageCaptchaInfo extends ImageCaptchaInfo {
*/
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,
Integer randomX,
@@ -1,6 +1,6 @@
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.EqualsAndHashCode;
import lombok.ToString;
@@ -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;
}
}
@@ -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 sun.font.FontDesignMetrics;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Area;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.QuadCurve2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;
@@ -12,6 +15,8 @@ import java.awt.image.WritableRaster;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* @Author: 天爱有情
@@ -314,7 +319,146 @@ public class CaptchaImageUtils {
}
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));
}
}
@@ -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;
}
}
@@ -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.generator.AbstractImageCaptchaGenerator;
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.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
@@ -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"));
}
}
}
@@ -1,7 +1,7 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.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.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.resource.ImageCaptchaResourceManager;
@@ -17,7 +17,7 @@ import java.util.Collection;
import java.util.LinkedList;
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;
/**
@@ -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;
}
}
@@ -1,8 +1,8 @@
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.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.model.dto.GenerateParam;
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.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_TEMPLATE_PATH;
@@ -1,7 +1,7 @@
package cloud.tianai.captcha.template.slider.generator.impl;
import cloud.tianai.captcha.template.slider.generator.AbstractImageCaptchaGenerator;
import cloud.tianai.captcha.template.slider.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.model.dto.GenerateParam;
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.concurrent.ThreadLocalRandom;
import static cloud.tianai.captcha.template.slider.common.util.CaptchaImageUtils.*;
import static cloud.tianai.captcha.template.slider.generator.common.util.CaptchaImageUtils.*;
/**
* @Author: 天爱有情
@@ -15,11 +15,11 @@ public interface ImageCaptchaValidator {
/**
* 计算滑块要背景图的百分比,基本校验
*
* @param x 凹槽的x轴
* @param bgImageWidth 背景图片的宽度
* @param pos 移动的位置
* @param maxPos 最大可移动的位置
* @return float
*/
float calcPercentage(int x, int bgImageWidth);
float calcPercentage(Number pos, Number maxPos);
/**
* 校验滑块百分比
@@ -46,7 +46,7 @@ public interface ImageCaptchaValidator {
* @param imageCaptchaInfo 生成的验证码数据
* @return Map<String, Object>
*/
Map<String, Object> generateSliderCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo);
Map<String, Object> generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo);
/**
* 校验用户滑动滑块是否正确
@@ -1,8 +1,10 @@
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.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.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.template.slider.validator.common.model.dto.SliderCaptchaTrack;
@@ -40,8 +42,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
}
@Override
public float calcPercentage(int x, int bgImageWidth) {
return (float) x / bgImageWidth;
public float calcPercentage(Number pos, Number maxPos) {
return pos.floatValue() / maxPos.floatValue();
}
@Override
@@ -62,9 +64,8 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
}
@Override
public Map<String, Object> generateSliderCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
public Map<String, Object> generateImageCaptchaValidData(ImageCaptchaInfo imageCaptchaInfo) {
Map<String, Object> map = new HashMap<>(8);
addPercentage(imageCaptchaInfo, map);
// 容错值
Float tolerant = imageCaptchaInfo.getTolerant();
if (tolerant != null && tolerant > 0) {
@@ -76,17 +77,58 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
type = CaptchaTypeConstant.SLIDER;
}
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;
}
@Override
public boolean valid(SliderCaptchaTrack sliderCaptchaTrack, Map<String, Object> sliderCaptchaValidData) {
Float oriPercentage = getFloatParam(PERCENTAGE_KEY, sliderCaptchaValidData);
// 读容错值
Float tolerantData = getFloatParam(TOLERANT_KEY, sliderCaptchaValidData, defaultTolerant);
// 读验证码类型
String type = getStringParam(TYPE_KEY, sliderCaptchaValidData, CaptchaTypeConstant.SLIDER);
Integer bgImageWidth = sliderCaptchaTrack.getBgImageWidth();
if (bgImageWidth == null || bgImageWidth < 1) {
// 没有背景图片宽度
@@ -97,34 +139,90 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator {
// 没有滑动轨迹
return false;
}
return doValid(sliderCaptchaTrack, sliderCaptchaValidData, oriPercentage, tolerantData, type);
return afterValid(sliderCaptchaTrack, sliderCaptchaValidData, tolerantData, type);
}
public boolean doValid(SliderCaptchaTrack sliderCaptchaTrack,
Map<String, Object> sliderCaptchaValidData,
Float oriPercentage,
Float tolerant,
String type) {
if (CaptchaTypeConstant.SLIDER.equals(type)
|| CaptchaTypeConstant.ROTATE.equals(type)
|| CaptchaTypeConstant.CONCAT.equals(type)) {
if (oriPercentage == null) {
// 没读取到百分比
return false;
}
List<SliderCaptchaTrack.Track> trackList = sliderCaptchaTrack.getTrackList();
// 取最后一个滑动轨迹
SliderCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1);
// 计算百分比
float calcPercentage = calcPercentage(lastTrack.getX(), sliderCaptchaTrack.getBgImageWidth());
// 校验百分比
return checkPercentage(calcPercentage, oriPercentage, tolerant);
public boolean afterValid(SliderCaptchaTrack sliderCaptchaTrack,
Map<String, Object> sliderCaptchaValidData,
Float tolerant,
String type) {
if (CaptchaUtils.isSliderCaptcha(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) {
// 没读取到百分比
return false;
}
List<SliderCaptchaTrack.Track> trackList = sliderCaptchaTrack.getTrackList();
// 取最后一个滑动轨迹
SliderCaptchaTrack.Track lastTrack = trackList.get(trackList.size() - 1);
// 计算百分比
float calcPercentage = calcPercentage(lastTrack.getX(), sliderCaptchaTrack.getBgImageWidth());
// 校验百分比
return checkPercentage(calcPercentage, oriPercentage, tolerant);
}
protected Float getFloatParam(String key, Map<String, Object> sliderCaptchaValidData) {
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;
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.ImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.generator.common.model.dto.RotateImageCaptchaInfo;
import cloud.tianai.captcha.template.slider.generator.impl.CacheImageCaptchaGenerator;
@@ -1,23 +1,33 @@
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.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.impl.DefaultImageCaptchaResourceManager;
import cloud.tianai.captcha.template.slider.validator.impl.BasicCaptchaTrackValidator;
import java.util.Map;
public class StandardWordClickImageCaptchaGeneratorTest {
public static void main(String[] args) {
ImageCaptchaResourceManager imageCaptchaResourceManager = new DefaultImageCaptchaResourceManager();
StandardClickImageCaptchaGenerator defaultImageCaptchaResourceManager =
new StandardClickImageCaptchaGenerator(imageCaptchaResourceManager, true);
StandardRandomWordClickImageCaptchaGenerator defaultImageCaptchaResourceManager =
new StandardRandomWordClickImageCaptchaGenerator(imageCaptchaResourceManager, true);
GenerateParam generateParam = new GenerateParam();
generateParam.setType(CaptchaTypeConstant.WORD_CLICK);
generateParam.setType(CaptchaTypeConstant.IMAGE_CLICK);
generateParam.setSliderFormatName("png");
ImageCaptchaInfo imageCaptchaInfo = defaultImageCaptchaResourceManager.generateCaptchaImage(generateParam);
BasicCaptchaTrackValidator basicCaptchaTrackValidator = new BasicCaptchaTrackValidator();
Map<String, Object> stringObjectMap = basicCaptchaTrackValidator.generateImageCaptchaValidData(imageCaptchaInfo);
System.out.println(stringObjectMap);
System.out.println(imageCaptchaInfo);
}
}