diff --git a/README.md b/README.md index 0c8ccac05..7b2080bab 100644 --- a/README.md +++ b/README.md @@ -170,28 +170,28 @@ ps:核心功能已经实现,正在对接微信小程序中... ### 后端 | 框架 | 说明 | 版本 | 学习指南 | -|---------------------------------------------------------------------------------------------|-----------------------|-----------|----------------------------------------------------------------| -| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.10 | [文档](https://github.com/YunaiV/SpringBoot-Labs) | -| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | | -| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.11 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | -| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | -| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | -| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | | -| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.4 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) | -| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | -| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | -| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | -| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.0 | [文档](https://doc.iocoder.cn/bpm/) | -| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | -| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | -| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) | -| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) | -| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.7 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) | -| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | | -| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) | -| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) | -| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - | -| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - | +|---------------------------------------------------------------------------------------------|-----------------------|---------|----------------------------------------------------------------| +| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.10 | [文档](https://github.com/YunaiV/SpringBoot-Labs) | +| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | | +| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.11 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | +| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | +| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | +| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | | +| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.4 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) | +| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | +| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | +| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | +| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.2 | [文档](https://doc.iocoder.cn/bpm/) | +| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | +| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | +| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) | +| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) | +| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.7 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) | +| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | | +| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) | +| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) | +| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - | +| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - | ### [管理后台 Vue2 前端](./yudao-ui-admin) @@ -202,16 +202,16 @@ ps:核心功能已经实现,正在对接微信小程序中... ### [管理后台 Vue3 前端](./yudao-ui-admin-vue3) -| 框架 | 说明 | 版本 | -|----------------------------------------------------------------------|------------------|--------| -| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.37 | -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 | -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 | -| [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.7.4 | -| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.17 | -| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.0 | -| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | -| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | +| 框架 | 说明 | 版本 | +|----------------------------------------------------------------------|-----------------|--------| +| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.37 | +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.4 | +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.12 | +| [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.7.4 | +| [pinia](https://pinia.vuejs.org/) | vuex5 | 2.0.17 | +| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 | +| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 | +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp) diff --git a/sql/mysql/update.sql b/sql/mysql/update.sql new file mode 100644 index 000000000..10df65590 --- /dev/null +++ b/sql/mysql/update.sql @@ -0,0 +1,7 @@ +-- ---------------------------- +-- 升级SQL文件,全新安装只需要执行ruoyi-vue-pro.sql文件即可 +-- 1.6.2 ==> 1.6.3 +-- ---------------------------- +-- 积木报表菜单 +INSERT INTO `system_menu` VALUES (1281, '可视化报表', '', 1, 12, 0, '/visualization', 'chart', NULL, 0, b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2022-07-10 20:33:30', b'0'); +INSERT INTO `system_menu` VALUES (1282, '积木报表', '', 2, 1, 1281, 'jm-report', '#', 'visualization/jm/index', 0, b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2022-07-10 20:33:26', b'0'); \ No newline at end of file diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 44d7bdf68..d7912ba69 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -41,14 +41,14 @@ 0.1.16 4.0.0 - 6.7.0 + 6.7.2 3.0.4 1.18.20 1.4.1.Final - 5.7.22 + 5.8.5 3.1.1 - 2.2 + 2.3 1.0.5 1.2.83 30.1.1-jre @@ -57,11 +57,12 @@ 3.8.0 0.1.55 2.4.1 + 1.3.0 8.2.2 - 4.5.25 - 2.1.0 - 3.1.471 + 4.6.0 + 2.2.1 + 3.1.561 1.2.7 1.4.0 1.5.2 @@ -130,6 +131,11 @@ yudao-spring-boot-starter-biz-error-code ${revision} + + cn.iocoder.boot + yudao-spring-boot-starter-captcha + ${revision} + @@ -452,6 +458,12 @@ ${tika-core.version} + + com.anji-plus + spring-boot-starter-captcha + ${aj-captcha.version} + + org.apache.velocity velocity-engine-core diff --git a/yudao-framework/pom.xml b/yudao-framework/pom.xml index b0f5702ae..2da02638d 100644 --- a/yudao-framework/pom.xml +++ b/yudao-framework/pom.xml @@ -39,6 +39,7 @@ yudao-spring-boot-starter-biz-error-code yudao-spring-boot-starter-flowable + yudao-spring-boot-starter-captcha yudao-framework diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java index 366f96008..4285b8f4c 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/ArrayUtils.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.framework.common.util.collection; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.util.ArrayUtil; import java.util.Collection; @@ -44,7 +45,7 @@ public class ArrayUtils { if (CollectionUtil.isEmpty(from)) { return (T[]) (new Object[0]); } - return ArrayUtil.toArray(from, (Class) CollectionUtil.getElementType(from.iterator())); + return ArrayUtil.toArray(from, (Class) IterUtil.getElementType(from.iterator())); } public static T get(T[] array, int index) { diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java index d9a01747d..6fcb29c95 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java @@ -17,16 +17,15 @@ import java.util.regex.Pattern; */ public class ValidationUtils { + private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[189]))\\d{8}$"); + private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*"); public static boolean isMobile(String mobile) { - if (StrUtil.length(mobile) != 11) { - return false; - } - // TODO 芋艿,后面完善手机校验 - return true; + return StringUtils.hasText(mobile) + && PATTERN_MOBILE.matcher(mobile).matches(); } public static boolean isURL(String url) { diff --git a/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java index 866dcb46d..58e1258b0 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-dict/src/main/java/cn/iocoder/yudao/framework/dict/core/util/DictFrameworkUtils.java @@ -27,7 +27,7 @@ public class DictFrameworkUtils { /** * 针对 {@link #getDictDataLabel(String, String)} 的缓存 */ - private static final LoadingCache, DictDataRespDTO> getDictDataCache = CacheUtils.buildAsyncReloadingCache( + private static final LoadingCache, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache( Duration.ofMinutes(1L), // 过期时间 1 分钟 new CacheLoader, DictDataRespDTO>() { @@ -41,7 +41,7 @@ public class DictFrameworkUtils { /** * 针对 {@link #parseDictDataValue(String, String)} 的缓存 */ - private static final LoadingCache, DictDataRespDTO> parseDictDataCache = CacheUtils.buildAsyncReloadingCache( + private static final LoadingCache, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache( Duration.ofMinutes(1L), // 过期时间 1 分钟 new CacheLoader, DictDataRespDTO>() { @@ -59,12 +59,12 @@ public class DictFrameworkUtils { @SneakyThrows public static String getDictDataLabel(String dictType, String value) { - return getDictDataCache.get(new KeyValue<>(dictType, value)).getLabel(); + return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel(); } @SneakyThrows public static String parseDictDataValue(String dictType, String label) { - return parseDictDataCache.get(new KeyValue<>(dictType, label)).getValue(); + return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue(); } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml index 4153c25c4..e6106b9d9 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/pom.xml @@ -52,12 +52,18 @@ com.alipay.sdk alipay-sdk-java - 4.17.9.ALL + 4.31.72.ALL + + + org.bouncycastle + bcprov-jdk15on + + com.github.binarywang weixin-java-pay - 4.1.9.B + 4.3.8.B diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java index 292b6cf01..3253709c8 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.framework.pay.core.client.impl; -import cn.hutool.extra.validation.ValidationUtil; import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; @@ -10,6 +9,8 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; import lombok.extern.slf4j.Slf4j; +import javax.validation.Validation; + import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; /** @@ -79,7 +80,7 @@ public abstract class AbstractPayClient implemen @Override public final PayCommonResult unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { - ValidationUtil.validate(reqDTO); + Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); // 执行短信发送 PayCommonResult result; try { diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java index a4c14f634..9ded6f026 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java @@ -53,9 +53,8 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest { "lrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZ" + "ikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); - // TODO @tina:= 前后要有空格哈 @InjectMocks - AlipayQrPayClient client=new AlipayQrPayClient(10L,config); + AlipayQrPayClient client = new AlipayQrPayClient(10L,config); @Mock private DefaultAlipayClient defaultAlipayClient; diff --git a/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml index 9572391db..2dc2fe688 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-biz-weixin/pom.xml @@ -35,12 +35,12 @@ com.github.binarywang wx-java-mp-spring-boot-starter - 4.3.4.B + 4.3.8.B com.github.binarywang wx-java-miniapp-spring-boot-starter - 4.3.4.B + 4.3.8.B diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/pom.xml b/yudao-framework/yudao-spring-boot-starter-captcha/pom.xml new file mode 100644 index 000000000..c2d237406 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-captcha/pom.xml @@ -0,0 +1,39 @@ + + + + cn.iocoder.boot + yudao-framework + ${revision} + + 4.0.0 + yudao-spring-boot-starter-captcha + jar + + ${project.artifactId} + 验证码拓展 + 1. 基于 aj-captcha 实现滑块验证码,文档:https://ajcaptcha.beliefteam.cn/captcha-doc/ + + + + + + org.springframework.boot + spring-boot-starter + + + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + + + + com.anji-plus + spring-boot-starter-captcha + + + + diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/config/YudaoCaptchaConfiguration.java b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/config/YudaoCaptchaConfiguration.java new file mode 100644 index 000000000..0f47b0844 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/config/YudaoCaptchaConfiguration.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.framework.captcha.config; + +import cn.hutool.core.util.ClassUtil; +import cn.iocoder.yudao.framework.captcha.core.enums.CaptchaRedisKeyConstants; +import cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl; +import com.anji.captcha.service.CaptchaCacheService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; + +@Configuration +public class YudaoCaptchaConfiguration { + + static { + // 手动加载 Lock4jRedisKeyConstants 类,因为它不会被使用到 + // 如果不加载,会导致 Redis 监控,看到它的 Redis Key 枚举 + ClassUtil.loadClass(CaptchaRedisKeyConstants.class.getName()); + } + + @Bean + public CaptchaCacheService captchaCacheService(StringRedisTemplate stringRedisTemplate) { + return new RedisCaptchaServiceImpl(stringRedisTemplate); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/enums/CaptchaRedisKeyConstants.java b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/enums/CaptchaRedisKeyConstants.java new file mode 100644 index 000000000..5d17e0477 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/enums/CaptchaRedisKeyConstants.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.framework.captcha.core.enums; + +import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; +import com.anji.captcha.model.vo.PointVO; +import org.redisson.api.RLock; + +import java.time.Duration; +import java.util.Map; + +import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH; +import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING; + +/** + * 验证码 Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface CaptchaRedisKeyConstants { + + RedisKeyDefine AJ_CAPTCHA_REQ_LIMIT = new RedisKeyDefine("验证码的请求限流", + "AJ.CAPTCHA.REQ.LIMIT-%s-%s", + STRING, Integer.class, Duration.ofSeconds(60)); // 例如说:验证失败 5 次,get 接口锁定 + + RedisKeyDefine AJ_CAPTCHA_RUNNING = new RedisKeyDefine("验证码的坐标", + "RUNNING:CAPTCHA:%s", // AbstractCaptchaService.REDIS_CAPTCHA_KEY + STRING, PointVO.class, Duration.ofSeconds(120)); // {"secretKey":"PP1w2Frr2KEejD2m","x":162,"y":5} + +} diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/service/RedisCaptchaServiceImpl.java b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/service/RedisCaptchaServiceImpl.java new file mode 100644 index 000000000..c14901efb --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/core/service/RedisCaptchaServiceImpl.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.framework.captcha.core.service; + +import com.anji.captcha.service.CaptchaCacheService; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; + +import javax.annotation.Resource; +import java.util.concurrent.TimeUnit; + +/** + * 基于 Redis 实现验证码的存储 + * + * @author 星语 + */ +@NoArgsConstructor // 保证 aj-captcha 的 SPI 创建 +@AllArgsConstructor +public class RedisCaptchaServiceImpl implements CaptchaCacheService { + + @Resource // 保证 aj-captcha 的 SPI 创建时的注入 + private StringRedisTemplate stringRedisTemplate; + + @Override + public String type() { + return "redis"; + } + + @Override + public void set(String key, String value, long expiresInSeconds) { + stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS); + } + + @Override + public boolean exists(String key) { + return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); + } + + @Override + public void delete(String key) { + stringRedisTemplate.delete(key); + } + + @Override + public String get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + @Override + public Long increment(String key, long val) { + return stringRedisTemplate.opsForValue().increment(key,val); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/package-info.java b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/package-info.java new file mode 100644 index 000000000..e78d9eab2 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/cn/iocoder/yudao/framework/captcha/package-info.java @@ -0,0 +1,7 @@ +/** + * 验证码拓展 + * 1. 基于 aj-captcha 实现滑块验证码,文档:https://ajcaptcha.beliefteam.cn/captcha-doc/ + * + * @author 星语 + */ +package cn.iocoder.yudao.framework.captcha; diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService new file mode 100644 index 000000000..afede97de --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaCacheService @@ -0,0 +1 @@ +cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..ed8b528ff --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.iocoder.yudao.framework.captcha.config.YudaoCaptchaConfiguration diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg1.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg1.png new file mode 100644 index 000000000..c48145769 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg1.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg2.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg2.png new file mode 100644 index 000000000..bf8fb38ff Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg2.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg3.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg3.png new file mode 100644 index 000000000..f871d3d12 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg3.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg4.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg4.png new file mode 100644 index 000000000..2e3d87166 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg4.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg5.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg5.png new file mode 100644 index 000000000..fe383b720 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg5.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg6.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg6.png new file mode 100644 index 000000000..5024ceb22 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg6.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg7.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg7.png new file mode 100644 index 000000000..efe76f8de Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg7.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg8.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg8.png new file mode 100644 index 000000000..2727aa324 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg8.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg9.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg9.png new file mode 100644 index 000000000..4463aa2fb Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/original/bg9.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/1.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/1.png new file mode 100644 index 000000000..ef1132471 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/1.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/10.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/10.png new file mode 100644 index 000000000..297e44cf4 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/10.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/11.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/11.png new file mode 100644 index 000000000..d9b1da8d7 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/11.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/12.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/12.png new file mode 100644 index 000000000..07e7313b4 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/12.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/13.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/13.png new file mode 100644 index 000000000..82c3dd969 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/13.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/14.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/14.png new file mode 100644 index 000000000..0b9a86615 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/14.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/15.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/15.png new file mode 100644 index 000000000..86b0d1cf1 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/15.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/16.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/16.png new file mode 100644 index 000000000..e90a6e292 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/16.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/17.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/17.png new file mode 100644 index 000000000..a82cbc7c4 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/17.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/18.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/18.png new file mode 100644 index 000000000..d3f3cfd03 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/18.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/19.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/19.png new file mode 100644 index 000000000..eb2855bd8 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/19.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/8.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/8.png new file mode 100644 index 000000000..3cb5ce1c8 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/8.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/9.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/9.png new file mode 100644 index 000000000..384d35415 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/11/9.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/2.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/2.png new file mode 100644 index 000000000..baf3f06d7 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/2.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/3.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/3.png new file mode 100644 index 000000000..ccaf61723 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/3.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/4.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/4.png new file mode 100644 index 000000000..7dab16223 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/jigsaw/slidingBlock/4.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg1.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg1.png new file mode 100644 index 000000000..14e73454a Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg1.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg10.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg10.png new file mode 100644 index 000000000..1ea1d6d59 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg10.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg2.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg2.png new file mode 100644 index 000000000..0edb32937 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg2.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg3.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg3.png new file mode 100644 index 000000000..91679960f Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg3.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg4.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg4.png new file mode 100644 index 000000000..e8e8e6c0c Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg4.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg5.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg5.png new file mode 100644 index 000000000..66a3181e7 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg5.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg6.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg6.png new file mode 100644 index 000000000..9b0f5d8c1 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg6.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg7.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg7.png new file mode 100644 index 000000000..db41c74a0 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg7.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg8.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg8.png new file mode 100644 index 000000000..349681306 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg8.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg9.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg9.png new file mode 100644 index 000000000..4e7b47752 Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/images/pic-click/bg9.png differ diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java index ed6d81baa..052c7232e 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java @@ -16,11 +16,11 @@ import java.util.Set; */ public class JsonLongSetTypeHandler extends AbstractJsonTypeHandler { - private static final TypeReference> typeReference = new TypeReference>(){}; + private static final TypeReference> TYPE_REFERENCE = new TypeReference>(){}; @Override protected Object parse(String json) { - return JsonUtils.parseObject(json, typeReference); + return JsonUtils.parseObject(json, TYPE_REFERENCE); } @Override diff --git a/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java b/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java index 882d8d3b1..4ef3b92f3 100644 --- a/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java +++ b/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/RedisKeyRegistry.java @@ -11,18 +11,18 @@ public class RedisKeyRegistry { /** * Redis RedisKeyDefine 数组 */ - private static final List defines = new ArrayList<>(); + private static final List DEFINES = new ArrayList<>(); public static void add(RedisKeyDefine define) { - defines.add(define); + DEFINES.add(define); } public static List list() { - return defines; + return DEFINES; } public static int size() { - return defines.size(); + return DEFINES.size(); } } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java index c9a789f41..1e7518787 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java @@ -81,7 +81,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap /** * 配置 URL 的安全配置 - * + *

