diff --git a/pom.xml b/pom.xml
index 58672cae4d5cbf83bca43bdfca288061a5891229..57ab3931d4b0a5a8920b9068e1ced5b1539334b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,8 @@
8.7.2-20241022
7.0.1
+
+ 1.5.1
3.2.2
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java b/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
index 1a476a94a66de72f32a4e252ca994db1a106a05b..f6986c85083f111ae3851cabbe13d4c480d26e10 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
+++ b/ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java
@@ -1,5 +1,10 @@
package org.dromara.web.controller;
+import cloud.tianai.captcha.application.ImageCaptchaApplication;
+import cloud.tianai.captcha.application.vo.CaptchaResponse;
+import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
+import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
+import cloud.tianai.captcha.common.response.ApiResponse;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
@@ -11,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
@@ -21,19 +27,26 @@ import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.common.web.enums.CaptchaType;
+import org.dromara.common.web.enums.InputCaptchaType;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
+import org.dromara.web.domain.bo.CaptchaBo;
import org.dromara.web.domain.vo.CaptchaVo;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.concurrent.ThreadLocalRandom;
/**
* 验证码操作处理
@@ -50,6 +63,8 @@ public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final MailProperties mailProperties;
+ private final ImageCaptchaApplication imageCaptchaApplication;
+
/**
* 短信验证码
*
@@ -109,15 +124,20 @@ public class CaptchaController {
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
+ captchaVo.setType(captchaProperties.getType());
+ if (CaptchaType.ACT.getType().equalsIgnoreCase(captchaProperties.getType())) {
+ return R.ok(captchaVo);
+ }
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
- CaptchaType captchaType = captchaProperties.getType();
- boolean isMath = CaptchaType.MATH == captchaType;
- Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
- CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
- AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
+ CaptchaProperties.InputCaptchaProperties inputCaptchaProperties = captchaProperties.getInput();
+ InputCaptchaType inputCaptchaType = inputCaptchaProperties.getType();
+ boolean isMath = InputCaptchaType.MATH == inputCaptchaType;
+ Integer length = isMath ? inputCaptchaProperties.getNumberLength() : inputCaptchaProperties.getCharLength();
+ CodeGenerator codeGenerator = ReflectUtils.newInstance(inputCaptchaType.getClazz(), length);
+ AbstractCaptcha captcha = SpringUtils.getBean(inputCaptchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
// 如果是数学验证码,使用SpEL表达式处理验证码结果
@@ -133,4 +153,33 @@ public class CaptchaController {
return R.ok(captchaVo);
}
+ /**
+ * 生成行为验证码
+ */
+ @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+ @PostMapping("/auth/captcha")
+ public CaptchaResponse getCaptcha() {
+ if (!captchaProperties.getEnable() || !CaptchaType.ACT.getType().equalsIgnoreCase(captchaProperties.getType())) {
+ throw new ServiceException("验证码未开启");
+ }
+ String type = captchaProperties.getAct().getType();
+ if ("RANDOM".equalsIgnoreCase(type)) {
+ type = Arrays.asList(CaptchaTypeConstant.SLIDER, CaptchaTypeConstant.CONCAT, CaptchaTypeConstant.ROTATE, CaptchaTypeConstant.WORD_IMAGE_CLICK)
+ .get(ThreadLocalRandom.current().nextInt(4));
+ }
+ return imageCaptchaApplication.generateCaptcha(type);
+ }
+
+ /**
+ * 校验行为验证码
+ */
+ @PostMapping("/auth/verify")
+ public ApiResponse> verify(@RequestBody CaptchaBo data) {
+ ApiResponse> response = imageCaptchaApplication.matching(data.getId(), data.getData());
+ if (response.isSuccess()) {
+ return ApiResponse.ofSuccess(Collections.singletonMap("id", data.getId()));
+ }
+ return response;
+ }
+
}
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/domain/bo/CaptchaBo.java b/ruoyi-admin/src/main/java/org/dromara/web/domain/bo/CaptchaBo.java
new file mode 100644
index 0000000000000000000000000000000000000000..67c51c69bb0784ee9b1507b833ef2cdf924fcb3d
--- /dev/null
+++ b/ruoyi-admin/src/main/java/org/dromara/web/domain/bo/CaptchaBo.java
@@ -0,0 +1,13 @@
+package org.dromara.web.domain.bo;
+
+import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
+import lombok.Data;
+
+@Data
+public class CaptchaBo {
+
+ private String id;
+
+ private ImageCaptchaTrack data;
+
+}
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java b/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java
index 664df1ee62d7ff7555ad9ab0d843197bfe0e5b32..4839515e8258997a243fb22c6ed5d03c2c595bce 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java
+++ b/ruoyi-admin/src/main/java/org/dromara/web/domain/vo/CaptchaVo.java
@@ -15,6 +15,11 @@ public class CaptchaVo {
*/
private Boolean captchaEnabled = true;
+ /**
+ * 验证码类型
+ */
+ private String type;
+
private String uuid;
/**
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
index e8e60e1c4aeba23d79209fe1acf0b552305cf7b1..64d937b89f324dcdbd47ac9f3ee192b629ec320c 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
+++ b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
@@ -1,5 +1,7 @@
package org.dromara.web.service.impl;
+import cloud.tianai.captcha.application.ImageCaptchaApplication;
+import cloud.tianai.captcha.spring.plugins.secondary.SecondaryVerificationApplication;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
@@ -24,6 +26,7 @@ import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
+import org.dromara.common.web.enums.CaptchaType;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -43,6 +46,8 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class PasswordAuthStrategy implements IAuthStrategy {
+ private final ImageCaptchaApplication imageCaptchaApplication;
+
private final CaptchaProperties captchaProperties;
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@@ -95,6 +100,13 @@ public class PasswordAuthStrategy implements IAuthStrategy {
* @param uuid 唯一标识
*/
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
+ if (CaptchaType.ACT.getType().equalsIgnoreCase(captchaProperties.getType()) && imageCaptchaApplication instanceof SecondaryVerificationApplication) {
+ if (!((SecondaryVerificationApplication) imageCaptchaApplication).secondaryVerification(uuid)) {
+ loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
+ throw new CaptchaException();
+ }
+ return;
+ }
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 33fbcb7abfb9624133f0c36337d34c64e1f42de1..a42a50db5e79bc06456d9d4d4c5fe098d5a69d02 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -7,18 +7,6 @@ ruoyi:
# 版权年份
copyrightYear: 2024
-captcha:
- enable: true
- # 页面 <参数设置> 可开启关闭 验证码校验
- # 验证码类型 math 数组计算 char 字符验证
- type: MATH
- # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
- category: CIRCLE
- # 数字验证码位数
- numberLength: 1
- # 字符验证码长度
- charLength: 4
-
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
@@ -109,6 +97,24 @@ sa-token:
# security配置
security:
+ #验证码
+ captcha:
+ # 是否开启验证码
+ enable: true
+ # 验证码类型 input 输入验证码 act 行为验证码
+ type: act
+ input:
+ # 验证码类型 math 数组计算 char 字符验证
+ type: char
+ # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰 random 随机行为验证码
+ category: line
+ # 数字验证码位数
+ numberLength: 1
+ # 字符验证码长度
+ charLength: 4
+ act:
+ # RANDOM 随机 SLIDER 滑块验证 ROTATE 旋转验证 CONCAT 滑动还原 WORD_IMAGE_CLICK 文字点选
+ type: RANDOM
# 排除路径
excludes:
- /*.html
@@ -120,6 +126,37 @@ security:
- /*/api-docs
- /*/api-docs/**
+# 滑块验证码配置, 详细请看 cloud.tianai.captcha.autoconfiguration.ImageCaptchaProperties 类
+captcha:
+ # 如果项目中使用到了redis,滑块验证码会自动把验证码数据存到redis中, 这里配置redis的key的前缀,默认是captcha:slider
+ prefix: "captcha_act:"
+ # 验证码过期时间,默认是2分钟,单位毫秒, 可以根据自身业务进行调整
+ expire:
+ # 默认缓存时间 2分钟
+ default: 20000
+ # 针对 点选验证码 过期时间设置为 2分钟, 因为点选验证码验证比较慢,把过期时间调整大一些
+ WORD_IMAGE_CLICK: 30000
+ # 使用加载系统自带的资源, 默认是 false(这里系统的默认资源包含 滑动验证码模板/旋转验证码模板,如果想使用系统的模板,这里设置为true)
+ init-default-resource: true
+ # 缓存控制, 默认为false不开启
+ local-cache-enabled: true
+ # 验证码会提前缓存一些生成好的验证数据, 默认是20
+ local-cache-size: 20
+ # 缓存拉取失败后等待时间 默认是 5秒钟
+ local-cache-wait-time: 5000
+ # 缓存检查间隔 默认是2秒钟
+ local-cache-period: 2000
+ # 配置字体包,供文字点选验证码使用,可以配置多个,不配置使用默认的字体
+ font-path:
+# - classpath:font/SimHei.ttf
+ secondary:
+ # 二次验证, 默认false 不开启
+ enabled: true
+ # 二次验证过期时间, 默认 2分钟
+ expire: 120000
+ # 二次验证缓存key前缀,默认是 captcha:secondary
+ keyPrefix: "captcha_secondary:"
+
# 多租户配置
tenant:
# 是否开启
diff --git a/ruoyi-admin/src/main/resources/bgimages/1.jpg b/ruoyi-admin/src/main/resources/bgimages/1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8c91e2b15cb16975f2ecb32c5e1a56fddad49af3
Binary files /dev/null and b/ruoyi-admin/src/main/resources/bgimages/1.jpg differ
diff --git a/ruoyi-admin/src/main/resources/bgimages/2.jpg b/ruoyi-admin/src/main/resources/bgimages/2.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a323a0d652bebe4d190203efe52f32220ba30def
Binary files /dev/null and b/ruoyi-admin/src/main/resources/bgimages/2.jpg differ
diff --git a/ruoyi-admin/src/main/resources/bgimages/3.jpg b/ruoyi-admin/src/main/resources/bgimages/3.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..023696896f76d0e83a6840fa50dc6ab1a93cc178
Binary files /dev/null and b/ruoyi-admin/src/main/resources/bgimages/3.jpg differ
diff --git a/ruoyi-admin/src/main/resources/bgimages/4.jpg b/ruoyi-admin/src/main/resources/bgimages/4.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d25dfce168180898bc1e521ddccc6ea011a4debd
Binary files /dev/null and b/ruoyi-admin/src/main/resources/bgimages/4.jpg differ
diff --git a/ruoyi-admin/src/main/resources/bgimages/5.jpg b/ruoyi-admin/src/main/resources/bgimages/5.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9dbcd1244be638a6a58d2b86197885729b9e85f8
Binary files /dev/null and b/ruoyi-admin/src/main/resources/bgimages/5.jpg differ
diff --git a/ruoyi-common/ruoyi-common-web/pom.xml b/ruoyi-common/ruoyi-common-web/pom.xml
index b250fa9d09267bb64050b0bc096c4271fa3698fd..dd8d025cb127c280d9fd8076422451ad3b37c51b 100644
--- a/ruoyi-common/ruoyi-common-web/pom.xml
+++ b/ruoyi-common/ruoyi-common-web/pom.xml
@@ -57,6 +57,12 @@
cn.hutool
hutool-crypto
+
+
+ cloud.tianai.captcha
+ tianai-captcha-springboot-starter
+ ${tianai-captcha.version}
+
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaResourceConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaResourceConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..de0f88d2c130d535694bb74c3e05a259f5b470f0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/CaptchaResourceConfig.java
@@ -0,0 +1,44 @@
+package org.dromara.common.web.config;
+
+import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
+import cloud.tianai.captcha.resource.ResourceStore;
+import cloud.tianai.captcha.resource.common.model.dto.Resource;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+@RequiredArgsConstructor
+public class CaptchaResourceConfig {
+
+ private final ResourceStore resourceStore;
+
+ @PostConstruct
+ public void init() {
+ // 添加自定义背景图片
+ resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/1.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/2.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/3.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/4.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/5.jpg", "default"));
+
+ resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource("classpath", "bgimages/1.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource("classpath", "bgimages/2.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource("classpath", "bgimages/3.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource("classpath", "bgimages/4.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.ROTATE, new Resource("classpath", "bgimages/5.jpg", "default"));
+
+ resourceStore.addResource(CaptchaTypeConstant.CONCAT, new Resource("classpath", "bgimages/1.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.CONCAT, new Resource("classpath", "bgimages/2.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.CONCAT, new Resource("classpath", "bgimages/3.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.CONCAT, new Resource("classpath", "bgimages/4.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.CONCAT, new Resource("classpath", "bgimages/5.jpg", "default"));
+
+ resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource("classpath", "bgimages/1.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource("classpath", "bgimages/2.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource("classpath", "bgimages/3.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource("classpath", "bgimages/4.jpg", "default"));
+ resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource("classpath", "bgimages/5.jpg", "default"));
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java
index bfc52f4502d13303bf4f6a649f91f95e000fe668..48542107101e5880770c63f5ff5fda803c540cbf 100644
--- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/properties/CaptchaProperties.java
@@ -1,8 +1,8 @@
package org.dromara.common.web.config.properties;
import org.dromara.common.web.enums.CaptchaCategory;
-import org.dromara.common.web.enums.CaptchaType;
import lombok.Data;
+import org.dromara.common.web.enums.InputCaptchaType;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -11,7 +11,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Lion Li
*/
@Data
-@ConfigurationProperties(prefix = "captcha")
+@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties {
private Boolean enable;
@@ -19,20 +19,48 @@ public class CaptchaProperties {
/**
* 验证码类型
*/
- private CaptchaType type;
+ private String type;
/**
- * 验证码类别
+ * 输入验证码的相关配置
*/
- private CaptchaCategory category;
+ private InputCaptchaProperties input;
/**
- * 数字验证码位数
+ * 行为验证码的相关配置
*/
- private Integer numberLength;
+ private ActCaptchaProperties act;
- /**
- * 字符验证码长度
- */
- private Integer charLength;
+ @Data
+ public static class InputCaptchaProperties {
+
+ /**
+ * 验证码类型 math 数组计算, char 字符验证
+ */
+ private InputCaptchaType type;
+
+ /**
+ * 验证码类别 line 线段干扰, circle 圆圈干扰, shear 扭曲干扰, random 随机行为验证码
+ */
+ private CaptchaCategory category;
+
+ /**
+ * 数字验证码位数
+ */
+ private Integer numberLength;
+
+ /**
+ * 字符验证码长度
+ */
+ private Integer charLength;
+ }
+
+ @Data
+ public static class ActCaptchaProperties {
+
+ /**
+ * 行为验证码类型 random 随机行为验证码
+ */
+ private String type;
+ }
}
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java
index d0b73345ac305cc02e1161fa2aabd4ca425a2463..627d000021467eb62327ede0134caf8c32d1ed27 100644
--- a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/CaptchaType.java
@@ -1,8 +1,5 @@
package org.dromara.common.web.enums;
-import cn.hutool.captcha.generator.CodeGenerator;
-import cn.hutool.captcha.generator.RandomGenerator;
-import org.dromara.common.web.utils.UnsignedMathGenerator;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -18,12 +15,12 @@ public enum CaptchaType {
/**
* 数字
*/
- MATH(UnsignedMathGenerator.class),
+ INPUT("INPUT"),
/**
* 字符
*/
- CHAR(RandomGenerator.class);
+ ACT("ACT");
- private final Class extends CodeGenerator> clazz;
+ private final String type;
}
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/InputCaptchaType.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/InputCaptchaType.java
new file mode 100644
index 0000000000000000000000000000000000000000..8df3a0ce5fa862391647a9ecbc7d5fa5b1e669e6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/enums/InputCaptchaType.java
@@ -0,0 +1,29 @@
+package org.dromara.common.web.enums;
+
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.web.utils.UnsignedMathGenerator;
+
+/**
+ * 验证码类型
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum InputCaptchaType {
+
+ /**
+ * 数字
+ */
+ MATH(UnsignedMathGenerator.class),
+
+ /**
+ * 字符
+ */
+ CHAR(RandomGenerator.class);
+
+ private final Class extends CodeGenerator> clazz;
+}