Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot
This commit is contained in:
@@ -1259,14 +1259,16 @@ CREATE TABLE `system_mail_log` (
|
|||||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||||
`user_id` bigint NULL DEFAULT NULL COMMENT '用户编号',
|
`user_id` bigint NULL DEFAULT NULL COMMENT '用户编号',
|
||||||
`user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型',
|
`user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型',
|
||||||
`to_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址',
|
`to_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址',
|
||||||
|
`cc_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '抄送邮箱地址',
|
||||||
|
`bcc_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密送邮箱地址',
|
||||||
`account_id` bigint NOT NULL COMMENT '邮箱账号编号',
|
`account_id` bigint NOT NULL COMMENT '邮箱账号编号',
|
||||||
`from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址',
|
`from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址',
|
||||||
`template_id` bigint NOT NULL COMMENT '模板编号',
|
`template_id` bigint NOT NULL COMMENT '模板编号',
|
||||||
`template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码',
|
`template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码',
|
||||||
`template_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版发送人名称',
|
`template_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版发送人名称',
|
||||||
`template_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件标题',
|
`template_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件标题',
|
||||||
`template_content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容',
|
`template_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容',
|
||||||
`template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件参数',
|
`template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件参数',
|
||||||
`send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态',
|
`send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态',
|
||||||
`send_time` datetime NULL DEFAULT NULL COMMENT '发送时间',
|
`send_time` datetime NULL DEFAULT NULL COMMENT '发送时间',
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public interface WebFilterOrderEnum {
|
|||||||
|
|
||||||
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
|
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
|
||||||
|
|
||||||
|
int API_ENCRYPT_FILTER = REQUEST_BODY_CACHE_FILTER + 1;
|
||||||
|
|
||||||
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
|
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
|
||||||
|
|
||||||
int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
|
int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
|
||||||
|
|||||||
@@ -25,16 +25,16 @@ public class CommonResult<T> implements Serializable {
|
|||||||
* @see ErrorCode#getCode()
|
* @see ErrorCode#getCode()
|
||||||
*/
|
*/
|
||||||
private Integer code;
|
private Integer code;
|
||||||
/**
|
|
||||||
* 返回数据
|
|
||||||
*/
|
|
||||||
private T data;
|
|
||||||
/**
|
/**
|
||||||
* 错误提示,用户可阅读
|
* 错误提示,用户可阅读
|
||||||
*
|
*
|
||||||
* @see ErrorCode#getMsg() ()
|
* @see ErrorCode#getMsg() ()
|
||||||
*/
|
*/
|
||||||
private String msg;
|
private String msg;
|
||||||
|
/**
|
||||||
|
* 返回数据
|
||||||
|
*/
|
||||||
|
private T data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将传入的 result 对象,转换成另外一个泛型结果的对象
|
* 将传入的 result 对象,转换成另外一个泛型结果的对象
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
public final class PageResult<T> implements Serializable {
|
public final class PageResult<T> implements Serializable {
|
||||||
|
|
||||||
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
private List<T> list;
|
|
||||||
|
|
||||||
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private Long total;
|
private Long total;
|
||||||
|
|
||||||
|
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private List<T> list;
|
||||||
|
|
||||||
public PageResult() {
|
public PageResult() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,13 @@
|
|||||||
<scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
|
<scope>provided</scope> <!-- 设置为 provided,主要是 GlobalExceptionHandler 使用 -->
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- xss -->
|
<!-- 工具类相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jsoup</groupId>
|
<groupId>org.jsoup</groupId>
|
||||||
<artifactId>jsoup</artifactId>
|
<artifactId>jsoup</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package cn.iocoder.yudao.framework.encrypt.config;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP API 加解密配置
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties(prefix = "yudao.api-encrypt")
|
||||||
|
@Validated
|
||||||
|
@Data
|
||||||
|
public class ApiEncryptProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启
|
||||||
|
*/
|
||||||
|
@NotNull(message = "是否开启不能为空")
|
||||||
|
private Boolean enable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求头(响应头)名称
|
||||||
|
*
|
||||||
|
* 1. 如果该请求头非空,则表示请求参数已被「前端」加密,「后端」需要解密
|
||||||
|
* 2. 如果该响应头非空,则表示响应结果已被「后端」加密,「前端」需要解密
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "请求头(响应头)名称不能为空")
|
||||||
|
private String header = "X-Api-Encrypt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对称加密算法,用于请求/响应的加解密
|
||||||
|
*
|
||||||
|
* 目前支持
|
||||||
|
* 【对称加密】:
|
||||||
|
* 1. {@link cn.hutool.crypto.symmetric.SymmetricAlgorithm#AES}
|
||||||
|
* 2. {@link cn.hutool.crypto.symmetric.SM4#ALGORITHM_NAME} (需要自己二次开发,成本低)
|
||||||
|
* 【非对称加密】
|
||||||
|
* 1. {@link cn.hutool.crypto.asymmetric.AsymmetricAlgorithm#RSA}
|
||||||
|
* 2. {@link cn.hutool.crypto.asymmetric.SM2} (需要自己二次开发,成本低)
|
||||||
|
*
|
||||||
|
* @see <a href="https://help.aliyun.com/zh/ssl-certificate/what-are-a-public-key-and-a-private-key">什么是公钥和私钥?</a>
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "对称加密算法不能为空")
|
||||||
|
private String algorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求的解密密钥
|
||||||
|
*
|
||||||
|
* 注意:
|
||||||
|
* 1. 如果是【对称加密】时,它「后端」对应的是“密钥”。对应的,「前端」也对应的也是“密钥”。
|
||||||
|
* 2. 如果是【非对称加密】时,它「后端」对应的是“私钥”。对应的,「前端」对应的是“公钥”。(重要!!!)
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "请求的解密密钥不能为空")
|
||||||
|
private String requestKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应的加密密钥
|
||||||
|
*
|
||||||
|
* 注意:
|
||||||
|
* 1. 如果是【对称加密】时,它「后端」对应的是“密钥”。对应的,「前端」也对应的也是“密钥”。
|
||||||
|
* 2. 如果是【非对称加密】时,它「后端」对应的是“公钥”。对应的,「前端」对应的是“私钥”。(重要!!!)
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "响应的加密密钥不能为空")
|
||||||
|
private String responseKey;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package cn.iocoder.yudao.framework.encrypt.config;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
|
||||||
|
import cn.iocoder.yudao.framework.encrypt.core.filter.ApiEncryptFilter;
|
||||||
|
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.createFilterBean;
|
||||||
|
|
||||||
|
@AutoConfiguration
|
||||||
|
@Slf4j
|
||||||
|
@EnableConfigurationProperties(ApiEncryptProperties.class)
|
||||||
|
@ConditionalOnProperty(prefix = "yudao.api-encrypt", name = "enable", havingValue = "true")
|
||||||
|
public class YudaoApiEncryptAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean<ApiEncryptFilter> apiEncryptFilter(WebProperties webProperties,
|
||||||
|
ApiEncryptProperties apiEncryptProperties,
|
||||||
|
RequestMappingHandlerMapping requestMappingHandlerMapping,
|
||||||
|
GlobalExceptionHandler globalExceptionHandler) {
|
||||||
|
ApiEncryptFilter filter = new ApiEncryptFilter(webProperties, apiEncryptProperties,
|
||||||
|
requestMappingHandlerMapping, globalExceptionHandler);
|
||||||
|
return createFilterBean(filter, WebFilterOrderEnum.API_ENCRYPT_FILTER);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package cn.iocoder.yudao.framework.encrypt.core.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP API 加解密注解
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ApiEncrypt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否对请求参数进行解密,默认 true
|
||||||
|
*/
|
||||||
|
boolean request() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否对响应结果进行加密,默认 true
|
||||||
|
*/
|
||||||
|
boolean response() default true;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package cn.iocoder.yudao.framework.encrypt.core.filter;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
|
||||||
|
import cn.hutool.crypto.asymmetric.KeyType;
|
||||||
|
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
|
||||||
|
import jakarta.servlet.ReadListener;
|
||||||
|
import jakarta.servlet.ServletInputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密请求 {@link HttpServletRequestWrapper} 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public class ApiDecryptRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
|
private final byte[] body;
|
||||||
|
|
||||||
|
public ApiDecryptRequestWrapper(HttpServletRequest request,
|
||||||
|
SymmetricDecryptor symmetricDecryptor,
|
||||||
|
AsymmetricDecryptor asymmetricDecryptor) throws IOException {
|
||||||
|
super(request);
|
||||||
|
// 读取 body,允许 HEX、BASE64 传输
|
||||||
|
String requestBody = StrUtil.utf8Str(
|
||||||
|
IoUtil.readBytes(request.getInputStream(), false));
|
||||||
|
|
||||||
|
// 解密 body
|
||||||
|
body = symmetricDecryptor != null ? symmetricDecryptor.decrypt(requestBody)
|
||||||
|
: asymmetricDecryptor.decrypt(requestBody, KeyType.PrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedReader getReader() {
|
||||||
|
return new BufferedReader(new InputStreamReader(this.getInputStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getContentLength() {
|
||||||
|
return body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getContentLengthLong() {
|
||||||
|
return body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServletInputStream getInputStream() {
|
||||||
|
ByteArrayInputStream stream = new ByteArrayInputStream(body);
|
||||||
|
return new ServletInputStream() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
return stream.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() {
|
||||||
|
return body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFinished() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package cn.iocoder.yudao.framework.encrypt.core.filter;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.crypto.SecureUtil;
|
||||||
|
import cn.hutool.crypto.asymmetric.AsymmetricDecryptor;
|
||||||
|
import cn.hutool.crypto.asymmetric.AsymmetricEncryptor;
|
||||||
|
import cn.hutool.crypto.symmetric.SymmetricDecryptor;
|
||||||
|
import cn.hutool.crypto.symmetric.SymmetricEncryptor;
|
||||||
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||||
|
import cn.iocoder.yudao.framework.encrypt.config.ApiEncryptProperties;
|
||||||
|
import cn.iocoder.yudao.framework.encrypt.core.annotation.ApiEncrypt;
|
||||||
|
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 加密过滤器,处理 {@link ApiEncrypt} 注解。
|
||||||
|
*
|
||||||
|
* 1. 解密请求参数
|
||||||
|
* 2. 加密响应结果
|
||||||
|
*
|
||||||
|
* 疑问:为什么不使用 SpringMVC 的 RequestBodyAdvice 或 ResponseBodyAdvice 机制呢?
|
||||||
|
* 回答:考虑到项目中会记录访问日志、异常日志,以及 HTTP API 签名等场景,最好是全局级、且提前做解析!!!
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ApiEncryptFilter extends ApiRequestFilter {
|
||||||
|
|
||||||
|
private final ApiEncryptProperties apiEncryptProperties;
|
||||||
|
|
||||||
|
private final RequestMappingHandlerMapping requestMappingHandlerMapping;
|
||||||
|
|
||||||
|
private final GlobalExceptionHandler globalExceptionHandler;
|
||||||
|
|
||||||
|
private final SymmetricDecryptor requestSymmetricDecryptor;
|
||||||
|
private final AsymmetricDecryptor requestAsymmetricDecryptor;
|
||||||
|
|
||||||
|
private final SymmetricEncryptor responseSymmetricEncryptor;
|
||||||
|
private final AsymmetricEncryptor responseAsymmetricEncryptor;
|
||||||
|
|
||||||
|
public ApiEncryptFilter(WebProperties webProperties,
|
||||||
|
ApiEncryptProperties apiEncryptProperties,
|
||||||
|
RequestMappingHandlerMapping requestMappingHandlerMapping,
|
||||||
|
GlobalExceptionHandler globalExceptionHandler) {
|
||||||
|
super(webProperties);
|
||||||
|
this.apiEncryptProperties = apiEncryptProperties;
|
||||||
|
this.requestMappingHandlerMapping = requestMappingHandlerMapping;
|
||||||
|
this.globalExceptionHandler = globalExceptionHandler;
|
||||||
|
if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "AES")) {
|
||||||
|
this.requestSymmetricDecryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getRequestKey()));
|
||||||
|
this.requestAsymmetricDecryptor = null;
|
||||||
|
this.responseSymmetricEncryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getResponseKey()));
|
||||||
|
this.responseAsymmetricEncryptor = null;
|
||||||
|
} else if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "RSA")) {
|
||||||
|
this.requestSymmetricDecryptor = null;
|
||||||
|
this.requestAsymmetricDecryptor = SecureUtil.rsa(apiEncryptProperties.getRequestKey(), null);
|
||||||
|
this.responseSymmetricEncryptor = null;
|
||||||
|
this.responseAsymmetricEncryptor = SecureUtil.rsa(null, apiEncryptProperties.getResponseKey());
|
||||||
|
} else {
|
||||||
|
// 补充说明:如果要支持 SM2、SM4 等算法,可在此处增加对应实例的创建,并添加相应的 Maven 依赖即可。
|
||||||
|
throw new IllegalArgumentException("不支持的加密算法:" + apiEncryptProperties.getAlgorithm());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
// 获取 @ApiEncrypt 注解
|
||||||
|
ApiEncrypt apiEncrypt = getApiEncrypt(request);
|
||||||
|
boolean requestEnable = apiEncrypt != null && apiEncrypt.request();
|
||||||
|
boolean responseEnable = apiEncrypt != null && apiEncrypt.response();
|
||||||
|
String encryptHeader = request.getHeader(apiEncryptProperties.getHeader());
|
||||||
|
if (!requestEnable && !responseEnable && StrUtil.isBlank(encryptHeader)) {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 解密请求
|
||||||
|
if (ObjectUtils.equalsAny(HttpMethod.valueOf(request.getMethod()),
|
||||||
|
HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)) {
|
||||||
|
try {
|
||||||
|
if (StrUtil.isNotBlank(encryptHeader)) {
|
||||||
|
request = new ApiDecryptRequestWrapper(request,
|
||||||
|
requestSymmetricDecryptor, requestAsymmetricDecryptor);
|
||||||
|
} else if (requestEnable) {
|
||||||
|
throw invalidParamException("请求未包含加密标头,请检查是否正确配置了加密标头");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
|
||||||
|
ServletUtils.writeJSON(response, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 执行过滤器链
|
||||||
|
if (responseEnable) {
|
||||||
|
// 特殊:仅包装,最后执行。目的:Response 内容可以被重复读取!!!
|
||||||
|
response = new ApiEncryptResponseWrapper(response);
|
||||||
|
}
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
|
||||||
|
// 3. 加密响应(真正执行)
|
||||||
|
if (responseEnable) {
|
||||||
|
((ApiEncryptResponseWrapper) response).encrypt(apiEncryptProperties,
|
||||||
|
responseSymmetricEncryptor, responseAsymmetricEncryptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 @ApiEncrypt 注解
|
||||||
|
*
|
||||||
|
* @param request 请求
|
||||||
|
*/
|
||||||
|
private ApiEncrypt getApiEncrypt(HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
HandlerExecutionChain mappingHandler = requestMappingHandlerMapping.getHandler(request);
|
||||||
|
if (mappingHandler == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object handler = mappingHandler.getHandler();
|
||||||
|
if (handler instanceof HandlerMethod handlerMethod) {
|
||||||
|
ApiEncrypt annotation = handlerMethod.getMethodAnnotation(ApiEncrypt.class);
|
||||||
|
if (annotation == null) {
|
||||||
|
annotation = handlerMethod.getBeanType().getAnnotation(ApiEncrypt.class);
|
||||||
|
}
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[getApiEncrypt][url({}/{}) 获取 @ApiEncrypt 注解失败]",
|
||||||
|
request.getRequestURI(), request.getMethod(), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package cn.iocoder.yudao.framework.encrypt.core.filter;
|
||||||
|
|
||||||
|
import cn.hutool.crypto.asymmetric.AsymmetricEncryptor;
|
||||||
|
import cn.hutool.crypto.asymmetric.KeyType;
|
||||||
|
import cn.hutool.crypto.symmetric.SymmetricEncryptor;
|
||||||
|
import cn.iocoder.yudao.framework.encrypt.config.ApiEncryptProperties;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.WriteListener;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密响应 {@link HttpServletResponseWrapper} 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public class ApiEncryptResponseWrapper extends HttpServletResponseWrapper {
|
||||||
|
|
||||||
|
private final ByteArrayOutputStream byteArrayOutputStream;
|
||||||
|
private final ServletOutputStream servletOutputStream;
|
||||||
|
private final PrintWriter printWriter;
|
||||||
|
|
||||||
|
public ApiEncryptResponseWrapper(HttpServletResponse response) {
|
||||||
|
super(response);
|
||||||
|
this.byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
this.servletOutputStream = this.getOutputStream();
|
||||||
|
this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void encrypt(ApiEncryptProperties properties,
|
||||||
|
SymmetricEncryptor symmetricEncryptor,
|
||||||
|
AsymmetricEncryptor asymmetricEncryptor) throws IOException {
|
||||||
|
// 1.1 清空 body
|
||||||
|
HttpServletResponse response = (HttpServletResponse) this.getResponse();
|
||||||
|
response.resetBuffer();
|
||||||
|
// 1.2 获取 body
|
||||||
|
this.flushBuffer();
|
||||||
|
byte[] body = byteArrayOutputStream.toByteArray();
|
||||||
|
|
||||||
|
// 2. 加密 body
|
||||||
|
String encryptedBody = symmetricEncryptor != null ? symmetricEncryptor.encryptBase64(body)
|
||||||
|
: asymmetricEncryptor.encryptBase64(body, KeyType.PublicKey);
|
||||||
|
response.getWriter().write(encryptedBody);
|
||||||
|
|
||||||
|
// 3. 添加加密 header 标识
|
||||||
|
this.addHeader(properties.getHeader(), "true");
|
||||||
|
// 特殊:特殊:https://juejin.cn/post/6867327674675625992
|
||||||
|
this.addHeader("Access-Control-Expose-Headers", properties.getHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrintWriter getWriter() {
|
||||||
|
return printWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushBuffer() throws IOException {
|
||||||
|
if (servletOutputStream != null) {
|
||||||
|
servletOutputStream.flush();
|
||||||
|
}
|
||||||
|
if (printWriter != null) {
|
||||||
|
printWriter.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
byteArrayOutputStream.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServletOutputStream getOutputStream() {
|
||||||
|
return new ServletOutputStream() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteListener(WriteListener writeListener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
byteArrayOutputStream.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
byteArrayOutputStream.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("NullableProblems")
|
||||||
|
public void write(byte[] b, int off, int len) {
|
||||||
|
byteArrayOutputStream.write(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* HTTP API 加密组件:支持 Request 和 Response 的加密、解密
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.framework.encrypt;
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
package cn.iocoder.yudao.framework.web.core.filter;
|
package cn.iocoder.yudao.framework.web.core.filter;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||||
|
|
||||||
import jakarta.servlet.ReadListener;
|
import jakarta.servlet.ReadListener;
|
||||||
import jakarta.servlet.ServletInputStream;
|
import jakarta.servlet.ServletInputStream;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,12 +28,22 @@ public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedReader getReader() throws IOException {
|
public BufferedReader getReader() {
|
||||||
return new BufferedReader(new InputStreamReader(this.getInputStream()));
|
return new BufferedReader(new InputStreamReader(this.getInputStream()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
public int getContentLength() {
|
||||||
|
return body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getContentLengthLong() {
|
||||||
|
return body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServletInputStream getInputStream() {
|
||||||
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
|
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
|
||||||
// 返回 ServletInputStream
|
// 返回 ServletInputStream
|
||||||
return new ServletInputStream() {
|
return new ServletInputStream() {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import cn.hutool.core.exceptions.ExceptionUtil;
|
|||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
|
||||||
import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
|
import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
|
||||||
import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
|
import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
|
||||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||||
@@ -17,6 +16,7 @@ import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
|||||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||||
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.ConstraintViolation;
|
import jakarta.validation.ConstraintViolation;
|
||||||
import jakarta.validation.ConstraintViolationException;
|
import jakarta.validation.ConstraintViolationException;
|
||||||
@@ -111,6 +111,9 @@ public class GlobalExceptionHandler {
|
|||||||
if (ex instanceof AccessDeniedException) {
|
if (ex instanceof AccessDeniedException) {
|
||||||
return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
|
return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
|
||||||
}
|
}
|
||||||
|
if (ex instanceof UncheckedExecutionException && ex.getCause() != ex) {
|
||||||
|
return allExceptionHandler(request, ex.getCause());
|
||||||
|
}
|
||||||
return defaultExceptionHandler(request, ex);
|
return defaultExceptionHandler(request, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +269,16 @@ public class GlobalExceptionHandler {
|
|||||||
return CommonResult.error(FORBIDDEN);
|
return CommonResult.error(FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 Guava UncheckedExecutionException
|
||||||
|
*
|
||||||
|
* 例如说,缓存加载报错,可见 <a href="https://t.zsxq.com/UszdH">https://t.zsxq.com/UszdH</a>
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(value = UncheckedExecutionException.class)
|
||||||
|
public CommonResult<?> uncheckedExecutionExceptionHandler(HttpServletRequest req, UncheckedExecutionException ex) {
|
||||||
|
return allExceptionHandler(req, ex.getCause());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理业务异常 ServiceException
|
* 处理业务异常 ServiceException
|
||||||
*
|
*
|
||||||
@@ -344,12 +357,12 @@ public class GlobalExceptionHandler {
|
|||||||
errorLog.setApplicationName(applicationName);
|
errorLog.setApplicationName(applicationName);
|
||||||
errorLog.setRequestUrl(request.getRequestURI());
|
errorLog.setRequestUrl(request.getRequestURI());
|
||||||
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
|
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
|
||||||
.put("query", JakartaServletUtil.getParamMap(request))
|
.put("query", ServletUtils.getParamMap(request))
|
||||||
.put("body", JakartaServletUtil.getBody(request)).build();
|
.put("body", ServletUtils.getBody(request)).build();
|
||||||
errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
|
errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
|
||||||
errorLog.setRequestMethod(request.getMethod());
|
errorLog.setRequestMethod(request.getMethod());
|
||||||
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
|
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
|
||||||
errorLog.setUserIp(JakartaServletUtil.getClientIP(request));
|
errorLog.setUserIp(ServletUtils.getClientIP(request));
|
||||||
errorLog.setExceptionTime(LocalDateTime.now());
|
errorLog.setExceptionTime(LocalDateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
|
|||||||
cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
|
cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
|
||||||
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
|
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
|
||||||
cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration
|
cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration
|
||||||
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration
|
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration
|
||||||
|
cn.iocoder.yudao.framework.encrypt.config.YudaoApiEncryptAutoConfiguration
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package cn.iocoder.yudao.framework.encrypt;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.crypto.SecureUtil;
|
||||||
|
import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm;
|
||||||
|
import cn.hutool.crypto.asymmetric.KeyType;
|
||||||
|
import cn.hutool.crypto.asymmetric.RSA;
|
||||||
|
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 各种 API 加解密的测试类:不是单测,而是方便大家生成密钥、加密、解密等操作。
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("ConstantValue")
|
||||||
|
public class ApiEncryptTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateAsymmetric() {
|
||||||
|
String asymmetricAlgorithm = AsymmetricAlgorithm.RSA.getValue();
|
||||||
|
// String asymmetricAlgorithm = "SM2";
|
||||||
|
// String asymmetricAlgorithm = SM4.ALGORITHM_NAME;
|
||||||
|
// String asymmetricAlgorithm = SymmetricAlgorithm.AES.getValue();
|
||||||
|
String requestClientKey = null;
|
||||||
|
String requestServerKey = null;
|
||||||
|
String responseClientKey = null;
|
||||||
|
String responseServerKey = null;
|
||||||
|
if (Objects.equals(asymmetricAlgorithm, AsymmetricAlgorithm.RSA.getValue())) {
|
||||||
|
// 请求的密钥
|
||||||
|
RSA requestRsa = SecureUtil.rsa();
|
||||||
|
requestClientKey = requestRsa.getPublicKeyBase64();
|
||||||
|
requestServerKey = requestRsa.getPrivateKeyBase64();
|
||||||
|
// 响应的密钥
|
||||||
|
RSA responseRsa = new RSA();
|
||||||
|
responseClientKey = responseRsa.getPrivateKeyBase64();
|
||||||
|
responseServerKey = responseRsa.getPublicKeyBase64();
|
||||||
|
} else if (Objects.equals(asymmetricAlgorithm, SymmetricAlgorithm.AES.getValue())) {
|
||||||
|
// AES 密钥可选 32、24、16 位
|
||||||
|
// 请求的密钥(前后端密钥一致)
|
||||||
|
requestClientKey = RandomUtil.randomNumbers(32);
|
||||||
|
requestServerKey = requestClientKey;
|
||||||
|
// 响应的密钥(前后端密钥一致)
|
||||||
|
responseClientKey = RandomUtil.randomNumbers(32);
|
||||||
|
responseServerKey = responseClientKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印结果
|
||||||
|
System.out.println("requestClientKey = " + requestClientKey);
|
||||||
|
System.out.println("requestServerKey = " + requestServerKey);
|
||||||
|
System.out.println("responseClientKey = " + responseClientKey);
|
||||||
|
System.out.println("responseServerKey = " + responseServerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncrypt_aes() {
|
||||||
|
String key = "52549111389893486934626385991395";
|
||||||
|
String body = "{\n" +
|
||||||
|
" \"username\": \"admin\",\n" +
|
||||||
|
" \"password\": \"admin123\",\n" +
|
||||||
|
" \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" +
|
||||||
|
" \"code\": \"1024\"\n" +
|
||||||
|
"}";
|
||||||
|
String encrypt = SecureUtil.aes(StrUtil.utf8Bytes(key))
|
||||||
|
.encryptBase64(body);
|
||||||
|
System.out.println("encrypt = " + encrypt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncrypt_rsa() {
|
||||||
|
String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB";
|
||||||
|
String body = "{\n" +
|
||||||
|
" \"username\": \"admin\",\n" +
|
||||||
|
" \"password\": \"admin123\",\n" +
|
||||||
|
" \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" +
|
||||||
|
" \"code\": \"1024\"\n" +
|
||||||
|
"}";
|
||||||
|
String encrypt = SecureUtil.rsa(null, key)
|
||||||
|
.encryptBase64(body, KeyType.PublicKey);
|
||||||
|
System.out.println("encrypt = " + encrypt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -72,6 +72,9 @@ public class BpmModelMetaInfoVO {
|
|||||||
@Schema(description = "允许撤销审批中的申请", example = "true")
|
@Schema(description = "允许撤销审批中的申请", example = "true")
|
||||||
private Boolean allowCancelRunningProcess;
|
private Boolean allowCancelRunningProcess;
|
||||||
|
|
||||||
|
@Schema(description = "允许允许审批人撤回任务", example = "false")
|
||||||
|
private Boolean allowWithdrawTask;
|
||||||
|
|
||||||
@Schema(description = "流程 ID 规则", example = "{}")
|
@Schema(description = "流程 ID 规则", example = "{}")
|
||||||
private ProcessIdRule processIdRule;
|
private ProcessIdRule processIdRule;
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,14 @@ public class BpmTaskController {
|
|||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/withdraw")
|
||||||
|
@Operation(summary = "撤回任务")
|
||||||
|
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
|
||||||
|
public CommonResult<Boolean> withdrawTask(@RequestParam("taskId") String taskId) {
|
||||||
|
taskService.withdrawTask(getLoginUserId(), taskId);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/list-by-parent-task-id")
|
@GetMapping("/list-by-parent-task-id")
|
||||||
@Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
|
@Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
|
||||||
@Parameter(name = "parentTaskId", description = "父级任务编号", required = true)
|
@Parameter(name = "parentTaskId", description = "父级任务编号", required = true)
|
||||||
|
|||||||
@@ -172,6 +172,11 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private Boolean allowCancelRunningProcess;
|
private Boolean allowCancelRunningProcess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否允许审批人撤回任务
|
||||||
|
*/
|
||||||
|
private Boolean allowWithdrawTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程 ID 规则
|
* 流程 ID 规则
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
|
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
|
||||||
ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
|
ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
|
||||||
ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
|
ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
|
||||||
|
ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!");
|
||||||
|
ErrorCode TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS = new ErrorCode(1_009_005_018, "撤回失败,未查询到用户已办任务!");
|
||||||
|
ErrorCode TASK_WITHDRAW_FAIL_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,此流程不允许撤回操作!");
|
||||||
|
ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,下一节点不满足撤回条件!");
|
||||||
|
|
||||||
// ========== 动态表单模块 1-009-010-000 ==========
|
// ========== 动态表单模块 1-009-010-000 ==========
|
||||||
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");
|
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public enum BpmReasonEnum {
|
|||||||
APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
|
APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
|
||||||
APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
|
APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
|
||||||
CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"),
|
CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"),
|
||||||
|
CANCEL_BY_WITHDRAW("前一任务撤回,系统自动取消"),
|
||||||
;
|
;
|
||||||
|
|
||||||
private final String reason;
|
private final String reason;
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
|
|||||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
|
||||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.flowable.bpmn.model.Activity;
|
import org.flowable.bpmn.model.*;
|
||||||
import org.flowable.bpmn.model.CallActivity;
|
|
||||||
import org.flowable.bpmn.model.FlowElement;
|
|
||||||
import org.flowable.bpmn.model.UserTask;
|
|
||||||
import org.flowable.engine.delegate.DelegateExecution;
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
|
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
|
||||||
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
|
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
|
||||||
@@ -85,6 +82,12 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) {
|
protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) {
|
||||||
|
// 参见 https://t.zsxq.com/53Meo 情况
|
||||||
|
if (execution.getCurrentFlowElement() instanceof CallActivity
|
||||||
|
|| execution.getCurrentFlowElement() instanceof SubProcess) {
|
||||||
|
super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F
|
// 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F
|
||||||
super.collectionExpression = null;
|
super.collectionExpression = null;
|
||||||
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
|
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
|
||||||
|
|||||||
@@ -852,7 +852,7 @@ public class BpmnModelUtils {
|
|||||||
} else if (flowNode instanceof ScriptTask) {
|
} else if (flowNode instanceof ScriptTask) {
|
||||||
skipExpression = ((ScriptTask) flowNode).getSkipExpression();
|
skipExpression = ((ScriptTask) flowNode).getSkipExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StrUtil.isEmpty(skipExpression)) {
|
if (StrUtil.isEmpty(skipExpression)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -908,6 +908,49 @@ public class BpmnModelUtils {
|
|||||||
return nextFlowNodes;
|
return nextFlowNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找起始节点下一个用户任务列表列表
|
||||||
|
*
|
||||||
|
* @param source 起始节点
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public static List<UserTask> getNextUserTasks(FlowElement source) {
|
||||||
|
return getNextUserTasks(source, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找起始节点下一个用户任务列表列表
|
||||||
|
* @param source 起始节点
|
||||||
|
* @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复
|
||||||
|
* @param userTaskList 用户任务列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public static List<UserTask> getNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
|
||||||
|
hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
|
||||||
|
userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
|
||||||
|
// 获取出口连线
|
||||||
|
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
|
||||||
|
if (!sequenceFlows.isEmpty()) {
|
||||||
|
for (SequenceFlow sequenceFlow : sequenceFlows) {
|
||||||
|
// 如果发现连线重复,说明循环了,跳过这个循环
|
||||||
|
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 添加已经走过的连线
|
||||||
|
hasSequenceFlow.add(sequenceFlow.getId());
|
||||||
|
FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
|
||||||
|
if (targetFlowElement instanceof UserTask) {
|
||||||
|
// 若节点为用户任务,加入到结果列表中
|
||||||
|
userTaskList.add((UserTask) targetFlowElement);
|
||||||
|
} else {
|
||||||
|
// 若节点非用户任务,继续递归查找下一个节点
|
||||||
|
getNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userTaskList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理排它网关
|
* 处理排它网关
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ import java.util.*;
|
|||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
|
||||||
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
|
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
|
||||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
|
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
|
||||||
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
|
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
|
||||||
@@ -221,11 +220,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
|
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
|
||||||
processDefinitionInfo,
|
processDefinitionInfo,
|
||||||
processVariables, activities);
|
processVariables, activities);
|
||||||
// 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点
|
|
||||||
if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
|
|
||||||
simulateActivityNodes.removeIf(node ->
|
|
||||||
BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 拼接最终数据
|
// 4. 拼接最终数据
|
||||||
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
|
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
|
||||||
@@ -415,7 +409,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
endActivities.forEach(activity -> {
|
endActivities.forEach(activity -> {
|
||||||
// StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点
|
// StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点
|
||||||
if (ELEMENT_EVENT_START.equals(activity.getActivityType())
|
if (ELEMENT_EVENT_START.equals(activity.getActivityType())
|
||||||
&& BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) {
|
&& BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())
|
||||||
|
&& !CollUtil.contains(activities, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
|
||||||
|
historicActivity -> historicActivity.getActivityId().equals(START_USER_NODE_ID))) {
|
||||||
ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)
|
ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)
|
||||||
.setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());
|
.setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());
|
||||||
ActivityNode startNode = new ActivityNode().setId(startTask.getId())
|
ActivityNode startNode = new ActivityNode().setId(startTask.getId())
|
||||||
@@ -555,7 +551,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
// 情况一:BPMN 设计器
|
// 情况一:BPMN 设计器
|
||||||
if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {
|
if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {
|
||||||
List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
|
List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
|
||||||
return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel,
|
return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(
|
||||||
|
startUserId, bpmnModel, flowElements,
|
||||||
processDefinitionInfo, processVariables, flowElement, runActivityIds));
|
processDefinitionInfo, processVariables, flowElement, runActivityIds));
|
||||||
}
|
}
|
||||||
// 情况二:SIMPLE 设计器
|
// 情况二:SIMPLE 设计器
|
||||||
@@ -563,7 +560,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(),
|
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(),
|
||||||
BpmSimpleModelNodeVO.class);
|
BpmSimpleModelNodeVO.class);
|
||||||
List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
|
List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
|
||||||
return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel,
|
return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(
|
||||||
|
startUserId, bpmnModel,
|
||||||
processDefinitionInfo, processVariables, simpleNode, runActivityIds));
|
processDefinitionInfo, processVariables, simpleNode, runActivityIds));
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
|
throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
|
||||||
@@ -618,8 +616,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,
|
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements,
|
||||||
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
|
BpmProcessDefinitionInfoDO processDefinitionInfo,
|
||||||
|
Map<String, Object> processVariables,
|
||||||
FlowElement node, Set<String> runActivityIds) {
|
FlowElement node, Set<String> runActivityIds) {
|
||||||
if (runActivityIds.contains(node.getId())) {
|
if (runActivityIds.contains(node.getId())) {
|
||||||
return null;
|
return null;
|
||||||
@@ -634,6 +633,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||||||
|
|
||||||
// 1. 开始节点
|
// 1. 开始节点
|
||||||
if (node instanceof StartEvent) {
|
if (node instanceof StartEvent) {
|
||||||
|
if (CollUtil.contains(flowElements, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
|
||||||
|
flowElement -> flowElement.getId().equals(START_USER_NODE_ID))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName())
|
return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName())
|
||||||
.setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType());
|
.setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,14 @@ public interface BpmTaskService {
|
|||||||
*/
|
*/
|
||||||
void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
|
void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回任务
|
||||||
|
*
|
||||||
|
* @param userId 用户编号
|
||||||
|
* @param taskId 任务编号
|
||||||
|
*/
|
||||||
|
void withdrawTask(Long userId, String taskId);
|
||||||
|
|
||||||
// ========== Event 事件相关方法 ==========
|
// ========== Event 事件相关方法 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
/**
|
/**
|
||||||
* 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务
|
* 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务
|
||||||
*
|
*
|
||||||
* @param userId 用户编号
|
* @param userId 用户编号
|
||||||
* @param processInstanceId 流程编号
|
* @param processInstanceId 流程编号
|
||||||
* @return 任务
|
* @return 任务
|
||||||
*/
|
*/
|
||||||
@@ -599,15 +599,15 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验选择的下一个节点的审批人,是否合法
|
* 校验选择的下一个节点的审批人,是否合法
|
||||||
*
|
* <p>
|
||||||
* 1. 是否有漏选:没有选择审批人
|
* 1. 是否有漏选:没有选择审批人
|
||||||
* 2. 是否有多选:非下一个节点
|
* 2. 是否有多选:非下一个节点
|
||||||
*
|
*
|
||||||
* @param taskDefinitionKey 当前任务节点标识
|
* @param taskDefinitionKey 当前任务节点标识
|
||||||
* @param variables 流程变量
|
* @param variables 流程变量
|
||||||
* @param bpmnModel 流程模型
|
* @param bpmnModel 流程模型
|
||||||
* @param nextAssignees 下一个节点审批人集合(参数)
|
* @param nextAssignees 下一个节点审批人集合(参数)
|
||||||
* @param processInstance 流程实例
|
* @param processInstance 流程实例
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
|
private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
|
||||||
@@ -659,7 +659,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
approveUserSelectAssignees = new HashMap<>();
|
approveUserSelectAssignees = new HashMap<>();
|
||||||
}
|
}
|
||||||
approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
|
approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
|
||||||
Map<String,List<Long>> existingApproveUserSelectAssignees = (Map<String,List<Long>>) variables.get(
|
Map<String, List<Long>> existingApproveUserSelectAssignees = (Map<String, List<Long>>) variables.get(
|
||||||
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
|
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES);
|
||||||
if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) {
|
if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) {
|
||||||
approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees);
|
approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees);
|
||||||
@@ -1177,6 +1177,63 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
|
processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void withdrawTask(Long userId, String taskId) {
|
||||||
|
// 1.1 查询本人已办任务
|
||||||
|
HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery()
|
||||||
|
.taskId(taskId).taskAssignee(userId.toString()).finished().singleResult();
|
||||||
|
if (ObjUtil.isNull(taskInstance)) {
|
||||||
|
throw exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
// 1.2 校验流程是否结束
|
||||||
|
ProcessInstance processInstance = processInstanceService.getProcessInstance(taskInstance.getProcessInstanceId());
|
||||||
|
if (ObjUtil.isNull(processInstance)) {
|
||||||
|
throw exception(TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING);
|
||||||
|
}
|
||||||
|
// 1.3 判断此流程是否允许撤回
|
||||||
|
BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(
|
||||||
|
processInstance.getProcessDefinitionId());
|
||||||
|
if (ObjUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) {
|
||||||
|
throw exception(TASK_WITHDRAW_FAIL_NOT_ALLOW);
|
||||||
|
}
|
||||||
|
// 1.4 判断下一个节点是否被审批过,如果是则无法撤回
|
||||||
|
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskInstance.getProcessDefinitionId());
|
||||||
|
UserTask userTask = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, taskInstance.getTaskDefinitionKey());
|
||||||
|
List<String> nextUserTaskKeys = convertList(BpmnModelUtils.getNextUserTasks(userTask), UserTask::getId);
|
||||||
|
if (CollUtil.isEmpty(nextUserTaskKeys)) {
|
||||||
|
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
|
||||||
|
}
|
||||||
|
// TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以;包括 todo 和 done 那边的查询哇??? 是的!
|
||||||
|
long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery()
|
||||||
|
.processInstanceId(processInstance.getProcessInstanceId()).taskDefinitionKeys(nextUserTaskKeys)
|
||||||
|
.taskCreatedAfter(taskInstance.getEndTime()).finished().count();
|
||||||
|
if (nextUserTaskFinishedCount > 0) {
|
||||||
|
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
|
||||||
|
}
|
||||||
|
// 1.5 获取需要撤回的运行任务
|
||||||
|
List<Task> runningTasks = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId())
|
||||||
|
.taskDefinitionKeys(nextUserTaskKeys).active().list();
|
||||||
|
if (CollUtil.isEmpty(runningTasks)) {
|
||||||
|
throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.1 取消当前任务
|
||||||
|
List<String> withdrawExecutionIds = new ArrayList<>();
|
||||||
|
for (Task task : runningTasks) {
|
||||||
|
// 标记撤回任务为取消
|
||||||
|
taskService.addComment(task.getId(), taskInstance.getProcessInstanceId(), BpmCommentTypeEnum.CANCEL.getType(),
|
||||||
|
BpmCommentTypeEnum.CANCEL.formatComment("前一节点撤回"));
|
||||||
|
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_WITHDRAW.getReason());
|
||||||
|
withdrawExecutionIds.add(task.getExecutionId());
|
||||||
|
}
|
||||||
|
// 2.2 执行撤回操作
|
||||||
|
runtimeService.createChangeActivityStateBuilder()
|
||||||
|
.processInstanceId(processInstance.getProcessInstanceId())
|
||||||
|
.moveExecutionsToSingleActivityId(withdrawExecutionIds, taskInstance.getTaskDefinitionKey())
|
||||||
|
.changeState();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验任务是否能被减签
|
* 校验任务是否能被减签
|
||||||
*
|
*
|
||||||
@@ -1223,7 +1280,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 任务前置通知
|
// 2. 任务前置通知
|
||||||
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){
|
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) {
|
||||||
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
|
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
|
||||||
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
|
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
|
||||||
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
|
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
|
||||||
@@ -1350,7 +1407,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
.taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus())
|
.taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus())
|
||||||
.finished();
|
.finished();
|
||||||
if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType())
|
if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType())
|
||||||
&& sameAssigneeQuery.count() > 0) {
|
&& sameAssigneeQuery.count() > 0) {
|
||||||
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
||||||
.setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName()));
|
.setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName()));
|
||||||
return;
|
return;
|
||||||
@@ -1362,7 +1419,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点
|
List<String> sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点
|
||||||
BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
|
BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())),
|
||||||
SequenceFlow::getSourceRef);
|
SequenceFlow::getSourceRef);
|
||||||
if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) {
|
if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) {
|
||||||
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
||||||
@@ -1387,7 +1444,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
|
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
|
||||||
if (userTaskElement.getId().equals(START_USER_NODE_ID)
|
if (userTaskElement.getId().equals(START_USER_NODE_ID)
|
||||||
&& (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
|
&& (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
|
||||||
|| BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
|
|| BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
|
||||||
&& ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
|
&& ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
|
||||||
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
||||||
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
|
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
|
||||||
@@ -1456,7 +1513,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 任务后置通知
|
// 任务后置通知
|
||||||
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){
|
if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())) {
|
||||||
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting();
|
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting();
|
||||||
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
|
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
|
||||||
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
|
setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public interface LogRecordConstants {
|
|||||||
String CRM_CLUE_CREATE_SUB_TYPE = "创建线索";
|
String CRM_CLUE_CREATE_SUB_TYPE = "创建线索";
|
||||||
String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
|
String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
|
||||||
String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索";
|
String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索";
|
||||||
String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
|
String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReqVO}}";
|
||||||
String CRM_CLUE_DELETE_SUB_TYPE = "删除线索";
|
String CRM_CLUE_DELETE_SUB_TYPE = "删除线索";
|
||||||
String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
|
String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
|
||||||
String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索";
|
String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索";
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class CrmClueServiceImpl implements CrmClueService {
|
|||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class));
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmClueSaveReqVO.class));
|
||||||
LogRecordContext.putVariable("clueName", oldClue.getName());
|
LogRecordContext.putVariable("clueName", oldClue.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,10 +159,10 @@ public class CrmContactServiceImpl implements CrmContactService {
|
|||||||
// 2. 删除联系人
|
// 2. 删除联系人
|
||||||
contactMapper.deleteById(id);
|
contactMapper.deleteById(id);
|
||||||
|
|
||||||
// 4.1 删除数据权限
|
// 4.1 删除商机关联
|
||||||
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
|
|
||||||
// 4.2 删除商机关联
|
|
||||||
contactBusinessService.deleteContactBusinessByContactId(id);
|
contactBusinessService.deleteContactBusinessByContactId(id);
|
||||||
|
// 4.2 删除数据权限
|
||||||
|
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
|
||||||
|
|
||||||
// 记录操作日志上下文
|
// 记录操作日志上下文
|
||||||
LogRecordContext.putVariable("contactName", contact.getName());
|
LogRecordContext.putVariable("contactName", contact.getName());
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class FileController {
|
|||||||
|
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
@Operation(summary = "上传文件", description = "模式一:后端上传文件")
|
@Operation(summary = "上传文件", description = "模式一:后端上传文件")
|
||||||
public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
|
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
|
||||||
MultipartFile file = uploadReqVO.getFile();
|
MultipartFile file = uploadReqVO.getFile();
|
||||||
byte[] content = IoUtil.readBytes(file.getInputStream());
|
byte[] content = IoUtil.readBytes(file.getInputStream());
|
||||||
return success(fileService.createFile(content, file.getOriginalFilename(),
|
return success(fileService.createFile(content, file.getOriginalFilename(),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package ${basePackage}.module.${table.moduleName}.service.${table.businessName};
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import ${jakartaPackage}.annotation.Resource;
|
import ${jakartaPackage}.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,7 @@
|
|||||||
await this.#[[$modal]]#.confirm('是否确认删除?')
|
await this.#[[$modal]]#.confirm('是否确认删除?')
|
||||||
try {
|
try {
|
||||||
await ${simpleClassName}Api.delete${subSimpleClassName}List(this.checkedIds);
|
await ${simpleClassName}Api.delete${subSimpleClassName}List(this.checkedIds);
|
||||||
|
this.checkedIds = [];
|
||||||
await this.getList();
|
await this.getList();
|
||||||
this.#[[$modal]]#.msgSuccess("删除成功");
|
this.#[[$modal]]#.msgSuccess("删除成功");
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
@@ -338,6 +338,7 @@ export default {
|
|||||||
await this.#[[$modal]]#.confirm('是否确认删除?')
|
await this.#[[$modal]]#.confirm('是否确认删除?')
|
||||||
try {
|
try {
|
||||||
await ${simpleClassName}Api.delete${simpleClassName}List(this.checkedIds);
|
await ${simpleClassName}Api.delete${simpleClassName}List(this.checkedIds);
|
||||||
|
this.checkedIds = [];
|
||||||
await this.getList();
|
await this.getList();
|
||||||
this.#[[$modal]]#.msgSuccess("删除成功");
|
this.#[[$modal]]#.msgSuccess("删除成功");
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ const handleDeleteBatch = async () => {
|
|||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
await ${simpleClassName}Api.delete${subSimpleClassName}List(checkedIds.value);
|
await ${simpleClassName}Api.delete${subSimpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
await getList();
|
await getList();
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ const handleDeleteBatch = async () => {
|
|||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
await ${simpleClassName}Api.delete${simpleClassName}List(checkedIds.value);
|
await ${simpleClassName}Api.delete${simpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
await getList();
|
await getList();
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${simpleClassName}List(checkedIds.value);
|
await delete${simpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
message.success( $t('ui.actionMessage.deleteSuccess') );
|
message.success( $t('ui.actionMessage.deleteSuccess') );
|
||||||
await getList();
|
await getList();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${subSimpleClassName}List(checkedIds.value);
|
await delete${subSimpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
message.success( $t('ui.actionMessage.deleteSuccess') );
|
message.success( $t('ui.actionMessage.deleteSuccess') );
|
||||||
await getList();
|
await getList();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${simpleClassName}List(checkedIds.value);
|
await delete${simpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess'),
|
content: $t('ui.actionMessage.deleteSuccess'),
|
||||||
key: 'action_key_msg',
|
key: 'action_key_msg',
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${subSimpleClassName}List(checkedIds.value);
|
await delete${subSimpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
key: 'action_key_msg',
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${simpleClassName}List(checkedIds.value);
|
await delete${simpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
await getList();
|
await getList();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${subSimpleClassName}List(checkedIds.value);
|
await delete${subSimpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
await getList();
|
await getList();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${simpleClassName}List(checkedIds.value);
|
await delete${simpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ async function handleDeleteBatch() {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await delete${subSimpleClassName}List(checkedIds.value);
|
await delete${subSimpleClassName}List(checkedIds.value);
|
||||||
|
checkedIds.value = [];
|
||||||
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import com.baomidou.mybatisplus.generator.config.po.TableField;
|
|||||||
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -60,15 +60,15 @@ public class CodegenServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private CodegenColumnMapper codegenColumnMapper;
|
private CodegenColumnMapper codegenColumnMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private DatabaseTableService databaseTableService;
|
private DatabaseTableService databaseTableService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private CodegenBuilder codegenBuilder;
|
private CodegenBuilder codegenBuilder;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private CodegenEngine codegenEngine;
|
private CodegenEngine codegenEngine;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private CodegenProperties codegenProperties;
|
private CodegenProperties codegenProperties;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
|
|||||||
import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
|
import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
|
||||||
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
|
||||||
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||||
@@ -46,10 +46,10 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private DataSourceConfigMapper dataSourceConfigMapper;
|
private DataSourceConfigMapper dataSourceConfigMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private AES aes;
|
private AES aes;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private DynamicDataSourceProperties dynamicDataSourceProperties;
|
private DynamicDataSourceProperties dynamicDataSourceProperties;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
|
|||||||
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
||||||
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
||||||
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
|
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.ibatis.type.JdbcType;
|
import org.apache.ibatis.type.JdbcType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||||
@@ -24,7 +24,7 @@ public class DatabaseTableServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private DatabaseTableServiceImpl databaseTableService;
|
private DatabaseTableServiceImpl databaseTableService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private DataSourceConfigService dataSourceConfigService;
|
private DataSourceConfigService dataSourceConfigService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -4,24 +4,24 @@ import cn.hutool.core.date.DatePattern;
|
|||||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
|
||||||
|
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;
|
||||||
|
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
|
||||||
|
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
|
||||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
|
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
|
||||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
|
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
|
||||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientFactory;
|
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientFactory;
|
||||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClient;
|
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClient;
|
||||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig;
|
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig;
|
||||||
import cn.iocoder.yudao.module.infra.framework.file.core.enums.FileStorageEnum;
|
import cn.iocoder.yudao.module.infra.framework.file.core.enums.FileStorageEnum;
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
|
||||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
|
|
||||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;
|
|
||||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
|
|
||||||
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
|
|
||||||
import lombok.Data;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -53,9 +53,9 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private FileConfigMapper fileConfigMapper;
|
private FileConfigMapper fileConfigMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private FileClientFactory fileClientFactory;
|
private FileClientFactory fileClientFactory;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@@ -35,7 +35,7 @@ public class FileServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private FileMapper fileMapper;
|
private FileMapper fileMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private FileConfigService fileConfigService;
|
private FileConfigService fileConfigService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.job.JobDO;
|
|||||||
import cn.iocoder.yudao.module.infra.dal.mysql.job.JobMapper;
|
import cn.iocoder.yudao.module.infra.dal.mysql.job.JobMapper;
|
||||||
import cn.iocoder.yudao.module.infra.enums.job.JobStatusEnum;
|
import cn.iocoder.yudao.module.infra.enums.job.JobStatusEnum;
|
||||||
import cn.iocoder.yudao.module.infra.job.job.JobLogCleanJob;
|
import cn.iocoder.yudao.module.infra.job.job.JobLogCleanJob;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||||
@@ -36,10 +35,10 @@ public class JobServiceImplTest extends BaseDbUnitTest {
|
|||||||
private JobServiceImpl jobService;
|
private JobServiceImpl jobService;
|
||||||
@Resource
|
@Resource
|
||||||
private JobMapper jobMapper;
|
private JobMapper jobMapper;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private SchedulerManager schedulerManager;
|
private SchedulerManager schedulerManager;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private JobLogCleanJob jobLogCleanJob;
|
private JobLogCleanJob jobLogCleanJob;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -48,9 +48,9 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Lazy
|
@Lazy
|
||||||
private ProductCommentServiceImpl productCommentService;
|
private ProductCommentServiceImpl productCommentService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductSpuService productSpuService;
|
private ProductSpuService productSpuService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductSkuService productSkuService;
|
private ProductSkuService productSkuService;
|
||||||
|
|
||||||
public String generateNo() {
|
public String generateNo() {
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
|
|||||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
|
import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
|
||||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
|
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
|
||||||
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
|
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -44,11 +44,11 @@ public class ProductSkuServiceTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private ProductSkuMapper productSkuMapper;
|
private ProductSkuMapper productSkuMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductSpuService productSpuService;
|
private ProductSpuService productSpuService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductPropertyService productPropertyService;
|
private ProductPropertyService productPropertyService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductPropertyValueService productPropertyValueService;
|
private ProductPropertyValueService productPropertyValueService;
|
||||||
|
|
||||||
public Long generateId() {
|
public Long generateId() {
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import jakarta.annotation.Resource;
|
|||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -58,15 +58,15 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private ProductSpuMapper productSpuMapper;
|
private ProductSpuMapper productSpuMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductSkuServiceImpl productSkuService;
|
private ProductSkuServiceImpl productSkuService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductCategoryServiceImpl categoryService;
|
private ProductCategoryServiceImpl categoryService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductBrandServiceImpl brandService;
|
private ProductBrandServiceImpl brandService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductPropertyService productPropertyService;
|
private ProductPropertyService productPropertyService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductPropertyValueService productPropertyValueService;
|
private ProductPropertyValueService productPropertyValueService;
|
||||||
|
|
||||||
public String generateNo() {
|
public String generateNo() {
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
|
|||||||
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
|
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
|
||||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
||||||
@@ -54,15 +54,15 @@ public class AfterSaleServiceTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private AfterSaleLogMapper tradeAfterSaleLogMapper;
|
private AfterSaleLogMapper tradeAfterSaleLogMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradeOrderUpdateService tradeOrderUpdateService;
|
private TradeOrderUpdateService tradeOrderUpdateService;
|
||||||
@Resource
|
@Resource
|
||||||
private TradeOrderQueryService tradeOrderQueryService;
|
private TradeOrderQueryService tradeOrderQueryService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayRefundApi payRefundApi;
|
private PayRefundApi payRefundApi;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradeOrderProperties tradeOrderProperties;
|
private TradeOrderProperties tradeOrderProperties;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.record.Broker
|
|||||||
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
|
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
|
||||||
import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageRecordMapper;
|
import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageRecordMapper;
|
||||||
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
|
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||||
@@ -39,9 +39,9 @@ public class BrokerageRecordServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private BrokerageRecordMapper brokerageRecordMapper;
|
private BrokerageRecordMapper brokerageRecordMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradeConfigService tradeConfigService;
|
private TradeConfigService tradeConfigService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private BrokerageUserService brokerageUserService;
|
private BrokerageUserService brokerageUserService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import jakarta.annotation.Resource;
|
|||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||||
@@ -36,14 +36,14 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private BrokerageWithdrawMapper brokerageWithdrawMapper;
|
private BrokerageWithdrawMapper brokerageWithdrawMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private BrokerageRecordService brokerageRecordService;
|
private BrokerageRecordService brokerageRecordService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private BrokerageUserService brokerageUserService;
|
private BrokerageUserService brokerageUserService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradeConfigService tradeConfigService;
|
private TradeConfigService tradeConfigService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private NotifyMessageSendApi notifyMessageSendApi;
|
private NotifyMessageSendApi notifyMessageSendApi;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ import jakarta.annotation.Resource;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
@@ -67,34 +67,34 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private TradeOrderItemMapper tradeOrderItemMapper;
|
private TradeOrderItemMapper tradeOrderItemMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberUserApi memberUserApi;
|
private MemberUserApi memberUserApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductSpuApi productSpuApi;
|
private ProductSpuApi productSpuApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductSkuApi productSkuApi;
|
private ProductSkuApi productSkuApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private ProductCommentApi productCommentApi;
|
private ProductCommentApi productCommentApi;
|
||||||
// @MockBean
|
// @MockitoBean
|
||||||
// private PriceApi priceApi;
|
// private PriceApi priceApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayOrderApi payOrderApi;
|
private PayOrderApi payOrderApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberAddressApi addressApi;
|
private MemberAddressApi addressApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private CouponApi couponApi;
|
private CouponApi couponApi;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradeOrderProperties tradeOrderProperties;
|
private TradeOrderProperties tradeOrderProperties;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradeNoRedisDAO tradeNoRedisDAO;
|
private TradeNoRedisDAO tradeNoRedisDAO;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradeOrderHandler tradeOrderHandler;
|
private TradeOrderHandler tradeOrderHandler;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private TradePriceCalculator tradePriceCalculator;
|
private TradePriceCalculator tradePriceCalculator;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private NotifyMessageSendApi notifyMessageSendApi;
|
private NotifyMessageSendApi notifyMessageSendApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private DeliveryExpressService deliveryExpressService;
|
private DeliveryExpressService deliveryExpressService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.member.service.auth;
|
package cn.iocoder.yudao.module.member.service.auth;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||||
@@ -8,14 +9,13 @@ import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
|||||||
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
|
||||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||||
import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
|
import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
|
||||||
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
|
||||||
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
||||||
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
|
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||||
@@ -36,17 +36,17 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private MemberAuthServiceImpl authService;
|
private MemberAuthServiceImpl authService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberUserService userService;
|
private MemberUserService userService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private SmsCodeApi smsCodeApi;
|
private SmsCodeApi smsCodeApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private LoginLogApi loginLogApi;
|
private LoginLogApi loginLogApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private OAuth2TokenCommonApi oauth2TokenApi;
|
private OAuth2TokenCommonApi oauth2TokenApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private SocialUserApi socialUserApi;
|
private SocialUserApi socialUserApi;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PasswordEncoder passwordEncoder;
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupUpdat
|
|||||||
import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
|
import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
|
||||||
import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper;
|
import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper;
|
||||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
||||||
@@ -42,7 +41,7 @@ public class MemberGroupServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private MemberGroupMapper groupMapper;
|
private MemberGroupMapper groupMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberUserService memberUserService;
|
private MemberUserService memberUserService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLeve
|
|||||||
import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
|
import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
|
||||||
import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
|
import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
|
||||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -40,11 +40,11 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private MemberLevelMapper memberlevelMapper;
|
private MemberLevelMapper memberlevelMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberLevelRecordService memberLevelRecordService;
|
private MemberLevelRecordService memberLevelRecordService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberExperienceRecordService memberExperienceRecordService;
|
private MemberExperienceRecordService memberExperienceRecordService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberUserService memberUserService;
|
private MemberUserService memberUserService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagUpdateReq
|
|||||||
import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
|
import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
|
||||||
import cn.iocoder.yudao.module.member.dal.mysql.tag.MemberTagMapper;
|
import cn.iocoder.yudao.module.member.dal.mysql.tag.MemberTagMapper;
|
||||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
|
||||||
@@ -39,7 +38,7 @@ public class MemberTagServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private MemberTagMapper tagMapper;
|
private MemberTagMapper tagMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberUserService memberUserService;
|
private MemberUserService memberUserService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -44,13 +44,13 @@ public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private MemberUserMapper userMapper;
|
private MemberUserMapper userMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private MemberAuthServiceImpl authService;
|
private MemberAuthServiceImpl authService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PasswordEncoder passwordEncoder;
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private SmsCodeApi smsCodeApi;
|
private SmsCodeApi smsCodeApi;
|
||||||
|
|
||||||
// TODO 芋艿:后续重构这个单测
|
// TODO 芋艿:后续重构这个单测
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ public class MpMessagePageReqVO extends PageParam {
|
|||||||
@Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
|
@Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
|
||||||
private String openid;
|
private String openid;
|
||||||
|
|
||||||
|
@Schema(description = "公众号粉丝 UserId", example = "1")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
@Schema(description = "创建时间")
|
@Schema(description = "创建时间")
|
||||||
private LocalDateTime[] createTime;
|
private LocalDateTime[] createTime;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public interface MpMessageMapper extends BaseMapperX<MpMessageDO> {
|
|||||||
.eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
|
.eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())
|
||||||
.eqIfPresent(MpMessageDO::getType, reqVO.getType())
|
.eqIfPresent(MpMessageDO::getType, reqVO.getType())
|
||||||
.eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid())
|
.eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid())
|
||||||
|
.eqIfPresent(MpMessageDO::getUserId, reqVO.getUserId())
|
||||||
.betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime())
|
.betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime())
|
||||||
.orderByDesc(MpMessageDO::getId));
|
.orderByDesc(MpMessageDO::getId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
|||||||
} else if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
|
} else if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
|
||||||
// 由于 rsaCertCheckV1 的第二个参数是 path,所以不能这么调用!!!通过阅读源码,发现可以采用如下方式!
|
// 由于 rsaCertCheckV1 的第二个参数是 path,所以不能这么调用!!!通过阅读源码,发现可以采用如下方式!
|
||||||
X509Certificate cert = AntCertificationUtil.getCertFromContent(config.getAlipayPublicCertContent());
|
X509Certificate cert = AntCertificationUtil.getCertFromContent(config.getAlipayPublicCertContent());
|
||||||
String publicKey = Base64.encodeBase64String(cert.getEncoded());
|
String publicKey = Base64.encodeBase64String(cert.getPublicKey().getEncoded());
|
||||||
verify = AlipaySignature.rsaCheckV1(params, publicKey,
|
verify = AlipaySignature.rsaCheckV1(params, publicKey,
|
||||||
StandardCharsets.UTF_8.name(), config.getSignType());
|
StandardCharsets.UTF_8.name(), config.getSignType());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -585,7 +585,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId());
|
||||||
return true;
|
return true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);
|
log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
|||||||
import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper;
|
import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper;
|
||||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||||
@@ -46,9 +46,9 @@ public class PayAppServiceTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private PayAppMapper appMapper;
|
private PayAppMapper appMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayOrderService orderService;
|
private PayOrderService orderService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayRefundService refundService;
|
private PayRefundService refundService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -2,23 +2,23 @@ package cn.iocoder.yudao.module.pay.service.channel;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig;
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
|
||||||
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
|
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
|
||||||
import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
|
import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
|
||||||
|
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -28,7 +28,8 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
|||||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
|
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@Import({PayChannelServiceImpl.class})
|
@Import({PayChannelServiceImpl.class})
|
||||||
public class PayChannelServiceTest extends BaseDbUnitTest {
|
public class PayChannelServiceTest extends BaseDbUnitTest {
|
||||||
@@ -39,9 +40,9 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private PayChannelMapper channelMapper;
|
private PayChannelMapper channelMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayClientFactory payClientFactory;
|
private PayClientFactory payClientFactory;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ import cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration;
|
|||||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundServiceImpl;
|
import cn.iocoder.yudao.module.pay.service.refund.PayRefundServiceImpl;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.redisson.api.RLock;
|
import org.redisson.api.RLock;
|
||||||
import org.redisson.api.RedissonClient;
|
import org.redisson.api.RedissonClient;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -54,9 +54,9 @@ public class PayNotifyServiceTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private PayNotifyServiceImpl notifyService;
|
private PayNotifyServiceImpl notifyService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayOrderService orderService;
|
private PayOrderService orderService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayRefundService refundService;
|
private PayRefundService refundService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@@ -64,7 +64,7 @@ public class PayNotifyServiceTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private PayNotifyLogMapper notifyLogMapper;
|
private PayNotifyLogMapper notifyLogMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private RedissonClient redissonClient;
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ package cn.iocoder.yudao.module.pay.service.order;
|
|||||||
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum;
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
||||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||||
@@ -19,9 +15,13 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
|
|||||||
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
|
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
|
||||||
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
|
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
|
||||||
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
|
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
|
||||||
|
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
||||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum;
|
||||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||||
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
||||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||||
@@ -29,8 +29,8 @@ import jakarta.annotation.Resource;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -65,13 +65,13 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private PayOrderExtensionMapper orderExtensionMapper;
|
private PayOrderExtensionMapper orderExtensionMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayProperties properties;
|
private PayProperties properties;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayAppService appService;
|
private PayAppService appService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayChannelService channelService;
|
private PayChannelService channelService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayNotifyService notifyService;
|
private PayNotifyService notifyService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ package cn.iocoder.yudao.module.pay.service.refund;
|
|||||||
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
|
||||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
||||||
@@ -16,21 +12,25 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
|||||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||||
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
|
import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
|
||||||
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
|
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
|
||||||
|
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
|
||||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
|
||||||
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
||||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||||
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
|
||||||
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
|
||||||
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
|
||||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.MockedStatic;
|
import org.mockito.MockedStatic;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||||
@@ -62,15 +62,15 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private PayRefundMapper refundMapper;
|
private PayRefundMapper refundMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayProperties payProperties;
|
private PayProperties payProperties;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayOrderService orderService;
|
private PayOrderService orderService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayAppService appService;
|
private PayAppService appService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayChannelService channelService;
|
private PayChannelService channelService;
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private PayNotifyService notifyService;
|
private PayNotifyService notifyService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ package cn.iocoder.yudao.module.report.service.goview;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||||
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
|
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.support.rowset.SqlRowSet;
|
import org.springframework.jdbc.support.rowset.SqlRowSet;
|
||||||
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
|
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -23,7 +23,7 @@ public class GoViewDataServiceImplTest extends BaseDbUnitTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private GoViewDataServiceImpl goViewDataService;
|
private GoViewDataServiceImpl goViewDataService;
|
||||||
|
|
||||||
@MockBean
|
@MockitoBean
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -21,13 +21,15 @@ public class MailSendApiImpl implements MailSendApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) {
|
public Long sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) {
|
||||||
return mailSendService.sendSingleMailToAdmin(reqDTO.getMail(), reqDTO.getUserId(),
|
return mailSendService.sendSingleMailToAdmin(reqDTO.getUserId(),
|
||||||
|
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
|
||||||
reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
|
reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) {
|
public Long sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) {
|
||||||
return mailSendService.sendSingleMailToMember(reqDTO.getMail(), reqDTO.getUserId(),
|
return mailSendService.sendSingleMailToMember(reqDTO.getUserId(),
|
||||||
|
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
|
||||||
reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
|
reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import lombok.Data;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.Email;
|
import jakarta.validation.constraints.Email;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,13 +18,24 @@ public class MailSendSingleToUserReqDTO {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户编号
|
* 用户编号
|
||||||
|
*
|
||||||
|
* 如果非空,则加载对应用户的邮箱,添加到 {@link #toMails} 中
|
||||||
*/
|
*/
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮箱
|
* 收件邮箱
|
||||||
*/
|
*/
|
||||||
@Email
|
private List<@Email String> toMails;
|
||||||
private String mail;
|
/**
|
||||||
|
* 抄送邮箱
|
||||||
|
*/
|
||||||
|
private List<@Email String> ccMails;
|
||||||
|
/**
|
||||||
|
* 密送邮箱
|
||||||
|
*/
|
||||||
|
private List<@Email String> bccMails;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮件模板编号
|
* 邮件模板编号
|
||||||
|
|||||||
@@ -11,6 +11,24 @@ tag: Yunai.local
|
|||||||
"code": "1024"
|
"code": "1024"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### 请求 /login 接口【加密 AES】 => 成功
|
||||||
|
POST {{baseUrl}}/system/auth/login
|
||||||
|
Content-Type: application/json
|
||||||
|
tenant-id: {{adminTenantId}}
|
||||||
|
tag: Yunai.local
|
||||||
|
X-API-ENCRYPT: true
|
||||||
|
|
||||||
|
WvSX9MOrenyGfBhEM0g1/hHgq8ocktMZ9OwAJ6MOG5FUrzYF/rG5JF1eMptQM1wT73VgDS05l/37WeRtad+JrqChAul/sR/SdOsUKqjBhvvQx1JVhzxr6s8uUP67aKTSZ6Psv7O32ELxXrzSaQvG5CInzz3w6sLtbNNLd1kXe6Q=
|
||||||
|
|
||||||
|
### 请求 /login 接口【加密 RSA】 => 成功
|
||||||
|
POST {{baseUrl}}/system/auth/login
|
||||||
|
Content-Type: application/json
|
||||||
|
tenant-id: {{adminTenantId}}
|
||||||
|
tag: Yunai.local
|
||||||
|
X-API-ENCRYPT: true
|
||||||
|
|
||||||
|
e7QZTork9ZV5CmgZvSd+cHZk3xdUxKtowLM02kOha+gxHK2H/daU8nVBYS3+bwuDRy5abf+Pz1QJJGVAEd27wwrXBmupOOA/bhpuzzDwcRuJRD+z+YgiNoEXFDRHERxPYlPqAe9zAHtihD0ceub1AjybQsEsROew4C3Q602XYW0=
|
||||||
|
|
||||||
### 请求 /login 接口 => 成功(无验证码)
|
### 请求 /login 接口 => 成功(无验证码)
|
||||||
POST {{baseUrl}}/system/auth/login
|
POST {{baseUrl}}/system/auth/login
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|||||||
@@ -56,6 +56,15 @@ public class DeptController {
|
|||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete-list")
|
||||||
|
@Operation(summary = "批量删除部门")
|
||||||
|
@Parameter(name = "ids", description = "编号列表", required = true)
|
||||||
|
@PreAuthorize("@ss.hasPermission('system:dept:delete')")
|
||||||
|
public CommonResult<Boolean> deleteDeptList(@RequestParam("ids") List<Long> ids) {
|
||||||
|
deptService.deleteDeptList(ids);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "获取部门列表")
|
@Operation(summary = "获取部门列表")
|
||||||
@PreAuthorize("@ss.hasPermission('system:dept:query')")
|
@PreAuthorize("@ss.hasPermission('system:dept:query')")
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ public class MailTemplateController {
|
|||||||
@Operation(summary = "发送短信")
|
@Operation(summary = "发送短信")
|
||||||
@PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')")
|
@PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')")
|
||||||
public CommonResult<Long> sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) {
|
public CommonResult<Long> sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) {
|
||||||
return success(mailSendService.sendSingleMailToAdmin(sendReqVO.getMail(), getLoginUserId(),
|
return success(mailSendService.sendSingleMailToAdmin(getLoginUserId(),
|
||||||
|
sendReqVO.getToMails(), sendReqVO.getCcMails(), sendReqVO.getBccMails(),
|
||||||
sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
|
sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ package cn.iocoder.yudao.module.system.controller.admin.mail.vo.log;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
|
||||||
|
|
||||||
@Schema(description = "管理后台 - 邮件日志 Response VO")
|
@Schema(description = "管理后台 - 邮件日志 Response VO")
|
||||||
@Data
|
@Data
|
||||||
public class MailLogRespVO {
|
public class MailLogRespVO {
|
||||||
@@ -22,8 +20,14 @@ public class MailLogRespVO {
|
|||||||
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", example = "2")
|
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", example = "2")
|
||||||
private Byte userType;
|
private Byte userType;
|
||||||
|
|
||||||
@Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "76854@qq.com")
|
@Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user1@example.com, user2@example.com")
|
||||||
private String toMail;
|
private List<String> toMails;
|
||||||
|
|
||||||
|
@Schema(description = "抄送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user3@example.com, user4@example.com")
|
||||||
|
private List<String> ccMails;
|
||||||
|
|
||||||
|
@Schema(description = "密送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user5@example.com, user6@example.com")
|
||||||
|
private List<String> bccMails;
|
||||||
|
|
||||||
@Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18107")
|
@Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18107")
|
||||||
private Long accountId;
|
private Long accountId;
|
||||||
|
|||||||
@@ -5,15 +5,22 @@ import lombok.Data;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Schema(description = "管理后台 - 邮件发送 Req VO")
|
@Schema(description = "管理后台 - 邮件发送 Req VO")
|
||||||
@Data
|
@Data
|
||||||
public class MailTemplateSendReqVO {
|
public class MailTemplateSendReqVO {
|
||||||
|
|
||||||
@Schema(description = "接收邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "7685413@qq.com")
|
@Schema(description = "接收邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user1@example.com, user2@example.com]")
|
||||||
@NotEmpty(message = "接收邮箱不能为空")
|
@NotEmpty(message = "接收邮箱不能为空")
|
||||||
private String mail;
|
private List<String> toMails;
|
||||||
|
|
||||||
|
@Schema(description = "抄送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user3@example.com, user4@example.com]")
|
||||||
|
private List<String> ccMails;
|
||||||
|
|
||||||
|
@Schema(description = "密送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user5@example.com, user6@example.com]")
|
||||||
|
private List<String> bccMails;
|
||||||
|
|
||||||
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
|
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
|
||||||
@NotNull(message = "模板编码不能为空")
|
@NotNull(message = "模板编码不能为空")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.mail;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||||
import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
|
import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
@@ -12,6 +13,7 @@ import lombok.*;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,10 +49,22 @@ public class MailLogDO extends BaseDO implements Serializable {
|
|||||||
* 枚举 {@link UserTypeEnum}
|
* 枚举 {@link UserTypeEnum}
|
||||||
*/
|
*/
|
||||||
private Integer userType;
|
private Integer userType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收邮箱地址
|
* 接收邮箱地址
|
||||||
*/
|
*/
|
||||||
private String toMail;
|
@TableField(typeHandler = StringListTypeHandler.class)
|
||||||
|
private List<String> toMails;
|
||||||
|
/**
|
||||||
|
* 接收邮箱地址
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = StringListTypeHandler.class)
|
||||||
|
private List<String> ccMails;
|
||||||
|
/**
|
||||||
|
* 密送邮箱地址
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = StringListTypeHandler.class)
|
||||||
|
private List<String> bccMails;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮箱账号编号
|
* 邮箱账号编号
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package cn.iocoder.yudao.module.system.dal.mysql.mail;
|
package cn.iocoder.yudao.module.system.dal.mysql.mail;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
@@ -14,11 +16,12 @@ public interface MailLogMapper extends BaseMapperX<MailLogDO> {
|
|||||||
return selectPage(reqVO, new LambdaQueryWrapperX<MailLogDO>()
|
return selectPage(reqVO, new LambdaQueryWrapperX<MailLogDO>()
|
||||||
.eqIfPresent(MailLogDO::getUserId, reqVO.getUserId())
|
.eqIfPresent(MailLogDO::getUserId, reqVO.getUserId())
|
||||||
.eqIfPresent(MailLogDO::getUserType, reqVO.getUserType())
|
.eqIfPresent(MailLogDO::getUserType, reqVO.getUserType())
|
||||||
.likeIfPresent(MailLogDO::getToMail, reqVO.getToMail())
|
|
||||||
.eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId())
|
.eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId())
|
||||||
.eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId())
|
.eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId())
|
||||||
.eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus())
|
.eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus())
|
||||||
.betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime())
|
.betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime())
|
||||||
|
.apply(StrUtil.isNotBlank(reqVO.getToMail()),
|
||||||
|
MyBatisUtils.findInSet("to_mails", reqVO.getToMail()))
|
||||||
.orderByDesc(MailLogDO::getId));
|
.orderByDesc(MailLogDO::getId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package cn.iocoder.yudao.module.system.framework.captcha.core;
|
||||||
|
|
||||||
|
import com.anji.captcha.model.common.RepCodeEnum;
|
||||||
|
import com.anji.captcha.model.common.ResponseModel;
|
||||||
|
import com.anji.captcha.model.vo.CaptchaVO;
|
||||||
|
import com.anji.captcha.service.impl.AbstractCaptchaService;
|
||||||
|
import com.anji.captcha.service.impl.CaptchaServiceFactory;
|
||||||
|
import com.anji.captcha.util.AESUtil;
|
||||||
|
import com.anji.captcha.util.ImageUtils;
|
||||||
|
import com.anji.captcha.util.RandomUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片文字验证码
|
||||||
|
*
|
||||||
|
* @author Tsui
|
||||||
|
* @since 2025/7/23 20:44
|
||||||
|
*/
|
||||||
|
public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码的基础字符
|
||||||
|
*/
|
||||||
|
private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
|
/**
|
||||||
|
* 验证码长度
|
||||||
|
*/
|
||||||
|
private static final Integer LENGTH = 4;
|
||||||
|
|
||||||
|
private static final int WIDTH = 120;
|
||||||
|
private static final int HEIGHT = 40;
|
||||||
|
private static final int LINES = 10;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Properties config) {
|
||||||
|
super.init(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy(Properties config) {
|
||||||
|
logger.info("start-clear-history-data-{}", captchaType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String captchaType() {
|
||||||
|
return "pictureWord";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseModel get(CaptchaVO captchaVO) {
|
||||||
|
String text = generateRandomText(LENGTH);
|
||||||
|
CaptchaVO imageData = getImageData(text);
|
||||||
|
// pointJson 不传到前端,只做后端校验,测试时放开
|
||||||
|
// imageData.setPointJson(text);
|
||||||
|
return ResponseModel.successData(imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseModel check(CaptchaVO captchaVO) {
|
||||||
|
ResponseModel r = super.check(captchaVO);
|
||||||
|
if (!validatedReq(r)) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取出验证码
|
||||||
|
String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
|
||||||
|
if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
|
||||||
|
return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
|
||||||
|
}
|
||||||
|
// 正确的验证码
|
||||||
|
String codeValue = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
|
||||||
|
String code = getCodeByCodeValue(codeValue);
|
||||||
|
String secretKey = getSecretKeyByCodeValue(codeValue);
|
||||||
|
// 验证码只用一次,即刻失效
|
||||||
|
CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
|
||||||
|
|
||||||
|
// 用户输入的验证码(CaptchaVO 中 没有预留字段,暂时用 pointJson 无需加解密)
|
||||||
|
String userCode = captchaVO.getPointJson();
|
||||||
|
if (!StringUtils.equalsIgnoreCase(code, userCode)) {
|
||||||
|
afterValidateFail(captchaVO);
|
||||||
|
return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验成功,将信息存入缓存
|
||||||
|
String value;
|
||||||
|
try {
|
||||||
|
value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(userCode), secretKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("AES 加密失败", e);
|
||||||
|
afterValidateFail(captchaVO);
|
||||||
|
return ResponseModel.errorMsg(e.getMessage());
|
||||||
|
}
|
||||||
|
String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
|
||||||
|
CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
|
||||||
|
captchaVO.setResult(true);
|
||||||
|
captchaVO.resetClientFlag();
|
||||||
|
return ResponseModel.successData(captchaVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseModel verification(CaptchaVO captchaVO) {
|
||||||
|
ResponseModel r = super.verification(captchaVO);
|
||||||
|
if (!validatedReq(r)) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
|
||||||
|
if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
|
||||||
|
return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
|
||||||
|
}
|
||||||
|
// 二次校验取值后,即刻失效
|
||||||
|
CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("验证码解析失败", e);
|
||||||
|
return ResponseModel.errorMsg(e.getMessage());
|
||||||
|
}
|
||||||
|
return ResponseModel.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private CaptchaVO getImageData(String text) {
|
||||||
|
CaptchaVO dataVO = new CaptchaVO();
|
||||||
|
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D g = image.createGraphics();
|
||||||
|
|
||||||
|
// 设置背景色
|
||||||
|
g.setColor(getRandomColor(200, 250));
|
||||||
|
g.fillRect(0, 0, WIDTH, HEIGHT);
|
||||||
|
// 绘制干扰线
|
||||||
|
for (int i = 0; i < LINES; i++) {
|
||||||
|
g.setColor(getRandomColor(100, 200));
|
||||||
|
int x1 = RandomUtil.randomInt(WIDTH);
|
||||||
|
int y1 = RandomUtil.randomInt(HEIGHT);
|
||||||
|
int x2 = RandomUtil.randomInt(WIDTH);
|
||||||
|
int y2 = RandomUtil.randomInt(HEIGHT);
|
||||||
|
g.drawLine(x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
// 设置字体
|
||||||
|
g.setFont(new Font("Arial", Font.BOLD, 24));
|
||||||
|
// 绘制验证码文本
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
g.setColor(getRandomColor(20, 130));
|
||||||
|
// 文字旋转
|
||||||
|
AffineTransform affineTransform = new AffineTransform();
|
||||||
|
int x = 20 + i * 20;
|
||||||
|
int y = 24 + RandomUtil.randomInt(8);
|
||||||
|
// 旋转范围 -45 ~ 45
|
||||||
|
affineTransform.setToRotation(Math.toRadians(RandomUtil.randomInt(-45, 45)), x, y);
|
||||||
|
g.setTransform(affineTransform);
|
||||||
|
g.drawString(text.charAt(i) + "", x, y);
|
||||||
|
}
|
||||||
|
// 添加噪点
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
int x = RandomUtil.randomInt(WIDTH);
|
||||||
|
int y = RandomUtil.randomInt(HEIGHT);
|
||||||
|
image.setRGB(x, y, getRandomColor(0, 255).getRGB());
|
||||||
|
}
|
||||||
|
g.dispose();
|
||||||
|
|
||||||
|
String secretKey = null;
|
||||||
|
if (captchaAesStatus) {
|
||||||
|
secretKey = AESUtil.getKey();
|
||||||
|
}
|
||||||
|
dataVO.setSecretKey(secretKey);
|
||||||
|
|
||||||
|
dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(image).replaceAll("\r|\n", ""));
|
||||||
|
dataVO.setToken(RandomUtils.getUUID());
|
||||||
|
// dataVO.setSecretKey(secretKey);
|
||||||
|
// 将坐标信息存入 redis 中
|
||||||
|
String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
|
||||||
|
CaptchaServiceFactory.getCache(cacheType).set(codeKey, getCodeValue(text, secretKey), EXPIRESIN_SECONDS);
|
||||||
|
return dataVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCodeValue(String text, String secretKey) {
|
||||||
|
return text + "," + secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCodeByCodeValue(String codeValue) {
|
||||||
|
return codeValue.split(",")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSecretKeyByCodeValue(String codeValue) {
|
||||||
|
return codeValue.split(",")[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getRandomColor(int min, int max) {
|
||||||
|
int minVal = Math.min(min, max);
|
||||||
|
int maxVal = Math.max(min, max);
|
||||||
|
int r = RandomUtil.randomInt(minVal, maxVal);
|
||||||
|
int g = RandomUtil.randomInt(minVal, maxVal);
|
||||||
|
int b = RandomUtil.randomInt(minVal, maxVal);
|
||||||
|
return new Color(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指定长度的随机字符串
|
||||||
|
*
|
||||||
|
* @param length 长度
|
||||||
|
* @return {@link String}
|
||||||
|
*/
|
||||||
|
public static String generateRandomText(int length) {
|
||||||
|
return RandomUtil.randomString(CHARACTERS, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -136,15 +136,20 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||||||
.map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue())))
|
.map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue())))
|
||||||
.collect(Collectors.joining("&"));
|
.collect(Collectors.joining("&"));
|
||||||
|
|
||||||
// 2.1 请求 Header
|
// 2. 请求 Body
|
||||||
|
String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空
|
||||||
|
String hashedRequestPayload = DigestUtil.sha256Hex(requestBody);
|
||||||
|
|
||||||
|
// 3.1 请求 Header
|
||||||
TreeMap<String, String> headers = new TreeMap<>();
|
TreeMap<String, String> headers = new TreeMap<>();
|
||||||
headers.put("host", HOST);
|
headers.put("host", HOST);
|
||||||
headers.put("x-acs-version", VERSION);
|
headers.put("x-acs-version", VERSION);
|
||||||
headers.put("x-acs-action", apiName);
|
headers.put("x-acs-action", apiName);
|
||||||
headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date()));
|
headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date()));
|
||||||
headers.put("x-acs-signature-nonce", IdUtil.randomUUID());
|
headers.put("x-acs-signature-nonce", IdUtil.randomUUID());
|
||||||
|
headers.put("x-acs-content-sha256", hashedRequestPayload);
|
||||||
|
|
||||||
// 2.2 构建签名 Header
|
// 3.2 构建签名 Header
|
||||||
StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起
|
StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起
|
||||||
StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔
|
StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔
|
||||||
headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-")
|
headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-")
|
||||||
@@ -157,13 +162,13 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||||||
});
|
});
|
||||||
String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1);
|
String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1);
|
||||||
|
|
||||||
// 3. 请求 Body
|
|
||||||
String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。
|
|
||||||
String hashedRequestBody = DigestUtil.sha256Hex(requestBody);
|
|
||||||
|
|
||||||
// 4. 构建 Authorization 签名
|
// 4. 构建 Authorization 签名
|
||||||
String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n"
|
String canonicalRequest = "POST" + "\n" +
|
||||||
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody;
|
"/" + "\n" +
|
||||||
|
queryString + "\n" +
|
||||||
|
canonicalHeaders + "\n" +
|
||||||
|
signedHeaders + "\n" +
|
||||||
|
hashedRequestPayload;
|
||||||
String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest);
|
String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest);
|
||||||
String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest;
|
String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest;
|
||||||
String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
|
String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名
|
||||||
@@ -184,7 +189,7 @@ public class AliyunSmsClient extends AbstractSmsClient {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private static String percentCode(String str) {
|
private static String percentCode(String str) {
|
||||||
Assert.notNull(str, "str 不能为空");
|
Assert.notNull(str, "str 不能为空");
|
||||||
return HttpUtils.encodeUtf8(str)
|
return URLEncoder.encode(str, StandardCharsets.UTF_8.name())
|
||||||
.replace("+", "%20") // 加号 "+" 被替换为 "%20"
|
.replace("+", "%20") // 加号 "+" 被替换为 "%20"
|
||||||
.replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
|
.replace("*", "%2A") // 星号 "*" 被替换为 "%2A"
|
||||||
.replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
|
.replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~"
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import lombok.Data;
|
|||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮箱发送消息
|
* 邮箱发送消息
|
||||||
*
|
*
|
||||||
@@ -21,8 +24,16 @@ public class MailSendMessage {
|
|||||||
/**
|
/**
|
||||||
* 接收邮件地址
|
* 接收邮件地址
|
||||||
*/
|
*/
|
||||||
@NotNull(message = "接收邮件地址不能为空")
|
@NotEmpty(message = "接收邮件地址不能为空")
|
||||||
private String mail;
|
private Collection<String> toMails;
|
||||||
|
/**
|
||||||
|
* 抄送邮件地址
|
||||||
|
*/
|
||||||
|
private Collection<String> ccMails;
|
||||||
|
/**
|
||||||
|
* 密送邮件地址
|
||||||
|
*/
|
||||||
|
private Collection<String> bccMails;
|
||||||
/**
|
/**
|
||||||
* 邮件账号编号
|
* 邮件账号编号
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mail 邮件相关消息的 Producer
|
* Mail 邮件相关消息的 Producer
|
||||||
*
|
*
|
||||||
@@ -24,17 +29,22 @@ public class MailProducer {
|
|||||||
* 发送 {@link MailSendMessage} 消息
|
* 发送 {@link MailSendMessage} 消息
|
||||||
*
|
*
|
||||||
* @param sendLogId 发送日志编码
|
* @param sendLogId 发送日志编码
|
||||||
* @param mail 接收邮件地址
|
* @param toMails 接收邮件地址
|
||||||
|
* @param ccMails 抄送邮件地址
|
||||||
|
* @param bccMails 密送邮件地址
|
||||||
* @param accountId 邮件账号编号
|
* @param accountId 邮件账号编号
|
||||||
* @param nickname 邮件发件人
|
* @param nickname 邮件发件人
|
||||||
* @param title 邮件标题
|
* @param title 邮件标题
|
||||||
* @param content 邮件内容
|
* @param content 邮件内容
|
||||||
*/
|
*/
|
||||||
public void sendMailSendMessage(Long sendLogId, String mail, Long accountId,
|
public void sendMailSendMessage(Long sendLogId,
|
||||||
String nickname, String title, String content) {
|
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||||
|
Long accountId, String nickname, String title, String content) {
|
||||||
MailSendMessage message = new MailSendMessage()
|
MailSendMessage message = new MailSendMessage()
|
||||||
.setLogId(sendLogId).setMail(mail).setAccountId(accountId)
|
.setLogId(sendLogId)
|
||||||
.setNickname(nickname).setTitle(title).setContent(content);
|
.setToMails(toMails).setCcMails(ccMails).setBccMails(bccMails)
|
||||||
|
.setAccountId(accountId).setNickname(nickname)
|
||||||
|
.setTitle(title).setContent(content);
|
||||||
applicationContext.publishEvent(message);
|
applicationContext.publishEvent(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ public interface DeptService {
|
|||||||
*/
|
*/
|
||||||
void deleteDept(Long id);
|
void deleteDept(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除部门
|
||||||
|
*
|
||||||
|
* @param ids 部门编号数组
|
||||||
|
*/
|
||||||
|
void deleteDeptList(List<Long> ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得部门信息
|
* 获得部门信息
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -88,6 +88,21 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
deptMapper.deleteById(id);
|
deptMapper.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
|
||||||
|
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
|
||||||
|
public void deleteDeptList(List<Long> ids) {
|
||||||
|
// 校验是否有子部门
|
||||||
|
for (Long id : ids) {
|
||||||
|
if (deptMapper.selectCountByParentId(id) > 0) {
|
||||||
|
throw exception(DEPT_EXITS_CHILDREN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除部门
|
||||||
|
deptMapper.deleteByIds(ids);
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void validateDeptExists(Long id) {
|
void validateDeptExists(Long id) {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
|
|||||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,18 +37,21 @@ public interface MailLogService {
|
|||||||
/**
|
/**
|
||||||
* 创建邮件日志
|
* 创建邮件日志
|
||||||
*
|
*
|
||||||
* @param userId 用户编码
|
* @param userId 用户编码
|
||||||
* @param userType 用户类型
|
* @param userType 用户类型
|
||||||
* @param toMail 收件人邮件
|
* @param toMails 收件人邮件
|
||||||
* @param account 邮件账号信息
|
* @param ccMails 收件人邮件
|
||||||
* @param template 模版信息
|
* @param bccMails 收件人邮件
|
||||||
|
* @param account 邮件账号信息
|
||||||
|
* @param template 模版信息
|
||||||
* @param templateContent 模版内容
|
* @param templateContent 模版内容
|
||||||
* @param templateParams 模版参数
|
* @param templateParams 模版参数
|
||||||
* @param isSend 是否发送成功
|
* @param isSend 是否发送成功
|
||||||
* @return 日志编号
|
* @return 日志编号
|
||||||
*/
|
*/
|
||||||
Long createMailLog(Long userId, Integer userType, String toMail,
|
Long createMailLog(Long userId, Integer userType,
|
||||||
MailAccountDO account, MailTemplateDO template ,
|
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||||
|
MailAccountDO account, MailTemplateDO template,
|
||||||
String templateContent, Map<String, Object> templateParams, Boolean isSend);
|
String templateContent, Map<String, Object> templateParams, Boolean isSend);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.system.service.mail;
|
package cn.iocoder.yudao.module.system.service.mail;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
|
||||||
@@ -12,8 +13,7 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
|
import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
|
||||||
|
|
||||||
@@ -41,7 +41,8 @@ public class MailLogServiceImpl implements MailLogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long createMailLog(Long userId, Integer userType, String toMail,
|
public Long createMailLog(Long userId, Integer userType,
|
||||||
|
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||||
MailAccountDO account, MailTemplateDO template,
|
MailAccountDO account, MailTemplateDO template,
|
||||||
String templateContent, Map<String, Object> templateParams, Boolean isSend) {
|
String templateContent, Map<String, Object> templateParams, Boolean isSend) {
|
||||||
MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();
|
MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();
|
||||||
@@ -49,7 +50,8 @@ public class MailLogServiceImpl implements MailLogService {
|
|||||||
logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus()
|
logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus()
|
||||||
: MailSendStatusEnum.IGNORE.getStatus())
|
: MailSendStatusEnum.IGNORE.getStatus())
|
||||||
// 用户信息
|
// 用户信息
|
||||||
.userId(userId).userType(userType).toMail(toMail)
|
.userId(userId).userType(userType)
|
||||||
|
.toMails(ListUtil.toList(toMails)).ccMails(ListUtil.toList(ccMails)).bccMails(ListUtil.toList(bccMails))
|
||||||
.accountId(account.getId()).fromMail(account.getMail())
|
.accountId(account.getId()).fromMail(account.getMail())
|
||||||
// 模板相关字段
|
// 模板相关字段
|
||||||
.templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname())
|
.templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname())
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package cn.iocoder.yudao.module.system.service.mail;
|
package cn.iocoder.yudao.module.system.service.mail;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||||
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
|
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,38 +17,53 @@ public interface MailSendService {
|
|||||||
/**
|
/**
|
||||||
* 发送单条邮件给管理后台的用户
|
* 发送单条邮件给管理后台的用户
|
||||||
*
|
*
|
||||||
* @param mail 邮箱
|
|
||||||
* @param userId 用户编码
|
* @param userId 用户编码
|
||||||
|
* @param toMails 收件邮箱
|
||||||
|
* @param ccMails 抄送邮箱
|
||||||
|
* @param bccMails 密送邮箱
|
||||||
* @param templateCode 邮件模版编码
|
* @param templateCode 邮件模版编码
|
||||||
* @param templateParams 邮件模版参数
|
* @param templateParams 邮件模版参数
|
||||||
* @return 发送日志编号
|
* @return 发送日志编号
|
||||||
*/
|
*/
|
||||||
Long sendSingleMailToAdmin(String mail, Long userId,
|
default Long sendSingleMailToAdmin(Long userId,
|
||||||
String templateCode, Map<String, Object> templateParams);
|
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||||
|
String templateCode, Map<String, Object> templateParams) {
|
||||||
|
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(),
|
||||||
|
templateCode, templateParams);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送单条邮件给用户 APP 的用户
|
* 发送单条邮件给用户 APP 的用户
|
||||||
*
|
*
|
||||||
* @param mail 邮箱
|
|
||||||
* @param userId 用户编码
|
* @param userId 用户编码
|
||||||
|
* @param toMails 收件邮箱
|
||||||
|
* @param ccMails 抄送邮箱
|
||||||
|
* @param bccMails 密送邮箱
|
||||||
* @param templateCode 邮件模版编码
|
* @param templateCode 邮件模版编码
|
||||||
* @param templateParams 邮件模版参数
|
* @param templateParams 邮件模版参数
|
||||||
* @return 发送日志编号
|
* @return 发送日志编号
|
||||||
*/
|
*/
|
||||||
Long sendSingleMailToMember(String mail, Long userId,
|
default Long sendSingleMailToMember(Long userId,
|
||||||
String templateCode, Map<String, Object> templateParams);
|
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||||
|
String templateCode, Map<String, Object> templateParams) {
|
||||||
|
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(),
|
||||||
|
templateCode, templateParams);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送单条邮件给用户
|
* 发送单条邮件
|
||||||
*
|
*
|
||||||
* @param mail 邮箱
|
* @param toMails 收件邮箱
|
||||||
* @param userId 用户编码
|
* @param ccMails 抄送邮箱
|
||||||
|
* @param bccMails 密送邮箱
|
||||||
|
* @param userId 用户编号
|
||||||
* @param userType 用户类型
|
* @param userType 用户类型
|
||||||
* @param templateCode 邮件模版编码
|
* @param templateCode 邮件模版编码
|
||||||
* @param templateParams 邮件模版参数
|
* @param templateParams 邮件模版参数
|
||||||
* @return 发送日志编号
|
* @return 发送日志编号
|
||||||
*/
|
*/
|
||||||
Long sendSingleMail(String mail, Long userId, Integer userType,
|
Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||||
|
Long userId, Integer userType,
|
||||||
String templateCode, Map<String, Object> templateParams);
|
String templateCode, Map<String, Object> templateParams);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package cn.iocoder.yudao.module.system.service.mail;
|
package cn.iocoder.yudao.module.system.service.mail;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Validator;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||||
@@ -13,10 +15,13 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
|||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.hutool.extra.mail.*;
|
import org.dromara.hutool.extra.mail.MailAccount;
|
||||||
|
import org.dromara.hutool.extra.mail.MailUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
@@ -49,56 +54,67 @@ public class MailSendServiceImpl implements MailSendService {
|
|||||||
private MailProducer mailProducer;
|
private MailProducer mailProducer;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long sendSingleMailToAdmin(String mail, Long userId,
|
public Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
|
||||||
String templateCode, Map<String, Object> templateParams) {
|
Long userId, Integer userType,
|
||||||
// 如果 mail 为空,则加载用户编号对应的邮箱
|
|
||||||
if (StrUtil.isEmpty(mail)) {
|
|
||||||
AdminUserDO user = adminUserService.getUser(userId);
|
|
||||||
if (user != null) {
|
|
||||||
mail = user.getEmail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 执行发送
|
|
||||||
return sendSingleMail(mail, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long sendSingleMailToMember(String mail, Long userId,
|
|
||||||
String templateCode, Map<String, Object> templateParams) {
|
|
||||||
// 如果 mail 为空,则加载用户编号对应的邮箱
|
|
||||||
if (StrUtil.isEmpty(mail)) {
|
|
||||||
mail = memberService.getMemberUserEmail(userId);
|
|
||||||
}
|
|
||||||
// 执行发送
|
|
||||||
return sendSingleMail(mail, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long sendSingleMail(String mail, Long userId, Integer userType,
|
|
||||||
String templateCode, Map<String, Object> templateParams) {
|
String templateCode, Map<String, Object> templateParams) {
|
||||||
// 校验邮箱模版是否合法
|
// 1.1 校验邮箱模版是否合法
|
||||||
MailTemplateDO template = validateMailTemplate(templateCode);
|
MailTemplateDO template = validateMailTemplate(templateCode);
|
||||||
// 校验邮箱账号是否合法
|
// 1.2 校验邮箱账号是否合法
|
||||||
MailAccountDO account = validateMailAccount(template.getAccountId());
|
MailAccountDO account = validateMailAccount(template.getAccountId());
|
||||||
|
// 1.3 校验邮件参数是否缺失
|
||||||
// 校验邮箱是否存在
|
|
||||||
mail = validateMail(mail);
|
|
||||||
validateTemplateParams(template, templateParams);
|
validateTemplateParams(template, templateParams);
|
||||||
|
|
||||||
|
// 2. 组装邮箱
|
||||||
|
String userMail = getUserMail(userId, userType);
|
||||||
|
Collection<String> toMailSet = new LinkedHashSet<>();
|
||||||
|
Collection<String> ccMailSet = new LinkedHashSet<>();
|
||||||
|
Collection<String> bccMailSet = new LinkedHashSet<>();
|
||||||
|
if (Validator.isEmail(userMail)) {
|
||||||
|
toMailSet.add(userMail);
|
||||||
|
}
|
||||||
|
if (CollUtil.isNotEmpty(toMails)) {
|
||||||
|
toMails.stream().filter(Validator::isEmail).forEach(toMailSet::add);
|
||||||
|
}
|
||||||
|
if (CollUtil.isNotEmpty(ccMails)) {
|
||||||
|
ccMails.stream().filter(Validator::isEmail).forEach(ccMailSet::add);
|
||||||
|
}
|
||||||
|
if (CollUtil.isNotEmpty(bccMails)) {
|
||||||
|
bccMails.stream().filter(Validator::isEmail).forEach(bccMailSet::add);
|
||||||
|
}
|
||||||
|
if (CollUtil.isEmpty(toMailSet)) {
|
||||||
|
throw exception(MAIL_SEND_MAIL_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
|
// 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
|
||||||
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
|
Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
|
||||||
String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams);
|
String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams);
|
||||||
String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams);
|
String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams);
|
||||||
Long sendLogId = mailLogService.createMailLog(userId, userType, mail,
|
Long sendLogId = mailLogService.createMailLog(userId, userType, toMailSet, ccMailSet, bccMailSet,
|
||||||
account, template, content, templateParams, isSend);
|
account, template, content, templateParams, isSend);
|
||||||
// 发送 MQ 消息,异步执行发送短信
|
// 发送 MQ 消息,异步执行发送短信
|
||||||
if (isSend) {
|
if (isSend) {
|
||||||
mailProducer.sendMailSendMessage(sendLogId, mail, account.getId(),
|
mailProducer.sendMailSendMessage(sendLogId, toMailSet, ccMailSet, bccMailSet,
|
||||||
template.getNickname(), title, content);
|
account.getId(), template.getNickname(), title, content);
|
||||||
}
|
}
|
||||||
return sendLogId;
|
return sendLogId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getUserMail(Long userId, Integer userType) {
|
||||||
|
if (userId == null || userType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (UserTypeEnum.ADMIN.getValue().equals(userType)) {
|
||||||
|
AdminUserDO user = adminUserService.getUser(userId);
|
||||||
|
if (user != null) {
|
||||||
|
return user.getEmail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (UserTypeEnum.MEMBER.getValue().equals(userType)) {
|
||||||
|
return memberService.getMemberUserEmail(userId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doSendMail(MailSendMessage message) {
|
public void doSendMail(MailSendMessage message) {
|
||||||
// 1. 创建发送账号
|
// 1. 创建发送账号
|
||||||
@@ -106,7 +122,7 @@ public class MailSendServiceImpl implements MailSendService {
|
|||||||
MailAccount mailAccount = buildMailAccount(account, message.getNickname());
|
MailAccount mailAccount = buildMailAccount(account, message.getNickname());
|
||||||
// 2. 发送邮件
|
// 2. 发送邮件
|
||||||
try {
|
try {
|
||||||
String messageId = MailUtil.send(mailAccount, message.getMail(),
|
String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(),
|
||||||
message.getTitle(), message.getContent(), true);
|
message.getTitle(), message.getContent(), true);
|
||||||
// 3. 更新结果(成功)
|
// 3. 更新结果(成功)
|
||||||
mailLogService.updateMailSendResult(message.getLogId(), messageId, null);
|
mailLogService.updateMailSendResult(message.getLogId(), messageId, null);
|
||||||
@@ -146,16 +162,8 @@ public class MailSendServiceImpl implements MailSendService {
|
|||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
String validateMail(String mail) {
|
|
||||||
if (StrUtil.isEmpty(mail)) {
|
|
||||||
throw exception(MAIL_SEND_MAIL_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
return mail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验邮件参数是否确实
|
* 校验邮件参数是否缺失
|
||||||
*
|
*
|
||||||
* @param template 邮箱模板
|
* @param template 邮箱模板
|
||||||
* @param templateParams 参数列表
|
* @param templateParams 参数列表
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user