* anyRequest | 匹配所有请求路径 * access | SpringEl表达式结果为true时可以访问 * anonymous | 匿名可以访问 @@ -109,8 +109,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap .headers().frameOptions().disable().and() // 一堆自定义的 Spring Security 处理器 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) - .accessDeniedHandler(accessDeniedHandler); - // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高 + .accessDeniedHandler(accessDeniedHandler); + // 登录、登录暂时不使用 Spring Security 的拓展点,主要考虑一方面拓展多用户、多种登录方式相对复杂,一方面用户的学习成本较高 // 获得 @PermitAll 带来的 URL 列表,免登录 Multimap permitAllUrls = getPermitAllUrlsFromAnnotations(); @@ -118,23 +118,25 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap httpSecurity // ①:全局共享规则 .authorizeRequests() - // 1.1 静态资源,可匿名访问 - .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() - // 1.2 设置 @PermitAll 无需认证 - .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll() - .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll() - .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll() - .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll() - // 1.3 基于 yudao.security.permit-all-urls 无需认证 - .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll() - // 1.4 设置 App API 无需认证 - .antMatchers(buildAppApi("/**")).permitAll() + // 1.1 静态资源,可匿名访问 + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() + // 1.2 设置 @PermitAll 无需认证 + .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll() + .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll() + .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll() + .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll() + // 1.3 基于 yudao.security.permit-all-urls 无需认证 + .antMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll() + // 1.4 设置 App API 无需认证 + .antMatchers(buildAppApi("/**")).permitAll() + // 1.5 验证码captcha 允许匿名访问 + .antMatchers("/captcha/get", "/captcha/check").permitAll() // ②:每个项目的自定义规则 .and().authorizeRequests(registry -> // 下面,循环设置自定义规则 authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry))) // ③:兜底规则,必须认证 .authorizeRequests() - .anyRequest().authenticated() + .anyRequest().authenticated() ; // 添加 Token Filter diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java index 5e46daa1e..1bdbe712f 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java @@ -17,19 +17,19 @@ public class TransmittableThreadLocalSecurityContextHolderStrategy implements Se /** * 使用 TransmittableThreadLocal 作为上下文 */ - private static final ThreadLocal contextHolder = new TransmittableThreadLocal<>(); + private static final ThreadLocal CONTEXT_HOLDER = new TransmittableThreadLocal<>(); @Override public void clearContext() { - contextHolder.remove(); + CONTEXT_HOLDER.remove(); } @Override public SecurityContext getContext() { - SecurityContext ctx = contextHolder.get(); + SecurityContext ctx = CONTEXT_HOLDER.get(); if (ctx == null) { ctx = createEmptyContext(); - contextHolder.set(ctx); + CONTEXT_HOLDER.set(ctx); } return ctx; } @@ -37,7 +37,7 @@ public class TransmittableThreadLocalSecurityContextHolderStrategy implements Se @Override public void setContext(SecurityContext context) { Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); - contextHolder.set(context); + CONTEXT_HOLDER.set(context); } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java index 89dfd1227..032370158 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java @@ -57,7 +57,7 @@ public class BpmMessageServiceImpl implements BpmMessageService { templateParams.put("taskName", reqDTO.getTaskName()); templateParams.put("startUserNickname", reqDTO.getStartUserNickname()); templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); - smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(), + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java index 7523fab6d..90f5816f3 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilder.java @@ -31,7 +31,7 @@ public class CodegenBuilder { * 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射 * 注意,字段的匹配以后缀的方式 */ - private static final Map columnListOperationConditionMappings = + private static final Map COLUMN_LIST_OPERATION_CONDITION_MAPPINGS = MapUtil.builder() .put("name", CodegenColumnListConditionEnum.LIKE) .put("time", CodegenColumnListConditionEnum.BETWEEN) @@ -42,7 +42,7 @@ public class CodegenBuilder { * 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射 * 注意,字段的匹配以后缀的方式 */ - private static final Map columnHtmlTypeMappings = + private static final Map COLUMN_HTML_TYPE_MAPPINGS = MapUtil.builder() .put("status", CodegenColumnHtmlTypeEnum.RADIO) .put("sex", CodegenColumnHtmlTypeEnum.RADIO) @@ -143,7 +143,7 @@ public class CodegenBuilder { column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) && !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递 // 处理 listOperationCondition 字段 - columnListOperationConditionMappings.entrySet().stream() + COLUMN_LIST_OPERATION_CONDITION_MAPPINGS.entrySet().stream() .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition())); if (column.getListOperationCondition() == null) { @@ -155,7 +155,7 @@ public class CodegenBuilder { private void processColumnUI(CodegenColumnDO column) { // 基于后缀进行匹配 - columnHtmlTypeMappings.entrySet().stream() + COLUMN_HTML_TYPE_MAPPINGS.entrySet().stream() .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType())); // 如果是 Boolean 类型时,设置为 radio 类型. diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 2f39519a3..97bb86262 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -12,8 +12,7 @@ public interface ErrorCodeConstants { // ========== AUTH 模块 1002000000 ========== ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用"); - ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在"); - ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确"); + ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确,原因:{}"); ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定"); ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期"); ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1002000007, "手机号不存在"); diff --git a/yudao-module-system/yudao-module-system-biz/pom.xml b/yudao-module-system/yudao-module-system-biz/pom.xml index 4dbb0973c..ecac34aa5 100644 --- a/yudao-module-system/yudao-module-system-biz/pom.xml +++ b/yudao-module-system/yudao-module-system-biz/pom.xml @@ -97,6 +97,11 @@ yudao-spring-boot-starter-excel + + cn.iocoder.boot + yudao-spring-boot-starter-captcha + + diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 0a136551f..9a9c0a95e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -55,7 +55,6 @@ public class AuthController { private PermissionService permissionService; @Resource private SocialUserService socialUserService; - @Resource private SecurityProperties securityProperties; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java index 67e80d24a..bafc322e2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java @@ -35,13 +35,11 @@ public class AuthLoginReqVO { // ========== 图片验证码相关 ========== - @ApiModelProperty(value = "验证码", required = true, example = "1024", notes = "验证码开启时,需要传递") + @ApiModelProperty(value = "验证码", required = true, + example = "PfcH6mgr8tpXuMWFjvW6YVaqrswIuwmWI5dsVZSg7sGpWtDCUbHuDEXl3cFB1+VvCC/rAkSwK8Fad52FSuncVg==", + notes = "验证码开启时,需要传递") @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class) - private String code; - - @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62", notes = "验证码开启时,需要传递") - @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class) - private String uuid; + private String captchaVerification; // ========== 绑定社交登录时,需要传递如下参数 ========== diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.http b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.http deleted file mode 100644 index 2033fac31..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.http +++ /dev/null @@ -1,3 +0,0 @@ -### 请求 /captcha/get-image 接口 => 成功 -GET {{baseUrl}}/system/captcha/get-image -tenant-id: {{adminTenentId}} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.java deleted file mode 100644 index 546bbde00..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/CaptchaController.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.system.controller.admin.common; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; -import cn.iocoder.yudao.module.system.service.common.CaptchaService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import javax.annotation.security.PermitAll; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Api(tags = "管理后台 - 验证码") -@RestController -@RequestMapping("/system/captcha") -public class CaptchaController { - - @Resource - private CaptchaService captchaService; - - @GetMapping("/get-image") - @PermitAll - @ApiOperation("生成图片验证码") - public CommonResult getCaptchaImage() { - return success(captchaService.getCaptchaImage()); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java deleted file mode 100644 index 382fafcb5..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/common/vo/CaptchaImageRespVO.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.system.controller.admin.common.vo; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@ApiModel("管理后台 - 验证码图片 Response VO") -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CaptchaImageRespVO { - - @ApiModelProperty(value = "是否开启", required = true, example = "true", notes = "如果为 false,则关闭验证码功能") - private Boolean enable; - - @ApiModelProperty(value = "uuid", example = "1b3b7d00-83a8-4638-9e37-d67011855968", - notes = "enable = true 时,非空!通过该 uuid 作为该验证码的标识") - private String uuid; - - @ApiModelProperty(value = "图片", notes = "enable = true 时,非空!验证码的图片内容,使用 Base64 编码") - private String img; - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/common/CaptchaConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/common/CaptchaConvert.java deleted file mode 100644 index 54d36bee9..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/common/CaptchaConvert.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.iocoder.yudao.module.system.convert.common; - -import cn.hutool.captcha.AbstractCaptcha; -import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -@Mapper -public interface CaptchaConvert { - - CaptchaConvert INSTANCE = Mappers.getMapper(CaptchaConvert.class); - - default CaptchaImageRespVO convert(String uuid, AbstractCaptcha captcha) { - return CaptchaImageRespVO.builder().uuid(uuid).img(captcha.getImageBase64()).build(); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaConfig.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaConfig.java deleted file mode 100644 index 4028f6cef..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package cn.iocoder.yudao.module.system.framework.captcha.config; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(CaptchaProperties.class) -public class CaptchaConfig { -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaProperties.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaProperties.java deleted file mode 100644 index 0d7cd0d20..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/CaptchaProperties.java +++ /dev/null @@ -1,38 +0,0 @@ -package cn.iocoder.yudao.module.system.framework.captcha.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.NotNull; -import java.time.Duration; - -@ConfigurationProperties(prefix = "yudao.captcha") -@Validated -@Data -public class CaptchaProperties { - - private static final Boolean ENABLE_DEFAULT = true; - - /** - * 是否开启 - * 注意,这里仅仅是后端 Server 是否校验,暂时不控制前端的逻辑 - */ - private Boolean enable = ENABLE_DEFAULT; - /** - * 验证码的过期时间 - */ - @NotNull(message = "验证码的过期时间不为空") - private Duration timeout; - /** - * 验证码的高度 - */ - @NotNull(message = "验证码的高度不能为空") - private Integer height; - /** - * 验证码的宽度 - */ - @NotNull(message = "验证码的宽度不能为空") - private Integer width; - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/package-info.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/package-info.java deleted file mode 100644 index ee406c079..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 基于 Hutool captcha 库,实现验证码功能 - */ -package cn.iocoder.yudao.module.system.framework.captcha; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 8f54e9f61..ce860add5 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -17,14 +17,17 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; -import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.member.MemberService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import com.anji.captcha.model.common.ResponseModel; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaService; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -47,8 +50,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private AdminUserService userService; @Resource - private CaptchaService captchaService; - @Resource private LoginLogService loginLogService; @Resource private OAuth2TokenService oauth2TokenService; @@ -56,13 +57,19 @@ public class AdminAuthServiceImpl implements AdminAuthService { private SocialUserService socialUserService; @Resource private MemberService memberService; - @Resource private Validator validator; - + @Resource + private CaptchaService captchaService; @Resource private SmsCodeApi smsCodeApi; + /** + * 验证码的开关,默认为 true + */ + @Value("${yudao.captcha.enable:true}") + private Boolean captchaEnable; + @Override public AdminUserDO authenticate(String username, String password) { final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; @@ -86,7 +93,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Override public AuthLoginRespVO login(AuthLoginReqVO reqVO) { - // 判断验证码是否正确 + // 校验验证码 verifyCaptcha(reqVO); // 使用账号密码,进行登录 @@ -97,7 +104,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())); } - // 创建 Token 令牌,记录登录日志 return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); } @@ -127,32 +133,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE); } - @VisibleForTesting - void verifyCaptcha(AuthLoginReqVO reqVO) { - // 如果验证码关闭,则不进行校验 - if (!captchaService.isCaptchaEnable()) { - return; - } - // 校验验证码 - ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); - // 验证码不存在 - final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; - String code = captchaService.getCaptchaCode(reqVO.getUuid()); - if (code == null) { - // 创建登录失败日志(验证码不存在) - createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_NOT_FOUND); - throw exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND); - } - // 验证码不正确 - if (!code.equals(reqVO.getCode())) { - // 创建登录失败日志(验证码不正确) - createLoginLog(null, reqVO.getUsername(), logTypeEnum, LoginResultEnum.CAPTCHA_CODE_ERROR); - throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); - } - // 正确,所以要删除下验证码 - captchaService.deleteCaptchaCode(reqVO.getUuid()); - } - private void createLoginLog(Long userId, String username, LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) { // 插入登录日志 @@ -197,6 +177,25 @@ public class AdminAuthServiceImpl implements AdminAuthService { return AuthConvert.INSTANCE.convert(accessTokenDO); } + @VisibleForTesting + void verifyCaptcha(AuthLoginReqVO reqVO) { + // 如果验证码关闭,则不进行校验 + if (!captchaEnable) { + return; + } + // 校验验证码 + ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification()); + ResponseModel response = captchaService.verification(captchaVO); + // 验证不通过 + if (!response.isSuccess()) { + // 创建登录失败日志(验证码不正确) + createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR); + throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg()); + } + } + private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { // 插入登陆日志 createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaService.java deleted file mode 100644 index ecb05d88a..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaService.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.system.service.common; - -import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; - -/** - * 验证码 Service 接口 - */ -public interface CaptchaService { - - /** - * 获得验证码图片 - * - * @return 验证码图片 - */ - CaptchaImageRespVO getCaptchaImage(); - - /** - * 是否开启图片验证码 - * - * @return 是否 - */ - Boolean isCaptchaEnable(); - - /** - * 获得 uuid 对应的验证码 - * - * @param uuid 验证码编号 - * @return 验证码 - */ - String getCaptchaCode(String uuid); - - /** - * 删除 uuid 对应的验证码 - * - * @param uuid 验证码编号 - */ - void deleteCaptchaCode(String uuid); - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java deleted file mode 100644 index f52f0ba3b..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.system.service.common; - -import cn.hutool.captcha.CaptchaUtil; -import cn.hutool.captcha.CircleCaptcha; -import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.module.system.convert.common.CaptchaConvert; -import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties; -import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; -import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; - -/** - * 验证码 Service 实现类 - */ -@Service -public class CaptchaServiceImpl implements CaptchaService { - - @Resource - private CaptchaProperties captchaProperties; - - /** - * 验证码是否开关 - * - * 虽然 {@link CaptchaProperties#getEnable()} 有该属性,但是 Apollo 在 Spring Boot 下无法刷新 @ConfigurationProperties 注解, - * 所以暂时只能这么处理~ - */ - @Value("${yudao.captcha.enable}") - private Boolean enable; - - @Resource - private CaptchaRedisDAO captchaRedisDAO; - - @Override - public CaptchaImageRespVO getCaptchaImage() { - if (!Boolean.TRUE.equals(enable)) { - return CaptchaImageRespVO.builder().enable(enable).build(); - } - // 生成验证码 - CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight()); - // 缓存到 Redis 中 - String uuid = IdUtil.fastSimpleUUID(); - captchaRedisDAO.set(uuid, captcha.getCode(), captchaProperties.getTimeout()); - // 返回 - return CaptchaConvert.INSTANCE.convert(uuid, captcha).setEnable(enable); - } - - @Override - public Boolean isCaptchaEnable() { - return enable; - } - - @Override - public String getCaptchaCode(String uuid) { - return captchaRedisDAO.get(uuid); - } - - @Override - public void deleteCaptchaCode(String uuid) { - captchaRedisDAO.delete(uuid); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java index 435e5791f..218778ac9 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java @@ -11,13 +11,12 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; -import cn.iocoder.yudao.module.system.service.common.CaptchaService; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.member.MemberService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; -import org.junit.jupiter.api.BeforeEach; +import com.anji.captcha.service.CaptchaService; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; @@ -57,11 +56,6 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { @MockBean private Validator validator; - @BeforeEach - public void setUp() { - when(captchaService.isCaptchaEnable()).thenReturn(true); - } - @Test public void testAuthenticate_success() { // 准备参数 @@ -138,82 +132,82 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { ); } - @Test - public void testCaptcha_success() { - // 准备参数 - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); +// @Test +// public void testCaptcha_success() { +// // 准备参数 +// AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); +// +// // mock 验证码正确 +// when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); +// +// // 调用 +// authService.verifyCaptcha(reqVO); +// // 断言 +// verify(captchaService).deleteCaptchaCode(reqVO.getUuid()); +// } +// +// @Test +// public void testCaptcha_notFound() { +// // 准备参数 +// AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); +// +// // 调用, 并断言异常 +// assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND); +// // 校验调用参数 +// verify(loginLogService, times(1)).createLoginLog( +// argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) +// && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult())) +// ); +// } - // mock 验证码正确 - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); +// @Test +// public void testCaptcha_codeError() { +// // 准备参数 +// AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); +// +// // mock 验证码不正确 +// String code = randomString(); +// when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code); +// +// // 调用, 并断言异常 +// assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR); +// // 校验调用参数 +// verify(loginLogService).createLoginLog( +// argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) +// && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult())) +// ); +// } - // 调用 - authService.verifyCaptcha(reqVO); - // 断言 - verify(captchaService).deleteCaptchaCode(reqVO.getUuid()); - } - - @Test - public void testCaptcha_notFound() { - // 准备参数 - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND); - // 校验调用参数 - verify(loginLogService, times(1)).createLoginLog( - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) - && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult())) - ); - } - - @Test - public void testCaptcha_codeError() { - // 准备参数 - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); - - // mock 验证码不正确 - String code = randomString(); - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code); - - // 调用, 并断言异常 - assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR); - // 校验调用参数 - verify(loginLogService).createLoginLog( - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) - && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult())) - ); - } - - @Test - public void testLogin_success() { - // 准备参数 - AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> - o.setUsername("test_username").setPassword("test_password")); - - // mock 验证码正确 - when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); - // mock user 数据 - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username") - .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus())); - when(userService.getUserByUsername(eq("test_username"))).thenReturn(user); - // mock password 匹配 - when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); - // mock 缓存登录用户到 Redis - OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) - .setUserType(UserTypeEnum.ADMIN.getValue())); - when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) - .thenReturn(accessTokenDO); - - // 调用, 并断言异常 - AuthLoginRespVO loginRespVO = authService.login(reqVO); - assertPojoEquals(accessTokenDO, loginRespVO); - // 校验调用参数 - verify(loginLogService).createLoginLog( - argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) - && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) - && o.getUserId().equals(user.getId())) - ); - } +// @Test +// public void testLogin_success() { +// // 准备参数 +// AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> +// o.setUsername("test_username").setPassword("test_password")); +// +// // mock 验证码正确 +// when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); +// // mock user 数据 +// AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername("test_username") +// .setPassword("test_password").setStatus(CommonStatusEnum.ENABLE.getStatus())); +// when(userService.getUserByUsername(eq("test_username"))).thenReturn(user); +// // mock password 匹配 +// when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); +// // mock 缓存登录用户到 Redis +// OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L) +// .setUserType(UserTypeEnum.ADMIN.getValue())); +// when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull())) +// .thenReturn(accessTokenDO); +// +// // 调用, 并断言异常 +// AuthLoginRespVO loginRespVO = authService.login(reqVO); +// assertPojoEquals(accessTokenDO, loginRespVO); +// // 校验调用参数 +// verify(loginLogService).createLoginLog( +// argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) +// && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) +// && o.getUserId().equals(user.getId())) +// ); +// } @Test public void testLogout_success() { diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceTest.java deleted file mode 100644 index 1948538d3..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/common/CaptchaServiceTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.system.service.common; - -import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO; -import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO; -import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties; -import cn.iocoder.yudao.framework.test.core.ut.BaseRedisUnitTest; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - -import javax.annotation.Resource; - -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; -import static org.junit.jupiter.api.Assertions.*; - -@Import({CaptchaServiceImpl.class, CaptchaProperties.class, CaptchaRedisDAO.class}) -public class CaptchaServiceTest extends BaseRedisUnitTest { - - @Resource - private CaptchaServiceImpl captchaService; - - @Resource - private CaptchaRedisDAO captchaRedisDAO; - @Resource - private CaptchaProperties captchaProperties; - - @Test - public void testGetCaptchaImage() { - // 调用 - CaptchaImageRespVO respVO = captchaService.getCaptchaImage(); - // 断言 - assertNotNull(respVO.getUuid()); - assertNotNull(respVO.getImg()); - String captchaCode = captchaRedisDAO.get(respVO.getUuid()); - assertNotNull(captchaCode); - } - - @Test - public void testGetCaptchaCode() { - // 准备参数 - String uuid = randomString(); - String code = randomString(); - // mock 数据 - captchaRedisDAO.set(uuid, code, captchaProperties.getTimeout()); - - // 调用 - String resultCode = captchaService.getCaptchaCode(uuid); - // 断言 - assertEquals(code, resultCode); - } - - @Test - public void testDeleteCaptchaCode() { - // 准备参数 - String uuid = randomString(); - String code = randomString(); - // mock 数据 - captchaRedisDAO.set(uuid, code, captchaProperties.getTimeout()); - - // 调用 - captchaService.deleteCaptchaCode(uuid); - // 断言 - assertNull(captchaRedisDAO.get(uuid)); - } - -} diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 073584275..fb31aa345 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -46,25 +46,25 @@ spring: master: name: ruoyi-vue-pro url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 -# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 -# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 -# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 -# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 + # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 + # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 + # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 username: root password: 123456 -# username: sa -# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + # username: sa + # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W slave: # 模拟从库,可根据自己需要修改 name: ruoyi-vue-pro url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 -# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 -# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 -# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 -# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 + # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 + # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 + # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 username: root password: 123456 -# username: sa -# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + # username: sa + # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 redis: diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 6de6ae46d..85d52550d 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -57,6 +57,25 @@ mybatis-plus: logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject +--- #################### 验证码相关配置 #################### + +aj: + captcha: + jigsaw: classpath:images/jigsaw # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径 + pic-click: classpath:images/pic-click # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径 + cache-type: redis # 缓存 local/redis... + cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存 + timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行 + type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选 + water-mark: 芋道源码 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode + interference-options: 2 # 滑动干扰项(0/1/2) + req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false + req-get-lock-limit: 5 # 验证失败5次,get接口锁定 + req-get-lock-seconds: 10 # 验证失败后,锁定时间间隔 + req-get-minute-limit: 30 # get 接口一分钟内请求数限制 + req-check-minute-limit: 60 # check 接口一分钟内请求数限制 + req-verify-minute-limit: 60 # verify 接口一分钟内请求数限制 + --- #################### 芋道相关配置 #################### yudao: @@ -75,9 +94,7 @@ yudao: version: ${yudao.info.version} base-package: ${yudao.info.base-package} captcha: - timeout: 5m - width: 160 - height: 60 + enable: true # 验证码的开关,默认为 true;注意,优先读取数据库 infra_config 的 yudao.captcha.enable,所以请从数据库修改,可能需要重启项目 codegen: base-package: ${yudao.info.base-package} db-schemas: ${spring.datasource.dynamic.datasource.master.name} @@ -92,12 +109,12 @@ yudao: enable: true ignore-urls: - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 - - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关 + - /captcha/get # 获取图片验证码,和租户无关 + - /captcha/check # 校验图片验证码,和租户无关 - /admin-api/infra/file/*/get/** # 获取图片,和租户无关 - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 - /app-api/pay/order/notify/* # 支付回调通知,不携带租户编号 -# - /jmreport/list - - /jmreport/* + - /jmreport/* # 积木报表,无法携带租户编号 ignore-tables: - system_tenant - system_tenant_package diff --git a/yudao-ui-admin-uniapp/api/login.js b/yudao-ui-admin-uniapp/api/login.js index 628e2a741..4a00d51f7 100644 --- a/yudao-ui-admin-uniapp/api/login.js +++ b/yudao-ui-admin-uniapp/api/login.js @@ -1,47 +1,60 @@ import request from '@/utils/request' // 登录方法 -export function login(username, password, code, uuid) { - const data = { - username, - password, - code, - uuid - } - return request({ - url: '/system/auth/login', - headers: { - isToken: false - }, - 'method': 'post', - 'data': data - }) +export function login(username, password, captchaVerification) { + const data = { + username, + password, + captchaVerification + } + return request({ + url: '/system/auth/login', + headers: { + isToken: false + }, + 'method': 'POST', + 'data': data + }) } // 获取用户详细信息 export function getInfo() { - return request({ - url: '/system/auth/get-permission-info', - 'method': 'get' - }) + return request({ + url: '/system/auth/get-permission-info', + 'method': 'GET' + }) } // 退出方法 export function logout() { - return request({ - url: '/system/auth/logout', - 'method': 'post' - }) + return request({ + url: '/system/auth/logout', + 'method': 'POST' + }) } // 获取验证码 -export function getCodeImg() { - return request({ - url: '/system/captcha/get-image', - headers: { - isToken: false - }, - method: 'get', - timeout: 20000 - }) +export function getCaptcha(data) { + return request({ + url: '/captcha/get', + headers: { + isToken: false, + isTenant: false + }, + method: 'POST', + 'data': data + }) +} + +// 验证验证码 +export function checkCaptcha(data) { + return request({ + url: '/captcha/check', + headers: { + isToken: false, + isTenant: false + }, + method: 'POST', + 'data': data + }) } diff --git a/yudao-ui-admin-uniapp/api/system/user.js b/yudao-ui-admin-uniapp/api/system/user.js index 9e9e7bf5e..59e9304cc 100644 --- a/yudao-ui-admin-uniapp/api/system/user.js +++ b/yudao-ui-admin-uniapp/api/system/user.js @@ -9,7 +9,7 @@ export function updateUserPwd(oldPassword, newPassword) { } return request({ url: '/system/user/profile/update-password', - method: 'put', + method: 'PUT', params: data }) } @@ -18,7 +18,7 @@ export function updateUserPwd(oldPassword, newPassword) { export function getUserProfile() { return request({ url: '/system/user/profile/get', - method: 'get' + method: 'GET' }) } @@ -26,7 +26,7 @@ export function getUserProfile() { export function updateUserProfile(data) { return request({ url: '/system/user/profile/update', - method: 'put', + method: 'PUT', data: data }) } @@ -35,7 +35,7 @@ export function updateUserProfile(data) { export function uploadAvatar(data) { return upload({ url: '/system/user/profile/update-avatar', - method: 'put', + method: 'PUT', name: data.name, filePath: data.filePath }) diff --git a/yudao-ui-admin-uniapp/components/verifition/Verify.vue b/yudao-ui-admin-uniapp/components/verifition/Verify.vue new file mode 100644 index 000000000..3fe4d884f --- /dev/null +++ b/yudao-ui-admin-uniapp/components/verifition/Verify.vue @@ -0,0 +1,469 @@ + + + diff --git a/yudao-ui-admin-uniapp/components/verifition/utils/ase.js b/yudao-ui-admin-uniapp/components/verifition/utils/ase.js new file mode 100644 index 000000000..1fdceed4f --- /dev/null +++ b/yudao-ui-admin-uniapp/components/verifition/utils/ase.js @@ -0,0 +1,14 @@ +import CryptoJS from 'crypto-js' +/** + * @word 要加密的内容 + * @keyWord String 服务器随机返回的关键字 + * */ +export function aesEncrypt(word, keyWord = "XwKsGlMcdPMEhR1B") { + var key = CryptoJS.enc.Utf8.parse(keyWord); + var srcs = CryptoJS.enc.Utf8.parse(word); + var encrypted = CryptoJS.AES.encrypt(srcs, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }); + return encrypted.toString(); +} diff --git a/yudao-ui-admin-uniapp/components/verifition/utils/request.js b/yudao-ui-admin-uniapp/components/verifition/utils/request.js new file mode 100644 index 000000000..e6a31b02a --- /dev/null +++ b/yudao-ui-admin-uniapp/components/verifition/utils/request.js @@ -0,0 +1,17 @@ +import config from '@/config' +const baseUrl = config.baseUrl +export const myRequest = (option = {}) => { + return new Promise((reslove, reject) => { + uni.request({ + url: baseUrl + option.url, + data: option.data, + method: option.method || "GET", + success: (result) => { + reslove(result) + }, + fail: (error) => { + reject(error) + } + }) + }) +} diff --git a/yudao-ui-admin-uniapp/components/verifition/verifyPoint/verifyPoint.vue b/yudao-ui-admin-uniapp/components/verifition/verifyPoint/verifyPoint.vue new file mode 100644 index 000000000..ea794f4c9 --- /dev/null +++ b/yudao-ui-admin-uniapp/components/verifition/verifyPoint/verifyPoint.vue @@ -0,0 +1,555 @@ + + + + diff --git a/yudao-ui-admin-uniapp/components/verifition/verifySlider/verifySlider.vue b/yudao-ui-admin-uniapp/components/verifition/verifySlider/verifySlider.vue new file mode 100644 index 000000000..24ccd1aeb --- /dev/null +++ b/yudao-ui-admin-uniapp/components/verifition/verifySlider/verifySlider.vue @@ -0,0 +1,659 @@ + + + diff --git a/yudao-ui-admin-uniapp/config.js b/yudao-ui-admin-uniapp/config.js index cd0a162b3..d8d38e8d2 100644 --- a/yudao-ui-admin-uniapp/config.js +++ b/yudao-ui-admin-uniapp/config.js @@ -1,7 +1,8 @@ // 应用全局配置 module.exports = { // baseUrl: 'http://localhost:8080', - baseUrl: 'http://localhost:48080/admin-api', + baseUrl: 'http://localhost:48080', + baseApi: '/admin-api', // 应用信息 appInfo: { // 应用名称 diff --git a/yudao-ui-admin-uniapp/package.json b/yudao-ui-admin-uniapp/package.json new file mode 100644 index 000000000..e5def356f --- /dev/null +++ b/yudao-ui-admin-uniapp/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "crypto-js": "^4.0.0" + } +} diff --git a/yudao-ui-admin-uniapp/pages/login.vue b/yudao-ui-admin-uniapp/pages/login.vue index 24bbdfd05..bb7908eb9 100644 --- a/yudao-ui-admin-uniapp/pages/login.vue +++ b/yudao-ui-admin-uniapp/pages/login.vue @@ -1,182 +1,168 @@ diff --git a/yudao-ui-admin-uniapp/static/images/default.jpg b/yudao-ui-admin-uniapp/static/images/default.jpg new file mode 100644 index 000000000..aa0237bb9 Binary files /dev/null and b/yudao-ui-admin-uniapp/static/images/default.jpg differ diff --git a/yudao-ui-admin-uniapp/store/modules/user.js b/yudao-ui-admin-uniapp/store/modules/user.js index d49cb9a9d..7d03c1a23 100644 --- a/yudao-ui-admin-uniapp/store/modules/user.js +++ b/yudao-ui-admin-uniapp/store/modules/user.js @@ -42,10 +42,9 @@ const user = { Login({ commit }, userInfo) { const username = userInfo.username.trim() const password = userInfo.password - const code = userInfo.code - const uuid = userInfo.uuid + const captchaVerification = userInfo.captchaVerification return new Promise((resolve, reject) => { - login(username, password, code, uuid).then(res => { + login(username, password, captchaVerification).then(res => { res = res.data; // 设置 token setToken(res) @@ -83,7 +82,6 @@ const user = { LogOut({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { - commit('SET_TOKEN', '') commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) removeToken() diff --git a/yudao-ui-admin-uniapp/utils/request.js b/yudao-ui-admin-uniapp/utils/request.js index 6c00a3f09..4799e7467 100644 --- a/yudao-ui-admin-uniapp/utils/request.js +++ b/yudao-ui-admin-uniapp/utils/request.js @@ -5,7 +5,7 @@ import errorCode from '@/utils/errorCode' import { toast, showConfirm, tansParams } from '@/utils/common' let timeout = 10000 -const baseUrl = config.baseUrl +const baseUrl = config.baseUrl + config.baseApi; const request = config => { // 是否需要设置 token diff --git a/yudao-ui-admin-vue3/.env b/yudao-ui-admin-vue3/.env index 1bcb92da6..a7bbb6b7f 100644 --- a/yudao-ui-admin-vue3/.env +++ b/yudao-ui-admin-vue3/.env @@ -9,3 +9,6 @@ VITE_OPEN=true # 租户开关 VITE_APP_TENANT_ENABLE=true + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=false diff --git a/yudao-ui-admin-vue3/README.md b/yudao-ui-admin-vue3/README.md index c477d2e75..8379c7f7e 100644 --- a/yudao-ui-admin-vue3/README.md +++ b/yudao-ui-admin-vue3/README.md @@ -10,7 +10,6 @@ Prettier Less Taiwind -

