diff --git a/pom.xml b/pom.xml
index a1a517a..24ab43f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,26 +12,27 @@
tianai-captcha
tianai-captcha-springboot-starter
+ tianai-captcha-platform
- 1.5.5
- 1.8
-
+ 2.0.0-SNAPSHOT
+ 21
+ 21
+ 21
+ 21
true
UTF-8
UTF-8
- false
- ossrh
- https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
- https://s01.oss.sonatype.org/content/repositories/snapshots
+ 3.4.5
+ 1.18.42
+ 2.0.17
+ 2.13.1
+ 1.22.0
-
-
-
-
-
+ true
+ ossrh
@@ -68,11 +69,10 @@
-
org.codehaus.mojo
flatten-maven-plugin
- 1.2.7
+ 1.7.0
true
resolveCiFriendliesOnly
@@ -96,75 +96,23 @@
org.apache.maven.plugins
- maven-javadoc-plugin
- 2.9.1
+ maven-compiler-plugin
+ 3.14.0
-
- true
- false
- false
-
-
-
- attach-javadocs
-
- jar
-
-
- -Xdoclint:none
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
- 2.2.1
-
-
- attach-sources
-
- jar-no-fork
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.5
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
- org.apache.maven.plugins
- maven-resources-plugin
- 2.6
-
-
- org.sonatype.central
- central-publishing-maven-plugin
- 0.5.0
- true
-
- ossrh
-
- true
+ 21
+ -parameters
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
-
-
diff --git a/tianai-captcha-springboot-starter/pom.xml b/tianai-captcha-springboot-starter/pom.xml
index 968dcb1..4ca279e 100644
--- a/tianai-captcha-springboot-starter/pom.xml
+++ b/tianai-captcha-springboot-starter/pom.xml
@@ -17,7 +17,7 @@
org.springframework.boot
spring-boot-dependencies
- 2.2.7.RELEASE
+ ${spring-boot.version}
pom
import
@@ -25,9 +25,7 @@
- 1.8
- 3.7
-
+ 21
true
UTF-8
UTF-8
@@ -43,7 +41,6 @@
org.apache.commons
commons-lang3
- ${commons-lang3.version}
com.google.code.gson
@@ -65,18 +62,14 @@
org.projectlombok
lombok
- true
+ ${lombok.version}
+ provided
cloud.tianai.captcha
tianai-captcha
${revision}
-
-
-
-
-
org.springframework.boot
spring-boot-starter-web
@@ -87,20 +80,22 @@
-
- org.springframework.boot
- spring-boot-maven-plugin
-
org.apache.maven.plugins
maven-compiler-plugin
+ 3.14.0
- 8
- 8
+ 21
-parameters
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
-
diff --git a/tianai-captcha/pom.xml b/tianai-captcha/pom.xml
index b8902e8..70c31d6 100644
--- a/tianai-captcha/pom.xml
+++ b/tianai-captcha/pom.xml
@@ -13,25 +13,23 @@
https://gitee.com/tianai/tianai-captcha
- 1.8
-
+ 21
true
UTF-8
UTF-8
-
org.projectlombok
lombok
- 1.18.42
- true
+ ${lombok.version}
+ provided
org.slf4j
slf4j-api
- 1.7.30
+ ${slf4j.version}
@@ -40,10 +38,17 @@
org.apache.maven.plugins
maven-compiler-plugin
+ 3.14.0
- 8
- 8
+ 21
-parameters
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java
index b8088b3..da6837d 100644
--- a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java
+++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/DefaultImageCaptchaApplication.java
@@ -167,6 +167,9 @@ public class DefaultImageCaptchaApplication implements ImageCaptchaApplication {
verificationVO.setBackgroundImageHeight(imageCaptchaInfo.getBackgroundImageHeight());
verificationVO.setTemplateImageWidth(imageCaptchaInfo.getTemplateImageWidth());
verificationVO.setTemplateImageHeight(imageCaptchaInfo.getTemplateImageHeight());
+ if (imageCaptchaInfo instanceof cloud.tianai.captcha.generator.common.model.dto.RotateImageCaptchaInfo) {
+ verificationVO.setDegree(((cloud.tianai.captcha.generator.common.model.dto.RotateImageCaptchaInfo) imageCaptchaInfo).getDegree());
+ }
verificationVO.setData(imageCaptchaInfo.getData() == null ? null : imageCaptchaInfo.getData().getViewData());
verificationVO.setId(id);
return ApiResponse.ofSuccess(verificationVO);
diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java
index 1aa3b08..ad9df1a 100644
--- a/tianai-captcha/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java
+++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/application/vo/ImageCaptchaVO.java
@@ -31,6 +31,8 @@ public class ImageCaptchaVO implements Serializable {
private Integer templateImageWidth;
/** 滑动图片高度.*/
private Integer templateImageHeight;
+ /** 旋转验证码初始角度(仅ROTATE类型).*/
+ private Double degree;
/** data 扩展数据.*/
private Object data;
}
diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/common/constant/CaptchaTypeConstant.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/common/constant/CaptchaTypeConstant.java
index 41bfa49..f91ebeb 100644
--- a/tianai-captcha/src/main/java/cloud/tianai/captcha/common/constant/CaptchaTypeConstant.java
+++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/common/constant/CaptchaTypeConstant.java
@@ -1,18 +1,20 @@
package cloud.tianai.captcha.common.constant;
-/**
- * @Author: 天爱有情
- * @date 2021/8/7 17:14
- * @Description 滑块类型
- */
public interface CaptchaTypeConstant {
- /** 滑块. */
String SLIDER = "SLIDER";
- /** 旋转. */
+ String SLIDER_V2 = "SLIDER_V2";
String ROTATE = "ROTATE";
- /** 拼接. */
String CONCAT = "CONCAT";
- /** 文字图片点选. */
String WORD_IMAGE_CLICK = "WORD_IMAGE_CLICK";
+ String ICON_CLICK = "ICON_CLICK";
+ String WORD_ORDER_CLICK = "WORD_ORDER_CLICK";
+ String CURVE_SLIDER = "CURVE_SLIDER";
+ String CURVE_SLIDER_V2 = "CURVE_SLIDER_V2";
+ String CURVE_SLIDER_V3 = "CURVE_SLIDER_V3";
+ String ANGLE = "ANGLE";
+ String SCRATCH = "SCRATCH";
+ String JIGSAW = "JIGSAW";
+ String CURVE_DRAW = "CURVE_DRAW";
+ String PROOF_OF_WORK = "PROOF_OF_WORK";
}
diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java
index 3e674a7..5accec8 100644
--- a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java
+++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/MultiImageCaptchaGenerator.java
@@ -47,14 +47,21 @@ public class MultiImageCaptchaGenerator extends AbstractImageCaptchaGenerator {
@Override
protected void doInit() {
- // 滑块验证码
addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(SLIDER, StandardSliderImageCaptchaGenerator::new));
- // 旋转验证码
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(SLIDER_V2, EnhancedSliderImageCaptchaGenerator::new));
addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(ROTATE, StandardRotateImageCaptchaGenerator::new));
- // 拼接验证码
addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(CONCAT, StandardConcatImageCaptchaGenerator::new));
- // 点选文字验证码
addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(WORD_IMAGE_CLICK, StandardWordClickImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(ICON_CLICK, IconClickImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(WORD_ORDER_CLICK, WordOrderClickImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(CURVE_SLIDER, CurveSliderImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(ANGLE, AngleRotateImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(SCRATCH, ScratchImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(JIGSAW, JigsawImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(CURVE_SLIDER_V2, CurveSliderV2ImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(CURVE_SLIDER_V3, CurveSliderV3ImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(CURVE_DRAW, CurveDrawImageCaptchaGenerator::new));
+ addImageCaptchaGeneratorProvider(new CommonImageCaptchaGeneratorProvider(PROOF_OF_WORK, ProofOfWorkCaptchaGenerator::new));
}
public void addImageCaptchaGeneratorProvider(ImageCaptchaGeneratorProvider provider) {
diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java
index a5dd5a8..e3bf428 100644
--- a/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java
+++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/generator/impl/StandardRotateImageCaptchaGenerator.java
@@ -60,6 +60,14 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen
Optional maskTemplateOptional = getTemplateImageOfOptional(templateResource, TEMPLATE_MASK_IMAGE_NAME);
if (maskTemplateOptional.isPresent()) {
maskTemplate = maskTemplateOptional.get();
+ } else {
+ // 没有mask.png时,生成实心圆形蒙版(alpha=255),确保切出完整图片内容
+ maskTemplate = new BufferedImage(fixedTemplate.getWidth(), fixedTemplate.getHeight(), java.awt.image.BufferedImage.TYPE_INT_ARGB);
+ java.awt.Graphics2D gMask = maskTemplate.createGraphics();
+ gMask.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
+ gMask.setColor(new java.awt.Color(255, 255, 255, 255));
+ gMask.fillOval(0, 0, fixedTemplate.getWidth(), fixedTemplate.getHeight());
+ gMask.dispose();
}
// 算出居中的x和y
@@ -68,21 +76,10 @@ public class StandardRotateImageCaptchaGenerator extends AbstractImageCaptchaGen
// 抠图部分
BufferedImage cutImage = CaptchaImageUtils.cutImage(background, maskTemplate, x, y);
- BufferedImage rotateFixed = fixedTemplate;
- BufferedImage rotateActive = activeTemplate;
- if (param.getObfuscate()) {
- int randomDegree = randomInt(10, 350);
- rotateFixed = CaptchaImageUtils.rotateImage(fixedTemplate, randomDegree);
- randomDegree = randomInt(10, 350);
- rotateActive = CaptchaImageUtils.rotateImage(activeTemplate, randomDegree);
- }
- CaptchaImageUtils.overlayImage(background, rotateFixed, x, y);
- CaptchaImageUtils.overlayImage(cutImage, rotateActive, 0, 0);
- // 随机旋转抠图部分
- // 随机x, 转换为角度
+ // 随机x,转换为角度
int randomX = randomInt(fixedTemplate.getWidth() + 10, background.getWidth() - 10);
double degree = 360d - randomX / ((background.getWidth()) / 360d);
- // 旋转的透明图片是一张正方形的
+ // 模板图:窄条透明画布,切片居中旋转放置(官方方式)
BufferedImage matrixTemplate = CaptchaImageUtils.createTransparentImage(cutImage.getWidth(), background.getHeight());
CaptchaImageUtils.centerOverlayAndRotateImage(matrixTemplate, cutImage, degree);
diff --git a/tianai-captcha/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java b/tianai-captcha/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java
index 00951f8..af2e950 100644
--- a/tianai-captcha/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java
+++ b/tianai-captcha/src/main/java/cloud/tianai/captcha/validator/impl/SimpleImageCaptchaValidator.java
@@ -53,6 +53,9 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
CaptchaTypeClassifier.addSliderCaptchaType(CaptchaTypeConstant.ROTATE);
CaptchaTypeClassifier.addSliderCaptchaType(CaptchaTypeConstant.SLIDER);
CaptchaTypeClassifier.addClickCaptchaType(CaptchaTypeConstant.WORD_IMAGE_CLICK);
+ CaptchaTypeClassifier.addClickCaptchaType(CaptchaTypeConstant.ICON_CLICK);
+ CaptchaTypeClassifier.addClickCaptchaType(CaptchaTypeConstant.WORD_ORDER_CLICK);
+ CaptchaTypeClassifier.addJigsawCaptchaType(CaptchaTypeConstant.JIGSAW);
}
public SimpleImageCaptchaValidator(float defaultTolerant) {
@@ -119,6 +122,25 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
if (CaptchaTypeClassifier.isSliderCaptcha(type)) {
// 滑动验证码
addPercentage(imageCaptchaInfo, map);
+ } else if (CaptchaTypeConstant.CURVE_DRAW.equals(type)) {
+ // 曲线绘制验证码
+ if (expand == null) {
+ throw new IllegalArgumentException("曲线绘制验证码扩展数据为空, info=" + imageCaptchaInfo);
+ }
+ try {
+ cloud.tianai.captcha.generator.impl.CurveDrawImageCaptchaGenerator.CurveDrawData curveData =
+ (cloud.tianai.captcha.generator.impl.CurveDrawImageCaptchaGenerator.CurveDrawData) expand;
+ StringBuilder sb = new StringBuilder();
+ for (java.awt.geom.Point2D.Double point : curveData.getSamplePoints()) {
+ sb.append(calcPercentage((float) point.x, imageCaptchaInfo.getBackgroundImageWidth()))
+ .append(",")
+ .append(calcPercentage((float) point.y, imageCaptchaInfo.getBackgroundImageHeight()))
+ .append(";");
+ }
+ map.put(PERCENTAGE_KEY, sb.toString());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("曲线绘制验证码扩展数据转换失败, info=" + imageCaptchaInfo, e);
+ }
} else if (CaptchaTypeClassifier.isClickCaptcha(type)) {
// 图片点选验证码
if (expand == null) {
@@ -158,6 +180,15 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
} else if (CaptchaTypeClassifier.isJigsawCaptcha(type)) {
// 拼图验证码
map.put(PERCENTAGE_KEY, expand);
+ } else if (CaptchaTypeConstant.SCRATCH.equals(type)) {
+ // 刮刮卡验证码 - 存储需要刮开的百分比阈值
+ String threshold = (expand != null) ? String.valueOf(expand) : "50";
+ map.put(PERCENTAGE_KEY, threshold);
+ } else if (CaptchaTypeConstant.PROOF_OF_WORK.equals(type)) {
+ // 工作量证明验证码 - 存储challenge和difficulty
+ if (expand != null) {
+ map.put("pow_data", expand);
+ }
}
}
@@ -236,12 +267,21 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
if (CaptchaTypeClassifier.isSliderCaptcha(type)) {
// 滑动类型验证码
return doValidSliderCaptcha(imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
+ } else if (CaptchaTypeConstant.CURVE_DRAW.equals(type)) {
+ // 曲线绘制验证码
+ return doValidCurveDrawCaptcha(imageCaptchaTrack, imageCaptchaValidData, tolerant);
} else if (CaptchaTypeClassifier.isClickCaptcha(type)) {
// 点选类型验证码
return doValidClickCaptcha(imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
} else if (CaptchaTypeClassifier.isJigsawCaptcha(type)) {
// 拼图类型验证码
return doValidJigsawCaptcha(imageCaptchaTrack, imageCaptchaValidData, tolerant, type);
+ } else if (CaptchaTypeConstant.SCRATCH.equals(type)) {
+ // 刮刮卡验证码
+ return doValidScratchCaptcha(imageCaptchaTrack, imageCaptchaValidData, tolerant);
+ } else if (CaptchaTypeConstant.PROOF_OF_WORK.equals(type)) {
+ // 工作量证明验证码
+ return doValidProofOfWork(imageCaptchaTrack, imageCaptchaValidData);
}
// 不支持的类型
log.warn("校验验证码警告, 不支持的验证码类型:{}, 请手动扩展 cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator.doValid 进行校验扩展", type);
@@ -254,7 +294,115 @@ public class SimpleImageCaptchaValidator implements ImageCaptchaValidator, Slide
}
String posArr = (String) imageCaptchaTrack.getData();
String successPosStr = imageCaptchaValidData.getString(PERCENTAGE_KEY, null);
- return successPosStr.equals(posArr);
+ return successPosStr != null && successPosStr.equals(posArr);
+ }
+
+ /**
+ * 校验刮刮卡验证码
+ * 检查用户刮开的面积是否超过阈值
+ */
+ public boolean doValidScratchCaptcha(ImageCaptchaTrack imageCaptchaTrack,
+ AnyMap imageCaptchaValidData,
+ Float tolerant) {
+ // data 字段存储刮开百分比 (0-100)
+ if (imageCaptchaTrack.getData() == null) {
+ return false;
+ }
+ int scratchPercent;
+ try {
+ scratchPercent = Integer.parseInt(String.valueOf(imageCaptchaTrack.getData()));
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ // 阈值默认50%
+ String thresholdStr = imageCaptchaValidData.getString(PERCENTAGE_KEY, "50");
+ int threshold = 50;
+ try {
+ threshold = Integer.parseInt(thresholdStr);
+ } catch (NumberFormatException ignored) {}
+ return scratchPercent >= threshold;
+ }
+
+ /**
+ * 校验工作量证明验证码
+ * 检查用户提交的nonce是否满足SHA-256前导零要求
+ */
+ public boolean doValidProofOfWork(ImageCaptchaTrack imageCaptchaTrack,
+ AnyMap imageCaptchaValidData) {
+ if (imageCaptchaTrack.getData() == null) {
+ return false;
+ }
+ String nonce = String.valueOf(imageCaptchaTrack.getData());
+ Object powData = imageCaptchaValidData.get("pow_data");
+ if (powData == null) {
+ return false;
+ }
+ // pow_data is the PoWData object from the generator
+ try {
+ cloud.tianai.captcha.generator.impl.ProofOfWorkCaptchaGenerator.PoWData data =
+ (cloud.tianai.captcha.generator.impl.ProofOfWorkCaptchaGenerator.PoWData) powData;
+ return cloud.tianai.captcha.generator.impl.ProofOfWorkCaptchaGenerator.verifyProof(
+ data.challenge, data.difficulty, nonce);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * 校验曲线绘制验证码
+ * 检查用户绘制的路径是否经过预期曲线的采样点附近
+ */
+ public boolean doValidCurveDrawCaptcha(ImageCaptchaTrack imageCaptchaTrack,
+ AnyMap imageCaptchaValidData,
+ Float tolerant) {
+ String validStr = imageCaptchaValidData.getString(PERCENTAGE_KEY, null);
+ if (ObjectUtils.isEmpty(validStr)) {
+ return false;
+ }
+ String[] splitArr = validStr.split(";");
+ List trackList = imageCaptchaTrack.getTrackList();
+ if (trackList == null || trackList.isEmpty()) {
+ return false;
+ }
+ // 取出所有 MOVE 类型的轨迹点(用户的绘制路径)
+ List drawTrackList = trackList
+ .stream()
+ .filter(t -> TrackTypeConstant.MOVE.equalsIgnoreCase(t.getType()))
+ .collect(Collectors.toList());
+ if (drawTrackList.isEmpty()) {
+ return false;
+ }
+ // 对每个预期采样点,检查用户路径中是否有接近的点
+ int matchedCount = 0;
+ int bgWidth = imageCaptchaTrack.getBgImageWidth();
+ int bgHeight = imageCaptchaTrack.getBgImageHeight();
+ for (String posStr : splitArr) {
+ if (ObjectUtils.isEmpty(posStr)) {
+ continue;
+ }
+ String[] posArr = posStr.split(",");
+ if (posArr.length < 2) {
+ continue;
+ }
+ float xPercentage = Float.parseFloat(posArr[0]);
+ float yPercentage = Float.parseFloat(posArr[1]);
+ // 检查用户路径中是否有接近该采样点的位置
+ boolean matched = false;
+ for (ImageCaptchaTrack.Track track : drawTrackList) {
+ float calcXPercentage = calcPercentage(track.getX(), bgWidth);
+ float calcYPercentage = calcPercentage(track.getY(), bgHeight);
+ if (checkPercentage(calcXPercentage, xPercentage, tolerant)
+ && checkPercentage(calcYPercentage, yPercentage, tolerant)) {
+ matched = true;
+ break;
+ }
+ }
+ if (matched) {
+ matchedCount++;
+ }
+ }
+ // 如果匹配率超过 70% 则通过
+ return (double) matchedCount / splitArr.length >= 0.7;
}
/**
diff --git a/tianai-captcha/src/main/resources/META-INF/cut-image/template/rotate_1/fixed.png b/tianai-captcha/src/main/resources/META-INF/cut-image/template/rotate_1/fixed.png
index 4295ba3..a2f1eb4 100644
Binary files a/tianai-captcha/src/main/resources/META-INF/cut-image/template/rotate_1/fixed.png and b/tianai-captcha/src/main/resources/META-INF/cut-image/template/rotate_1/fixed.png differ