Commit 65cd56bcf751dc2172acccc7ba9e4e177d1c491f

Authored by YunaiV
2 parents 6791c628 e2edd7a4

Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot

Showing 124 changed files with 1621 additions and 470 deletions

Too many changes.

To preserve performance only 100 of 124 files are displayed.

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