## 介绍 diff --git a/yudao-ui-admin-vue3/package.json b/yudao-ui-admin-vue3/package.json index c221f0d68..81c41cc42 100644 --- a/yudao-ui-admin-vue3/package.json +++ b/yudao-ui-admin-vue3/package.json @@ -26,12 +26,13 @@ }, "dependencies": { "@iconify/iconify": "^2.2.1", - "@vueuse/core": "^9.0.2", + "@vueuse/core": "^9.1.0", "@wangeditor/editor": "^5.1.14", "@wangeditor/editor-for-vue": "^5.1.10", - "@zxcvbn-ts/core": "^2.0.3", + "@zxcvbn-ts/core": "^2.0.4", "animate.css": "^4.1.1", "axios": "^0.27.2", + "crypto-js": "^4.1.1", "dayjs": "^1.11.4", "echarts": "^5.3.3", "echarts-wordcloud": "^2.0.0", @@ -48,7 +49,7 @@ "url": "^0.11.0", "vue": "3.2.37", "vue-cropper": "^1.0.3", - "vue-i18n": "9.2.0", + "vue-i18n": "9.2.2", "vue-router": "^4.1.3", "vue-types": "^4.2.1", "web-storage-cache": "^1.1.1" @@ -56,17 +57,17 @@ "devDependencies": { "@commitlint/cli": "^17.0.3", "@commitlint/config-conventional": "^17.0.3", - "@iconify/json": "^2.1.86", - "@intlify/vite-plugin-vue-i18n": "^5.0.1", - "@purge-icons/generated": "^0.8.1", + "@iconify/json": "^2.1.89", + "@intlify/vite-plugin-vue-i18n": "^6.0.0", + "@purge-icons/generated": "^0.9.0", "@types/intro.js": "^5.1.0", "@types/lodash-es": "^4.17.6", - "@types/node": "^18.6.3", + "@types/node": "^18.6.5", "@types/nprogress": "^0.2.0", "@types/qrcode": "^1.4.2", "@types/qs": "^6.9.7", - "@typescript-eslint/eslint-plugin": "^5.32.0", - "@typescript-eslint/parser": "^5.32.0", + "@typescript-eslint/eslint-plugin": "^5.33.0", + "@typescript-eslint/parser": "^5.33.0", "@vitejs/plugin-vue": "^3.0.1", "@vitejs/plugin-vue-jsx": "^2.0.0", "autoprefixer": "^10.4.8", @@ -78,7 +79,7 @@ "less": "^4.1.3", "lint-staged": "^13.0.3", "plop": "^3.1.1", - "postcss": "^8.4.14", + "postcss": "^8.4.16", "postcss-html": "^1.5.0", "postcss-less": "^6.0.0", "prettier": "^2.7.1", @@ -91,16 +92,16 @@ "stylelint-config-standard": "^26.0.0", "stylelint-order": "^5.0.0", "typescript": "4.7.4", - "unplugin-vue-define-options": "^0.7.1", - "vite": "3.0.4", + "unplugin-vue-define-options": "^0.7.3", + "vite": "3.0.5", "vite-plugin-compression": "^0.5.1", "vite-plugin-eslint": "^1.7.0", "vite-plugin-html": "^3.2.0", - "vite-plugin-purge-icons": "^0.8.2", + "vite-plugin-purge-icons": "^0.9.0", "vite-plugin-style-import": "^2.0.0", "vite-plugin-svg-icons": "^2.0.1", "vite-plugin-windicss": "^1.8.7", - "vue-tsc": "^0.39.4", + "vue-tsc": "^0.39.5", "windicss": "^3.5.6" }, "engines": { diff --git a/yudao-ui-admin-vue3/src/api/login/index.ts b/yudao-ui-admin-vue3/src/api/login/index.ts index 485d273c9..6b53f84e5 100644 --- a/yudao-ui-admin-vue3/src/api/login/index.ts +++ b/yudao-ui-admin-vue3/src/api/login/index.ts @@ -18,11 +18,6 @@ export interface SmsLoginVO { code: string } -// 获取验证码 -export const getCodeImgApi = () => { - return request.get({ url: '/system/captcha/get-image' }) -} - // 登录 export const loginApi = (data: UserLoginVO) => { return request.post({ url: '/system/auth/login', data }) diff --git a/yudao-ui-admin-vue3/src/api/login/types.ts b/yudao-ui-admin-vue3/src/api/login/types.ts index 75abe28ce..1a91aecc1 100644 --- a/yudao-ui-admin-vue3/src/api/login/types.ts +++ b/yudao-ui-admin-vue3/src/api/login/types.ts @@ -1,8 +1,7 @@ export type UserLoginVO = { username: string password: string - code: string - uuid: string + captchaVerification: string } export type TokenType = { diff --git a/yudao-ui-admin-vue3/src/api/system/user/profile/index.ts b/yudao-ui-admin-vue3/src/api/system/user/profile/index.ts index a5a24490f..3eab59e01 100644 --- a/yudao-ui-admin-vue3/src/api/system/user/profile/index.ts +++ b/yudao-ui-admin-vue3/src/api/system/user/profile/index.ts @@ -24,6 +24,6 @@ export const updateUserPwdApi = (oldPassword: string, newPassword: string) => { } // 用户头像上传 -export const uploadAvatarApi = (params) => { - return request.upload({ url: '/system/user/profile/update-avatar', params }) +export const uploadAvatarApi = (data) => { + return request.upload({ url: '/system/user/profile/update-avatar', data: data }) } diff --git a/yudao-ui-admin-vue3/src/components/DictTag/src/DictTag.vue b/yudao-ui-admin-vue3/src/components/DictTag/src/DictTag.vue index 0ad3ec69a..5bbb9104c 100644 --- a/yudao-ui-admin-vue3/src/components/DictTag/src/DictTag.vue +++ b/yudao-ui-admin-vue3/src/components/DictTag/src/DictTag.vue @@ -8,7 +8,7 @@ const props = defineProps({ required: true }, value: { - type: [String, Number] as PropType, + type: [String, Number, Boolean] as PropType, required: true } }) diff --git a/yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue b/yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue index f623b735a..f78ffd5a9 100644 --- a/yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue +++ b/yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue @@ -8,6 +8,8 @@ import { ElMessage } from 'element-plus' import { useLocaleStore } from '@/store/modules/locale' import { getAccessToken, getTenantId } from '@/utils/auth' +type InsertFnType = (url: string, alt: string, href: string) => void + const localeStore = useLocaleStore() const currentLocale = computed(() => localeStore.getCurrentLocale) @@ -85,29 +87,58 @@ const editorConfig = computed((): IEditorConfig => { ['uploadImage']: { server: import.meta.env.VITE_UPLOAD_URL, // 单个文件的最大体积限制,默认为 2M - maxFileSize: 2 * 1024 * 1024, + maxFileSize: 5 * 1024 * 1024, // 最多可上传几个文件,默认为 100 maxNumberOfFiles: 10, // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 [] allowedFileTypes: ['image/*'], // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。 - meta: {}, + meta: { updateSupport: 0 }, // 将 meta 拼接到 url 参数中,默认 false - metaWithUrl: false, + metaWithUrl: true, // 自定义增加 http header headers: { - Accept: 'image/*', + Accept: '*', Authorization: 'Bearer ' + getAccessToken(), 'tenant-id': getTenantId() }, // 跨域是否传递 cookie ,默认为 false - withCredentials: false, + withCredentials: true, // 超时时间,默认为 10 秒 - timeout: 5 * 1000 // 5 秒 + timeout: 5 * 1000, // 5 秒 + + // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image + fieldName: 'file', + + // 上传之前触发 + onBeforeUpload(file: File) { + console.log(file) + return file + }, + // 上传进度的回调函数 + onProgress(progress: number) { + // progress 是 0-100 的数字 + console.log('progress', progress) + }, + onSuccess(file: File, res: any) { + console.log('onSuccess', file, res) + }, + onFailed(file: File, res: any) { + alert(res.message) + console.log('onFailed', file, res) + }, + onError(file: File, err: any, res: any) { + alert(err.message) + console.error('onError', file, err, res) + }, + // 自定义插入图片 + customInsert(res: any, insertFn: InsertFnType) { + insertFn(res.data, 'image', res.data) + } } }, uploadImgShowBase64: true diff --git a/yudao-ui-admin-vue3/src/components/UserInfo/src/UserInfo.vue b/yudao-ui-admin-vue3/src/components/UserInfo/src/UserInfo.vue index 47c48213d..5bccbe429 100644 --- a/yudao-ui-admin-vue3/src/components/UserInfo/src/UserInfo.vue +++ b/yudao-ui-admin-vue3/src/components/UserInfo/src/UserInfo.vue @@ -2,18 +2,11 @@ import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus' import { useI18n } from '@/hooks/web/useI18n' import { useCache } from '@/hooks/web/useCache' -import { removeToken } from '@/utils/auth' -import { resetRouter } from '@/router' import { useRouter } from 'vue-router' import { useDesign } from '@/hooks/web/useDesign' -import { useTagsViewStore } from '@/store/modules/tagsView' import avatarImg from '@/assets/imgs/avatar.gif' - -const tagsViewStore = useTagsViewStore() - -const { getPrefixCls } = useDesign() - -const prefixCls = getPrefixCls('user-info') +import { useUserStore } from '@/store/modules/user' +import { useTagsViewStore } from '@/store/modules/tagsView' const { t } = useI18n() @@ -21,6 +14,14 @@ const { wsCache } = useCache() const { push, replace } = useRouter() +const userStore = useUserStore() + +const tagsViewStore = useTagsViewStore() + +const { getPrefixCls } = useDesign() + +const prefixCls = getPrefixCls('user-info') + const user = wsCache.get('user') const avatar = user.user.avatar ? user.user.avatar : avatarImg @@ -34,10 +35,8 @@ const loginOut = () => { type: 'warning' }) .then(async () => { - resetRouter() // 重置静态路由表 - wsCache.clear() - removeToken() - tagsViewStore.delAllViews() + userStore.loginOut() + tagsViewStore.delAllViews replace('/login') }) .catch(() => {}) diff --git a/yudao-ui-admin-vue3/src/components/Verifition/index.ts b/yudao-ui-admin-vue3/src/components/Verifition/index.ts new file mode 100644 index 000000000..bcfe6d940 --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/index.ts @@ -0,0 +1,3 @@ +import Verify from './src/Verify.vue' + +export { Verify } diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/Verify.vue b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify.vue new file mode 100644 index 000000000..70bfce7ef --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify.vue @@ -0,0 +1,438 @@ + + + diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifyPoints.vue b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifyPoints.vue new file mode 100644 index 000000000..ea02b7834 --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifyPoints.vue @@ -0,0 +1,281 @@ + + diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifySlide.vue b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifySlide.vue new file mode 100644 index 000000000..2d59023bd --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifySlide.vue @@ -0,0 +1,426 @@ + + diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/index.ts b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/index.ts new file mode 100644 index 000000000..0daa63a56 --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/Verify/index.ts @@ -0,0 +1,4 @@ +import VerifySlide from './VerifySlide.vue' +import VerifyPoints from './VerifyPoints.vue' + +export { VerifySlide, VerifyPoints } diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/api/index.ts b/yudao-ui-admin-vue3/src/components/Verifition/src/api/index.ts new file mode 100644 index 000000000..6a67b7119 --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/api/index.ts @@ -0,0 +1,24 @@ +/** + * 此处可直接引用自己项目封装好的 axios 配合后端联调 + */ + +import request from './../utils/axios' //组件内部封装的axios +// import request from "@/api/axios.js" //调用项目封装的axios + +//获取验证图片 以及token +export function reqGet(data) { + return request({ + url: '/captcha/get', + method: 'post', + data + }) +} + +//滑动或者点选验证 +export function reqCheck(data) { + return request({ + url: '/captcha/check', + method: 'post', + data + }) +} diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/utils/ase.ts b/yudao-ui-admin-vue3/src/components/Verifition/src/utils/ase.ts new file mode 100644 index 000000000..d2e6b988f --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/utils/ase.ts @@ -0,0 +1,14 @@ +import CryptoJS from 'crypto-js' +/** + * @word 要加密的内容 + * @keyWord String 服务器随机返回的关键字 + * */ +export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') { + const key = CryptoJS.enc.Utf8.parse(keyWord) + const srcs = CryptoJS.enc.Utf8.parse(word) + const encrypted = CryptoJS.AES.encrypt(srcs, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }) + return encrypted.toString() +} diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/utils/axios.ts b/yudao-ui-admin-vue3/src/components/Verifition/src/utils/axios.ts new file mode 100644 index 000000000..ca68097e7 --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/utils/axios.ts @@ -0,0 +1,26 @@ +import axios from 'axios' + +axios.defaults.baseURL = import.meta.env.VITE_BASE_URL + +const service = axios.create({ + timeout: 40000, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json; charset=UTF-8' + } +}) +service.interceptors.request.use( + (config) => { + return config + }, + (error) => { + Promise.reject(error) + } +) + +// response interceptor +service.interceptors.response.use((response) => { + const res = response.data + return res +}) +export default service diff --git a/yudao-ui-admin-vue3/src/components/Verifition/src/utils/util.ts b/yudao-ui-admin-vue3/src/components/Verifition/src/utils/util.ts new file mode 100644 index 000000000..15c16270d --- /dev/null +++ b/yudao-ui-admin-vue3/src/components/Verifition/src/utils/util.ts @@ -0,0 +1,97 @@ +export function resetSize(vm) { + let img_width, img_height, bar_width, bar_height //图片的宽度、高度,移动条的宽度、高度 + const EmployeeWindow = window as any + const parentWidth = vm.$el.parentNode.offsetWidth || EmployeeWindow.offsetWidth + const parentHeight = vm.$el.parentNode.offsetHeight || EmployeeWindow.offsetHeight + if (vm.imgSize.width.indexOf('%') != -1) { + img_width = (parseInt(vm.imgSize.width) / 100) * parentWidth + 'px' + } else { + img_width = vm.imgSize.width + } + + if (vm.imgSize.height.indexOf('%') != -1) { + img_height = (parseInt(vm.imgSize.height) / 100) * parentHeight + 'px' + } else { + img_height = vm.imgSize.height + } + + if (vm.barSize.width.indexOf('%') != -1) { + bar_width = (parseInt(vm.barSize.width) / 100) * parentWidth + 'px' + } else { + bar_width = vm.barSize.width + } + + if (vm.barSize.height.indexOf('%') != -1) { + bar_height = (parseInt(vm.barSize.height) / 100) * parentHeight + 'px' + } else { + bar_height = vm.barSize.height + } + + return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height } +} + +export const _code_chars = [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' +] +export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0'] +export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC'] diff --git a/yudao-ui-admin-vue3/src/config/axios/config.ts b/yudao-ui-admin-vue3/src/config/axios/config.ts index 050ce251b..811650873 100644 --- a/yudao-ui-admin-vue3/src/config/axios/config.ts +++ b/yudao-ui-admin-vue3/src/config/axios/config.ts @@ -1,10 +1,5 @@ const config: { - base_url: { - base: string - dev: string - pro: string - test: string - } + base_url: string result_code: number | string default_headers: AxiosHeaders request_timeout: number @@ -12,20 +7,7 @@ const config: { /** * api请求基础路径 */ - base_url: { - // 开发环境接口前缀 - base: '', - - // 打包开发环境接口前缀 - dev: '', - - // 打包生产环境接口前缀 - pro: '', - - // 打包测试环境接口前缀 - test: '' - }, - + base_url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL, /** * 接口成功返回状态码 */ diff --git a/yudao-ui-admin-vue3/src/config/axios/index.ts b/yudao-ui-admin-vue3/src/config/axios/index.ts index 14bd90bad..75d4139d1 100644 --- a/yudao-ui-admin-vue3/src/config/axios/index.ts +++ b/yudao-ui-admin-vue3/src/config/axios/index.ts @@ -1,15 +1,15 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' -import { ElMessage, ElNotification } from 'element-plus' +import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' import qs from 'qs' import { config } from '@/config/axios/config' -import { getAccessToken, getTenantId, removeToken } from '@/utils/auth' +import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth' import errorCode from './errorCode' import { useI18n } from '@/hooks/web/useI18n' +import { resetRouter } from '@/router' +import { useCache } from '@/hooks/web/useCache' const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE -const BASE_URL = import.meta.env.VITE_BASE_URL -const BASE_API = import.meta.env.VITE_API_URL -const { result_code, base_url } = config +const { result_code, base_url, request_timeout } = config // 需要忽略的提示。忽略后,自动 Promise.reject('error') const ignoreMsgs = [ @@ -20,16 +20,14 @@ const ignoreMsgs = [ export const isRelogin = { show: false } // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现 // 请求队列 -// const requestList = [] +let requestList: any[] = [] // 是否正在刷新中 -// const isRefreshToken = false - -export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH] +let isRefreshToken = false // 创建axios实例 const service: AxiosInstance = axios.create({ - baseURL: BASE_URL + BASE_API, // api 的 base_url - timeout: config.request_timeout, // 请求超时时间 + baseURL: base_url, // api 的 base_url + timeout: request_timeout, // 请求超时时间 withCredentials: false // 禁用 Cookie 等信息 }) @@ -112,16 +110,38 @@ service.interceptors.response.use( return Promise.reject(msg) } else if (code === 401) { // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 - return handleAuthorized() - // if (!isRefreshToken) { - // isRefreshToken = true - // // 1. 如果获取不到刷新令牌,则只能执行登出操作 - // if (!getRefreshToken()) { - // return handleAuthorized() - // } - // // 2. 进行刷新访问令牌 - // // TODO: 引入refreshToken会循环依赖报错 - // } + if (!isRefreshToken) { + isRefreshToken = true + // 1. 如果获取不到刷新令牌,则只能执行登出操作 + if (!getRefreshToken()) { + return handleAuthorized() + } + // 2. 进行刷新访问令牌 + try { + const refreshTokenRes = await refreshToken() + // 2.1 刷新成功,则回放队列的请求 + 当前请求 + setToken(refreshTokenRes.data) + requestList.forEach((cb: any) => cb()) + return service(response.config) + } catch (e) { + // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 + // 2.2 刷新失败,只回放队列的请求 + requestList.forEach((cb: any) => cb()) + // 提示是否要登出。即不回放当前请求!不然会形成递归 + return handleAuthorized() + } finally { + requestList = [] + isRefreshToken = false + } + } else { + // 添加到队列,等待刷新获取到新的令牌 + return new Promise((resolve) => { + requestList.push(() => { + ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + resolve(service(response.config)) + }) + }) + } } else if (code === 500) { ElMessage.error(t('sys.api.errMsg500')) return Promise.reject(new Error(msg)) @@ -165,12 +185,33 @@ service.interceptors.response.use( return Promise.reject(error) } ) + +const refreshToken = async () => { + return await service({ + url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(), + method: 'post' + }) +} const handleAuthorized = () => { const { t } = useI18n() if (!isRelogin.show) { - removeToken() isRelogin.show = true - ElNotification.error(t('sys.api.timeoutMessage')) + ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), { + confirmButtonText: t('login.relogin'), + cancelButtonText: t('common.cancel'), + type: 'warning' + }) + .then(() => { + const { wsCache } = useCache() + resetRouter() // 重置静态路由表 + wsCache.clear() + removeToken() + isRelogin.show = false + window.location.href = '/' + }) + .catch(() => { + isRelogin.show = false + }) } return Promise.reject(t('sys.api.timeoutMessage')) } diff --git a/yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts b/yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts index e26d2dbac..b75c7db88 100644 --- a/yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts +++ b/yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts @@ -80,6 +80,8 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => { const options: ComponentOptions[] = [] let comonentProps = {} if (schemaItem.dictType) { + const allOptions: ComponentOptions = { label: '全部', value: '' } + options.push(allOptions) getIntDictOptions(schemaItem.dictType).forEach((dict) => { options.push(dict) }) diff --git a/yudao-ui-admin-vue3/src/locales/en.ts b/yudao-ui-admin-vue3/src/locales/en.ts index b31226dad..a7463a4f3 100644 --- a/yudao-ui-admin-vue3/src/locales/en.ts +++ b/yudao-ui-admin-vue3/src/locales/en.ts @@ -128,6 +128,13 @@ export default { btnRegister: 'Sign up', SmsSendMsg: 'code has been sent' }, + captcha: { + verification: 'Please complete security verification', + slide: 'Swipe right to complete verification', + point: 'Please click', + success: 'Verification succeeded', + fail: 'verification failed' + }, router: { login: 'Login', home: 'Home', @@ -191,7 +198,6 @@ export default { yield: 'Yield', dynamic: 'Dynamic', push: 'push', - pushCode: 'push code to Github', follow: 'Follow' }, form: { diff --git a/yudao-ui-admin-vue3/src/locales/zh-CN.ts b/yudao-ui-admin-vue3/src/locales/zh-CN.ts index 9659babc3..57a684c00 100644 --- a/yudao-ui-admin-vue3/src/locales/zh-CN.ts +++ b/yudao-ui-admin-vue3/src/locales/zh-CN.ts @@ -128,6 +128,13 @@ export default { btnRegister: '注册', SmsSendMsg: '验证码已发送' }, + captcha: { + verification: '请完成安全验证', + slide: '向右滑动完成验证', + point: '请依次点击', + success: '验证成功', + fail: '验证失败' + }, router: { login: '登录', home: '首页', @@ -191,7 +198,6 @@ export default { yield: '产量', dynamic: '动态', push: '推送', - pushCode: '推送 代码到 Github', follow: '关注' }, form: { diff --git a/yudao-ui-admin-vue3/src/router/index.ts b/yudao-ui-admin-vue3/src/router/index.ts index 812ec4f7a..6cdec5ee1 100644 --- a/yudao-ui-admin-vue3/src/router/index.ts +++ b/yudao-ui-admin-vue3/src/router/index.ts @@ -2,20 +2,16 @@ import type { App } from 'vue' import { getAccessToken } from '@/utils/auth' import type { RouteRecordRaw } from 'vue-router' import remainingRouter from './modules/remaining' -import { useCache } from '@/hooks/web/useCache' import { useTitle } from '@/hooks/web/useTitle' import { useNProgress } from '@/hooks/web/useNProgress' import { usePageLoading } from '@/hooks/web/usePageLoading' import { createRouter, createWebHashHistory } from 'vue-router' import { usePermissionStoreWithOut } from '@/store/modules/permission' import { useDictStoreWithOut } from '@/store/modules/dict' +import { useUserStoreWithOut } from '@/store/modules/user' import { listSimpleDictDataApi } from '@/api/system/dict/dict.data' - -const permissionStore = usePermissionStoreWithOut() - -const dictStore = useDictStoreWithOut() - -const { wsCache } = useCache() +import { isRelogin } from '@/config/axios' +import { getInfoApi } from '@/api/login' const { start, done } = useNProgress() @@ -23,7 +19,7 @@ const { loadStart, loadDone } = usePageLoading() // 创建路由实例 const router = createRouter({ - history: createWebHashHistory(), + history: createWebHashHistory(), // createWebHashHistory URL带#,createWebHistory URL不带# strict: true, routes: remainingRouter as RouteRecordRaw[], scrollBehavior: () => ({ left: 0, top: 0 }) @@ -47,37 +43,37 @@ router.beforeEach(async (to, from, next) => { if (to.path === '/login') { next({ path: '/' }) } else { - if (!dictStore.getIsSetDict) { - // 获取所有字典 + // 获取所有字典 + const dictStore = useDictStoreWithOut() + const userStore = useUserStoreWithOut() + const permissionStore = usePermissionStoreWithOut() + if (!dictStore.getHasDictData) { const res = await listSimpleDictDataApi() - if (res) { - dictStore.setDictMap(res) - dictStore.setIsSetDict(true) - } + dictStore.setDictMap(res) } - if (permissionStore.getIsAddRouters) { + if (userStore.getRoles.length === 0) { + isRelogin.show = true + const res = await getInfoApi() + await userStore.setUserInfoAction(res) + isRelogin.show = false + // 后端过滤菜单 + await permissionStore.generateRoutes() + permissionStore.getAddRouters.forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表 + }) + const redirectPath = from.query.redirect || to.path + const redirect = decodeURIComponent(redirectPath as string) + const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } + next(nextData) + } else { next() - return } - // 开发者可根据实际情况进行修改 - const roleRouters = wsCache.get('roleRouters') || [] - - await permissionStore.generateRoutes(roleRouters as AppCustomRouteRecordRaw[]) - - permissionStore.getAddRouters.forEach((route) => { - router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表 - }) - const redirectPath = from.query.redirect || to.path - const redirect = decodeURIComponent(redirectPath as string) - const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect } - permissionStore.setIsAddRouters(true) - next(nextData) } } else { if (whiteList.indexOf(to.path) !== -1) { next() } else { - next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 + next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 } } }) diff --git a/yudao-ui-admin-vue3/src/store/modules/dict.ts b/yudao-ui-admin-vue3/src/store/modules/dict.ts index 11ee352c1..7cd1d725b 100644 --- a/yudao-ui-admin-vue3/src/store/modules/dict.ts +++ b/yudao-ui-admin-vue3/src/store/modules/dict.ts @@ -13,14 +13,12 @@ export interface DictTypeType { dictValue: DictValueType[] } export interface DictState { - isSetDict: boolean dictMap: Recordable } export const useDictStore = defineStore({ id: 'dict', state: (): DictState => ({ - isSetDict: false, dictMap: {} }), persist: { @@ -30,8 +28,12 @@ export const useDictStore = defineStore({ getDictMap(): Recordable { return this.dictMap }, - getIsSetDict(): boolean { - return this.isSetDict + getHasDictData(): boolean { + if (this.dictMap.length > 0) { + return true + } else { + return false + } } }, actions: { @@ -53,9 +55,6 @@ export const useDictStore = defineStore({ }) }) this.dictMap = dictMap - }, - setIsSetDict(isSetDict: boolean) { - this.isSetDict = isSetDict } } }) diff --git a/yudao-ui-admin-vue3/src/store/modules/permission.ts b/yudao-ui-admin-vue3/src/store/modules/permission.ts index dfce2949c..cb76ef6f5 100644 --- a/yudao-ui-admin-vue3/src/store/modules/permission.ts +++ b/yudao-ui-admin-vue3/src/store/modules/permission.ts @@ -2,12 +2,15 @@ import { defineStore } from 'pinia' import { store } from '../index' import { cloneDeep } from 'lodash-es' import remainingRouter from '@/router/modules/remaining' -import { generateRoutes, flatMultiLevelRoutes } from '@/utils/routerHelper' +import { generateRoute, flatMultiLevelRoutes } from '@/utils/routerHelper' +import { getAsyncRoutesApi } from '@/api/login' +import { useCache } from '@/hooks/web/useCache' + +const { wsCache } = useCache() export interface PermissionState { routers: AppRouteRecordRaw[] addRouters: AppRouteRecordRaw[] - isAddRouters: boolean menuTabRouters: AppRouteRecordRaw[] } @@ -16,7 +19,6 @@ export const usePermissionStore = defineStore({ state: (): PermissionState => ({ routers: [], addRouters: [], - isAddRouters: false, menuTabRouters: [] }), persist: { @@ -29,18 +31,21 @@ export const usePermissionStore = defineStore({ getAddRouters(): AppRouteRecordRaw[] { return flatMultiLevelRoutes(cloneDeep(this.addRouters)) }, - getIsAddRouters(): boolean { - return this.isAddRouters - }, getMenuTabRouters(): AppRouteRecordRaw[] { return this.menuTabRouters } }, actions: { - generateRoutes(routers?: AppCustomRouteRecordRaw[] | string[]): Promise { - return new Promise((resolve) => { - let routerMap: AppRouteRecordRaw[] = [] - routerMap = generateRoutes(routers as AppCustomRouteRecordRaw[]) + async generateRoutes(): Promise { + return new Promise(async (resolve) => { + let res: AppCustomRouteRecordRaw[] + if (wsCache.get('roleRouters')) { + res = wsCache.get('roleRouters') as AppCustomRouteRecordRaw[] + } else { + res = await getAsyncRoutesApi() + wsCache.set('roleRouters', res) + } + const routerMap: AppRouteRecordRaw[] = generateRoute(res as AppCustomRouteRecordRaw[]) // 动态路由,404一定要放到最后面 this.addRouters = routerMap.concat([ { @@ -58,9 +63,6 @@ export const usePermissionStore = defineStore({ resolve() }) }, - setIsAddRouters(state: boolean): void { - this.isAddRouters = state - }, setMenuTabRouters(routers: AppRouteRecordRaw[]): void { this.menuTabRouters = routers } diff --git a/yudao-ui-admin-vue3/src/store/modules/user.ts b/yudao-ui-admin-vue3/src/store/modules/user.ts index 72889c22c..a8aa17de8 100644 --- a/yudao-ui-admin-vue3/src/store/modules/user.ts +++ b/yudao-ui-admin-vue3/src/store/modules/user.ts @@ -1,18 +1,19 @@ import { store } from '../index' import { defineStore } from 'pinia' -import { getAccessToken } from '@/utils/auth' +import { getAccessToken, removeToken } from '@/utils/auth' import { useCache } from '@/hooks/web/useCache' const { wsCache } = useCache() +interface UserVO { + id: number + avatar: string + nickname: string +} interface UserInfoVO { - permissions: [] - roles: [] - user: { - avatar: string - id: number - nickname: string - } + permissions: string[] + roles: string[] + user: UserVO } export const useUserStore = defineStore({ @@ -26,8 +27,19 @@ export const useUserStore = defineStore({ nickname: '' } }), + getters: { + getPermissions(): string[] { + return this.permissions + }, + getRoles(): string[] { + return this.roles + }, + getUser(): UserVO { + return this.user + } + }, actions: { - async getUserInfoAction(userInfo: UserInfoVO) { + async setUserInfoAction(userInfo: UserInfoVO) { if (!getAccessToken()) { this.resetState() return null @@ -37,6 +49,11 @@ export const useUserStore = defineStore({ this.user = userInfo.user wsCache.set('user', userInfo) }, + loginOut() { + removeToken() + wsCache.clear() + this.resetState() + }, resetState() { this.permissions = [] this.roles = [] diff --git a/yudao-ui-admin-vue3/src/utils/routerHelper.ts b/yudao-ui-admin-vue3/src/utils/routerHelper.ts index a0176ad0f..73e673037 100644 --- a/yudao-ui-admin-vue3/src/utils/routerHelper.ts +++ b/yudao-ui-admin-vue3/src/utils/routerHelper.ts @@ -48,7 +48,7 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal } // 后端控制路由生成 -export const generateRoutes = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => { +export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => { const res: AppRouteRecordRaw[] = [] const modulesRoutesKeys = Object.keys(modules) for (const route of routes) { @@ -88,7 +88,7 @@ export const generateRoutes = (routes: AppCustomRouteRecordRaw[]): AppRouteRecor data.component = modules[modulesRoutesKeys[index]] } if (route.children) { - data.children = generateRoutes(route.children) + data.children = generateRoute(route.children) } res.push(data) } diff --git a/yudao-ui-admin-vue3/src/views/Login/components/LoginForm.vue b/yudao-ui-admin-vue3/src/views/Login/components/LoginForm.vue index bb876e0a3..bb4e73cf5 100644 --- a/yudao-ui-admin-vue3/src/views/Login/components/LoginForm.vue +++ b/yudao-ui-admin-vue3/src/views/Login/components/LoginForm.vue @@ -21,22 +21,19 @@ import { getPassword, getTenantName } from '@/utils/auth' -import { useUserStoreWithOut } from '@/store/modules/user' -import { useCache } from '@/hooks/web/useCache' import { usePermissionStore } from '@/store/modules/permission' import { useRouter } from 'vue-router' import { useI18n } from '@/hooks/web/useI18n' import { required } from '@/utils/formRules' import { Icon } from '@/components/Icon' import { LoginStateEnum, useLoginState, useFormValid } from './useLogin' -import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router' +import type { RouteLocationNormalizedLoaded } from 'vue-router' +import { Verify } from '@/components/Verifition' -const { currentRoute, addRoute, push } = useRouter() +const { currentRoute, push } = useRouter() const permissionStore = usePermissionStore() -const userStore = useUserStoreWithOut() const formLogin = ref() const { validForm } = useFormValid(formLogin) -const { wsCache } = useCache() const { setLoginState, getLoginState } = useLoginState() const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN) const iconSize = 30 @@ -46,13 +43,6 @@ const { t } = useI18n() const iconHouse = useIcon({ icon: 'ep:house' }) const iconAvatar = useIcon({ icon: 'ep:avatar' }) const iconLock = useIcon({ icon: 'ep:lock' }) -const iconCircleCheck = useIcon({ icon: 'ep:circle-check' }) -const LoginCaptchaRules = { - tenantName: [required], - username: [required], - password: [required], - code: [required] -} const LoginRules = { tenantName: [required], username: [required], @@ -60,10 +50,9 @@ const LoginRules = { } const loginLoading = ref(false) const loginData = reactive({ - codeImg: '', isShowPassword: false, - captchaEnable: true, - tenantEnable: true, + captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE, + tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE, token: '', loading: { signIn: false @@ -72,20 +61,25 @@ const loginData = reactive({ tenantName: '芋道源码', username: 'admin', password: 'admin123', - rememberMe: false, - code: '', - uuid: '' + captchaVerification: '', + rememberMe: false } }) +// blockPuzzle 滑块 clickWord 点击文字 +const verify = ref() +const captchaType = ref('blockPuzzle') // 获取验证码 const getCode = async () => { - const res = await LoginApi.getCodeImgApi() - loginData.captchaEnable = res.enable - if (res.enable) { - loginData.codeImg = 'data:image/gif;base64,' + res.img - loginData.loginForm.uuid = res.uuid + // 情况一,未开启:则直接登录 + if (!loginData.captchaEnable) { + await handleLogin({}) + return } + + // 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录 + // 弹出验证码 + verify.value.show() } //获取租户ID const getTenantId = async () => { @@ -107,37 +101,22 @@ const getCookie = () => { } } // 登录 -const handleLogin = async () => { +const handleLogin = async (params) => { + loginLoading.value = true await getTenantId() const data = await validForm() - if (!data) return - loginLoading.value = true - await LoginApi.loginApi(loginData.loginForm) - .then(async (res) => { - setToken(res) - const userInfo = await LoginApi.getInfoApi() - await userStore.getUserInfoAction(userInfo) - await getRoutes() - }) - .catch(() => { - getCode() - }) - .finally(() => { - loginLoading.value = false - }) -} - -// 获取路由 -const getRoutes = async () => { - // 后端过滤菜单 - const res = await LoginApi.getAsyncRoutesApi() - wsCache.set('roleRouters', res) - await permissionStore.generateRoutes(res) - permissionStore.getAddRouters.forEach((route) => { - addRoute(route as RouteRecordRaw) // 动态添加可访问路由表 - }) - permissionStore.setIsAddRouters(true) + if (!data) { + loginLoading.value = false + return + } + loginData.loginForm.captchaVerification = params.captchaVerification + const res = await LoginApi.loginApi(loginData.loginForm) + setToken(res) + if (!redirect.value) { + redirect.value = '/' + } push({ path: redirect.value || permissionStore.addRouters[0].path }) + loginLoading.value = false } // 社交登录 @@ -159,15 +138,14 @@ watch( immediate: true } ) -onMounted(async () => { - await getCode() +onMounted(() => { getCookie() })