!246 集成aj-captcha
|  | @ -47,14 +47,14 @@ | ||||||
|         <jedis-mock.version>0.1.16</jedis-mock.version> |         <jedis-mock.version>0.1.16</jedis-mock.version> | ||||||
|         <mockito-inline.version>4.0.0</mockito-inline.version> |         <mockito-inline.version>4.0.0</mockito-inline.version> | ||||||
|         <!-- Bpm 工作流相关 --> |         <!-- Bpm 工作流相关 --> | ||||||
|         <flowable.version>6.7.0</flowable.version> |         <flowable.version>6.7.2</flowable.version> | ||||||
|         <!-- 工具类相关 --> |         <!-- 工具类相关 --> | ||||||
|         <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version> |         <jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version> | ||||||
|         <lombok.version>1.18.20</lombok.version> |         <lombok.version>1.18.20</lombok.version> | ||||||
|         <mapstruct.version>1.4.1.Final</mapstruct.version> |         <mapstruct.version>1.4.1.Final</mapstruct.version> | ||||||
|         <hutool.version>5.7.22</hutool.version> |         <hutool.version>5.8.5</hutool.version> | ||||||
|         <easyexcel.verion>2.2.7</easyexcel.verion> |         <easyexcel.verion>2.2.7</easyexcel.verion> | ||||||
|         <velocity.version>2.2</velocity.version> |         <velocity.version>2.3</velocity.version> | ||||||
|         <screw.version>1.0.5</screw.version> |         <screw.version>1.0.5</screw.version> | ||||||
|         <fastjson.version>2.0.5</fastjson.version> |         <fastjson.version>2.0.5</fastjson.version> | ||||||
|         <guava.version>30.1.1-jre</guava.version> |         <guava.version>30.1.1-jre</guava.version> | ||||||
|  | @ -63,11 +63,12 @@ | ||||||
|         <commons-net.version>3.8.0</commons-net.version> |         <commons-net.version>3.8.0</commons-net.version> | ||||||
|         <jsch.version>0.1.55</jsch.version> |         <jsch.version>0.1.55</jsch.version> | ||||||
|         <tika-core.version>2.4.1</tika-core.version> |         <tika-core.version>2.4.1</tika-core.version> | ||||||
|  |         <aj-captcha.version>1.3.0</aj-captcha.version> | ||||||
|         <!-- 三方云服务相关 --> |         <!-- 三方云服务相关 --> | ||||||
|         <minio.version>8.2.2</minio.version> |         <minio.version>8.2.2</minio.version> | ||||||
|         <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version> |         <aliyun-java-sdk-core.version>4.6.0</aliyun-java-sdk-core.version> | ||||||
|         <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version> |         <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version> | ||||||
|         <tencentcloud-sdk-java.version>3.1.471</tencentcloud-sdk-java.version> |         <tencentcloud-sdk-java.version>3.1.561</tencentcloud-sdk-java.version> | ||||||
|         <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version> |         <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version> | ||||||
|         <justauth.version>1.4.0</justauth.version> |         <justauth.version>1.4.0</justauth.version> | ||||||
|     </properties> |     </properties> | ||||||
|  | @ -148,6 +149,11 @@ | ||||||
|                 <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId> |                 <artifactId>yudao-spring-boot-starter-biz-error-code</artifactId> | ||||||
|                 <version>${revision}</version> |                 <version>${revision}</version> | ||||||
|             </dependency> |             </dependency> | ||||||
|  |             <dependency> | ||||||
|  |                 <groupId>cn.iocoder.cloud</groupId> | ||||||
|  |                 <artifactId>yudao-spring-boot-starter-captcha</artifactId> | ||||||
|  |                 <version>${revision}</version> | ||||||
|  |             </dependency> | ||||||
| 
 | 
 | ||||||
|             <!-- Spring 核心 --> |             <!-- Spring 核心 --> | ||||||
|             <dependency> |             <dependency> | ||||||
|  | @ -494,6 +500,12 @@ | ||||||
|                 <version>${tika-core.version}</version> |                 <version>${tika-core.version}</version> | ||||||
|             </dependency> |             </dependency> | ||||||
| 
 | 
 | ||||||
|  |             <dependency> | ||||||
|  |                 <groupId>com.anji-plus</groupId> | ||||||
|  |                 <artifactId>spring-boot-starter-captcha</artifactId> | ||||||
|  |                 <version>${aj-captcha.version}</version> | ||||||
|  |             </dependency> | ||||||
|  | 
 | ||||||
|             <dependency> |             <dependency> | ||||||
|                 <groupId>org.apache.velocity</groupId> |                 <groupId>org.apache.velocity</groupId> | ||||||
|                 <artifactId>velocity-engine-core</artifactId> |                 <artifactId>velocity-engine-core</artifactId> | ||||||
|  |  | ||||||
|  | @ -40,8 +40,8 @@ | ||||||
|         <module>yudao-spring-boot-starter-biz-data-permission</module> |         <module>yudao-spring-boot-starter-biz-data-permission</module> | ||||||
|         <module>yudao-spring-boot-starter-biz-error-code</module> |         <module>yudao-spring-boot-starter-biz-error-code</module> | ||||||
| 
 | 
 | ||||||
|         <module>yudao-spring-boot-starter-activiti</module> |  | ||||||
|         <module>yudao-spring-boot-starter-flowable</module> |         <module>yudao-spring-boot-starter-flowable</module> | ||||||
|  |         <module>yudao-spring-boot-starter-captcha</module> | ||||||
|     </modules> |     </modules> | ||||||
| 
 | 
 | ||||||
|     <artifactId>yudao-framework</artifactId> |     <artifactId>yudao-framework</artifactId> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package cn.iocoder.yudao.framework.common.util.collection; | package cn.iocoder.yudao.framework.common.util.collection; | ||||||
| 
 | 
 | ||||||
