@ -47,15 +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 工作流相关 -->
|
||||||
<activiti.version>7.1.0.M6</activiti.version>
|
<flowable.version>6.7.2</flowable.version>
|
||||||
<flowable.version>6.7.0</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>
|
||||||
@ -64,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>
|
||||||
@ -149,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>
|
||||||
@ -419,41 +424,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 工作流相关 -->
|
<!-- 工作流相关 -->
|
||||||
<dependency>
|
|
||||||
<groupId>org.activiti</groupId>
|
|
||||||
<artifactId>activiti-spring-boot-starter</artifactId>
|
|
||||||
<version>${activiti.version}</version>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>de.odysseus.juel</groupId>
|
|
||||||
<artifactId>juel-api</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>de.odysseus.juel</groupId>
|
|
||||||
<artifactId>juel-spi</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.mybatis</groupId>
|
|
||||||
<artifactId>mybatis</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<artifactId>el-api</artifactId>
|
|
||||||
<groupId>javax.el</groupId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.activiti</groupId>
|
|
||||||
<artifactId>activiti-image-generator</artifactId>
|
|
||||||
<version>${activiti.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.iocoder.cloud</groupId>
|
|
||||||
<artifactId>yudao-spring-boot-starter-activiti</artifactId>
|
|
||||||
<version>${revision}</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- 工作流相关 flowable -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.iocoder.cloud</groupId>
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
|
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
|
||||||
@ -530,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,5 +1,7 @@
|
|||||||
package cn.iocoder.yudao.framework.common.util.date;
|
package cn.iocoder.yudao.framework.common.util.date;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -120,4 +122,17 @@ public class DateUtils {
|
|||||||
return c.getTime();
|
return c.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否今天
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 是否
|
||||||
|
*/
|
||||||
|
public static boolean isToday(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return DateUtil.isSameDay(date, new Date());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
39
yudao-framework/yudao-spring-boot-starter-captcha/pom.xml
Normal file
@ -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
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
<!-- 其它环境 -->
|
<!-- 其它环境 -->
|
||||||
<springProfile name="dev,default">
|
<springProfile name="dev,test,stage,prod,default">
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
<appender-ref ref="ASYNC"/>
|
<appender-ref ref="ASYNC"/>
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>
|
<description>
|
||||||
bpm-base 模块,实现公用的工作流的逻辑,提供给 bpm-activiti 和 bpm-flowable 复用
|
bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能,基于 Flowable 6 版本实现。
|
||||||
</description>
|
例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等 </description>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Spring Cloud 基础 -->
|
<!-- Spring Cloud 基础 -->
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.bpm.dal.mysql.task;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
|
||||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmActivityDO;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
|
||||||
import org.apache.ibatis.annotations.Param;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Mapper
|
|
||||||
public interface BpmActivityMapper extends BaseMapperX<BpmActivityDO> {
|
|
||||||
|
|
||||||
|
|
||||||
// TODO @ke:可以试试,把 activiti 的表,映射成对应的实体,然后读取下。我们尽量避免 xml 操作,因为要做多 db 类型的支持,例如说 oracle 等。通过 mybatis plus 帮助我们生成不同数据库的表操作
|
|
||||||
/**
|
|
||||||
* 获取指定流程的历史任务
|
|
||||||
*
|
|
||||||
* @param procInstId 流程id
|
|
||||||
*
|
|
||||||
* @return 返回历史任务
|
|
||||||
*/
|
|
||||||
List<BpmActivityDO> listAllByProcInstIdAndDelete(@Param("procInstId") String procInstId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 逻辑删除hiActInst表任务
|
|
||||||
*
|
|
||||||
* @param taskIdList 任务列表
|
|
||||||
*
|
|
||||||
* @return 返回是否成功
|
|
||||||
*/
|
|
||||||
Boolean delHiActInstByTaskId(@Param("taskIdList") List<String> taskIdList);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 逻辑删除hiTaskInst任务
|
|
||||||
*
|
|
||||||
* @param taskIdList 任务列表
|
|
||||||
*
|
|
||||||
* @return 返回是否成功
|
|
||||||
*/
|
|
||||||
Boolean delHiTaskInstByTaskId(@Param("taskIdList") List<String> taskIdList);
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能,基于 activiti 7 版本实现。
|
* bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能,基于 Flowable 6 版本实现。
|
||||||
* 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
|
* 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
|
||||||
*
|
*
|
||||||
* bpm 解释:https://baike.baidu.com/item/BPM/1933
|
* bpm 解释:https://baike.baidu.com/item/BPM/1933
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
<!-- 其它环境 -->
|
<!-- 其它环境 -->
|
||||||
<springProfile name="dev,default">
|
<springProfile name="dev,test,stage,prod,default">
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
<appender-ref ref="ASYNC"/>
|
<appender-ref ref="ASYNC"/>
|
||||||
|
@ -154,12 +154,12 @@ public class CodegenServiceImpl implements CodegenService {
|
|||||||
// 构建 CodegenColumnDO 数组,只同步新增的字段
|
// 构建 CodegenColumnDO 数组,只同步新增的字段
|
||||||
List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
|
List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
|
||||||
Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);
|
Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);
|
||||||
// 移除已经存在的字段
|
|
||||||
tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
|
|
||||||
// 计算需要删除的字段
|
// 计算需要删除的字段
|
||||||
Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
|
Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
|
||||||
Set<Long> deleteColumnIds = codegenColumns.stream().filter(column -> !tableFieldNames.contains(column.getColumnName()))
|
Set<Long> deleteColumnIds = codegenColumns.stream().filter(column -> !tableFieldNames.contains(column.getColumnName()))
|
||||||
.map(CodegenColumnDO::getId).collect(Collectors.toSet());
|
.map(CodegenColumnDO::getId).collect(Collectors.toSet());
|
||||||
|
// 移除已经存在的字段
|
||||||
|
tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
|
||||||
if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
|
if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
|
||||||
throw exception(CODEGEN_SYNC_NONE_CHANGE);
|
throw exception(CODEGEN_SYNC_NONE_CHANGE);
|
||||||
}
|
}
|
||||||
|
@ -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 类型.
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
@pagination="getList"/>
|
@pagination="getList"/>
|
||||||
|
|
||||||
<!-- 对话框(添加 / 修改) -->
|
<!-- 对话框(添加 / 修改) -->
|
||||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
<el-dialog :title="title" :visible.sync="open" width="500px" v-dialogDrag append-to-body>
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||||
#foreach($column in $columns)
|
#foreach($column in $columns)
|
||||||
#if ($column.createOperation || $column.updateOperation)
|
#if ($column.createOperation || $column.updateOperation)
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
<!-- 其它环境 -->
|
<!-- 其它环境 -->
|
||||||
<springProfile name="dev,default">
|
<springProfile name="dev,test,stage,prod,default">
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
<appender-ref ref="ASYNC"/>
|
<appender-ref ref="ASYNC"/>
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.sms;
|
|||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeCheckReqDTO;
|
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeCheckReqDTO;
|
||||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
||||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||||
@ -52,21 +53,22 @@ public class SmsCodeServiceImpl implements SmsCodeService {
|
|||||||
// 校验是否可以发送验证码,不用筛选场景
|
// 校验是否可以发送验证码,不用筛选场景
|
||||||
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
|
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
|
||||||
if (lastSmsCode != null) {
|
if (lastSmsCode != null) {
|
||||||
if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
|
|
||||||
throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
|
|
||||||
}
|
|
||||||
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
|
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
|
||||||
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
|
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
|
||||||
throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
|
throw ServiceExceptionUtil.exception(SMS_CODE_SEND_TOO_FAST);
|
||||||
}
|
}
|
||||||
|
if (DateUtils.isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限
|
||||||
|
lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
|
||||||
|
throw ServiceExceptionUtil.exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
|
||||||
|
}
|
||||||
// TODO 芋艿:提升,每个 IP 每天可发送数量
|
// TODO 芋艿:提升,每个 IP 每天可发送数量
|
||||||
// TODO 芋艿:提升,每个 IP 每小时可发送数量
|
// TODO 芋艿:提升,每个 IP 每小时可发送数量
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建验证码记录
|
// 创建验证码记录
|
||||||
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
|
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
|
||||||
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code)
|
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)
|
||||||
.scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
|
.todayIndex(lastSmsCode != null && DateUtils.isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
|
||||||
.createIp(ip).used(false).build();
|
.createIp(ip).used(false).build();
|
||||||
smsCodeMapper.insert(newSmsCode);
|
smsCodeMapper.insert(newSmsCode);
|
||||||
return code;
|
return code;
|
||||||
|
@ -416,7 +416,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
|
AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
|
||||||
if (existUser == null) {
|
if (existUser == null) {
|
||||||
userMapper.insert(UserConvert.INSTANCE.convert(importUser)
|
userMapper.insert(UserConvert.INSTANCE.convert(importUser)
|
||||||
.setPassword(encodePassword(userInitPassword))); // 设置默认密码
|
.setPassword(encodePassword(userInitPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组
|
||||||
respVO.getCreateUsernames().add(importUser.getUsername());
|
respVO.getCreateUsernames().add(importUser.getUsername());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
</root>
|
</root>
|
||||||
</springProfile>
|
</springProfile>
|
||||||
<!-- 其它环境 -->
|
<!-- 其它环境 -->
|
||||||
<springProfile name="dev,default">
|
<springProfile name="dev,test,stage,prod,default">
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
<appender-ref ref="ASYNC"/>
|
<appender-ref ref="ASYNC"/>
|
||||||
|
@ -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() {
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -24,28 +24,6 @@ public class ProductSpuManager {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ProductSpuFeign productSpuFeign;
|
private ProductSpuFeign productSpuFeign;
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建商品 SPU
|
|
||||||
*
|
|
||||||
* @param createVO 创建商品 SPU VO
|
|
||||||
* @return 商品 SPU
|
|
||||||
*/
|
|
||||||
public Integer createProductSpu(ProductSpuCreateReqVO createVO) {
|
|
||||||
CommonResult<Integer> createProductSpuResult = productSpuFeign.createProductSpu(ProductSpuConvert.INSTANCE.convert(createVO));
|
|
||||||
createProductSpuResult.checkError();
|
|
||||||
return createProductSpuResult.getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新商品 SPU
|
|
||||||
*
|
|
||||||
* @param updateVO 更新商品 SPU VO
|
|
||||||
*/
|
|
||||||
public void updateProductSpu(ProductSpuUpdateReqVO updateVO) {
|
|
||||||
CommonResult<Boolean> updateProductSpuResult = productSpuFeign.updateProductSpu(ProductSpuConvert.INSTANCE.convert(updateVO));
|
|
||||||
updateProductSpuResult.checkError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得商品 SPU
|
* 获得商品 SPU
|
||||||
*
|
*
|
||||||
|
@ -23,26 +23,6 @@ public interface ProductSpuFeign {
|
|||||||
@GetMapping(value = "/product/spu/get")
|
@GetMapping(value = "/product/spu/get")
|
||||||
CommonResult<ProductSpuRespDTO> getProductSpu(@RequestParam("productSpuId") Integer productSpuId);
|
CommonResult<ProductSpuRespDTO> getProductSpu(@RequestParam("productSpuId") Integer productSpuId);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建商品 SPU
|
|
||||||
*
|
|
||||||
* @param createDTO 创建商品 SPU DTO
|
|
||||||
* @return 商品 SPU编号
|
|
||||||
*/
|
|
||||||
@PostMapping(value = "/product/spu/create")
|
|
||||||
CommonResult<Integer> createProductSpu(@RequestBody ProductSpuAndSkuCreateReqDTO createDTO);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新商品 SPU
|
|
||||||
*
|
|
||||||
* @param updateDTO 更新商品 SPU DTO
|
|
||||||
*/
|
|
||||||
@PostMapping(value = "/product/spu/update")
|
|
||||||
CommonResult<Boolean> updateProductSpu(@RequestBody ProductSpuAndSkuUpdateReqDTO updateDTO);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得商品 SPU列表
|
* 获得商品 SPU列表
|
||||||
*
|
*
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
package cn.iocoder.mall.productservice.rpc.spu.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
import javax.validation.Valid;
|
|
||||||
import javax.validation.constraints.Min;
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商品 SPU 和 SKU 创建 Request DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Accessors(chain = true)
|
|
||||||
public class ProductSpuAndSkuCreateReqDTO implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SKU 信息
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Accessors(chain = true)
|
|
||||||
public static class Sku implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规格值数组
|
|
||||||
*/
|
|
||||||
@NotNull(message = "规格值数组不能为空")
|
|
||||||
private List<Integer> attrValueIds;
|
|
||||||
/**
|
|
||||||
* 价格,单位:分
|
|
||||||
*/
|
|
||||||
@NotNull(message = "价格不能为空")
|
|
||||||
@Min(value = 1L, message = "最小价格为 1")
|
|
||||||
private Integer price;
|
|
||||||
/**
|
|
||||||
* 库存数量
|
|
||||||
*/
|
|
||||||
@NotNull(message = "库存数量不能为空")
|
|
||||||
@Min(value = 1L, message = "最小库存为 1")
|
|
||||||
private Integer quantity;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 基本信息 =========
|
|
||||||
/**
|
|
||||||
* SPU 名字
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "SPU 名字不能为空")
|
|
||||||
private String name;
|
|
||||||
/**
|
|
||||||
* 卖点
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "卖点不能为空")
|
|
||||||
private String sellPoint;
|
|
||||||
/**
|
|
||||||
* 描述
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "描述不能为空")
|
|
||||||
private String description;
|
|
||||||
/**
|
|
||||||
* 分类编号
|
|
||||||
*/
|
|
||||||
@NotNull(message = "分类编号不能为空")
|
|
||||||
private Integer cid;
|
|
||||||
/**
|
|
||||||
* 商品主图地址
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "商品主图地址不能为空")
|
|
||||||
private List<String> picUrls;
|
|
||||||
|
|
||||||
// ========== 其他信息 =========
|
|
||||||
/**
|
|
||||||
* 是否上架商品
|
|
||||||
*/
|
|
||||||
@NotNull(message = "是否上架商品不能为空")
|
|
||||||
private Boolean visible;
|
|
||||||
|
|
||||||
// ========== SKU =========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SKU 数组
|
|
||||||
*/
|
|
||||||
@NotNull(message = "SKU 不能为空")
|
|
||||||
@Valid
|
|
||||||
private List<Sku> skus;
|
|
||||||
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
package cn.iocoder.mall.productservice.rpc.spu.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
import javax.validation.Valid;
|
|
||||||
import javax.validation.constraints.Min;
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商品 SPU 和 SKU 更新 Request DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Accessors(chain = true)
|
|
||||||
public class ProductSpuAndSkuUpdateReqDTO implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SKU 信息
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Accessors(chain = true)
|
|
||||||
public static class Sku implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规格值数组
|
|
||||||
*/
|
|
||||||
@NotNull(message = "规格值数组不能为空")
|
|
||||||
private List<Integer> attrValueIds;
|
|
||||||
/**
|
|
||||||
* 价格,单位:分
|
|
||||||
*/
|
|
||||||
@NotNull(message = "价格不能为空")
|
|
||||||
@Min(value = 1L, message = "最小价格为 1")
|
|
||||||
private Integer price;
|
|
||||||
/**
|
|
||||||
* 库存数量
|
|
||||||
*/
|
|
||||||
@NotNull(message = "库存数量不能为空")
|
|
||||||
@Min(value = 1L, message = "最小库存为 1")
|
|
||||||
private Integer quantity;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spu 编号
|
|
||||||
*/
|
|
||||||
@NotNull(message = "SPU 编号不能为空")
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
// ========== 基本信息 =========
|
|
||||||
/**
|
|
||||||
* SPU 名字
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "SPU 名字不能为空")
|
|
||||||
private String name;
|
|
||||||
/**
|
|
||||||
* 卖点
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "卖点不能为空")
|
|
||||||
private String sellPoint;
|
|
||||||
/**
|
|
||||||
* 描述
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "描述不能为空")
|
|
||||||
private String description;
|
|
||||||
/**
|
|
||||||
* 分类编号
|
|
||||||
*/
|
|
||||||
@NotNull(message = "分类编号不能为空")
|
|
||||||
private Integer cid;
|
|
||||||
/**
|
|
||||||
* 商品主图地址
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "商品主图地址不能为空")
|
|
||||||
private List<String> picUrls;
|
|
||||||
|
|
||||||
// ========== 其他信息 =========
|
|
||||||
/**
|
|
||||||
* 是否上架商品
|
|
||||||
*/
|
|
||||||
@NotNull(message = "是否上架商品不能为空")
|
|
||||||
private Boolean visible;
|
|
||||||
|
|
||||||
// ========== SKU =========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SKU 数组
|
|
||||||
*/
|
|
||||||
@NotNull(message = "SKU 不能为空")
|
|
||||||
@Valid
|
|
||||||
private List<Sku> skus;
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
### /product/spu/get 获得商品 SPU
|
|
||||||
GET http://localhost:38082/product/spu/get?productSpuId=32
|
|
||||||
###
|
|
||||||
|
|
||||||
### /product/spu/get 获得商品 SPU
|
|
||||||
GET http://localhost:38082/product/spu/lislistProductSpuIdst?lastSpuId=30&limit=10
|
|
||||||
###
|
|
||||||
|
|
||||||
|
|
||||||
### /product/spu/get 获得商品 SPU
|
|
||||||
GET http://localhost:38082/product/spu/getProductSpuDetail?productSpuId=32&fields=attr,sku
|
|
||||||
###
|
|
||||||
|
|
@ -31,24 +31,6 @@ public class ProductSpuController {
|
|||||||
return success(productSpuManager.getProductSpu(productSpuId));
|
return success(productSpuManager.getProductSpu(productSpuId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新商品 SPU
|
|
||||||
*
|
|
||||||
* @param updateDTO 更新商品 SPU DTO
|
|
||||||
*/
|
|
||||||
@PostMapping("/update")
|
|
||||||
@ApiOperation("更新商品 SPU")
|
|
||||||
public CommonResult<Boolean> updateProductSpu(@Valid @RequestBody ProductSpuAndSkuUpdateReqDTO updateDTO) {
|
|
||||||
productSpuManager.updateProductSpu(updateDTO);
|
|
||||||
return success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/create")
|
|
||||||
@ApiOperation("创建商品 SPU")
|
|
||||||
public CommonResult<Integer> createProductSpu(@Valid @RequestBody ProductSpuAndSkuCreateReqDTO createDTO) {
|
|
||||||
return success(productSpuManager.createProductSpu(createDTO));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@ApiOperation("获得商品 SPU 列表")
|
@ApiOperation("获得商品 SPU 列表")
|
||||||
@ApiImplicitParam(name = "productSpuIds", value = "商品 SPU 编号列表", required = true)
|
@ApiImplicitParam(name = "productSpuIds", value = "商品 SPU 编号列表", required = true)
|
||||||
|
@ -51,69 +51,6 @@ public class ProductSpuManager {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ProductMQProducer productMQProducer;
|
private ProductMQProducer productMQProducer;
|
||||||
|
|
||||||
private static ProductSpuManager self() {
|
|
||||||
return (ProductSpuManager) AopContext.currentProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建商品 SPU 和 SKU
|
|
||||||
*
|
|
||||||
* @param createDTO 创建商品 SPU 和 SKU DTO
|
|
||||||
* @return 商品 SPU
|
|
||||||
*/
|
|
||||||
public Integer createProductSpu(ProductSpuAndSkuCreateReqDTO createDTO) {
|
|
||||||
// 创建商品 SPU 和 SKU。注意,这里要调用 self() 方法,因为需要创建事务,否则会失效
|
|
||||||
Integer spuId = self().createProductSpu0(createDTO);
|
|
||||||
// 发送商品创建的 MQ 消息
|
|
||||||
productMQProducer.sendProductUpdateMessage(spuId);
|
|
||||||
return spuId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public Integer createProductSpu0(ProductSpuAndSkuCreateReqDTO createDTO) {
|
|
||||||
// 校验商品分类是否合法
|
|
||||||
this.checkProductCategory(createDTO.getCid());
|
|
||||||
// 创建商品 SKU 对象,并进行校验
|
|
||||||
List<ProductSkuCreateOrUpdateBO> skuBOs = ProductSpuConvert.INSTANCE.convert(createDTO.getSkus());
|
|
||||||
this.checkProductAttr(skuBOs);
|
|
||||||
// 插入商品 SPU 记录
|
|
||||||
ProductSpuCreateBO spuCreateBO = ProductSpuConvert.INSTANCE.convert(createDTO).setSort(0);
|
|
||||||
spuCreateBO.setPrice(skuBOs.stream().min(Comparator.comparing(ProductSkuCreateOrUpdateBO::getPrice)).get().getPrice()); // 求最小价格
|
|
||||||
spuCreateBO.setQuantity(skuBOs.stream().mapToInt(ProductSkuCreateOrUpdateBO::getQuantity).sum()); // 求库存之和
|
|
||||||
ProductSpuBO spuBO = productSpuService.createProductSpu(spuCreateBO);
|
|
||||||
// 插入商品 SKU 记录
|
|
||||||
productSkuService.createProductSkus(spuBO.getId(), skuBOs);
|
|
||||||
return spuBO.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新商品 SPU
|
|
||||||
*
|
|
||||||
* @param updateDTO 更新商品 SPU DTO
|
|
||||||
*/
|
|
||||||
public void updateProductSpu(ProductSpuAndSkuUpdateReqDTO updateDTO) {
|
|
||||||
// 更新商品 SPU 和 SKU。注意,这里要调用 self() 方法,因为需要创建事务,否则会失效
|
|
||||||
self().updateProductSpu0(updateDTO);
|
|
||||||
// 发送商品创建的 MQ 消息
|
|
||||||
productMQProducer.sendProductUpdateMessage(updateDTO.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void updateProductSpu0(ProductSpuAndSkuUpdateReqDTO updateDTO) {
|
|
||||||
// 校验商品分类是否合法
|
|
||||||
this.checkProductCategory(updateDTO.getCid());
|
|
||||||
// 创建商品 SKU 对象,并进行校验
|
|
||||||
List<ProductSkuCreateOrUpdateBO> skuBOs = ProductSpuConvert.INSTANCE.convert02(updateDTO.getSkus());
|
|
||||||
this.checkProductAttr(skuBOs);
|
|
||||||
// 更新商品 SPU 记录
|
|
||||||
ProductSpuUpdateBO spuUpdateBO = ProductSpuConvert.INSTANCE.convert(updateDTO);
|
|
||||||
spuUpdateBO.setPrice(skuBOs.stream().min(Comparator.comparing(ProductSkuCreateOrUpdateBO::getPrice)).get().getPrice()); // 求最小价格
|
|
||||||
spuUpdateBO.setQuantity(skuBOs.stream().mapToInt(ProductSkuCreateOrUpdateBO::getQuantity).sum()); // 求库存之和
|
|
||||||
productSpuService.updateProductSpu(spuUpdateBO);
|
|
||||||
// 更新商品 SKU 记录
|
|
||||||
productSkuService.updateProductSkus(updateDTO.getId(), skuBOs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得商品 SPU
|
* 获得商品 SPU
|
||||||
*
|
*
|
||||||
|
@ -31,44 +31,6 @@ public class ProductSkuService {
|
|||||||
productSkuMapper.insertList(skus);
|
productSkuMapper.insertList(skus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void updateProductSkus(Integer spuId, List<ProductSkuCreateOrUpdateBO> skuUpdateBOs) {
|
|
||||||
List<ProductSkuDO> existsSkus = productSkuMapper.selectListBySpuIdAndStatus(spuId,
|
|
||||||
CommonStatusEnum.ENABLE.getValue());
|
|
||||||
List<ProductSkuDO> insertSkus = new ArrayList<>(); // 1、找不到,进行插入
|
|
||||||
List<Integer> deleteSkus = new ArrayList<>(); // 2、多余的,删除
|
|
||||||
List<ProductSkuDO> updateSkus = new ArrayList<>(); // 3、找的到,进行更新。
|
|
||||||
for (ProductSkuCreateOrUpdateBO skuUpdateDTO : skuUpdateBOs) {
|
|
||||||
ProductSkuDO existsSku = findProductSku(skuUpdateDTO.getAttrValueIds(), existsSkus);
|
|
||||||
// 3、找的到,进行更新。
|
|
||||||
if (existsSku != null) {
|
|
||||||
// 移除
|
|
||||||
existsSkus.remove(existsSku);
|
|
||||||
// 创建 ProductSkuDO
|
|
||||||
updateSkus.add(ProductSkuConvert.INSTANCE.convert(skuUpdateDTO).setId(existsSku.getId()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 1、找不到,进行插入
|
|
||||||
ProductSkuDO insertSku = ProductSkuConvert.INSTANCE.convert(skuUpdateDTO)
|
|
||||||
.setSpuId(spuId).setStatus(CommonStatusEnum.ENABLE.getValue());
|
|
||||||
insertSkus.add(insertSku);
|
|
||||||
}
|
|
||||||
// 2、多余的,删除
|
|
||||||
if (!existsSkus.isEmpty()) {
|
|
||||||
deleteSkus.addAll(existsSkus.stream().map(ProductSkuDO::getId).collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
// 执行修改 Sku
|
|
||||||
if (!insertSkus.isEmpty()) {
|
|
||||||
productSkuMapper.insertList(insertSkus);
|
|
||||||
}
|
|
||||||
if (!updateSkus.isEmpty()) {
|
|
||||||
updateSkus.forEach(productSkuDO -> productSkuMapper.updateById(productSkuDO));
|
|
||||||
}
|
|
||||||
if (!deleteSkus.isEmpty()) {
|
|
||||||
productSkuMapper.deleteBatchIds(deleteSkus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得 sku 数组中,指定规格的 sku
|
* 获得 sku 数组中,指定规格的 sku
|
||||||
*
|
*
|
||||||
|