| import cn.hutool.core.collection.CollectionUtil; | import cn.hutool.core.collection.CollectionUtil; | ||||||
|  | import cn.hutool.core.collection.IterUtil; | ||||||
| import cn.hutool.core.util.ArrayUtil; | import cn.hutool.core.util.ArrayUtil; | ||||||
| 
 | 
 | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | @ -44,7 +45,7 @@ public class ArrayUtils { | ||||||
|         if (CollectionUtil.isEmpty(from)) { |         if (CollectionUtil.isEmpty(from)) { | ||||||
|             return (T[]) (new Object[0]); |             return (T[]) (new Object[0]); | ||||||
|         } |         } | ||||||
|         return ArrayUtil.toArray(from, (Class<T>) CollectionUtil.getElementType(from.iterator())); |         return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static <T> T get(T[] array, int index) { |     public static <T> T get(T[] array, int index) { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package cn.iocoder.yudao.framework.common.util.validation; | package cn.iocoder.yudao.framework.common.util.validation; | ||||||
| 
 | 
 | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
| import cn.hutool.core.util.StrUtil; |  | ||||||
| import org.springframework.util.StringUtils; | import org.springframework.util.StringUtils; | ||||||
| 
 | 
 | ||||||
| import javax.validation.ConstraintViolation; | import javax.validation.ConstraintViolation; | ||||||
|  | @ -17,16 +16,15 @@ import java.util.regex.Pattern; | ||||||
|  */ |  */ | ||||||
| public class ValidationUtils { | 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_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$]*"); |     private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*"); | ||||||
| 
 | 
 | ||||||
|     public static boolean isMobile(String mobile) { |     public static boolean isMobile(String mobile) { | ||||||
|         if (StrUtil.length(mobile) != 11) { |         return StringUtils.hasText(mobile) | ||||||
|             return false; |                 && PATTERN_MOBILE.matcher(mobile).matches(); | ||||||
|         } |  | ||||||
|         // TODO 芋艿,后面完善手机校验
 |  | ||||||
|         return true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static boolean isURL(String url) { |     public static boolean isURL(String url) { | ||||||
|  |  | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" |  | ||||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |  | ||||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |  | ||||||
|     <parent> |  | ||||||
|         <groupId>cn.iocoder.cloud</groupId> |  | ||||||
|         <artifactId>yudao-framework</artifactId> |  | ||||||
|         <version>${revision}</version> |  | ||||||
|     </parent> |  | ||||||
|     <modelVersion>4.0.0</modelVersion> |  | ||||||
|     <artifactId>yudao-spring-boot-starter-activiti</artifactId> |  | ||||||
|     <packaging>jar</packaging> |  | ||||||
| 
 |  | ||||||
|     <name>${project.artifactId}</name> |  | ||||||
|     <description>Activiti 拓展</description> |  | ||||||
|     <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |  | ||||||
| 
 |  | ||||||
|     <dependencies> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>cn.iocoder.cloud</groupId> |  | ||||||
|             <artifactId>yudao-common</artifactId> |  | ||||||
|         </dependency> |  | ||||||
| 
 |  | ||||||
|         <!-- Web 相关 --> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>cn.iocoder.cloud</groupId> |  | ||||||
|             <artifactId>yudao-spring-boot-starter-security</artifactId> |  | ||||||
|         </dependency> |  | ||||||
| 
 |  | ||||||
|         <!-- DB 相关 --> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>cn.iocoder.cloud</groupId> |  | ||||||
|             <artifactId>yudao-spring-boot-starter-mybatis</artifactId> |  | ||||||
|         </dependency> |  | ||||||
| 
 |  | ||||||
|         <!-- 工作流相关 --> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>org.activiti</groupId> |  | ||||||
|             <artifactId>activiti-spring-boot-starter</artifactId> |  | ||||||
|         </dependency> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>org.activiti</groupId> |  | ||||||
|             <artifactId>activiti-image-generator</artifactId> |  | ||||||
|         </dependency> |  | ||||||
| 
 |  | ||||||
|     </dependencies> |  | ||||||
| 
 |  | ||||||
| </project> |  | ||||||
|  | @ -1,45 +0,0 @@ | ||||||
| package cn.iocoder.yudao.framework.activiti.config; |  | ||||||
| 
 |  | ||||||
| import cn.iocoder.yudao.framework.activiti.core.web.ActivitiWebFilter; |  | ||||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; |  | ||||||
| import org.activiti.image.ProcessDiagramGenerator; |  | ||||||
| import org.activiti.image.impl.DefaultProcessDiagramGenerator; |  | ||||||
| import org.activiti.spring.SpringProcessEngineConfiguration; |  | ||||||
| import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; |  | ||||||
| import org.apache.ibatis.session.SqlSessionFactory; |  | ||||||
| import org.apache.ibatis.transaction.TransactionFactory; |  | ||||||
| import org.mybatis.spring.transaction.SpringManagedTransactionFactory; |  | ||||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; |  | ||||||
| import org.springframework.context.annotation.Bean; |  | ||||||
| import org.springframework.context.annotation.Configuration; |  | ||||||
| import org.springframework.transaction.PlatformTransactionManager; |  | ||||||
| 
 |  | ||||||
| @Configuration |  | ||||||
| public class YudaoActivitiConfiguration { |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Activiti 流程图的生成器。目前管理后台的流程图 svg,通过它绘制生成。 |  | ||||||
|      */ |  | ||||||
|     @Bean |  | ||||||
|     public ProcessDiagramGenerator processDiagramGenerator() { |  | ||||||
|         return new DefaultProcessDiagramGenerator(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Bean |  | ||||||
|     public FilterRegistrationBean<ActivitiWebFilter> activitiWebFilter() { |  | ||||||
|         FilterRegistrationBean<ActivitiWebFilter> registrationBean = new FilterRegistrationBean<>(); |  | ||||||
|         registrationBean.setFilter(new ActivitiWebFilter()); |  | ||||||
|         registrationBean.setOrder(WebFilterOrderEnum.ACTIVITI_FILTER); |  | ||||||
|         return registrationBean; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * ProcessEngineConfigurationConfigurer 实现类,设置事务管理器,保证 ACT_ 表和自己的表的事务一致性 |  | ||||||
|      */ |  | ||||||
|     @Bean |  | ||||||
|     public ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer( |  | ||||||
|             PlatformTransactionManager platformTransactionManager) { |  | ||||||
|         return processEngineConfiguration -> processEngineConfiguration.setTransactionManager(platformTransactionManager); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,109 +0,0 @@ | ||||||
| package cn.iocoder.yudao.framework.activiti.core.util; |  | ||||||
| 
 |  | ||||||
| import cn.hutool.core.util.ArrayUtil; |  | ||||||
| import cn.hutool.core.util.ObjectUtil; |  | ||||||
| import cn.hutool.core.util.ReflectUtil; |  | ||||||
| import cn.hutool.core.util.StrUtil; |  | ||||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |  | ||||||
| import com.alibaba.ttl.TransmittableThreadLocal; |  | ||||||
| import org.activiti.bpmn.converter.BpmnXMLConverter; |  | ||||||
| import org.activiti.bpmn.model.BpmnModel; |  | ||||||
| import org.activiti.bpmn.model.FlowElement; |  | ||||||
| import org.activiti.bpmn.model.Process; |  | ||||||
| import org.activiti.engine.impl.identity.Authentication; |  | ||||||
| import org.activiti.engine.impl.util.io.BytesStreamSource; |  | ||||||
| 
 |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Objects; |  | ||||||
| import java.util.function.Consumer; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Activiti 工具类 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| public class ActivitiUtils { |  | ||||||
| 
 |  | ||||||
|     static { |  | ||||||
|         setAuthenticationThreadLocal(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // ========== Authentication 相关 ==========
 |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 反射修改 Authentication 的 authenticatedUserIdThreadLocal 静态变量,使用 TTL 线程变量 |  | ||||||
|      * 目的:保证 @Async 等异步执行时,变量丢失的问题 |  | ||||||
|      */ |  | ||||||
|     private static void setAuthenticationThreadLocal() { |  | ||||||
|         ReflectUtil.setFieldValue(Authentication.class, "authenticatedUserIdThreadLocal", |  | ||||||
|                 new TransmittableThreadLocal<String>()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static void setAuthenticatedUserId(Long userId) { |  | ||||||
|         Authentication.setAuthenticatedUserId(String.valueOf(userId)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static void clearAuthenticatedUserId() { |  | ||||||
|         Authentication.setAuthenticatedUserId(null); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static boolean equals(String userIdStr, Long userId) { |  | ||||||
|         return Objects.equals(userId, NumberUtils.parseLong(userIdStr)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // ========== BPMN XML 相关 ==========
 |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 构建对应的 BPMN Model |  | ||||||
|      * |  | ||||||
|      * @param bpmnBytes 原始的 BPMN XML 字节数组 |  | ||||||
|      * @return BPMN Model |  | ||||||
|      */ |  | ||||||
|     public static BpmnModel buildBpmnModel(byte[] bpmnBytes) { |  | ||||||
|         // 转换成 BpmnModel 对象
 |  | ||||||
|         BpmnXMLConverter converter = new BpmnXMLConverter(); |  | ||||||
|         return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 获得 BPMN 流程中,指定的元素们 |  | ||||||
|      * |  | ||||||
|      * @param model |  | ||||||
|      * @param clazz 指定元素。例如说,{@link org.activiti.bpmn.model.UserTask}、{@link org.activiti.bpmn.model.Gateway} 等等 |  | ||||||
|      * @return 元素们 |  | ||||||
|      */ |  | ||||||
|     public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) { |  | ||||||
|         List<T> result = new ArrayList<>(); |  | ||||||
|         model.getProcesses().forEach(process -> { |  | ||||||
|             process.getFlowElements().forEach(flowElement -> { |  | ||||||
|                 if (flowElement.getClass().isAssignableFrom(clazz)) { |  | ||||||
|                     result.add((T) flowElement); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static String getBpmnXml(BpmnModel model) { |  | ||||||
|         if (model == null) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return StrUtil.utf8Str(getBpmnBytes(model)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static byte[] getBpmnBytes(BpmnModel model) { |  | ||||||
|         if (model == null) { |  | ||||||
|             return new byte[0]; |  | ||||||
|         } |  | ||||||
|         BpmnXMLConverter converter = new BpmnXMLConverter(); |  | ||||||
|         return converter.convertToXML(model); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static boolean equals(BpmnModel oldModel, BpmnModel newModel) { |  | ||||||
|         // 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较
 |  | ||||||
|         return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,37 +0,0 @@ | ||||||
| package cn.iocoder.yudao.framework.activiti.core.web; |  | ||||||
| 
 |  | ||||||
| import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils; |  | ||||||
| import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |  | ||||||
| import org.springframework.web.filter.OncePerRequestFilter; |  | ||||||
| 
 |  | ||||||
| import javax.servlet.FilterChain; |  | ||||||
| import javax.servlet.ServletException; |  | ||||||
| import javax.servlet.http.HttpServletRequest; |  | ||||||
| import javax.servlet.http.HttpServletResponse; |  | ||||||
| import java.io.IOException; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Activiti Web 过滤器,将 userId 设置到 {@link org.activiti.engine.impl.identity.Authentication} 中 |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| public class ActivitiWebFilter extends OncePerRequestFilter { |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) |  | ||||||
|             throws ServletException, IOException { |  | ||||||
|         try { |  | ||||||
|             // 设置工作流的用户
 |  | ||||||
|             Long userId = SecurityFrameworkUtils.getLoginUserId(); |  | ||||||
|             if (userId != null) { |  | ||||||
|                 ActivitiUtils.setAuthenticatedUserId(userId); |  | ||||||
|             } |  | ||||||
|             // 过滤
 |  | ||||||
|             chain.doFilter(request, response); |  | ||||||
|         } finally { |  | ||||||
|             // 清理
 |  | ||||||
|             ActivitiUtils.clearAuthenticatedUserId(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| package cn.iocoder.yudao.framework.activiti; |  | ||||||
|  | @ -1,2 +0,0 @@ | ||||||
| org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |  | ||||||
|   cn.iocoder.yudao.framework.activiti.config.YudaoActivitiConfiguration |  | ||||||
|  | @ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.dict.core.util; | ||||||
| 
 | 
 | ||||||
| import cn.hutool.core.util.ObjectUtil; | import cn.hutool.core.util.ObjectUtil; | ||||||
| import cn.iocoder.yudao.framework.common.core.KeyValue; | import cn.iocoder.yudao.framework.common.core.KeyValue; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; |  | ||||||
| import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; | import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; | ||||||
| import cn.iocoder.yudao.module.system.api.dict.DictDataApi; | import cn.iocoder.yudao.module.system.api.dict.DictDataApi; | ||||||
| import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; | import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; | ||||||
|  | @ -28,7 +27,7 @@ public class DictFrameworkUtils { | ||||||
|     /** |     /** | ||||||
|      * 针对 {@link #getDictDataLabel(String, String)} 的缓存 |      * 针对 {@link #getDictDataLabel(String, String)} 的缓存 | ||||||
|      */ |      */ | ||||||
|     private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> getDictDataCache = CacheUtils.buildAsyncReloadingCache( |     private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache( | ||||||
|             Duration.ofMinutes(1L), // 过期时间 1 分钟
 |             Duration.ofMinutes(1L), // 过期时间 1 分钟
 | ||||||
|             new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() { |             new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() { | ||||||
| 
 | 
 | ||||||
|  | @ -43,7 +42,7 @@ public class DictFrameworkUtils { | ||||||
|     /** |     /** | ||||||
|      * 针对 {@link #parseDictDataValue(String, String)} 的缓存 |      * 针对 {@link #parseDictDataValue(String, String)} 的缓存 | ||||||
|      */ |      */ | ||||||
|     private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> parseDictDataCache = CacheUtils.buildAsyncReloadingCache( |     private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache( | ||||||
|             Duration.ofMinutes(1L), // 过期时间 1 分钟
 |             Duration.ofMinutes(1L), // 过期时间 1 分钟
 | ||||||
|             new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() { |             new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() { | ||||||
| 
 | 
 | ||||||
|  | @ -62,12 +61,12 @@ public class DictFrameworkUtils { | ||||||
| 
 | 
 | ||||||
|     @SneakyThrows |     @SneakyThrows | ||||||
|     public static String getDictDataLabel(String dictType, String value) { |     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 |     @SneakyThrows | ||||||
|     public static String parseDictDataValue(String dictType, String label) { |     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(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -52,12 +52,18 @@ | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>com.alipay.sdk</groupId> |             <groupId>com.alipay.sdk</groupId> | ||||||
|             <artifactId>alipay-sdk-java</artifactId> |             <artifactId>alipay-sdk-java</artifactId> | ||||||
|             <version>4.17.9.ALL</version> |             <version>4.31.72.ALL</version> | ||||||
|  |             <exclusions> | ||||||
|  |                 <exclusion> | ||||||
|  |                     <groupId>org.bouncycastle</groupId> | ||||||
|  |                     <artifactId>bcprov-jdk15on</artifactId> | ||||||
|  |                 </exclusion> | ||||||
|  |             </exclusions> | ||||||
|         </dependency> |         </dependency> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>com.github.binarywang</groupId> |             <groupId>com.github.binarywang</groupId> | ||||||
|             <artifactId>weixin-java-pay</artifactId> |             <artifactId>weixin-java-pay</artifactId> | ||||||
|             <version>4.1.9.B</version> |             <version>4.3.8.B</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|         <!-- TODO 芋艿:清理 --> |         <!-- TODO 芋艿:清理 --> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| package cn.iocoder.yudao.framework.pay.core.client.impl; | 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.AbstractPayCodeMapping; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | 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 cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| 
 | 
 | ||||||
|  | import javax.validation.Validation; | ||||||
|  | 
 | ||||||
| import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -79,7 +80,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { |     public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { | ||||||
|         ValidationUtil.validate(reqDTO); |         Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); | ||||||
|         // 执行短信发送
 |         // 执行短信发送
 | ||||||
|         PayCommonResult<?> result; |         PayCommonResult<?> result; | ||||||
|         try { |         try { | ||||||
|  |  | ||||||
|  | @ -53,9 +53,8 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest { | ||||||
|                 "lrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZ" + |                 "lrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZ" + | ||||||
|                 "ikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); |                 "ikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB"); | ||||||
| 
 | 
 | ||||||
|     // TODO @tina:= 前后要有空格哈
 |  | ||||||
|     @InjectMocks |     @InjectMocks | ||||||
|     AlipayQrPayClient client=new AlipayQrPayClient(10L,config); |     AlipayQrPayClient client = new AlipayQrPayClient(10L, config); | ||||||
| 
 | 
 | ||||||
|     @Mock |     @Mock | ||||||
|     private DefaultAlipayClient defaultAlipayClient; |     private DefaultAlipayClient defaultAlipayClient; | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ | ||||||
|             <groupId>com.github.binarywang</groupId> |             <groupId>com.github.binarywang</groupId> | ||||||
| <!--            <artifactId>weixin-java-mp</artifactId>--> | <!--            <artifactId>weixin-java-mp</artifactId>--> | ||||||
|             <artifactId>wx-java-mp-spring-boot-starter</artifactId> |             <artifactId>wx-java-mp-spring-boot-starter</artifactId> | ||||||
|             <version>4.1.9.B</version> |             <version>4.3.8.B</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|         <!-- TODO 芋艿:清理 --> |         <!-- TODO 芋艿:清理 --> | ||||||
|     </dependencies> |     </dependencies> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||||
|  |          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||||
|  |          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||||
|  |     <parent> | ||||||
|  |         <groupId>cn.iocoder.cloud</groupId> | ||||||
|  |         <artifactId>yudao-framework</artifactId> | ||||||
|  |         <version>${revision}</version> | ||||||
|  |     </parent> | ||||||
|  |     <modelVersion>4.0.0</modelVersion> | ||||||
|  |     <artifactId>yudao-spring-boot-starter-captcha</artifactId> | ||||||
|  |     <packaging>jar</packaging> | ||||||
|  | 
 | ||||||
|  |     <name>${project.artifactId}</name> | ||||||
|  |     <description>验证码拓展 | ||||||
|  |         1. 基于 aj-captcha 实现滑块验证码,文档:https://ajcaptcha.beliefteam.cn/captcha-doc/ | ||||||
|  |     </description> | ||||||
|  | 
 | ||||||
|  |     <dependencies> | ||||||
|  |         <!-- Spring 核心 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.springframework.boot</groupId> | ||||||
|  |             <artifactId>spring-boot-starter</artifactId> | ||||||
|  |         </dependency> | ||||||
|  | 
 | ||||||
|  |         <!-- DB 相关 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>cn.iocoder.cloud</groupId> | ||||||
|  |             <artifactId>yudao-spring-boot-starter-redis</artifactId> | ||||||
|  |         </dependency> | ||||||
|  | 
 | ||||||
|  |         <!-- 验证码相关 --> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>com.anji-plus</groupId> | ||||||
|  |             <artifactId>spring-boot-starter-captcha</artifactId> | ||||||
|  |         </dependency> | ||||||
|  |     </dependencies> | ||||||
|  | 
 | ||||||
|  | </project> | ||||||
|  | @ -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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | package cn.iocoder.yudao.framework.captcha.core.enums; | ||||||
|  | 
 | ||||||
|  | import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; | ||||||
|  | import com.anji.captcha.model.vo.PointVO; | ||||||
|  | 
 | ||||||
|  | import java.time.Duration; | ||||||
|  | 
 | ||||||
|  | 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}
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | /** | ||||||
|  |  * 验证码拓展 | ||||||
|  |  * 1. 基于 aj-captcha 实现滑块验证码,文档:https://ajcaptcha.beliefteam.cn/captcha-doc/
 | ||||||
|  |  * | ||||||
|  |  * @author 星语 | ||||||
|  |  */ | ||||||
|  | package cn.iocoder.yudao.framework.captcha; | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | cn.iocoder.yudao.framework.captcha.core.service.RedisCaptchaServiceImpl | ||||||
|  | @ -0,0 +1,2 @@ | ||||||
|  | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ | ||||||
|  |   cn.iocoder.yudao.framework.captcha.config.YudaoCaptchaConfiguration | ||||||
| After Width: | Height: | Size: 17 KiB | 
| After Width: | Height: | Size: 28 KiB | 
| After Width: | Height: | Size: 25 KiB | 
| After Width: | Height: | Size: 27 KiB | 
| After Width: | Height: | Size: 24 KiB | 
| After Width: | Height: | Size: 19 KiB | 
| After Width: | Height: | Size: 21 KiB | 
| After Width: | Height: | Size: 30 KiB | 
| After Width: | Height: | Size: 26 KiB | 
| After Width: | Height: | Size: 2.1 KiB | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 1.6 KiB | 
| After Width: | Height: | Size: 2.2 KiB | 
| After Width: | Height: | Size: 2.2 KiB | 
| After Width: | Height: | Size: 2.2 KiB | 
| After Width: | Height: | Size: 24 KiB | 
| After Width: | Height: | Size: 27 KiB | 
| After Width: | Height: | Size: 28 KiB | 
| After Width: | Height: | Size: 23 KiB | 
| After Width: | Height: | Size: 25 KiB | 
| After Width: | Height: | Size: 16 KiB | 
| After Width: | Height: | Size: 23 KiB | 
| After Width: | Height: | Size: 26 KiB | 
| After Width: | Height: | Size: 27 KiB | 
| After Width: | Height: | Size: 29 KiB | 
|  | @ -16,11 +16,11 @@ import java.util.Set; | ||||||
|  */ |  */ | ||||||
| public class JsonLongSetTypeHandler extends AbstractJsonTypeHandler<Object> { | public class JsonLongSetTypeHandler extends AbstractJsonTypeHandler<Object> { | ||||||
| 
 | 
 | ||||||
|     private static final TypeReference<Set<Long>> typeReference = new TypeReference<Set<Long>>(){}; |     private static final TypeReference<Set<Long>> TYPE_REFERENCE = new TypeReference<Set<Long>>(){}; | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected Object parse(String json) { |     protected Object parse(String json) { | ||||||
|         return JsonUtils.parseObject(json, typeReference); |         return JsonUtils.parseObject(json, TYPE_REFERENCE); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |  | ||||||
|  | @ -11,18 +11,18 @@ public class RedisKeyRegistry { | ||||||
|     /** |     /** | ||||||
|      * Redis RedisKeyDefine 数组 |      * Redis RedisKeyDefine 数组 | ||||||
|      */ |      */ | ||||||
|     private static final List<RedisKeyDefine> defines = new ArrayList<>(); |     private static final List<RedisKeyDefine> DEFINES = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|     public static void add(RedisKeyDefine define) { |     public static void add(RedisKeyDefine define) { | ||||||
|         defines.add(define); |         DEFINES.add(define); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static List<RedisKeyDefine> list() { |     public static List<RedisKeyDefine> list() { | ||||||
|         return defines; |         return DEFINES; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static int size() { |     public static int size() { | ||||||
|         return defines.size(); |         return DEFINES.size(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,19 +17,19 @@ public class TransmittableThreadLocalSecurityContextHolderStrategy implements Se | ||||||
|     /** |     /** | ||||||
|      * 使用 TransmittableThreadLocal 作为上下文 |      * 使用 TransmittableThreadLocal 作为上下文 | ||||||
|      */ |      */ | ||||||
|     private static final ThreadLocal<SecurityContext> contextHolder = new TransmittableThreadLocal<>(); |     private static final ThreadLocal<SecurityContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>(); | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void clearContext() { |     public void clearContext() { | ||||||
|         contextHolder.remove(); |         CONTEXT_HOLDER.remove(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public SecurityContext getContext() { |     public SecurityContext getContext() { | ||||||
|         SecurityContext ctx = contextHolder.get(); |         SecurityContext ctx = CONTEXT_HOLDER.get(); | ||||||
|         if (ctx == null) { |         if (ctx == null) { | ||||||
|             ctx = createEmptyContext(); |             ctx = createEmptyContext(); | ||||||
|             contextHolder.set(ctx); |             CONTEXT_HOLDER.set(ctx); | ||||||
|         } |         } | ||||||
|         return ctx; |         return ctx; | ||||||
|     } |     } | ||||||
|  | @ -37,7 +37,7 @@ public class TransmittableThreadLocalSecurityContextHolderStrategy implements Se | ||||||
|     @Override |     @Override | ||||||
|     public void setContext(SecurityContext context) { |     public void setContext(SecurityContext context) { | ||||||
|         Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); |         Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); | ||||||
|         contextHolder.set(context); |         CONTEXT_HOLDER.set(context); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  |  | ||||||
|  | @ -57,7 +57,7 @@ public class BpmMessageServiceImpl implements BpmMessageService { | ||||||
|         templateParams.put("taskName", reqDTO.getTaskName()); |         templateParams.put("taskName", reqDTO.getTaskName()); | ||||||
|         templateParams.put("startUserNickname", reqDTO.getStartUserNickname()); |         templateParams.put("startUserNickname", reqDTO.getStartUserNickname()); | ||||||
|         templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); |         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)); |                 BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ public class CodegenBuilder { | ||||||
|      * 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射 |      * 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射 | ||||||
|      * 注意,字段的匹配以后缀的方式 |      * 注意,字段的匹配以后缀的方式 | ||||||
|      */ |      */ | ||||||
|     private static final Map<String, CodegenColumnListConditionEnum> columnListOperationConditionMappings = |     private static final Map<String, CodegenColumnListConditionEnum> COLUMN_LIST_OPERATION_CONDITION_MAPPINGS = | ||||||
|             MapUtil.<String, CodegenColumnListConditionEnum>builder() |             MapUtil.<String, CodegenColumnListConditionEnum>builder() | ||||||
|                     .put("name", CodegenColumnListConditionEnum.LIKE) |                     .put("name", CodegenColumnListConditionEnum.LIKE) | ||||||
|                     .put("time", CodegenColumnListConditionEnum.BETWEEN) |                     .put("time", CodegenColumnListConditionEnum.BETWEEN) | ||||||
|  | @ -42,7 +42,7 @@ public class CodegenBuilder { | ||||||
|      * 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射 |      * 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射 | ||||||
|      * 注意,字段的匹配以后缀的方式 |      * 注意,字段的匹配以后缀的方式 | ||||||
|      */ |      */ | ||||||
|     private static final Map<String, CodegenColumnHtmlTypeEnum> columnHtmlTypeMappings = |     private static final Map<String, CodegenColumnHtmlTypeEnum> COLUMN_HTML_TYPE_MAPPINGS = | ||||||
|             MapUtil.<String, CodegenColumnHtmlTypeEnum>builder() |             MapUtil.<String, CodegenColumnHtmlTypeEnum>builder() | ||||||
|                     .put("status", CodegenColumnHtmlTypeEnum.RADIO) |                     .put("status", CodegenColumnHtmlTypeEnum.RADIO) | ||||||
|                     .put("sex", CodegenColumnHtmlTypeEnum.RADIO) |                     .put("sex", CodegenColumnHtmlTypeEnum.RADIO) | ||||||
|  | @ -143,7 +143,7 @@ public class CodegenBuilder { | ||||||
|         column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) |         column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) | ||||||
|                 && !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递
 |                 && !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递
 | ||||||
|         // 处理 listOperationCondition 字段
 |         // 处理 listOperationCondition 字段
 | ||||||
|         columnListOperationConditionMappings.entrySet().stream() |         COLUMN_LIST_OPERATION_CONDITION_MAPPINGS.entrySet().stream() | ||||||
|                 .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) |                 .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) | ||||||
|                 .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition())); |                 .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition())); | ||||||
|         if (column.getListOperationCondition() == null) { |         if (column.getListOperationCondition() == null) { | ||||||
|  | @ -155,7 +155,7 @@ public class CodegenBuilder { | ||||||
| 
 | 
 | ||||||
|     private void processColumnUI(CodegenColumnDO column) { |     private void processColumnUI(CodegenColumnDO column) { | ||||||
|         // 基于后缀进行匹配
 |         // 基于后缀进行匹配
 | ||||||
|         columnHtmlTypeMappings.entrySet().stream() |         COLUMN_HTML_TYPE_MAPPINGS.entrySet().stream() | ||||||
|                 .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) |                 .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) | ||||||
|                 .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType())); |                 .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType())); | ||||||
|         // 如果是 Boolean 类型时,设置为 radio 类型.
 |         // 如果是 Boolean 类型时,设置为 radio 类型.
 | ||||||
|  |  | ||||||
|  | @ -12,8 +12,7 @@ public interface ErrorCodeConstants { | ||||||
|     // ========== AUTH 模块 1002000000 ==========
 |     // ========== AUTH 模块 1002000000 ==========
 | ||||||
|     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); |     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确"); | ||||||
|     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用"); |     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_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定"); | ||||||
|     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期"); |     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期"); | ||||||
|     ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1002000007, "手机号不存在"); |     ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1002000007, "手机号不存在"); | ||||||
|  |  | ||||||
|  | @ -141,6 +141,11 @@ | ||||||
|             <artifactId>yudao-spring-boot-starter-excel</artifactId> |             <artifactId>yudao-spring-boot-starter-excel</artifactId> | ||||||
|         </dependency> |         </dependency> | ||||||
| 
 | 
 | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>cn.iocoder.cloud</groupId> | ||||||
|  |             <artifactId>yudao-spring-boot-starter-captcha</artifactId> | ||||||
|  |         </dependency> | ||||||
|  | 
 | ||||||
|         <!-- 监控相关 --> |         <!-- 监控相关 --> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>cn.iocoder.cloud</groupId> |             <groupId>cn.iocoder.cloud</groupId> | ||||||
|  |  | ||||||
|  | @ -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) |     @NotEmpty(message = "验证码不能为空", groups = CodeEnableGroup.class) | ||||||
|     private String code; |     private String captchaVerification; | ||||||
| 
 |  | ||||||
|     @ApiModelProperty(value = "验证码的唯一标识", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62", notes = "验证码开启时,需要传递") |  | ||||||
|     @NotEmpty(message = "唯一标识不能为空", groups = CodeEnableGroup.class) |  | ||||||
|     private String uuid; |  | ||||||
| 
 | 
 | ||||||
|     // ========== 绑定社交登录时,需要传递如下参数 ==========
 |     // ========== 绑定社交登录时,需要传递如下参数 ==========
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| ### 请求 /captcha/get-image 接口 => 成功 |  | ||||||
| GET {{baseUrl}}/system/captcha/get-image |  | ||||||
| tenant-id: {{adminTenentId}} |  | ||||||
|  | @ -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<CaptchaImageRespVO> getCaptchaImage() { |  | ||||||
|         return success(captchaService.getCaptchaImage()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -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; |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -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(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -14,10 +14,6 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S | ||||||
|  */ |  */ | ||||||
| public interface RedisKeyConstants { | public interface RedisKeyConstants { | ||||||
| 
 | 
 | ||||||
|     RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存", |  | ||||||
|             "captcha_code:%s", // 参数为 uuid
 |  | ||||||
|             STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); |  | ||||||
| 
 |  | ||||||
|     RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存", |     RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存", | ||||||
|             "oauth2_access_token:%s", // 参数为访问令牌 token
 |             "oauth2_access_token:%s", // 参数为访问令牌 token
 | ||||||
|             STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); |             STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); | ||||||
|  |  | ||||||
|  | @ -1,41 +0,0 @@ | ||||||
| package cn.iocoder.yudao.module.system.dal.redis.common; |  | ||||||
| 
 |  | ||||||
| import org.springframework.data.redis.core.StringRedisTemplate; |  | ||||||
| import org.springframework.stereotype.Repository; |  | ||||||
| 
 |  | ||||||
| import javax.annotation.Resource; |  | ||||||
| import java.time.Duration; |  | ||||||
| 
 |  | ||||||
| import static cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants.CAPTCHA_CODE; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * 验证码的 Redis DAO |  | ||||||
|  * |  | ||||||
|  * @author 芋道源码 |  | ||||||
|  */ |  | ||||||
| @Repository |  | ||||||
| public class CaptchaRedisDAO { |  | ||||||
| 
 |  | ||||||
|     @Resource |  | ||||||
|     private StringRedisTemplate stringRedisTemplate; |  | ||||||
| 
 |  | ||||||
|     public String get(String uuid) { |  | ||||||
|         String redisKey = formatKey(uuid); |  | ||||||
|         return stringRedisTemplate.opsForValue().get(redisKey); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void set(String uuid, String code, Duration timeout) { |  | ||||||
|         String redisKey = formatKey(uuid); |  | ||||||
|         stringRedisTemplate.opsForValue().set(redisKey, code, timeout); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void delete(String uuid) { |  | ||||||
|         String redisKey = formatKey(uuid); |  | ||||||
|         stringRedisTemplate.delete(redisKey); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static String formatKey(String uuid) { |  | ||||||
|         return String.format(CAPTCHA_CODE.getKeyTemplate(), uuid); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -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 { |  | ||||||
| } |  | ||||||
|  | @ -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; |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,4 +0,0 @@ | ||||||
| /** |  | ||||||
|  * 基于 Hutool captcha 库,实现验证码功能 |  | ||||||
|  */ |  | ||||||
| package cn.iocoder.yudao.module.system.framework.captcha; |  | ||||||
|  | @ -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.logger.LoginResultEnum; | ||||||
| import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants; | import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants; | ||||||
| import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; | 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.logger.LoginLogService; | ||||||
| import cn.iocoder.yudao.module.system.service.member.MemberService; | 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.oauth2.OAuth2TokenService; | ||||||
| import cn.iocoder.yudao.module.system.service.social.SocialUserService; | import cn.iocoder.yudao.module.system.service.social.SocialUserService; | ||||||
| import cn.iocoder.yudao.module.system.service.user.AdminUserService; | 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 com.google.common.annotations.VisibleForTesting; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| 
 | 
 | ||||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||||
|  | @ -47,8 +50,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||||||
|     @Resource |     @Resource | ||||||
|     private AdminUserService userService; |     private AdminUserService userService; | ||||||
|     @Resource |     @Resource | ||||||
|     private CaptchaService captchaService; |  | ||||||
|     @Resource |  | ||||||
|     private LoginLogService loginLogService; |     private LoginLogService loginLogService; | ||||||
|     @Resource |     @Resource | ||||||
|     private OAuth2TokenService oauth2TokenService; |     private OAuth2TokenService oauth2TokenService; | ||||||
|  | @ -60,9 +61,17 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||||||
|     @Resource |     @Resource | ||||||
|     private Validator validator; |     private Validator validator; | ||||||
| 
 | 
 | ||||||
|  |     @Resource | ||||||
|  |     private CaptchaService captchaService; | ||||||
|     @Resource |     @Resource | ||||||
|     private SmsCodeApi smsCodeApi; |     private SmsCodeApi smsCodeApi; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 验证码的开关,默认为 true | ||||||
|  |      */ | ||||||
|  |     @Value("${yudao.captcha.enable:true}") | ||||||
|  |     private Boolean captchaEnable; | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public AdminUserDO authenticate(String username, String password) { |     public AdminUserDO authenticate(String username, String password) { | ||||||
|         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; |         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; | ||||||
|  | @ -130,27 +139,20 @@ public class AdminAuthServiceImpl implements AdminAuthService { | ||||||
|     @VisibleForTesting |     @VisibleForTesting | ||||||
|     void verifyCaptcha(AuthLoginReqVO reqVO) { |     void verifyCaptcha(AuthLoginReqVO reqVO) { | ||||||
|         // 如果验证码关闭,则不进行校验
 |         // 如果验证码关闭,则不进行校验
 | ||||||
|         if (!captchaService.isCaptchaEnable()) { |         if (!captchaEnable) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         // 校验验证码
 |         // 校验验证码
 | ||||||
|         ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); |         ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); | ||||||
|         // 验证码不存在
 |         CaptchaVO captchaVO = new CaptchaVO(); | ||||||
|         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; |         captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification()); | ||||||
|         String code = captchaService.getCaptchaCode(reqVO.getUuid()); |         ResponseModel response = captchaService.verification(captchaVO); | ||||||
|         if (code == null) { |         // 验证不通过
 | ||||||
|             // 创建登录失败日志(验证码不存在)
 |         if (!response.isSuccess()) { | ||||||
|             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); |             createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR); | ||||||
|             throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); |             throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg()); | ||||||
|         } |         } | ||||||
|         // 正确,所以要删除下验证码
 |  | ||||||
|         captchaService.deleteCaptchaCode(reqVO.getUuid()); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void createLoginLog(Long userId, String username, |     private void createLoginLog(Long userId, String username, | ||||||
|  |  | ||||||
|  | @ -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); |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -91,6 +91,25 @@ xxl: | ||||||
|       logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 |       logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 | ||||||
|     accessToken: default_token # 执行器通讯TOKEN |     accessToken: default_token # 执行器通讯TOKEN | ||||||
| 
 | 
 | ||||||
|  | --- #################### 验证码相关配置 #################### | ||||||
|  | 
 | ||||||
|  | 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: | yudao: | ||||||
|  | @ -106,9 +125,7 @@ yudao: | ||||||
|     version: ${yudao.info.version} |     version: ${yudao.info.version} | ||||||
|     base-package: ${yudao.info.base-package} |     base-package: ${yudao.info.base-package} | ||||||
|   captcha: |   captcha: | ||||||
|     timeout: 5m |     enable: true # 验证码的开关,默认为 true;注意,优先读取数据库 infra_config 的 yudao.captcha.enable,所以请从数据库修改,可能需要重启项目 | ||||||
|     width: 160 |  | ||||||
|     height: 60 |  | ||||||
|   error-code: # 错误码相关配置项 |   error-code: # 错误码相关配置项 | ||||||
|     constants-class-list: |     constants-class-list: | ||||||
|       - cn.iocoder.yudao.module.system.enums.ErrorCodeConstants |       - cn.iocoder.yudao.module.system.enums.ErrorCodeConstants | ||||||
|  |  | ||||||
|  | @ -5,19 +5,16 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||||
| import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; | ||||||
| import cn.iocoder.yudao.framework.test.core.util.AssertUtils; | import cn.iocoder.yudao.framework.test.core.util.AssertUtils; | ||||||
| import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; | import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; | ||||||
| import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginReqVO; |  | ||||||
| import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginRespVO; |  | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; | 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.dal.dataobject.user.AdminUserDO; | ||||||
| import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | 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.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.logger.LoginLogService; | ||||||
| import cn.iocoder.yudao.module.system.service.member.MemberService; | 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.oauth2.OAuth2TokenService; | ||||||
| import cn.iocoder.yudao.module.system.service.social.SocialUserService; | import cn.iocoder.yudao.module.system.service.social.SocialUserService; | ||||||
| import cn.iocoder.yudao.module.system.service.user.AdminUserService; | 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.junit.jupiter.api.Test; | ||||||
| import org.springframework.boot.test.mock.mockito.MockBean; | import org.springframework.boot.test.mock.mockito.MockBean; | ||||||
| import org.springframework.context.annotation.Import; | import org.springframework.context.annotation.Import; | ||||||
|  | @ -26,10 +23,10 @@ import javax.annotation.Resource; | ||||||
| import javax.validation.Validator; | import javax.validation.Validator; | ||||||
| 
 | 
 | ||||||
| import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; | ||||||
| import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; |  | ||||||
| import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | ||||||
| import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | ||||||
| import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS; | ||||||
|  | import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_USER_DISABLED; | ||||||
| import static org.mockito.ArgumentMatchers.eq; | import static org.mockito.ArgumentMatchers.eq; | ||||||
| import static org.mockito.Mockito.*; | import static org.mockito.Mockito.*; | ||||||
| 
 | 
 | ||||||
|  | @ -57,11 +54,6 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { | ||||||
|     @MockBean |     @MockBean | ||||||
|     private Validator validator; |     private Validator validator; | ||||||
| 
 | 
 | ||||||
|     @BeforeEach |  | ||||||
|     public void setUp() { |  | ||||||
|         when(captchaService.isCaptchaEnable()).thenReturn(true); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Test |     @Test | ||||||
|     public void testAuthenticate_success() { |     public void testAuthenticate_success() { | ||||||
|         // 准备参数
 |         // 准备参数
 | ||||||
|  | @ -138,82 +130,82 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test | //    @Test
 | ||||||
|     public void testCaptcha_success() { | //    public void testCaptcha_success() {
 | ||||||
|         // 准备参数
 | //        // 准备参数
 | ||||||
|         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); | //        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 验证码正确
 | //    @Test
 | ||||||
|         when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode()); | //    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
 | ||||||
|         authService.verifyCaptcha(reqVO); | //    public void testLogin_success() {
 | ||||||
|         // 断言
 | //        // 准备参数
 | ||||||
|         verify(captchaService).deleteCaptchaCode(reqVO.getUuid()); | //        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
 | ||||||
|     } | //                o.setUsername("test_username").setPassword("test_password"));
 | ||||||
| 
 | //
 | ||||||
|     @Test | //        // mock 验证码正确
 | ||||||
|     public void testCaptcha_notFound() { | //        when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(reqVO.getCode());
 | ||||||
|         // 准备参数
 | //        // mock user 数据
 | ||||||
|         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); | //        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);
 | ||||||
|         assertServiceException(() -> authService.verifyCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_NOT_FOUND); | //        // mock password 匹配
 | ||||||
|         // 校验调用参数
 | //        when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true);
 | ||||||
|         verify(loginLogService, times(1)).createLoginLog( | //        // mock 缓存登录用户到 Redis
 | ||||||
|             argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) | //        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)
 | ||||||
|                     && o.getResult().equals(LoginResultEnum.CAPTCHA_NOT_FOUND.getResult())) | //                .setUserType(UserTypeEnum.ADMIN.getValue()));
 | ||||||
|         ); | //        when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq("default"), isNull()))
 | ||||||
|     } | //                .thenReturn(accessTokenDO);
 | ||||||
| 
 | //
 | ||||||
|     @Test | //        // 调用, 并断言异常
 | ||||||
|     public void testCaptcha_codeError() { | //        AuthLoginRespVO loginRespVO = authService.login(reqVO);
 | ||||||
|         // 准备参数
 | //        assertPojoEquals(accessTokenDO, loginRespVO);
 | ||||||
|         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class); | //        // 校验调用参数
 | ||||||
| 
 | //        verify(loginLogService).createLoginLog(
 | ||||||
|         // mock 验证码不正确
 | //            argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
 | ||||||
|         String code = randomString(); | //                    && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
 | ||||||
|         when(captchaService.getCaptchaCode(reqVO.getUuid())).thenReturn(code); | //                    && o.getUserId().equals(user.getId()))
 | ||||||
| 
 | //        );
 | ||||||
|         // 调用, 并断言异常
 | //    }
 | ||||||
|         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 |     @Test | ||||||
|     public void testLogout_success() { |     public void testLogout_success() { | ||||||
|  | @ -228,7 +220,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { | ||||||
|         authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType()); |         authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType()); | ||||||
|         // 校验调用参数
 |         // 校验调用参数
 | ||||||
|         verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType()) |         verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType()) | ||||||
|                     && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())) |                 && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())) | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
 YunaiV
						YunaiV