From a1164f24e0434fee7f72f13b623041b7866e45b4 Mon Sep 17 00:00:00 2001 From: jinmh716 Date: Fri, 24 Jan 2025 11:19:33 +0800 Subject: [PATCH 01/26] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E9=82=AE=E4=BB=B6=E5=8F=91=E9=80=81=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8A=84=E9=80=81=E5=92=8C=E5=AF=86=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/ruoyi-vue-pro.sql | 6 +- .../admin/mail/MailTemplateController.java | 2 +- .../admin/mail/vo/log/MailLogRespVO.java | 11 +-- .../vo/template/MailTemplateSendReqVO.java | 11 ++- .../system/dal/dataobject/mail/MailLogDO.java | 9 ++- .../mq/message/mail/MailSendMessage.java | 14 +++- .../system/mq/producer/mail/MailProducer.java | 35 +++++++-- .../system/service/mail/MailLogService.java | 20 ++++++ .../service/mail/MailLogServiceImpl.java | 27 +++++++ .../system/service/mail/MailSendService.java | 44 ++++++++++++ .../service/mail/MailSendServiceImpl.java | 71 ++++++++++++++++++- 11 files changed, 230 insertions(+), 20 deletions(-) diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 0c810bef85..0771be45af 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -1091,14 +1091,16 @@ CREATE TABLE `system_mail_log` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', `user_id` bigint NULL DEFAULT NULL COMMENT '用户编号', `user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型', - `to_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址', + `to_mail` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址', + `cc_mail` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '抄送邮箱地址', + `bcc_mail` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密送邮箱地址', `account_id` bigint NOT NULL COMMENT '邮箱账号编号', `from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址', `template_id` bigint NOT NULL COMMENT '模板编号', `template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码', `template_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版发送人名称', `template_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件标题', - `template_content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容', + `template_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容', `template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件参数', `send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态', `send_time` datetime NULL DEFAULT NULL COMMENT '发送时间', diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java index 22e2658ef3..57dcc41e42 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java @@ -82,7 +82,7 @@ public class MailTemplateController { @Operation(summary = "发送短信") @PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')") public CommonResult sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) { - return success(mailSendService.sendSingleMailToAdmin(sendReqVO.getMail(), getLoginUserId(), + return success(mailSendService.sendMultipleMailToAdmin(sendReqVO.getToMails(), sendReqVO.getCcMails(), sendReqVO.getBccMails(), getLoginUserId(), sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams())); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java index 49edfffefb..b53488510c 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java @@ -2,13 +2,10 @@ package cn.iocoder.yudao.module.system.controller.admin.mail.vo.log; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; import java.util.Map; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - @Schema(description = "管理后台 - 邮件日志 Response VO") @Data public class MailLogRespVO { @@ -22,9 +19,15 @@ public class MailLogRespVO { @Schema(description = "用户类型,参见 UserTypeEnum 枚举", example = "2") private Byte userType; - @Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "76854@qq.com") + @Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user1@example.com, user2@example.com") private String toMail; + @Schema(description = "抄送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user3@example.com, user4@example.com") + private String ccMail; + + @Schema(description = "密送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user5@example.com, user6@example.com") + private String bccMail; + @Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18107") private Long accountId; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java index b76b7ffccd..f125d77e9a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java @@ -5,15 +5,22 @@ import lombok.Data; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import java.util.List; import java.util.Map; @Schema(description = "管理后台 - 邮件发送 Req VO") @Data public class MailTemplateSendReqVO { - @Schema(description = "接收邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "7685413@qq.com") + @Schema(description = "接收邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user1@example.com, user2@example.com]") @NotEmpty(message = "接收邮箱不能为空") - private String mail; + private List toMails; + + @Schema(description = "抄送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user3@example.com, user4@example.com]") + private List ccMails; + + @Schema(description = "密送邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "[user5@example.com, user6@example.com]") + private List bccMails; @Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01") @NotNull(message = "模板编码不能为空") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java index 76e1e2ec5d..988273ec59 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java @@ -49,7 +49,14 @@ public class MailLogDO extends BaseDO implements Serializable { * 接收邮箱地址 */ private String toMail; - + /** + * 接收邮箱地址 + */ + private String ccMail; + /** + * 接收邮箱地址 + */ + private String bccMail; /** * 邮箱账号编号 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java index 8d5af7c4cf..06c4a5102b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java @@ -5,6 +5,8 @@ import lombok.Data; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import java.util.List; + /** * 邮箱发送消息 * @@ -21,8 +23,16 @@ public class MailSendMessage { /** * 接收邮件地址 */ - @NotNull(message = "接收邮件地址不能为空") - private String mail; + @NotEmpty(message = "接收邮件地址不能为空") + private List toMails; + /** + * 抄送邮件地址 + */ + private List ccMails; + /** + * 密送邮件地址 + */ + private List bccMails; /** * 邮件账号编号 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java index 5a44218bb2..8a485c7fe0 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java @@ -7,6 +7,10 @@ import org.springframework.stereotype.Component; import jakarta.annotation.Resource; +import java.util.List; + +import static java.util.Collections.singletonList; + /** * Mail 邮件相关消息的 Producer * @@ -24,17 +28,36 @@ public class MailProducer { * 发送 {@link MailSendMessage} 消息 * * @param sendLogId 发送日志编码 - * @param mail 接收邮件地址 + * @param mail 接收邮件地址 * @param accountId 邮件账号编号 - * @param nickname 邮件发件人 - * @param title 邮件标题 - * @param content 邮件内容 + * @param nickname 邮件发件人 + * @param title 邮件标题 + * @param content 邮件内容 */ public void sendMailSendMessage(Long sendLogId, String mail, Long accountId, String nickname, String title, String content) { + sendMailSendMessage(sendLogId, singletonList(mail), null, null, accountId, nickname, title, content); + } + + /** + * 发送 {@link MailSendMessage} 消息 + * + * @param sendLogId 发送日志编码 + * @param toMails 接收邮件地址 + * @param ccMails 抄送邮件地址 + * @param bccMails 密送邮件地址 + * @param accountId 邮件账号编号 + * @param nickname 邮件发件人 + * @param title 邮件标题 + * @param content 邮件内容 + */ + public void sendMailSendMessage(Long sendLogId, List toMails, List ccMails, List bccMails, + Long accountId, String nickname, String title, String content) { MailSendMessage message = new MailSendMessage() - .setLogId(sendLogId).setMail(mail).setAccountId(accountId) - .setNickname(nickname).setTitle(title).setContent(content); + .setLogId(sendLogId) + .setToMails(toMails).setCcMails(ccMails).setBccMails(bccMails) + .setAccountId(accountId).setNickname(nickname) + .setTitle(title).setContent(content); applicationContext.publishEvent(message); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java index 4a0b204385..168179aec7 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO; +import java.util.List; import java.util.Map; /** @@ -49,6 +50,25 @@ public interface MailLogService { MailAccountDO account, MailTemplateDO template , String templateContent, Map templateParams, Boolean isSend); + /** + * 创建邮件日志 + * + * @param userId 用户编码 + * @param userType 用户类型 + * @param toMails 收件人邮件 + * @param ccMails 收件人邮件 + * @param bccMails 收件人邮件 + * @param account 邮件账号信息 + * @param template 模版信息 + * @param templateContent 模版内容 + * @param templateParams 模版参数 + * @param isSend 是否发送成功 + * @return 日志编号 + */ + Long createMailLog(Long userId, Integer userType, List toMails, List ccMails, List bccMails, + MailAccountDO account, MailTemplateDO template, + String templateContent, Map templateParams, Boolean isSend); + /** * 更新邮件发送结果 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java index 827d0c56e9..28d233da58 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.system.service.mail; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; @@ -12,6 +13,7 @@ import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import java.time.LocalDateTime; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -61,6 +63,31 @@ public class MailLogServiceImpl implements MailLogService { return logDO.getId(); } + @Override + public Long createMailLog(Long userId, Integer userType, List toMails, List ccMails, List bccMails, + MailAccountDO account, MailTemplateDO template, + String templateContent, Map templateParams, Boolean isSend) { + String toMail = CollUtil.isEmpty(toMails) ? "" : String.join(",", toMails); + String ccMail = CollUtil.isEmpty(ccMails) ? "" : String.join(",", ccMails); + String bccMail = CollUtil.isEmpty(bccMails) ? "" : String.join(",", bccMails); + + MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder(); + // 根据是否要发送,设置状态 + logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus() + : MailSendStatusEnum.IGNORE.getStatus()) + // 用户信息 + .userId(userId).userType(userType).toMail(toMail).ccMail(ccMail).bccMail(bccMail) + .accountId(account.getId()).fromMail(account.getMail()) + // 模板相关字段 + .templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname()) + .templateTitle(template.getTitle()).templateContent(templateContent).templateParams(templateParams); + + // 插入数据库 + MailLogDO logDO = logDOBuilder.build(); + mailLogMapper.insert(logDO); + return logDO.getId(); + } + @Override public void updateMailSendResult(Long logId, String messageId, Exception exception) { // 1. 成功 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java index 898816868f..5300157b15 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.mail; import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage; +import java.util.List; import java.util.Map; /** @@ -49,6 +50,49 @@ public interface MailSendService { Long sendSingleMail(String mail, Long userId, Integer userType, String templateCode, Map templateParams); + /** + * 发送多条邮件给管理后台的用户 + * + * @param toMails 收件邮箱 + * @param ccMails 抄送邮箱 + * @param bccMails 密送邮箱 + * @param userId 用户编码 + * @param templateCode 邮件模版编码 + * @param templateParams 邮件模版参数 + * @return 发送日志编号 + */ + Long sendMultipleMailToAdmin(List toMails, List ccMails, List bccMails, + Long userId, String templateCode, Map templateParams); + + /** + * 发送多条邮件给用户 APP 的用户 + * + * @param toMails 收件邮箱 + * @param ccMails 抄送邮箱 + * @param bccMails 密送邮箱 + * @param userId 用户编码 + * @param templateCode 邮件模版编码 + * @param templateParams 邮件模版参数 + * @return 发送日志编号 + */ + Long sendMultipleMailToMember(List toMails, List ccMails, List bccMails, + Long userId, String templateCode, Map templateParams); + + /** + * 发送单条邮件给用户 + * + * @param toMails 收件邮箱 + * @param ccMails 抄送邮箱 + * @param bccMails 密送邮箱 + * @param userId 用户编码 + * @param userType 用户类型 + * @param templateCode 邮件模版编码 + * @param templateParams 邮件模版参数 + * @return 发送日志编号 + */ + Long sendMultipleMail(List toMails, List ccMails, List bccMails, + Long userId, Integer userType, String templateCode, Map templateParams); + /** * 执行真正的邮件发送 * 注意,该方法仅仅提供给 MQ Consumer 使用 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java index 306b05c048..580777ca70 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.system.service.mail; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -17,10 +18,12 @@ import org.dromara.hutool.extra.mail.*; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; +import static java.util.Collections.singletonList; /** * 邮箱发送 Service 实现类 @@ -99,6 +102,70 @@ public class MailSendServiceImpl implements MailSendService { return sendLogId; } + @Override + public Long sendMultipleMailToAdmin(List toMails, List ccMails, List bccMails, Long userId, String templateCode, Map templateParams) { + // 如果 mail 为空,则加载用户编号对应的邮箱 + if (CollUtil.isEmpty(toMails)) { + AdminUserDO user = adminUserService.getUser(userId); + if (user != null) { + toMails = singletonList(user.getEmail()); + } + } + // 执行发送 + return sendMultipleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); + } + + @Override + public Long sendMultipleMailToMember(List toMails, List ccMails, List bccMails, Long userId, String templateCode, Map templateParams) { + // 如果 mail 为空,则加载用户编号对应的邮箱 + if (CollUtil.isEmpty(toMails)) { + toMails = singletonList(memberService.getMemberUserEmail(userId)); + } + // 执行发送 + return sendMultipleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams); + } + + @Override + public Long sendMultipleMail(List toMails, List ccMails, List bccMails, Long userId, Integer userType, String templateCode, Map templateParams) { + // 校验邮箱模版是否合法 + MailTemplateDO template = validateMailTemplate(templateCode); + // 校验邮箱账号是否合法 + MailAccountDO account = validateMailAccount(template.getAccountId()); + // 校验邮件参数是否缺失 + validateTemplateParams(template, templateParams); + + if (CollUtil.isEmpty(toMails)) { + throw exception(MAIL_SEND_MAIL_NOT_EXISTS); + } + // 校验邮箱是否存在 + for (String mail : toMails) { + validateMail(mail); + } + if (CollUtil.isNotEmpty(ccMails)) { + for (String mail : ccMails) { + validateMail(mail); + } + } + if (CollUtil.isNotEmpty(bccMails)) { + for (String mail : bccMails) { + validateMail(mail); + } + } + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()); + String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams); + String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams); + Long sendLogId = mailLogService.createMailLog(userId, userType, toMails, ccMails, bccMails, + account, template, content, templateParams, isSend); + // 发送 MQ 消息,异步执行发送短信 + if (isSend) { + mailProducer.sendMailSendMessage(sendLogId, toMails, ccMails, bccMails, account.getId(), + template.getNickname(), title, content); + } + return sendLogId; + } + @Override public void doSendMail(MailSendMessage message) { // 1. 创建发送账号 @@ -106,7 +173,7 @@ public class MailSendServiceImpl implements MailSendService { MailAccount mailAccount = buildMailAccount(account, message.getNickname()); // 2. 发送邮件 try { - String messageId = MailUtil.send(mailAccount, message.getMail(), + String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(), message.getTitle(), message.getContent(), true); // 3. 更新结果(成功) mailLogService.updateMailSendResult(message.getLogId(), messageId, null); @@ -155,7 +222,7 @@ public class MailSendServiceImpl implements MailSendService { } /** - * 校验邮件参数是否确实 + * 校验邮件参数是否缺失 * * @param template 邮箱模板 * @param templateParams 参数列表 From f88f8d21583397e90003416fd4d69c449daeef02 Mon Sep 17 00:00:00 2001 From: zhangxiaoxiang <1090510535@qq.com> Date: Tue, 22 Jul 2025 23:05:24 +0800 Subject: [PATCH 02/26] =?UTF-8?q?refactor(yudao-common):=E8=B0=83=E6=95=B4?= =?UTF-8?q?=20CommonResult=20=E5=92=8C=20PageResult=20=E7=B1=BB=E7=9A=84?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -调整 CommonResult 类中 code、msg 和 data 属性的顺序,使代码结构更合理 -调整 PageResult 类中 total 和 list 属性的顺序,符合常规的数据结构展示逻辑 --- .../iocoder/yudao/framework/common/pojo/CommonResult.java | 8 ++++---- .../iocoder/yudao/framework/common/pojo/PageResult.java | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java index ac74103154..afb9cd3063 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/CommonResult.java @@ -25,16 +25,16 @@ public class CommonResult implements Serializable { * @see ErrorCode#getCode() */ private Integer code; - /** - * 返回数据 - */ - private T data; /** * 错误提示,用户可阅读 * * @see ErrorCode#getMsg() () */ private String msg; + /** + * 返回数据 + */ + private T data; /** * 将传入的 result 对象,转换成另外一个泛型结果的对象 diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageResult.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageResult.java index ff9087a81f..47c59d1d9e 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageResult.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/PageResult.java @@ -11,12 +11,12 @@ import java.util.List; @Data public final class PageResult implements Serializable { - @Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED) - private List list; - @Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED) private Long total; + @Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED) + private List list; + public PageResult() { } From 413eb23893111cac8babc99cc9fd78644a308984 Mon Sep 17 00:00:00 2001 From: DanielTsui <1146818706@qq.com> Date: Thu, 24 Jul 2025 15:28:43 +0800 Subject: [PATCH 03/26] =?UTF-8?q?feat.=E6=96=B0=E5=A2=9E=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E5=AD=97=E6=AF=8D=E6=95=B0=E5=AD=97=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81spi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/PictureWordCaptchaServiceImpl.java | 227 ++++++++++++++++++ .../com.anji.captcha.service.CaptchaService | 1 + 2 files changed, 228 insertions(+) create mode 100644 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java create mode 100644 yudao-module-system/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java new file mode 100644 index 0000000000..354da03e51 --- /dev/null +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java @@ -0,0 +1,227 @@ +package cn.iocoder.yudao.module.system.framework.captcha.core; + +import com.anji.captcha.model.common.CaptchaTypeEnum; +import com.anji.captcha.model.common.RepCodeEnum; +import com.anji.captcha.model.common.ResponseModel; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.impl.AbstractCaptchaService; +import com.anji.captcha.service.impl.CaptchaServiceFactory; +import com.anji.captcha.util.AESUtil; +import com.anji.captcha.util.ImageUtils; +import com.anji.captcha.util.RandomUtils; +import org.apache.commons.lang3.StringUtils; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.util.Properties; +import java.util.Random; + +/** + * 图片文字验证码 + * + * @author Tsui + * @since 2025/7/23 20:44 + */ +public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { + + private static final int WIDTH = 120; + private static final int HEIGHT = 40; + private static final int LINES = 10; + private static final Random RANDOM = new Random(); + private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + + @Override + public void init(Properties config) { + super.init(config); + } + + @Override + public void destroy(Properties config) { + logger.info("start-clear-history-data-{}", captchaType()); + } + + @Override + public String captchaType() { + return "pictureWord"; +// return CaptchaTypeEnum.PICTURE_WORD.getCodeValue(); + } + + @Override + public ResponseModel get(CaptchaVO captchaVO) { + String text = generateRandomText(4); + + CaptchaVO imageData = getImageData(text); + + // pointJson不传到前端,只做后端校验,测试时放开 +// imageData.setPointJson(text); + + return ResponseModel.successData(imageData); + } + + @Override + public ResponseModel check(CaptchaVO captchaVO) { + ResponseModel r = super.check(captchaVO); + if (!validatedReq(r)) { + return r; + } + + // 取出验证码 + String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken()); + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) { + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID); + } + // 正确的验证码 + String codeValue = CaptchaServiceFactory.getCache(cacheType).get(codeKey); + String code = getCodeByCodeValue(codeValue); + String secretKey = getSecretKeyByCodeValue(codeValue); + //验证码只用一次,即刻失效 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey); + + // 用户输入的验证码(CaptchaVO中没有预留字段,暂时用pointJson,无需加解密) + String userCode = captchaVO.getPointJson(); + if (!StringUtils.equalsIgnoreCase(code, userCode)) { + afterValidateFail(captchaVO); + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR); + } + + //校验成功,将信息存入缓存 + String value; + try { + value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(userCode), secretKey); + } catch (Exception e) { + logger.error("AES加密失败", e); + afterValidateFail(captchaVO); + return ResponseModel.errorMsg(e.getMessage()); + } + String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value); + CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE); + captchaVO.setResult(true); + captchaVO.resetClientFlag(); + return ResponseModel.successData(captchaVO); + } + + @Override + public ResponseModel verification(CaptchaVO captchaVO) { + ResponseModel r = super.verification(captchaVO); + if (!validatedReq(r)) { + return r; + } + try { + String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification()); + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) { + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID); + } + //二次校验取值后,即刻失效 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey); + } catch (Exception e) { + logger.error("验证码解析失败", e); + return ResponseModel.errorMsg(e.getMessage()); + } + return ResponseModel.success(); + } + + + private CaptchaVO getImageData(String text) { + CaptchaVO dataVO = new CaptchaVO(); + BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + + // 设置背景色 + g.setColor(getRandomColor(200, 250)); + g.fillRect(0, 0, WIDTH, HEIGHT); + + // 绘制干扰线 + for (int i = 0; i < LINES; i++) { + g.setColor(getRandomColor(100, 200)); + int x1 = RANDOM.nextInt(WIDTH); + int y1 = RANDOM.nextInt(HEIGHT); + int x2 = RANDOM.nextInt(WIDTH); + int y2 = RANDOM.nextInt(HEIGHT); + g.drawLine(x1, y1, x2, y2); + } + + // 设置字体 + g.setFont(new Font("Arial", Font.BOLD, 24)); + + // 绘制验证码文本 + for (int i = 0; i < text.length(); i++) { + g.setColor(getRandomColor(20, 130)); + + // 文字旋转 + AffineTransform affineTransform = new AffineTransform(); + int x = 20 + i * 20; + int y = 24 + RANDOM.nextInt(8); + // 旋转范围 -45 ~ 45 + affineTransform.setToRotation(Math.toRadians(RandomUtils.getRandomInt(-45, 45)), x, y); + g.setTransform(affineTransform); + + g.drawString(text.charAt(i) + "", x, y); + } + + // 添加噪点 + for (int i = 0; i < 100; i++) { + int x = RANDOM.nextInt(WIDTH); + int y = RANDOM.nextInt(HEIGHT); + image.setRGB(x, y, getRandomColor(0, 255).getRGB()); + } + + g.dispose(); + + String secretKey = null; + if (captchaAesStatus) { + secretKey = AESUtil.getKey(); + } + dataVO.setSecretKey(secretKey); + + dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(image).replaceAll("\r|\n", "")); + dataVO.setToken(RandomUtils.getUUID()); +// dataVO.setSecretKey(secretKey); + //将坐标信息存入redis中 + String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken()); + CaptchaServiceFactory.getCache(cacheType).set(codeKey, getCodeValue(text, secretKey), EXPIRESIN_SECONDS); + return dataVO; + } + + private String getCodeValue(String text, String secretKey) { + return text + "," + secretKey; + } + + private String getCodeByCodeValue(String codeValue) { + return codeValue.split(",")[0]; + } + + private String getSecretKeyByCodeValue(String codeValue) { + return codeValue.split(",")[1]; + } + + private Color getRandomColor(int min, int max) { + if (min > max) { + int temp = min; + min = max; + max = temp; + } + int r = min + RANDOM.nextInt(max - min); + int g = min + RANDOM.nextInt(max - min); + int b = min + RANDOM.nextInt(max - min); + return new Color(r, g, b); + } + + /** + * 生成指定长度的随机字符串 + * + * @param length 长度 + * @return {@link String} + * @author Rex + * @since 2025/6/26 15:20 + */ + public static String generateRandomText(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + int index = RANDOM.nextInt(CHARACTERS.length()); + sb.append(CHARACTERS.charAt(index)); + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/yudao-module-system/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService b/yudao-module-system/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService new file mode 100644 index 0000000000..80adf6ddb6 --- /dev/null +++ b/yudao-module-system/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService @@ -0,0 +1 @@ +cn.iocoder.yudao.module.system.framework.captcha.core.PictureWordCaptchaServiceImpl \ No newline at end of file From d2bf9945f7690760f7f881e2b406c0e41b6d6e85 Mon Sep 17 00:00:00 2001 From: LesanOuO <1960681385@qq.com> Date: Sat, 2 Aug 2025 14:14:42 +0800 Subject: [PATCH 04/26] =?UTF-8?q?feat:=20=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=AE=A1=E6=89=B9=E4=BA=BA=E6=92=A4=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/BpmModelMetaInfoVO.java | 3 + .../admin/task/BpmTaskController.java | 8 ++ .../BpmProcessDefinitionInfoDO.java | 5 + .../module/bpm/enums/ErrorCodeConstants.java | 4 + .../module/bpm/enums/task/BpmReasonEnum.java | 1 + .../flowable/core/util/BpmnModelUtils.java | 42 ++++++++ .../bpm/service/task/BpmTaskService.java | 8 ++ .../bpm/service/task/BpmTaskServiceImpl.java | 96 +++++++++++++++---- 8 files changed, 151 insertions(+), 16 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index d2316f58eb..dca75851e9 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -97,6 +97,9 @@ public class BpmModelMetaInfoVO { @Schema(description = "任务后置通知设置", example = "{}") private HttpRequestSetting taskAfterTriggerSetting; + @Schema(description = "允许允许审批人撤回任务", example = "false") + private Boolean allowWithdrawTask; + @Schema(description = "流程 ID 规则") @Data @Valid diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java index b796c5c17e..b327b8e777 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java @@ -219,6 +219,14 @@ public class BpmTaskController { return success(true); } + @PutMapping("/withdraw") + @Operation(summary = "撤回任务") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult withdrawTask(@RequestParam("taskId") String taskId) { + taskService.withdrawTask(getLoginUserId(), taskId); + return success(true); + } + @GetMapping("/list-by-parent-task-id") @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index c2799ef67f..7f10bda388 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -219,4 +219,9 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; + /** + * 是否允许审批人撤回任务 + */ + private Boolean allowWithdrawTask; + } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index 4f7d7bf7ce..dabedb5463 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -61,6 +61,10 @@ public interface ErrorCodeConstants { ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!"); ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!"); ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!"); + ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!"); + ErrorCode TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS = new ErrorCode(1_009_005_018, "撤回失败,未查询到用户已办任务!"); + ErrorCode TASK_WITHDRAW_FAIL_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,此流程不允许撤回操作!"); + ErrorCode TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW = new ErrorCode(1_009_005_019, "撤回失败,下一节点不满足撤回条件!"); // ========== 动态表单模块 1-009-010-000 ========== ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在"); diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java index 46d1482a5e..6ce6f65b82 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java @@ -35,6 +35,7 @@ public enum BpmReasonEnum { APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"), + CANCEL_BY_WITHDRAW("前一任务撤回,系统自动取消"), ; private final String reason; diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 1bafa578db..978693c0c8 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -908,6 +908,48 @@ public class BpmnModelUtils { return nextFlowNodes; } + /** + * 查找起始节点下一个用户任务列表列表 + * @param source 起始节点 + * @return 结果 + */ + public static List getNextUserTasks(FlowElement source) { + return getNextUserTasks(source, null, null); + } + + /** + * 查找起始节点下一个用户任务列表列表 + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 用户任务列表 + * @return 结果 + */ + public static List getNextUserTasks(FlowElement source, Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>()); + userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>()); + // 获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + if (!sequenceFlows.isEmpty()) { + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement(); + if (targetFlowElement instanceof UserTask) { + // 若节点为用户任务,加入到结果列表中 + userTaskList.add((UserTask) targetFlowElement); + } else { + // 若节点非用户任务,继续递归查找下一个节点 + getNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList); + } + } + } + return userTaskList; + } + /** * 处理排它网关 * diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index 0a5c866fda..34db2876fa 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -250,6 +250,14 @@ public interface BpmTaskService { */ void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); + /** + * 撤回任务 + * + * @param userId 用户编号 + * @param taskId 任务编号 + */ + void withdrawTask(Long userId, String taskId); + // ========== Event 事件相关方法 ========== /** diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 8ea607a773..d75a91c12b 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -40,10 +40,9 @@ import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.*; -import org.flowable.engine.HistoryService; -import org.flowable.engine.ManagementService; -import org.flowable.engine.RuntimeService; -import org.flowable.engine.TaskService; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.engine.*; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.runtime.ActivityInstance; import org.flowable.engine.runtime.Execution; @@ -62,6 +61,7 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -196,7 +196,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务 * - * @param userId 用户编号 + * @param userId 用户编号 * @param processInstanceId 流程编号 * @return 任务 */ @@ -599,15 +599,15 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 校验选择的下一个节点的审批人,是否合法 - * + *

* 1. 是否有漏选:没有选择审批人 * 2. 是否有多选:非下一个节点 * * @param taskDefinitionKey 当前任务节点标识 - * @param variables 流程变量 - * @param bpmnModel 流程模型 - * @param nextAssignees 下一个节点审批人集合(参数) - * @param processInstance 流程实例 + * @param variables 流程变量 + * @param bpmnModel 流程模型 + * @param nextAssignees 下一个节点审批人集合(参数) + * @param processInstance 流程实例 */ @SuppressWarnings("unchecked") private Map validateAndSetNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel, @@ -659,7 +659,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { approveUserSelectAssignees = new HashMap<>(); } approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); - Map> existingApproveUserSelectAssignees = (Map>) variables.get( + Map> existingApproveUserSelectAssignees = (Map>) variables.get( BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) { approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees); @@ -1177,6 +1177,70 @@ public class BpmTaskServiceImpl implements BpmTaskService { processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); } + @Override + @Transactional(rollbackFor = Exception.class) + public void withdrawTask(Long userId, String taskId) { + // 1.查询本人已办任务 + HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery() + .taskId(taskId).taskAssignee(userId.toString()).finished().singleResult(); + if (ObjectUtil.isNull(taskInstance)) { + throw exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS); + } + // 2.校验流程是否结束 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(taskInstance.getProcessInstanceId()) + .active() + .singleResult(); + if (ObjectUtil.isNull(processInstance)) { + throw exception(TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING); + } + // 3.判断此流程是否允许撤回 + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService + .getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); + if (ObjectUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) { + throw exception(TASK_WITHDRAW_FAIL_NOT_ALLOW); + } + // 4.判断此任务下一节点是否满足撤回 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskInstance.getProcessDefinitionId()); + UserTask userTask = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, taskInstance.getTaskDefinitionKey()); + List nextUserTaskList = BpmnModelUtils.getNextUserTasks(userTask); + List nextUserTaskKeys = nextUserTaskList.stream().map(UserTask::getId).toList(); + if (CollUtil.isEmpty(nextUserTaskKeys)) { + throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); + } + long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(processInstance.getProcessInstanceId()) + .taskDefinitionKeys(nextUserTaskKeys) + .taskCreatedAfter(taskInstance.getEndTime()) // TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以 + .finished() + .count(); + if (nextUserTaskFinishedCount > 0) { + throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); + } + // 5.获取需要撤回的运行任务 + List runningTaskList = taskService.createTaskQuery() + .processInstanceId(processInstance.getProcessInstanceId()) + .taskDefinitionKeys(nextUserTaskKeys) + .active().list(); + if (CollUtil.isEmpty(runningTaskList)) { + throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); + } + List withdrawExecutionIds = new ArrayList<>(); + for (Task task : runningTaskList) { + // 标记撤回任务为取消 + // TODO @芋艿:是否需要添加被撤回状态? + taskService.addComment(task.getId(), taskInstance.getProcessInstanceId(), BpmCommentTypeEnum.CANCEL.getType(), + BpmCommentTypeEnum.CANCEL.formatComment("前一节点撤回")); + updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_WITHDRAW.getReason()); + withdrawExecutionIds.add(task.getExecutionId()); + } + // 6.执行撤回操作 + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstance.getProcessInstanceId()) + .moveExecutionsToSingleActivityId(withdrawExecutionIds, taskInstance.getTaskDefinitionKey()) + .changeState(); + } + /** * 校验任务是否能被减签 * @@ -1223,7 +1287,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 2. 任务前置通知 - if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){ + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) { BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); @@ -1350,7 +1414,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) .finished(); if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) - && sameAssigneeQuery.count() > 0) { + && sameAssigneeQuery.count() > 0) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); return; @@ -1362,7 +1426,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点 - BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), + BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), SequenceFlow::getSourceRef); if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) @@ -1387,7 +1451,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); if (userTaskElement.getId().equals(START_USER_NODE_ID) && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 - || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 + || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); @@ -1456,7 +1520,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 任务后置通知 - if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){ + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())) { BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); From 51e5294a3e5cc2a1af47abab60b8f11035a84c6c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 15:44:19 +0800 Subject: [PATCH 05/26] =?UTF-8?q?review=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91=E6=92=A4=E5=9B=9E=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=88=E6=B2=A1=E9=97=AE=E9=A2=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/BpmModelMetaInfoVO.java | 6 +- .../BpmProcessDefinitionInfoDO.java | 10 ++-- .../flowable/core/util/BpmnModelUtils.java | 3 +- .../bpm/service/task/BpmTaskServiceImpl.java | 59 ++++++++----------- 4 files changed, 36 insertions(+), 42 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index dca75851e9..943a82d546 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/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 { @Schema(description = "允许撤销审批中的申请", example = "true") private Boolean allowCancelRunningProcess; + @Schema(description = "允许允许审批人撤回任务", example = "false") + private Boolean allowWithdrawTask; + @Schema(description = "流程 ID 规则", example = "{}") private ProcessIdRule processIdRule; @@ -97,9 +100,6 @@ public class BpmModelMetaInfoVO { @Schema(description = "任务后置通知设置", example = "{}") private HttpRequestSetting taskAfterTriggerSetting; - @Schema(description = "允许允许审批人撤回任务", example = "false") - private Boolean allowWithdrawTask; - @Schema(description = "流程 ID 规则") @Data @Valid diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 7f10bda388..37e2c4462d 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -172,6 +172,11 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { */ private Boolean allowCancelRunningProcess; + /** + * 是否允许审批人撤回任务 + */ + private Boolean allowWithdrawTask; + /** * 流程 ID 规则 */ @@ -219,9 +224,4 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; - /** - * 是否允许审批人撤回任务 - */ - private Boolean allowWithdrawTask; - } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 978693c0c8..a3414cedb4 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -852,7 +852,7 @@ public class BpmnModelUtils { } else if (flowNode instanceof ScriptTask) { skipExpression = ((ScriptTask) flowNode).getSkipExpression(); } - + if (StrUtil.isEmpty(skipExpression)) { return false; } @@ -910,6 +910,7 @@ public class BpmnModelUtils { /** * 查找起始节点下一个用户任务列表列表 + * * @param source 起始节点 * @return 结果 */ diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index d75a91c12b..133f05c76b 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -40,9 +40,10 @@ import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.*; -import org.flowable.common.engine.api.FlowableException; -import org.flowable.common.engine.api.FlowableObjectNotFoundException; -import org.flowable.engine.*; +import org.flowable.engine.HistoryService; +import org.flowable.engine.ManagementService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.runtime.ActivityInstance; import org.flowable.engine.runtime.Execution; @@ -61,7 +62,6 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -1180,61 +1180,54 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) public void withdrawTask(Long userId, String taskId) { - // 1.查询本人已办任务 + // 1.1 查询本人已办任务 HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery() .taskId(taskId).taskAssignee(userId.toString()).finished().singleResult(); - if (ObjectUtil.isNull(taskInstance)) { + if (ObjUtil.isNull(taskInstance)) { throw exception(TASK_WITHDRAW_FAIL_TASK_NOT_EXISTS); } - // 2.校验流程是否结束 - ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() - .processInstanceId(taskInstance.getProcessInstanceId()) - .active() - .singleResult(); - if (ObjectUtil.isNull(processInstance)) { + // 1.2 校验流程是否结束 + ProcessInstance processInstance = processInstanceService.getProcessInstance(taskInstance.getProcessInstanceId()); + if (ObjUtil.isNull(processInstance)) { throw exception(TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING); } - // 3.判断此流程是否允许撤回 - BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService - .getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); - if (ObjectUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) { + // 1.3 判断此流程是否允许撤回 + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo( + processInstance.getProcessDefinitionId()); + if (ObjUtil.isNull(processDefinitionInfo) || !Boolean.TRUE.equals(processDefinitionInfo.getAllowWithdrawTask())) { throw exception(TASK_WITHDRAW_FAIL_NOT_ALLOW); } - // 4.判断此任务下一节点是否满足撤回 + // 1.4 判断下一个节点是否被审批过,如果是则无法撤回 BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskInstance.getProcessDefinitionId()); UserTask userTask = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, taskInstance.getTaskDefinitionKey()); - List nextUserTaskList = BpmnModelUtils.getNextUserTasks(userTask); - List nextUserTaskKeys = nextUserTaskList.stream().map(UserTask::getId).toList(); + List nextUserTaskKeys = convertList(BpmnModelUtils.getNextUserTasks(userTask), UserTask::getId); if (CollUtil.isEmpty(nextUserTaskKeys)) { throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); } + // TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以;包括 todo 和 done 那边的查询哇??? long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery() - .processInstanceId(processInstance.getProcessInstanceId()) - .taskDefinitionKeys(nextUserTaskKeys) - .taskCreatedAfter(taskInstance.getEndTime()) // TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以 - .finished() - .count(); + .processInstanceId(processInstance.getProcessInstanceId()).taskDefinitionKeys(nextUserTaskKeys) + .taskCreatedAfter(taskInstance.getEndTime()).finished().count(); if (nextUserTaskFinishedCount > 0) { throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); } - // 5.获取需要撤回的运行任务 - List runningTaskList = taskService.createTaskQuery() - .processInstanceId(processInstance.getProcessInstanceId()) - .taskDefinitionKeys(nextUserTaskKeys) - .active().list(); - if (CollUtil.isEmpty(runningTaskList)) { + // 1.5 获取需要撤回的运行任务 + List runningTasks = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()) + .taskDefinitionKeys(nextUserTaskKeys).active().list(); + if (CollUtil.isEmpty(runningTasks)) { throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); } + + // 2.1 取消当前任务 List withdrawExecutionIds = new ArrayList<>(); - for (Task task : runningTaskList) { + for (Task task : runningTasks) { // 标记撤回任务为取消 - // TODO @芋艿:是否需要添加被撤回状态? taskService.addComment(task.getId(), taskInstance.getProcessInstanceId(), BpmCommentTypeEnum.CANCEL.getType(), BpmCommentTypeEnum.CANCEL.formatComment("前一节点撤回")); updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_WITHDRAW.getReason()); withdrawExecutionIds.add(task.getExecutionId()); } - // 6.执行撤回操作 + // 2.2 执行撤回操作 runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstance.getProcessInstanceId()) .moveExecutionsToSingleActivityId(withdrawExecutionIds, taskInstance.getTaskDefinitionKey()) From d4b1acb11e9191c508475d03690855cd2b0aeadc Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 16:40:09 +0800 Subject: [PATCH 06/26] =?UTF-8?q?feat=EF=BC=9A=E3=80=90system=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=AE=A1=E7=90=86=E3=80=91dept=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/dept/DeptController.java | 9 +++++ .../system/service/dept/DeptService.java | 7 ++++ .../system/service/dept/DeptServiceImpl.java | 15 ++++++++ .../service/dept/DeptServiceImplTest.java | 34 +++++++++++++++++-- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java index 7873d00f0a..7a243b778d 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java @@ -56,6 +56,15 @@ public class DeptController { return success(true); } + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除部门") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('system:dept:delete')") + public CommonResult deleteDeptList(@RequestParam("ids") List ids) { + deptService.deleteDeptList(ids); + return success(true); + } + @GetMapping("/list") @Operation(summary = "获取部门列表") @PreAuthorize("@ss.hasPermission('system:dept:query')") diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java index a0b765e590..06a688e606 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java @@ -36,6 +36,13 @@ public interface DeptService { */ void deleteDept(Long id); + /** + * 批量删除部门 + * + * @param ids 部门编号数组 + */ + void deleteDeptList(List ids); + /** * 获得部门信息 * diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java index 946d92df3b..6086474c60 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java @@ -88,6 +88,21 @@ public class DeptServiceImpl implements DeptService { deptMapper.deleteById(id); } + @Override + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 + public void deleteDeptList(List ids) { + // 校验是否有子部门 + for (Long id : ids) { + if (deptMapper.selectCountByParentId(id) > 0) { + throw exception(DEPT_EXITS_CHILDREN); + } + } + + // 批量删除部门 + deptMapper.deleteByIds(ids); + } + @VisibleForTesting void validateDeptExists(Long id) { if (id == null) { diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java index bcf55bda66..df16d4fc97 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java @@ -105,12 +105,40 @@ public class DeptServiceImplTest extends BaseDbUnitTest { } @Test - public void testValidateDeptExists_notFound() { + public void testDeleteDeptList_success() { + // mock 数据 + DeptDO deptDO1 = randomPojo(DeptDO.class); + deptMapper.insert(deptDO1); + DeptDO deptDO2 = randomPojo(DeptDO.class); + deptMapper.insert(deptDO2); // 准备参数 - Long id = randomLongId(); + List ids = Arrays.asList(deptDO1.getId(), deptDO2.getId()); + + // 调用 + deptService.deleteDeptList(ids); + // 校验数据不存在了 + assertNull(deptMapper.selectById(deptDO1.getId())); + assertNull(deptMapper.selectById(deptDO2.getId())); + } + + @Test + public void testDeleteDeptList_exitsChildren() { + // mock 数据 + DeptDO parentDept = randomPojo(DeptDO.class); + deptMapper.insert(parentDept); + DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> { + o.setParentId(parentDept.getId()); + o.setStatus(randomCommonStatus()); + }); + deptMapper.insert(childrenDeptDO); + DeptDO anotherDept = randomPojo(DeptDO.class); + deptMapper.insert(anotherDept); + + // 准备参数(包含有子部门的 parentDept) + List ids = Arrays.asList(parentDept.getId(), anotherDept.getId()); // 调用, 并断言异常 - assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND); + assertServiceException(() -> deptService.deleteDeptList(ids), DEPT_EXITS_CHILDREN); } @Test From e79005a0a0a88dff1a7c89af5d6403af222d487e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 17:32:12 +0800 Subject: [PATCH 07/26] =?UTF-8?q?reactor=EF=BC=9A@MockBean=20=3D>=20@Mocki?= =?UTF-8?q?toBean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codegen/java/test/serviceTest.vm | 1 - .../codegen/CodegenServiceImplTest.java | 10 +++---- .../db/DataSourceConfigServiceImplTest.java | 8 ++--- .../db/DatabaseTableServiceImplTest.java | 6 ++-- .../file/FileConfigServiceImplTest.java | 24 +++++++-------- .../service/file/FileServiceImplTest.java | 4 +-- .../infra/service/job/JobServiceImplTest.java | 9 +++--- .../java/InfraStudentServiceImplTest | 1 - .../java/InfraStudentServiceImplTest | 1 - .../java/InfraStudentServiceImplTest | 1 - .../vue2_one/java/InfraStudentServiceImplTest | 1 - .../java/InfraCategoryServiceImplTest | 1 - .../java/InfraStudentServiceImplTest | 1 - .../java/InfraStudentServiceImplTest | 1 - .../java/InfraStudentServiceImplTest | 1 - .../vue3_one/java/InfraStudentServiceImplTest | 1 - .../java/InfraCategoryServiceImplTest | 1 - .../ProductCommentServiceImplTest.java | 6 ++-- .../service/sku/ProductSkuServiceTest.java | 10 +++---- .../spu/ProductSpuServiceImplTest.java | 12 ++++---- .../aftersale/AfterSaleServiceTest.java | 10 +++---- .../BrokerageRecordServiceImplTest.java | 8 ++--- .../BrokerageWithdrawServiceImplTest.java | 10 +++---- .../order/TradeOrderUpdateServiceTest.java | 30 +++++++++---------- .../service/auth/MemberAuthServiceTest.java | 18 +++++------ .../group/MemberGroupServiceImplTest.java | 9 +++--- .../level/MemberLevelServiceImplTest.java | 14 ++++----- .../service/tag/MemberTagServiceImplTest.java | 9 +++--- .../user/MemberUserServiceImplTest.java | 8 ++--- .../pay/service/app/PayAppServiceTest.java | 8 ++--- .../channel/PayChannelServiceTest.java | 25 ++++++++-------- .../service/notify/PayNotifyServiceTest.java | 10 +++---- .../service/order/PayOrderServiceTest.java | 18 +++++------ .../service/refund/PayRefundServiceTest.java | 22 +++++++------- .../goview/GoViewDataServiceImplTest.java | 6 ++-- .../auth/AdminAuthServiceImplTest.java | 18 +++++------ .../service/dict/DictDataServiceImplTest.java | 10 +++---- .../service/dict/DictTypeServiceImplTest.java | 10 +++---- .../mail/MailAccountServiceImplTest.java | 10 +++---- .../oauth2/OAuth2ApproveServiceImplTest.java | 12 ++++---- .../oauth2/OAuth2TokenServiceImplTest.java | 6 ++-- .../permission/MenuServiceImplTest.java | 12 ++++---- .../permission/PermissionServiceTest.java | 14 ++++----- .../permission/RoleServiceImplTest.java | 6 ++-- .../service/sms/SmsChannelServiceTest.java | 21 ++++++------- .../service/sms/SmsCodeServiceImplTest.java | 6 ++-- .../sms/SmsTemplateServiceImplTest.java | 18 +++++------ .../social/SocialClientServiceImplTest.java | 14 ++++----- .../social/SocialUserServiceImplTest.java | 4 +-- .../tenant/TenantPackageServiceImplTest.java | 10 +++---- .../service/tenant/TenantServiceImplTest.java | 16 +++++----- .../user/AdminUserServiceImplTest.java | 16 +++++----- 52 files changed, 249 insertions(+), 259 deletions(-) diff --git a/yudao-module-infra/src/main/resources/codegen/java/test/serviceTest.vm b/yudao-module-infra/src/main/resources/codegen/java/test/serviceTest.vm index bfd4600f5e..974e8e8404 100644 --- a/yudao-module-infra/src/main/resources/codegen/java/test/serviceTest.vm +++ b/yudao-module-infra/src/main/resources/codegen/java/test/serviceTest.vm @@ -2,7 +2,6 @@ package ${basePackage}.module.${table.moduleName}.service.${table.businessName}; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import ${jakartaPackage}.annotation.Resource; diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java index 8ca4a0e326..1e2ecbc27c 100644 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java +++ b/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; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.util.Arrays; import java.util.Collections; @@ -60,15 +60,15 @@ public class CodegenServiceImplTest extends BaseDbUnitTest { @Resource private CodegenColumnMapper codegenColumnMapper; - @MockBean + @MockitoBean private DatabaseTableService databaseTableService; - @MockBean + @MockitoBean private CodegenBuilder codegenBuilder; - @MockBean + @MockitoBean private CodegenEngine codegenEngine; - @MockBean + @MockitoBean private CodegenProperties codegenProperties; @Test diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java index 4be9e28589..a51c3d1baa 100755 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DataSourceConfigServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper; import com.baomidou.dynamic.datasource.creator.DataSourceProperty; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; +import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.stubbing.Answer; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.List; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; @@ -46,10 +46,10 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest { @Resource private DataSourceConfigMapper dataSourceConfigMapper; - @MockBean + @MockitoBean private AES aes; - @MockBean + @MockitoBean private DynamicDataSourceProperties dynamicDataSourceProperties; @BeforeEach diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImplTest.java index e7a5072a04..15d05bb49d 100644 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImplTest.java +++ b/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; import com.baomidou.mybatisplus.generator.config.po.TableField; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; +import jakarta.annotation.Resource; import org.apache.ibatis.type.JdbcType; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.List; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; @@ -24,7 +24,7 @@ public class DatabaseTableServiceImplTest extends BaseDbUnitTest { @Resource private DatabaseTableServiceImpl databaseTableService; - @MockBean + @MockitoBean private DataSourceConfigService dataSourceConfigService; @Test diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java index cedb8754a2..a9e546fc10 100755 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java +++ b/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; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.map.MapUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; +import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientFactory; import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClient; import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig; import cn.iocoder.yudao.module.infra.framework.file.core.enums.FileStorageEnum; -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO; -import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO; -import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper; -import lombok.Data; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; import jakarta.validation.Validator; +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.io.Serializable; import java.time.LocalDateTime; import java.util.Map; @@ -53,9 +53,9 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @Resource private FileConfigMapper fileConfigMapper; - @MockBean + @MockitoBean private Validator validator; - @MockBean + @MockitoBean private FileClientFactory fileClientFactory; @Test diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java index c109b25a5c..9537ee0298 100644 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java +++ b/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; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.time.LocalDateTime; import java.util.concurrent.atomic.AtomicReference; @@ -35,7 +35,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { @Resource private FileMapper fileMapper; - @MockBean + @MockitoBean private FileConfigService fileConfigService; @BeforeEach diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImplTest.java index 7cd160c3d3..596d04d9e5 100644 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.infra.dal.mysql.job.JobMapper; import cn.iocoder.yudao.module.infra.enums.job.JobStatusEnum; import cn.iocoder.yudao.module.infra.job.job.JobLogCleanJob; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.quartz.SchedulerException; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; - -import jakarta.annotation.Resource; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; @@ -36,10 +35,10 @@ public class JobServiceImplTest extends BaseDbUnitTest { private JobServiceImpl jobService; @Resource private JobMapper jobMapper; - @MockBean + @MockitoBean private SchedulerManager schedulerManager; - @MockBean + @MockitoBean private JobLogCleanJob jobLogCleanJob; @Test diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_erp/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_erp/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_erp/java/InfraStudentServiceImplTest +++ b/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; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_inner/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_inner/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_inner/java/InfraStudentServiceImplTest +++ b/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; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_normal/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_normal/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_master_normal/java/InfraStudentServiceImplTest +++ b/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; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_one/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue2_one/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_one/java/InfraStudentServiceImplTest +++ b/yudao-module-infra/src/test/resources/codegen/windows10/vue2_one/java/InfraStudentServiceImplTest @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_tree/java/InfraCategoryServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue2_tree/java/InfraCategoryServiceImplTest index efb70fd332..74c58e1470 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue2_tree/java/InfraCategoryServiceImplTest +++ b/yudao-module-infra/src/test/resources/codegen/windows10/vue2_tree/java/InfraCategoryServiceImplTest @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_erp/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_erp/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_erp/java/InfraStudentServiceImplTest +++ b/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; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_inner/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_inner/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_inner/java/InfraStudentServiceImplTest +++ b/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; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_normal/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_normal/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_master_normal/java/InfraStudentServiceImplTest +++ b/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; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_one/java/InfraStudentServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue3_one/java/InfraStudentServiceImplTest index b5f4bf0ff2..fa325938c3 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_one/java/InfraStudentServiceImplTest +++ b/yudao-module-infra/src/test/resources/codegen/windows10/vue3_one/java/InfraStudentServiceImplTest @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_tree/java/InfraCategoryServiceImplTest b/yudao-module-infra/src/test/resources/codegen/windows10/vue3_tree/java/InfraCategoryServiceImplTest index efb70fd332..74c58e1470 100644 --- a/yudao-module-infra/src/test/resources/codegen/windows10/vue3_tree/java/InfraCategoryServiceImplTest +++ b/yudao-module-infra/src/test/resources/codegen/windows10/vue3_tree/java/InfraCategoryServiceImplTest @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.demo; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; diff --git a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java b/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java index 1b92e21f24..0e853f98cd 100644 --- a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImplTest.java +++ b/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; import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.time.LocalDateTime; import java.util.Date; @@ -48,9 +48,9 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest { @Lazy private ProductCommentServiceImpl productCommentService; - @MockBean + @MockitoBean private ProductSpuService productSpuService; - @MockBean + @MockitoBean private ProductSkuService productSkuService; public String generateNo() { diff --git a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java b/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java index 5bd79a2d2e..f1b9cfac47 100644 --- a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceTest.java +++ b/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; import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.Arrays; import java.util.List; @@ -44,11 +44,11 @@ public class ProductSkuServiceTest extends BaseDbUnitTest { @Resource private ProductSkuMapper productSkuMapper; - @MockBean + @MockitoBean private ProductSpuService productSpuService; - @MockBean + @MockitoBean private ProductPropertyService productPropertyService; - @MockBean + @MockitoBean private ProductPropertyValueService productPropertyValueService; public Long generateId() { diff --git a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java b/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java index 0273465dac..e2056fde58 100755 --- a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java +++ b/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; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.math.RoundingMode; import java.util.ArrayList; @@ -58,15 +58,15 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @Resource private ProductSpuMapper productSpuMapper; - @MockBean + @MockitoBean private ProductSkuServiceImpl productSkuService; - @MockBean + @MockitoBean private ProductCategoryServiceImpl categoryService; - @MockBean + @MockitoBean private ProductBrandServiceImpl brandService; - @MockBean + @MockitoBean private ProductPropertyService productPropertyService; - @MockBean + @MockitoBean private ProductPropertyValueService productPropertyValueService; public String generateNo() { diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceTest.java index 4ae510a958..85f5ca4c65 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceTest.java +++ b/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; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; @@ -54,15 +54,15 @@ public class AfterSaleServiceTest extends BaseDbUnitTest { @Resource private AfterSaleLogMapper tradeAfterSaleLogMapper; - @MockBean + @MockitoBean private TradeOrderUpdateService tradeOrderUpdateService; @Resource private TradeOrderQueryService tradeOrderQueryService; - @MockBean + @MockitoBean private PayRefundApi payRefundApi; - @MockBean + @MockitoBean private TradeOrderProperties tradeOrderProperties; @Test diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java index 06d9bffe07..141e865eed 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImplTest.java +++ b/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 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO; import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageRecordMapper; import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.math.RoundingMode; import static cn.hutool.core.util.RandomUtil.randomEle; @@ -39,9 +39,9 @@ public class BrokerageRecordServiceImplTest extends BaseDbUnitTest { @Resource private BrokerageRecordMapper brokerageRecordMapper; - @MockBean + @MockitoBean private TradeConfigService tradeConfigService; - @MockBean + @MockitoBean private BrokerageUserService brokerageUserService; @Test diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java index 386a7dd6c0..61a1453994 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java +++ b/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; import jakarta.validation.Validator; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; @@ -36,14 +36,14 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest { @Resource private BrokerageWithdrawMapper brokerageWithdrawMapper; - @MockBean + @MockitoBean private BrokerageRecordService brokerageRecordService; - @MockBean + @MockitoBean private BrokerageUserService brokerageUserService; - @MockBean + @MockitoBean private TradeConfigService tradeConfigService; - @MockBean + @MockitoBean private NotifyMessageSendApi notifyMessageSendApi; @Resource diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java index fb19f074b8..690f41b9b2 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java +++ b/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; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.time.Duration; @@ -67,34 +67,34 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @Resource private TradeOrderItemMapper tradeOrderItemMapper; - @MockBean + @MockitoBean private MemberUserApi memberUserApi; - @MockBean + @MockitoBean private ProductSpuApi productSpuApi; - @MockBean + @MockitoBean private ProductSkuApi productSkuApi; - @MockBean + @MockitoBean private ProductCommentApi productCommentApi; - // @MockBean + // @MockitoBean // private PriceApi priceApi; - @MockBean + @MockitoBean private PayOrderApi payOrderApi; - @MockBean + @MockitoBean private MemberAddressApi addressApi; - @MockBean + @MockitoBean private CouponApi couponApi; - @MockBean + @MockitoBean private TradeOrderProperties tradeOrderProperties; - @MockBean + @MockitoBean private TradeNoRedisDAO tradeNoRedisDAO; - @MockBean + @MockitoBean private TradeOrderHandler tradeOrderHandler; - @MockBean + @MockitoBean private TradePriceCalculator tradePriceCalculator; - @MockBean + @MockitoBean private NotifyMessageSendApi notifyMessageSendApi; - @MockBean + @MockitoBean private DeliveryExpressService deliveryExpressService; @BeforeEach diff --git a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java b/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java index 6eed0f1753..1fa4241e8c 100644 --- a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java +++ b/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceTest.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.member.service.auth; +import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; @@ -8,14 +9,13 @@ import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; import cn.iocoder.yudao.module.member.service.user.MemberUserService; import cn.iocoder.yudao.module.system.api.logger.LoginLogApi; -import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.api.social.SocialUserApi; -import org.springframework.boot.test.mock.mockito.MockBean; +import jakarta.annotation.Resource; import org.springframework.context.annotation.Import; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.function.Consumer; import static cn.hutool.core.util.RandomUtil.randomEle; @@ -36,17 +36,17 @@ public class MemberAuthServiceTest extends BaseDbAndRedisUnitTest { @Resource private MemberAuthServiceImpl authService; - @MockBean + @MockitoBean private MemberUserService userService; - @MockBean + @MockitoBean private SmsCodeApi smsCodeApi; - @MockBean + @MockitoBean private LoginLogApi loginLogApi; - @MockBean + @MockitoBean private OAuth2TokenCommonApi oauth2TokenApi; - @MockBean + @MockitoBean private SocialUserApi socialUserApi; - @MockBean + @MockitoBean private PasswordEncoder passwordEncoder; @Resource diff --git a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java b/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java index 3762b0f3b6..d1ec8752a2 100644 --- a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java +++ b/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 import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO; import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper; import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; @@ -42,7 +41,7 @@ public class MemberGroupServiceImplTest extends BaseDbUnitTest { @Resource private MemberGroupMapper groupMapper; - @MockBean + @MockitoBean private MemberUserService memberUserService; @Test diff --git a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java b/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java index 9986b4a718..17f034857d 100644 --- a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java +++ b/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 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO; import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper; import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.List; import java.util.function.Consumer; @@ -40,11 +40,11 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest { @Resource private MemberLevelMapper memberlevelMapper; - @MockBean + @MockitoBean private MemberLevelRecordService memberLevelRecordService; - @MockBean + @MockitoBean private MemberExperienceRecordService memberExperienceRecordService; - @MockBean + @MockitoBean private MemberUserService memberUserService; @Test diff --git a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImplTest.java b/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImplTest.java index 1bc361cd09..011d6c372a 100644 --- a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/tag/MemberTagServiceImplTest.java +++ b/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 import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO; import cn.iocoder.yudao.module.member.dal.mysql.tag.MemberTagMapper; import cn.iocoder.yudao.module.member.service.user.MemberUserService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; @@ -39,7 +38,7 @@ public class MemberTagServiceImplTest extends BaseDbUnitTest { @Resource private MemberTagMapper tagMapper; - @MockBean + @MockitoBean private MemberUserService memberUserService; @Test diff --git a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java b/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java index f0e469304d..ec827484b3 100644 --- a/yudao-module-member/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java +++ b/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; import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.util.function.Consumer; @@ -44,13 +44,13 @@ public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest { @Resource private MemberUserMapper userMapper; - @MockBean + @MockitoBean private MemberAuthServiceImpl authService; - @MockBean + @MockitoBean private PasswordEncoder passwordEncoder; - @MockBean + @MockitoBean private SmsCodeApi smsCodeApi; // TODO 芋艿:后续重构这个单测 diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java index 326d58617e..0831999165 100644 --- a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java +++ b/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; import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.Map; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; @@ -46,9 +46,9 @@ public class PayAppServiceTest extends BaseDbUnitTest { @Resource private PayAppMapper appMapper; - @MockBean + @MockitoBean private PayOrderService orderService; - @MockBean + @MockitoBean private PayRefundService refundService; @Test diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java index 4e05b87c03..eb8f69474d 100644 --- a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java +++ b/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; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClientFactory; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig; import com.alibaba.fastjson.JSON; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; import jakarta.validation.Validator; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.Collections; import java.util.List; @@ -28,7 +28,8 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @Import({PayChannelServiceImpl.class}) public class PayChannelServiceTest extends BaseDbUnitTest { @@ -39,9 +40,9 @@ public class PayChannelServiceTest extends BaseDbUnitTest { @Resource private PayChannelMapper channelMapper; - @MockBean + @MockitoBean private PayClientFactory payClientFactory; - @MockBean + @MockitoBean private Validator validator; @Test diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java index cfd01ae626..3a39172bf3 100644 --- a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceTest.java +++ b/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; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundServiceImpl; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.time.Duration; import java.util.List; @@ -54,9 +54,9 @@ public class PayNotifyServiceTest extends BaseDbUnitTest { @Resource private PayNotifyServiceImpl notifyService; - @MockBean + @MockitoBean private PayOrderService orderService; - @MockBean + @MockitoBean private PayRefundService refundService; @Resource @@ -64,7 +64,7 @@ public class PayNotifyServiceTest extends BaseDbUnitTest { @Resource private PayNotifyLogMapper notifyLogMapper; - @MockBean + @MockitoBean private RedissonClient redissonClient; @Test diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java index d5e449acd9..6fb69594b6 100755 --- a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java +++ b/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; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; -import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO; @@ -19,9 +15,13 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper; import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; @@ -29,8 +29,8 @@ import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.time.Duration; import java.time.LocalDateTime; @@ -65,13 +65,13 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest { @Resource private PayOrderExtensionMapper orderExtensionMapper; - @MockBean + @MockitoBean private PayProperties properties; - @MockBean + @MockitoBean private PayAppService appService; - @MockBean + @MockitoBean private PayChannelService channelService; - @MockBean + @MockitoBean private PayNotifyService notifyService; @BeforeEach diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java index 87c4cdbe43..59c7e2533d 100755 --- a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java +++ b/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; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; -import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; @@ -16,21 +12,25 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.PayChannelEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; @@ -62,15 +62,15 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @Resource private PayRefundMapper refundMapper; - @MockBean + @MockitoBean private PayProperties payProperties; - @MockBean + @MockitoBean private PayOrderService orderService; - @MockBean + @MockitoBean private PayAppService appService; - @MockBean + @MockitoBean private PayChannelService channelService; - @MockBean + @MockitoBean private PayNotifyService notifyService; @BeforeEach diff --git a/yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/service/goview/GoViewDataServiceImplTest.java b/yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/service/goview/GoViewDataServiceImplTest.java index 370d3ac3f8..55795cbfba 100644 --- a/yudao-module-report/src/test/java/cn/iocoder/yudao/module/report/service/goview/GoViewDataServiceImplTest.java +++ b/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; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.jdbc.support.rowset.SqlRowSetMetaData; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -23,7 +23,7 @@ public class GoViewDataServiceImplTest extends BaseDbUnitTest { @Resource private GoViewDataServiceImpl goViewDataService; - @MockBean + @MockitoBean private JdbcTemplate jdbcTemplate; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java index 62baea325a..0475437750 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java @@ -26,8 +26,8 @@ import jakarta.validation.Validation; import jakarta.validation.Validator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; @@ -45,21 +45,21 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { @Resource private AdminAuthServiceImpl authService; - @MockBean + @MockitoBean private AdminUserService userService; - @MockBean + @MockitoBean private CaptchaService captchaService; - @MockBean + @MockitoBean private LoginLogService loginLogService; - @MockBean + @MockitoBean private SocialUserService socialUserService; - @MockBean + @MockitoBean private SmsCodeApi smsCodeApi; - @MockBean + @MockitoBean private OAuth2TokenService oauth2TokenService; - @MockBean + @MockitoBean private MemberService memberService; - @MockBean + @MockitoBean private Validator validator; @BeforeEach diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImplTest.java index 999570ee60..31886c885b 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImplTest.java @@ -9,11 +9,11 @@ import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataSave import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO; import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.List; import java.util.function.Consumer; @@ -35,7 +35,7 @@ public class DictDataServiceImplTest extends BaseDbUnitTest { @Resource private DictDataMapper dictDataMapper; - @MockBean + @MockitoBean private DictTypeService dictTypeService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImplTest.java index e04b96f4b3..c73cc57fad 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/dict/DictTypeServiceImplTest.java @@ -8,11 +8,11 @@ import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypePage import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO; import cn.iocoder.yudao.module.system.dal.mysql.dict.DictTypeMapper; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.List; import java.util.function.Consumer; @@ -36,7 +36,7 @@ public class DictTypeServiceImplTest extends BaseDbUnitTest { @Resource private DictTypeMapper dictTypeMapper; - @MockBean + @MockitoBean private DictDataService dictDataService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java index a3f237e86e..3bab82510c 100755 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java @@ -6,11 +6,11 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.List; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; @@ -36,7 +36,7 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest { @Resource private MailAccountMapper mailAccountMapper; - @MockBean + @MockitoBean private MailTemplateService mailTemplateService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java index 142201c296..97739174d2 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java @@ -8,21 +8,23 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ApproveMapper; +import jakarta.annotation.Resource; import org.assertj.core.util.Lists; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import static cn.hutool.core.util.RandomUtil.*; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -41,7 +43,7 @@ public class OAuth2ApproveServiceImplTest extends BaseDbUnitTest { @Resource private OAuth2ApproveMapper oauth2ApproveMapper; - @MockBean + @MockitoBean private OAuth2ClientService oauth2ClientService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java index 03f78b4ca7..0aad4446a1 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java @@ -19,8 +19,8 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService; import jakarta.annotation.Resource; import org.assertj.core.util.Lists; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.time.LocalDateTime; import java.util.List; @@ -52,9 +52,9 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { @Resource private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO; - @MockBean + @MockitoBean private OAuth2ClientService oauth2ClientService; - @MockBean + @MockitoBean private AdminUserService adminUserService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java index cc393796cc..00378581c6 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImplTest.java @@ -8,11 +8,11 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper; import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum; import cn.iocoder.yudao.module.system.service.tenant.TenantService; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.Collection; import java.util.Collections; import java.util.List; @@ -40,9 +40,9 @@ public class MenuServiceImplTest extends BaseDbUnitTest { @Resource private MenuMapper menuMapper; - @MockBean + @MockitoBean private PermissionService permissionService; - @MockBean + @MockitoBean private TenantService tenantService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java index 9180444910..52ba3f9389 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.hutool.core.collection.CollUtil; import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; -import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; @@ -16,12 +16,12 @@ import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import java.util.Set; @@ -48,13 +48,13 @@ public class PermissionServiceTest extends BaseDbUnitTest { @Resource private UserRoleMapper userRoleMapper; - @MockBean + @MockitoBean private RoleService roleService; - @MockBean + @MockitoBean private MenuService menuService; - @MockBean + @MockitoBean private DeptService deptService; - @MockBean + @MockitoBean private AdminUserService userService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java index fc87193c44..101db9f993 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java @@ -10,12 +10,12 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper; import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; import java.util.Set; @@ -44,7 +44,7 @@ public class RoleServiceImplTest extends BaseDbUnitTest { @Resource private RoleMapper roleMapper; - @MockBean + @MockitoBean private PermissionService permissionService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java index 295911a178..1e0890bbb4 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java @@ -3,19 +3,19 @@ package cn.iocoder.yudao.module.system.service.sms; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; -import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory; -import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; @@ -28,7 +28,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @Import(SmsChannelServiceImpl.class) public class SmsChannelServiceTest extends BaseDbUnitTest { @@ -39,9 +40,9 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { @Resource private SmsChannelMapper smsChannelMapper; - @MockBean + @MockitoBean private SmsClientFactory smsClientFactory; - @MockBean + @MockitoBean private SmsTemplateService smsTemplateService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java index 093a5aeff7..b492f1268d 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java @@ -12,8 +12,8 @@ import cn.iocoder.yudao.module.system.framework.sms.config.SmsCodeProperties; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.time.Duration; import java.time.LocalDateTime; @@ -38,9 +38,9 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { @Resource private SmsCodeMapper smsCodeMapper; - @MockBean + @MockitoBean private SmsCodeProperties smsCodeProperties; - @MockBean + @MockitoBean private SmsSendService smsSendService; @BeforeEach diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java index 22f737d80e..344f566847 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImplTest.java @@ -5,9 +5,6 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; -import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; -import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO; @@ -15,12 +12,15 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO; import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper; import cn.iocoder.yudao.module.system.enums.sms.SmsTemplateTypeEnum; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import com.google.common.collect.Lists; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -45,9 +45,9 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest { @Resource private SmsTemplateMapper smsTemplateMapper; - @MockBean + @MockitoBean private SmsChannelService smsChannelService; - @MockBean + @MockitoBean private SmsClient smsClient; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java index 5d75e8834b..ae87de0fa2 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java @@ -28,9 +28,9 @@ import me.zhyd.oauth.request.AuthRequest; import me.zhyd.oauth.utils.AuthStateUtils; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; @@ -55,18 +55,18 @@ public class SocialClientServiceImplTest extends BaseDbUnitTest { @Resource private SocialClientMapper socialClientMapper; - @MockBean + @MockitoBean private AuthRequestFactory authRequestFactory; - @MockBean + @MockitoBean private WxMpService wxMpService; - @MockBean + @MockitoBean private WxMpProperties wxMpProperties; - @MockBean + @MockitoBean private StringRedisTemplate stringRedisTemplate; - @MockBean + @MockitoBean private WxMaService wxMaService; - @MockBean + @MockitoBean private WxMaProperties wxMaProperties; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java index 84164155e5..ece91fb08c 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java @@ -14,8 +14,8 @@ import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import jakarta.annotation.Resource; import me.zhyd.oauth.model.AuthUser; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.util.List; @@ -50,7 +50,7 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest { @Resource private SocialUserBindMapper socialUserBindMapper; - @MockBean + @MockitoBean private SocialClientService socialClientService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java index 0e030f3b2a..626b754cde 100755 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImplTest.java @@ -8,11 +8,11 @@ import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.Tenant import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO; import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; - import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; @@ -42,7 +42,7 @@ public class TenantPackageServiceImplTest extends BaseDbUnitTest { @Resource private TenantPackageMapper tenantPackageMapper; - @MockBean + @MockitoBean private TenantService tenantService; @Test diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java index f4d4f862bc..cba0d8f888 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java @@ -20,12 +20,12 @@ import cn.iocoder.yudao.module.system.service.permission.RoleService; import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler; import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler; import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -import jakarta.annotation.Resource; import java.time.LocalDateTime; import java.util.Arrays; import java.util.Collections; @@ -60,17 +60,17 @@ public class TenantServiceImplTest extends BaseDbUnitTest { @Resource private TenantMapper tenantMapper; - @MockBean + @MockitoBean private TenantProperties tenantProperties; - @MockBean + @MockitoBean private TenantPackageService tenantPackageService; - @MockBean + @MockitoBean private AdminUserService userService; - @MockBean + @MockitoBean private RoleService roleService; - @MockBean + @MockitoBean private MenuService menuService; - @MockBean + @MockitoBean private PermissionService permissionService; @BeforeEach diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java index 6256c424d9..e5aa65a459 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java @@ -31,9 +31,9 @@ import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.stubbing.Answer; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.util.Collection; import java.util.List; @@ -68,19 +68,19 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { @Resource private UserPostMapper userPostMapper; - @MockBean + @MockitoBean private DeptService deptService; - @MockBean + @MockitoBean private PostService postService; - @MockBean + @MockitoBean private PermissionService permissionService; - @MockBean + @MockitoBean private PasswordEncoder passwordEncoder; - @MockBean + @MockitoBean private TenantService tenantService; - @MockBean + @MockitoBean private FileApi fileApi; - @MockBean + @MockitoBean private ConfigApi configApi; @BeforeEach From 8ee1fef9e49130776bd733da1b0fa93606f51396 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 19:41:59 +0800 Subject: [PATCH 08/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90crm=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86=E3=80=91=E8=81=94=E7=B3=BB=E4=BA=BA?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=B6=EF=BC=8C=E6=A0=A1=E9=AA=8C=E6=9D=83?= =?UTF-8?q?=E9=99=90=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/crm/service/contact/CrmContactServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java index 958fc7520d..c3855f70aa 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java @@ -159,10 +159,10 @@ public class CrmContactServiceImpl implements CrmContactService { // 2. 删除联系人 contactMapper.deleteById(id); - // 4.1 删除数据权限 - permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id); - // 4.2 删除商机关联 + // 4.1 删除商机关联 contactBusinessService.deleteContactBusinessByContactId(id); + // 4.2 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id); // 记录操作日志上下文 LogRecordContext.putVariable("contactName", contact.getName()); From 4fbdd3dc5cc87005bdfe7451b30c4c72c7567235 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 20:56:25 +0800 Subject: [PATCH 09/26] =?UTF-8?q?feat=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91=E5=85=BC=E5=AE=B9=20bpmn=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E4=B8=8B=EF=BC=8C=E4=BD=BF=E7=94=A8=20START?= =?UTF-8?q?=5FUSER=5FNODE=5FID=20=E7=9A=84=E9=A2=84=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceServiceImpl.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 30adda6341..2adc2a05dd 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -67,7 +67,6 @@ import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; @@ -415,7 +414,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService endActivities.forEach(activity -> { // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点 if (ELEMENT_EVENT_START.equals(activity.getActivityType()) - && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) { + && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType()) + && !CollUtil.contains(activities, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent + historicActivity -> historicActivity.getActivityId().equals(START_USER_NODE_ID))) { ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID) .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); ActivityNode startNode = new ActivityNode().setId(startTask.getId()) @@ -555,7 +556,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 情况一:BPMN 设计器 if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { List flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); - return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, + return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn( + startUserId, bpmnModel, flowElements, processDefinitionInfo, processVariables, flowElement, runActivityIds)); } // 情况二:SIMPLE 设计器 @@ -563,7 +565,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); - return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, + return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple( + startUserId, bpmnModel, processDefinitionInfo, processVariables, simpleNode, runActivityIds)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); @@ -618,8 +621,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService return null; } - private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, - BpmProcessDefinitionInfoDO processDefinitionInfo, Map processVariables, + private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List flowElements, + BpmProcessDefinitionInfoDO processDefinitionInfo, + Map processVariables, FlowElement node, Set runActivityIds) { if (runActivityIds.contains(node.getId())) { return null; @@ -634,6 +638,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 1. 开始节点 if (node instanceof StartEvent) { + if (CollUtil.contains(flowElements, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent + flowElement -> flowElement.getId().equals(START_USER_NODE_ID))) { + return null; + } return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName()) .setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()); } From 2653b614d17e1b2e60b692738d20644a04979c87 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 22:08:39 +0800 Subject: [PATCH 10/26] =?UTF-8?q?feat=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91=E5=AE=A1=E6=89=B9=E4=BA=BA=E8=87=AA?= =?UTF-8?q?=E9=80=89=E6=97=B6=EF=BC=8C=E7=9B=B8=E5=90=8C=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/service/task/BpmProcessInstanceServiceImpl.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 2adc2a05dd..b4a871e145 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -220,11 +220,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService List simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo, processVariables, activities); - // 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点 - if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) { - simulateActivityNodes.removeIf(node -> - BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy())); - } // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, From 192bbf6e929bf916e6d3a8058c8b318206541475 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 3 Aug 2025 19:16:37 +0800 Subject: [PATCH 11/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91BpmSequentialMultiInstanceBehavior?= =?UTF-8?q?=20=E5=85=BC=E5=AE=B9=20CallActivity=E3=80=81SubProcess=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmParallelMultiInstanceBehavior.java | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index 57f4d393f3..87e6a605ff 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -7,44 +7,35 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; -import org.flowable.bpmn.model.Activity; -import org.flowable.bpmn.model.CallActivity; -import org.flowable.bpmn.model.FlowElement; -import org.flowable.bpmn.model.UserTask; +import org.flowable.bpmn.model.*; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; -import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.engine.impl.persistence.entity.ExecutionEntity; import java.util.List; import java.util.Set; /** - * 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配 - * 第一步,基于分配规则,计算出分配任务的【多个】候选人们。 - * 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它 + * 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配 * - * @author kemengkai - * @since 2022-04-21 16:57 + * 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样 + * + * @author 芋道源码 */ @Setter -public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior { +public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior { private BpmTaskCandidateInvoker taskCandidateInvoker; - public BpmParallelMultiInstanceBehavior(Activity activity, - AbstractBpmnActivityBehavior innerActivityBehavior) { + public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) { super(activity, innerActivityBehavior); } /** - * 重写该方法,主要实现两个功能: - * 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的 - * 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人 + * 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似 * - * 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量 - * - * @param execution 执行任务 - * @return 数量 + * 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序! */ @Override protected int resolveNrOfInstances(DelegateExecution execution) { @@ -58,8 +49,9 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); // 第二步,获取任务的所有处理人 + // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人 @SuppressWarnings("unchecked") - Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class); + Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class); if (assigneeUserIds == null) { assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); if (CollUtil.isEmpty(assigneeUserIds)) { @@ -88,4 +80,19 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav return super.resolveNrOfInstances(execution); } + @Override + protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) { + // 参见 https://t.zsxq.com/53Meo 情况 + if (execution.getCurrentFlowElement() instanceof CallActivity + || execution.getCurrentFlowElement() instanceof SubProcess) { + super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter); + return; + } + // 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F + super.collectionExpression = null; + super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); + super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); + super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter); + } + } From 3d68072584e89246942ef1307c338d1a8e4044f5 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 3 Aug 2025 19:45:55 +0800 Subject: [PATCH 12/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90framework=20?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E3=80=91GlobalExceptionHandler=20=E5=A4=84?= =?UTF-8?q?=E7=90=86=20guava=20UncheckedExecutionException=20=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=EF=BC=8Chttps://t.zsxq.com/UszdH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao-spring-boot-starter-web/pom.xml | 8 ++++++- .../core/handler/GlobalExceptionHandler.java | 21 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-web/pom.xml b/yudao-framework/yudao-spring-boot-starter-web/pom.xml index d04db57c47..92ebc918f0 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-web/pom.xml @@ -53,7 +53,13 @@ provided - + + + com.google.guava + guava + provided + + org.jsoup jsoup diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java index 627a5ea784..9f2d6bd087 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java +++ b/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; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.JakartaServletUtil; import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi; import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO; import cn.iocoder.yudao.framework.common.exception.ServiceException; @@ -23,6 +22,7 @@ import jakarta.validation.ConstraintViolationException; import jakarta.validation.ValidationException; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.concurrent.UncheckedExecutionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.access.AccessDeniedException; import org.springframework.util.Assert; @@ -111,6 +111,9 @@ public class GlobalExceptionHandler { if (ex instanceof AccessDeniedException) { return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); } + if (ex instanceof UncheckedExecutionException && ex.getCause() != ex) { + return allExceptionHandler(request, ex.getCause()); + } return defaultExceptionHandler(request, ex); } @@ -266,6 +269,16 @@ public class GlobalExceptionHandler { return CommonResult.error(FORBIDDEN); } + /** + * 处理 Guava UncheckedExecutionException + * + * 例如说,缓存加载报错,可见 https://t.zsxq.com/UszdH + */ + @ExceptionHandler(value = UncheckedExecutionException.class) + public CommonResult uncheckedExecutionExceptionHandler(HttpServletRequest req, UncheckedExecutionException ex) { + return allExceptionHandler(req, ex.getCause()); + } + /** * 处理业务异常 ServiceException * @@ -344,12 +357,12 @@ public class GlobalExceptionHandler { errorLog.setApplicationName(applicationName); errorLog.setRequestUrl(request.getRequestURI()); Map requestParams = MapUtil.builder() - .put("query", JakartaServletUtil.getParamMap(request)) - .put("body", JakartaServletUtil.getBody(request)).build(); + .put("query", ServletUtils.getParamMap(request)) + .put("body", ServletUtils.getBody(request)).build(); errorLog.setRequestParams(JsonUtils.toJsonString(requestParams)); errorLog.setRequestMethod(request.getMethod()); errorLog.setUserAgent(ServletUtils.getUserAgent(request)); - errorLog.setUserIp(JakartaServletUtil.getClientIP(request)); + errorLog.setUserIp(ServletUtils.getClientIP(request)); errorLog.setExceptionTime(LocalDateTime.now()); } From 896113bb404efb6f06012be04c2edc80f763cc62 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 3 Aug 2025 19:46:56 +0800 Subject: [PATCH 13/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90framework=20?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E3=80=91GlobalExceptionHandler=20=E5=A4=84?= =?UTF-8?q?=E7=90=86=20guava=20UncheckedExecutionException=20=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=EF=BC=8Chttps://t.zsxq.com/UszdH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/web/core/handler/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java index 9f2d6bd087..22234ba3bf 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java @@ -16,13 +16,13 @@ import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.google.common.util.concurrent.UncheckedExecutionException; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.ValidationException; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.concurrent.UncheckedExecutionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.access.AccessDeniedException; import org.springframework.util.Assert; From dd1696878a4aa4d63ece40129a11b31f1d964892 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 3 Aug 2025 19:53:54 +0800 Subject: [PATCH 14/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90infra=20=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=E3=80=91FileController=20=E7=9A=84?= =?UTF-8?q?=20uploadFile=20=E6=8E=A5=E5=8F=A3=EF=BC=8C=E7=BC=BA=E5=B0=91?= =?UTF-8?q?=20@Valid=20=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/infra/controller/admin/file/FileController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index d5611b7a06..afcf716237 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -43,7 +43,7 @@ public class FileController { @PostMapping("/upload") @Operation(summary = "上传文件", description = "模式一:后端上传文件") - public CommonResult uploadFile(FileUploadReqVO uploadReqVO) throws Exception { + public CommonResult uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception { MultipartFile file = uploadReqVO.getFile(); byte[] content = IoUtil.readBytes(file.getInputStream()); return success(fileService.createFile(content, file.getOriginalFilename(), From 7d1946d91e1115f4d3cb79e76524eb48472b6b91 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 4 Aug 2025 10:00:08 +0800 Subject: [PATCH 15/26] =?UTF-8?q?reactor=EF=BC=9A=E3=80=90system=20?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=8A=9F=E8=83=BD=E3=80=91=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E2=80=9C=E6=96=87=E5=AD=97=E9=AA=8C=E8=AF=81=E7=A0=81=E2=80=9D?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/PictureWordCaptchaServiceImpl.java | 79 ++++++++----------- .../src/main/resources/application.yaml | 2 +- 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java index 354da03e51..ed15a0277b 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.system.framework.captcha.core; -import com.anji.captcha.model.common.CaptchaTypeEnum; import com.anji.captcha.model.common.RepCodeEnum; import com.anji.captcha.model.common.ResponseModel; import com.anji.captcha.model.vo.CaptchaVO; @@ -10,12 +9,12 @@ import com.anji.captcha.util.AESUtil; import com.anji.captcha.util.ImageUtils; import com.anji.captcha.util.RandomUtils; import org.apache.commons.lang3.StringUtils; +import cn.hutool.core.util.RandomUtil; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.util.Properties; -import java.util.Random; /** * 图片文字验证码 @@ -25,11 +24,18 @@ import java.util.Random; */ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { + /** + * 验证码的基础字符 + */ + private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + /** + * 验证码长度 + */ + private static final Integer LENGTH = 4; + private static final int WIDTH = 120; private static final int HEIGHT = 40; private static final int LINES = 10; - private static final Random RANDOM = new Random(); - private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; @Override public void init(Properties config) { @@ -44,18 +50,14 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @Override public String captchaType() { return "pictureWord"; -// return CaptchaTypeEnum.PICTURE_WORD.getCodeValue(); } @Override public ResponseModel get(CaptchaVO captchaVO) { - String text = generateRandomText(4); - + String text = generateRandomText(LENGTH); CaptchaVO imageData = getImageData(text); - - // pointJson不传到前端,只做后端校验,测试时放开 + // pointJson 不传到前端,只做后端校验,测试时放开 // imageData.setPointJson(text); - return ResponseModel.successData(imageData); } @@ -75,22 +77,22 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { String codeValue = CaptchaServiceFactory.getCache(cacheType).get(codeKey); String code = getCodeByCodeValue(codeValue); String secretKey = getSecretKeyByCodeValue(codeValue); - //验证码只用一次,即刻失效 + // 验证码只用一次,即刻失效 CaptchaServiceFactory.getCache(cacheType).delete(codeKey); - // 用户输入的验证码(CaptchaVO中没有预留字段,暂时用pointJson,无需加解密) + // 用户输入的验证码(CaptchaVO 中 没有预留字段,暂时用 pointJson 无需加解密) String userCode = captchaVO.getPointJson(); if (!StringUtils.equalsIgnoreCase(code, userCode)) { afterValidateFail(captchaVO); return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR); } - //校验成功,将信息存入缓存 + // 校验成功,将信息存入缓存 String value; try { value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(userCode), secretKey); } catch (Exception e) { - logger.error("AES加密失败", e); + logger.error("AES 加密失败", e); afterValidateFail(captchaVO); return ResponseModel.errorMsg(e.getMessage()); } @@ -112,7 +114,7 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) { return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID); } - //二次校验取值后,即刻失效 + // 二次校验取值后,即刻失效 CaptchaServiceFactory.getCache(cacheType).delete(codeKey); } catch (Exception e) { logger.error("验证码解析失败", e); @@ -130,42 +132,35 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { // 设置背景色 g.setColor(getRandomColor(200, 250)); g.fillRect(0, 0, WIDTH, HEIGHT); - // 绘制干扰线 for (int i = 0; i < LINES; i++) { g.setColor(getRandomColor(100, 200)); - int x1 = RANDOM.nextInt(WIDTH); - int y1 = RANDOM.nextInt(HEIGHT); - int x2 = RANDOM.nextInt(WIDTH); - int y2 = RANDOM.nextInt(HEIGHT); + int x1 = RandomUtil.randomInt(WIDTH); + int y1 = RandomUtil.randomInt(HEIGHT); + int x2 = RandomUtil.randomInt(WIDTH); + int y2 = RandomUtil.randomInt(HEIGHT); g.drawLine(x1, y1, x2, y2); } - // 设置字体 g.setFont(new Font("Arial", Font.BOLD, 24)); - // 绘制验证码文本 for (int i = 0; i < text.length(); i++) { g.setColor(getRandomColor(20, 130)); - // 文字旋转 AffineTransform affineTransform = new AffineTransform(); int x = 20 + i * 20; - int y = 24 + RANDOM.nextInt(8); + int y = 24 + RandomUtil.randomInt(8); // 旋转范围 -45 ~ 45 - affineTransform.setToRotation(Math.toRadians(RandomUtils.getRandomInt(-45, 45)), x, y); + affineTransform.setToRotation(Math.toRadians(RandomUtil.randomInt(-45, 45)), x, y); g.setTransform(affineTransform); - g.drawString(text.charAt(i) + "", x, y); } - // 添加噪点 for (int i = 0; i < 100; i++) { - int x = RANDOM.nextInt(WIDTH); - int y = RANDOM.nextInt(HEIGHT); + int x = RandomUtil.randomInt(WIDTH); + int y = RandomUtil.randomInt(HEIGHT); image.setRGB(x, y, getRandomColor(0, 255).getRGB()); } - g.dispose(); String secretKey = null; @@ -177,7 +172,7 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(image).replaceAll("\r|\n", "")); dataVO.setToken(RandomUtils.getUUID()); // dataVO.setSecretKey(secretKey); - //将坐标信息存入redis中 + // 将坐标信息存入 redis 中 String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken()); CaptchaServiceFactory.getCache(cacheType).set(codeKey, getCodeValue(text, secretKey), EXPIRESIN_SECONDS); return dataVO; @@ -196,14 +191,11 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { } private Color getRandomColor(int min, int max) { - if (min > max) { - int temp = min; - min = max; - max = temp; - } - int r = min + RANDOM.nextInt(max - min); - int g = min + RANDOM.nextInt(max - min); - int b = min + RANDOM.nextInt(max - min); + int minVal = Math.min(min, max); + int maxVal = Math.max(min, max); + int r = RandomUtil.randomInt(minVal, maxVal); + int g = RandomUtil.randomInt(minVal, maxVal); + int b = RandomUtil.randomInt(minVal, maxVal); return new Color(r, g, b); } @@ -212,16 +204,9 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { * * @param length 长度 * @return {@link String} - * @author Rex - * @since 2025/6/26 15:20 */ public static String generateRandomText(int length) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - int index = RANDOM.nextInt(CHARACTERS.length()); - sb.append(CHARACTERS.charAt(index)); - } - return sb.toString(); + return RandomUtil.randomString(CHARACTERS, length); } } \ No newline at end of file diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 6519cbf0e5..d82d3974a5 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -107,7 +107,7 @@ aj: cache-type: redis # 缓存 local/redis... cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存 timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行 - type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选 + type: blockPuzzle # 验证码类型 default 三种都实例化。blockPuzzle 滑块拼图、clickWord 文字点选、pictureWord 文本输入 water-mark: 芋道源码 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode interference-options: 0 # 滑动干扰项(0/1/2) req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false From 8108b9b82650b407c5eee2cb67983ff7c8be49c0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 4 Aug 2025 12:52:07 +0800 Subject: [PATCH 16/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91BpmSequentialMultiInstanceBehavior?= =?UTF-8?q?=20=E5=85=BC=E5=AE=B9=20CallActivity=E3=80=81SubProcess=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmParallelMultiInstanceBehavior.java | 49 ++++++++----------- .../BpmSequentialMultiInstanceBehavior.java | 11 +++-- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index 87e6a605ff..57f4d393f3 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -7,35 +7,44 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; -import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Activity; +import org.flowable.bpmn.model.CallActivity; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; -import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; -import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; import java.util.List; import java.util.Set; /** - * 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配 + * 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配 + * 第一步,基于分配规则,计算出分配任务的【多个】候选人们。 + * 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它 * - * 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样 - * - * @author 芋道源码 + * @author kemengkai + * @since 2022-04-21 16:57 */ @Setter -public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior { +public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior { private BpmTaskCandidateInvoker taskCandidateInvoker; - public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) { + public BpmParallelMultiInstanceBehavior(Activity activity, + AbstractBpmnActivityBehavior innerActivityBehavior) { super(activity, innerActivityBehavior); } /** - * 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似 + * 重写该方法,主要实现两个功能: + * 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的 + * 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人 * - * 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序! + * 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量 + * + * @param execution 执行任务 + * @return 数量 */ @Override protected int resolveNrOfInstances(DelegateExecution execution) { @@ -49,9 +58,8 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); // 第二步,获取任务的所有处理人 - // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人 @SuppressWarnings("unchecked") - Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class); + Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class); if (assigneeUserIds == null) { assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); if (CollUtil.isEmpty(assigneeUserIds)) { @@ -80,19 +88,4 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB return super.resolveNrOfInstances(execution); } - @Override - protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) { - // 参见 https://t.zsxq.com/53Meo 情况 - if (execution.getCurrentFlowElement() instanceof CallActivity - || execution.getCurrentFlowElement() instanceof SubProcess) { - super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter); - return; - } - // 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F - super.collectionExpression = null; - super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); - super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); - super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter); - } - } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java index 21b4f9ee8d..e48b1f95d5 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java +++ b/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 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; -import org.flowable.bpmn.model.Activity; -import org.flowable.bpmn.model.CallActivity; -import org.flowable.bpmn.model.FlowElement; -import org.flowable.bpmn.model.UserTask; +import org.flowable.bpmn.model.*; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; @@ -85,6 +82,12 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB @Override protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) { + // 参见 https://t.zsxq.com/53Meo 情况 + if (execution.getCurrentFlowElement() instanceof CallActivity + || execution.getCurrentFlowElement() instanceof SubProcess) { + super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter); + return; + } // 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F super.collectionExpression = null; super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId()); From 9d26bf9224ad28d17fbed8533d319d45e1bd0261 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 4 Aug 2025 18:41:17 +0800 Subject: [PATCH 17/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90pay=20=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E3=80=91=E4=BF=AE=E5=A4=8D=20expireOrder=20logger=20?= =?UTF-8?q?=E6=96=87=E6=A1=88=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/pay/service/order/PayOrderServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java index 31532e5c65..b7f18abd65 100755 --- a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java +++ b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java @@ -585,7 +585,7 @@ public class PayOrderServiceImpl implements PayOrderService { log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); return false; } - log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); + log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId()); return true; } catch (Throwable e) { log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e); From 93cb8bad066c077d9ddc8de7a9f698e0b48fe52c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 5 Aug 2025 21:14:08 +0800 Subject: [PATCH 18/26] =?UTF-8?q?feat=EF=BC=9A=E3=80=90system=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=8A=9F=E8=83=BD=E3=80=91=E9=82=AE=E7=AE=B1=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=8A=84=E9=80=81=E3=80=81=E5=AF=86=E9=80=81=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/ruoyi-vue-pro.sql | 6 +- .../system/api/mail/MailSendApiImpl.java | 6 +- .../mail/dto/MailSendSingleToUserReqDTO.java | 19 ++- .../admin/mail/MailTemplateController.java | 3 +- .../admin/mail/vo/log/MailLogRespVO.java | 7 +- .../system/dal/dataobject/mail/MailLogDO.java | 15 +- .../system/dal/mysql/mail/MailLogMapper.java | 5 +- .../mq/message/mail/MailSendMessage.java | 7 +- .../system/mq/producer/mail/MailProducer.java | 19 +-- .../system/service/mail/MailLogService.java | 21 +-- .../service/mail/MailLogServiceImpl.java | 37 +---- .../system/service/mail/MailSendService.java | 81 ++++------ .../service/mail/MailSendServiceImpl.java | 145 ++++++------------ .../service/mail/MailLogServiceImplTest.java | 26 +++- .../service/mail/MailSendServiceImplTest.java | 123 +++++++-------- .../src/test/resources/sql/create_tables.sql | 4 +- 16 files changed, 215 insertions(+), 309 deletions(-) diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index c37fffca10..b9dad19d90 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -1259,9 +1259,9 @@ CREATE TABLE `system_mail_log` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', `user_id` bigint NULL DEFAULT NULL COMMENT '用户编号', `user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型', - `to_mail` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址', - `cc_mail` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '抄送邮箱地址', - `bcc_mail` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密送邮箱地址', + `to_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址', + `cc_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '抄送邮箱地址', + `bcc_mails` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密送邮箱地址', `account_id` bigint NOT NULL COMMENT '邮箱账号编号', `from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址', `template_id` bigint NOT NULL COMMENT '模板编号', diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java index 0a8910e1c3..59bb505891 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java @@ -21,13 +21,15 @@ public class MailSendApiImpl implements MailSendApi { @Override public Long sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) { - return mailSendService.sendSingleMailToAdmin(reqDTO.getMail(), reqDTO.getUserId(), + return mailSendService.sendSingleMailToAdmin(reqDTO.getUserId(), + reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(), reqDTO.getTemplateCode(), reqDTO.getTemplateParams()); } @Override public Long sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) { - return mailSendService.sendSingleMailToMember(reqDTO.getMail(), reqDTO.getUserId(), + return mailSendService.sendSingleMailToMember(reqDTO.getUserId(), + reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(), reqDTO.getTemplateCode(), reqDTO.getTemplateParams()); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java index 0481d59225..2d67a78087 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java @@ -4,6 +4,8 @@ import lombok.Data; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; + +import java.util.List; import java.util.Map; /** @@ -16,13 +18,24 @@ public class MailSendSingleToUserReqDTO { /** * 用户编号 + * + * 如果非空,则加载对应用户的邮箱,添加到 {@link #toMails} 中 */ private Long userId; + /** - * 邮箱 + * 收件邮箱 */ - @Email - private String mail; + private List<@Email String> toMails; + /** + * 抄送邮箱 + */ + private List<@Email String> ccMails; + /** + * 密送邮箱 + */ + private List<@Email String> bccMails; + /** * 邮件模板编号 diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java index f85f9e34e4..52ac150875 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java @@ -91,7 +91,8 @@ public class MailTemplateController { @Operation(summary = "发送短信") @PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')") public CommonResult sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) { - return success(mailSendService.sendMultipleMailToAdmin(sendReqVO.getToMails(), sendReqVO.getCcMails(), sendReqVO.getBccMails(), getLoginUserId(), + return success(mailSendService.sendSingleMailToAdmin(getLoginUserId(), + sendReqVO.getToMails(), sendReqVO.getCcMails(), sendReqVO.getBccMails(), sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams())); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java index b53488510c..8e67d1df73 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.util.List; import java.util.Map; @Schema(description = "管理后台 - 邮件日志 Response VO") @@ -20,13 +21,13 @@ public class MailLogRespVO { private Byte userType; @Schema(description = "接收邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user1@example.com, user2@example.com") - private String toMail; + private List toMails; @Schema(description = "抄送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user3@example.com, user4@example.com") - private String ccMail; + private List ccMails; @Schema(description = "密送邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user5@example.com, user6@example.com") - private String bccMail; + private List bccMails; @Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18107") private Long accountId; diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java index 9fac8582c1..756dba2ad7 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java +++ b/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; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; @@ -12,6 +13,7 @@ import lombok.*; import java.io.Serializable; import java.time.LocalDateTime; +import java.util.List; import java.util.Map; /** @@ -47,18 +49,23 @@ public class MailLogDO extends BaseDO implements Serializable { * 枚举 {@link UserTypeEnum} */ private Integer userType; + /** * 接收邮箱地址 */ - private String toMail; + @TableField(typeHandler = StringListTypeHandler.class) + private List toMails; /** * 接收邮箱地址 */ - private String ccMail; + @TableField(typeHandler = StringListTypeHandler.class) + private List ccMails; /** - * 接收邮箱地址 + * 密送邮箱地址 */ - private String bccMail; + @TableField(typeHandler = StringListTypeHandler.class) + private List bccMails; + /** * 邮箱账号编号 * diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java index 6b147cff62..44fab07a0d 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.system.dal.mysql.mail; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO; import org.apache.ibatis.annotations.Mapper; @@ -14,11 +16,12 @@ public interface MailLogMapper extends BaseMapperX { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(MailLogDO::getUserId, reqVO.getUserId()) .eqIfPresent(MailLogDO::getUserType, reqVO.getUserType()) - .likeIfPresent(MailLogDO::getToMail, reqVO.getToMail()) .eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId()) .eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId()) .eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus()) .betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime()) + .apply(StrUtil.isNotBlank(reqVO.getToMail()), + MyBatisUtils.findInSet("to_mails", reqVO.getToMail())) .orderByDesc(MailLogDO::getId)); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java index 06c4a5102b..03a4b7f198 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java @@ -5,6 +5,7 @@ import lombok.Data; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import java.util.Collection; import java.util.List; /** @@ -24,15 +25,15 @@ public class MailSendMessage { * 接收邮件地址 */ @NotEmpty(message = "接收邮件地址不能为空") - private List toMails; + private Collection toMails; /** * 抄送邮件地址 */ - private List ccMails; + private Collection ccMails; /** * 密送邮件地址 */ - private List bccMails; + private Collection bccMails; /** * 邮件账号编号 */ diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java index 8a485c7fe0..07aabb00a8 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Component; import jakarta.annotation.Resource; +import java.util.Collection; import java.util.List; import static java.util.Collections.singletonList; @@ -24,21 +25,6 @@ public class MailProducer { @Resource private ApplicationContext applicationContext; - /** - * 发送 {@link MailSendMessage} 消息 - * - * @param sendLogId 发送日志编码 - * @param mail 接收邮件地址 - * @param accountId 邮件账号编号 - * @param nickname 邮件发件人 - * @param title 邮件标题 - * @param content 邮件内容 - */ - public void sendMailSendMessage(Long sendLogId, String mail, Long accountId, - String nickname, String title, String content) { - sendMailSendMessage(sendLogId, singletonList(mail), null, null, accountId, nickname, title, content); - } - /** * 发送 {@link MailSendMessage} 消息 * @@ -51,7 +37,8 @@ public class MailProducer { * @param title 邮件标题 * @param content 邮件内容 */ - public void sendMailSendMessage(Long sendLogId, List toMails, List ccMails, List bccMails, + public void sendMailSendMessage(Long sendLogId, + Collection toMails, Collection ccMails, Collection bccMails, Long accountId, String nickname, String title, String content) { MailSendMessage message = new MailSendMessage() .setLogId(sendLogId) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java index 168179aec7..1c66e55ef4 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -33,23 +34,6 @@ public interface MailLogService { */ MailLogDO getMailLog(Long id); - /** - * 创建邮件日志 - * - * @param userId 用户编码 - * @param userType 用户类型 - * @param toMail 收件人邮件 - * @param account 邮件账号信息 - * @param template 模版信息 - * @param templateContent 模版内容 - * @param templateParams 模版参数 - * @param isSend 是否发送成功 - * @return 日志编号 - */ - Long createMailLog(Long userId, Integer userType, String toMail, - MailAccountDO account, MailTemplateDO template , - String templateContent, Map templateParams, Boolean isSend); - /** * 创建邮件日志 * @@ -65,7 +49,8 @@ public interface MailLogService { * @param isSend 是否发送成功 * @return 日志编号 */ - Long createMailLog(Long userId, Integer userType, List toMails, List ccMails, List bccMails, + Long createMailLog(Long userId, Integer userType, + Collection toMails, Collection ccMails, Collection bccMails, MailAccountDO account, MailTemplateDO template, String templateContent, Map templateParams, Boolean isSend); diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java index 28d233da58..c17abaf016 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.system.service.mail; -import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; @@ -13,9 +13,7 @@ import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage; @@ -43,7 +41,8 @@ public class MailLogServiceImpl implements MailLogService { } @Override - public Long createMailLog(Long userId, Integer userType, String toMail, + public Long createMailLog(Long userId, Integer userType, + Collection toMails, Collection ccMails, Collection bccMails, MailAccountDO account, MailTemplateDO template, String templateContent, Map templateParams, Boolean isSend) { MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder(); @@ -51,32 +50,8 @@ public class MailLogServiceImpl implements MailLogService { logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus() : MailSendStatusEnum.IGNORE.getStatus()) // 用户信息 - .userId(userId).userType(userType).toMail(toMail) - .accountId(account.getId()).fromMail(account.getMail()) - // 模板相关字段 - .templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname()) - .templateTitle(template.getTitle()).templateContent(templateContent).templateParams(templateParams); - - // 插入数据库 - MailLogDO logDO = logDOBuilder.build(); - mailLogMapper.insert(logDO); - return logDO.getId(); - } - - @Override - public Long createMailLog(Long userId, Integer userType, List toMails, List ccMails, List bccMails, - MailAccountDO account, MailTemplateDO template, - String templateContent, Map templateParams, Boolean isSend) { - String toMail = CollUtil.isEmpty(toMails) ? "" : String.join(",", toMails); - String ccMail = CollUtil.isEmpty(ccMails) ? "" : String.join(",", ccMails); - String bccMail = CollUtil.isEmpty(bccMails) ? "" : String.join(",", bccMails); - - MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder(); - // 根据是否要发送,设置状态 - logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus() - : MailSendStatusEnum.IGNORE.getStatus()) - // 用户信息 - .userId(userId).userType(userType).toMail(toMail).ccMail(ccMail).bccMail(bccMail) + .userId(userId).userType(userType) + .toMails(ListUtil.toList(toMails)).ccMails(ListUtil.toList(ccMails)).bccMails(ListUtil.toList(bccMails)) .accountId(account.getId()).fromMail(account.getMail()) // 模板相关字段 .templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname()) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java index 5300157b15..1b600bc90c 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java @@ -1,8 +1,9 @@ package cn.iocoder.yudao.module.system.service.mail; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage; -import java.util.List; +import java.util.Collection; import java.util.Map; /** @@ -16,83 +17,55 @@ public interface MailSendService { /** * 发送单条邮件给管理后台的用户 * - * @param mail 邮箱 * @param userId 用户编码 + * @param toMails 收件邮箱 + * @param ccMails 抄送邮箱 + * @param bccMails 密送邮箱 * @param templateCode 邮件模版编码 * @param templateParams 邮件模版参数 * @return 发送日志编号 */ - Long sendSingleMailToAdmin(String mail, Long userId, - String templateCode, Map templateParams); + default Long sendSingleMailToAdmin(Long userId, + Collection toMails, Collection ccMails, Collection bccMails, + String templateCode, Map templateParams) { + return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(), + templateCode, templateParams); + } /** * 发送单条邮件给用户 APP 的用户 * - * @param mail 邮箱 * @param userId 用户编码 + * @param toMails 收件邮箱 + * @param ccMails 抄送邮箱 + * @param bccMails 密送邮箱 * @param templateCode 邮件模版编码 * @param templateParams 邮件模版参数 * @return 发送日志编号 */ - Long sendSingleMailToMember(String mail, Long userId, - String templateCode, Map templateParams); + default Long sendSingleMailToMember(Long userId, + Collection toMails, Collection ccMails, Collection bccMails, + String templateCode, Map templateParams) { + return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(), + templateCode, templateParams); + } /** - * 发送单条邮件给用户 + * 发送单条邮件 * - * @param mail 邮箱 - * @param userId 用户编码 + * @param toMails 收件邮箱 + * @param ccMails 抄送邮箱 + * @param bccMails 密送邮箱 + * @param userId 用户编号 * @param userType 用户类型 * @param templateCode 邮件模版编码 * @param templateParams 邮件模版参数 * @return 发送日志编号 */ - Long sendSingleMail(String mail, Long userId, Integer userType, + Long sendSingleMail(Collection toMails, Collection ccMails, Collection bccMails, + Long userId, Integer userType, String templateCode, Map templateParams); - /** - * 发送多条邮件给管理后台的用户 - * - * @param toMails 收件邮箱 - * @param ccMails 抄送邮箱 - * @param bccMails 密送邮箱 - * @param userId 用户编码 - * @param templateCode 邮件模版编码 - * @param templateParams 邮件模版参数 - * @return 发送日志编号 - */ - Long sendMultipleMailToAdmin(List toMails, List ccMails, List bccMails, - Long userId, String templateCode, Map templateParams); - - /** - * 发送多条邮件给用户 APP 的用户 - * - * @param toMails 收件邮箱 - * @param ccMails 抄送邮箱 - * @param bccMails 密送邮箱 - * @param userId 用户编码 - * @param templateCode 邮件模版编码 - * @param templateParams 邮件模版参数 - * @return 发送日志编号 - */ - Long sendMultipleMailToMember(List toMails, List ccMails, List bccMails, - Long userId, String templateCode, Map templateParams); - - /** - * 发送单条邮件给用户 - * - * @param toMails 收件邮箱 - * @param ccMails 抄送邮箱 - * @param bccMails 密送邮箱 - * @param userId 用户编码 - * @param userType 用户类型 - * @param templateCode 邮件模版编码 - * @param templateParams 邮件模版参数 - * @return 发送日志编号 - */ - Long sendMultipleMail(List toMails, List ccMails, List bccMails, - Long userId, Integer userType, String templateCode, Map templateParams); - /** * 执行真正的邮件发送 * 注意,该方法仅仅提供给 MQ Consumer 使用 diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java index 580777ca70..682696f932 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.service.mail; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Validator; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -14,16 +15,17 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService; import com.google.common.annotations.VisibleForTesting; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.dromara.hutool.extra.mail.*; +import org.dromara.hutool.extra.mail.MailAccount; +import org.dromara.hutool.extra.mail.MailUtil; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.util.List; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; -import static java.util.Collections.singletonList; /** * 邮箱发送 Service 实现类 @@ -52,120 +54,67 @@ public class MailSendServiceImpl implements MailSendService { private MailProducer mailProducer; @Override - public Long sendSingleMailToAdmin(String mail, Long userId, - String templateCode, Map templateParams) { - // 如果 mail 为空,则加载用户编号对应的邮箱 - if (StrUtil.isEmpty(mail)) { - AdminUserDO user = adminUserService.getUser(userId); - if (user != null) { - mail = user.getEmail(); - } - } - // 执行发送 - return sendSingleMail(mail, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); - } - - @Override - public Long sendSingleMailToMember(String mail, Long userId, - String templateCode, Map templateParams) { - // 如果 mail 为空,则加载用户编号对应的邮箱 - if (StrUtil.isEmpty(mail)) { - mail = memberService.getMemberUserEmail(userId); - } - // 执行发送 - return sendSingleMail(mail, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams); - } - - @Override - public Long sendSingleMail(String mail, Long userId, Integer userType, + public Long sendSingleMail(Collection toMails, Collection ccMails, Collection bccMails, + Long userId, Integer userType, String templateCode, Map templateParams) { - // 校验邮箱模版是否合法 + // 1.1 校验邮箱模版是否合法 MailTemplateDO template = validateMailTemplate(templateCode); - // 校验邮箱账号是否合法 + // 1.2 校验邮箱账号是否合法 MailAccountDO account = validateMailAccount(template.getAccountId()); - - // 校验邮箱是否存在 - mail = validateMail(mail); + // 1.3 校验邮件参数是否缺失 validateTemplateParams(template, templateParams); - // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 - Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()); - String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams); - String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams); - Long sendLogId = mailLogService.createMailLog(userId, userType, mail, - account, template, content, templateParams, isSend); - // 发送 MQ 消息,异步执行发送短信 - if (isSend) { - mailProducer.sendMailSendMessage(sendLogId, mail, account.getId(), - template.getNickname(), title, content); + // 2. 组装邮箱 + String userMail = getUserMail(userId, userType); + Collection toMailSet = new LinkedHashSet<>(); + Collection ccMailSet = new LinkedHashSet<>(); + Collection bccMailSet = new LinkedHashSet<>(); + if (Validator.isEmail(userMail)) { + toMailSet.add(userMail); } - return sendLogId; - } - - @Override - public Long sendMultipleMailToAdmin(List toMails, List ccMails, List bccMails, Long userId, String templateCode, Map templateParams) { - // 如果 mail 为空,则加载用户编号对应的邮箱 - if (CollUtil.isEmpty(toMails)) { - AdminUserDO user = adminUserService.getUser(userId); - if (user != null) { - toMails = singletonList(user.getEmail()); - } - } - // 执行发送 - return sendMultipleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); - } - - @Override - public Long sendMultipleMailToMember(List toMails, List ccMails, List bccMails, Long userId, String templateCode, Map templateParams) { - // 如果 mail 为空,则加载用户编号对应的邮箱 - if (CollUtil.isEmpty(toMails)) { - toMails = singletonList(memberService.getMemberUserEmail(userId)); - } - // 执行发送 - return sendMultipleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams); - } - - @Override - public Long sendMultipleMail(List toMails, List ccMails, List bccMails, Long userId, Integer userType, String templateCode, Map templateParams) { - // 校验邮箱模版是否合法 - MailTemplateDO template = validateMailTemplate(templateCode); - // 校验邮箱账号是否合法 - MailAccountDO account = validateMailAccount(template.getAccountId()); - // 校验邮件参数是否缺失 - validateTemplateParams(template, templateParams); - - if (CollUtil.isEmpty(toMails)) { - throw exception(MAIL_SEND_MAIL_NOT_EXISTS); - } - // 校验邮箱是否存在 - for (String mail : toMails) { - validateMail(mail); + if (CollUtil.isNotEmpty(toMails)) { + toMails.stream().filter(Validator::isEmail).forEach(toMailSet::add); } if (CollUtil.isNotEmpty(ccMails)) { - for (String mail : ccMails) { - validateMail(mail); - } + ccMails.stream().filter(Validator::isEmail).forEach(ccMailSet::add); } if (CollUtil.isNotEmpty(bccMails)) { - for (String mail : bccMails) { - validateMail(mail); - } + bccMails.stream().filter(Validator::isEmail).forEach(bccMailSet::add); + } + if (CollUtil.isEmpty(toMailSet)) { + throw exception(MAIL_SEND_MAIL_NOT_EXISTS); } // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()); String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams); String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams); - Long sendLogId = mailLogService.createMailLog(userId, userType, toMails, ccMails, bccMails, + Long sendLogId = mailLogService.createMailLog(userId, userType, toMailSet, ccMailSet, bccMailSet, account, template, content, templateParams, isSend); // 发送 MQ 消息,异步执行发送短信 if (isSend) { - mailProducer.sendMailSendMessage(sendLogId, toMails, ccMails, bccMails, account.getId(), - template.getNickname(), title, content); + mailProducer.sendMailSendMessage(sendLogId, toMailSet, ccMailSet, bccMailSet, + account.getId(), template.getNickname(), title, content); } return sendLogId; } + private String getUserMail(Long userId, Integer userType) { + if (userId == null || userType == null) { + return null; + } + if (UserTypeEnum.ADMIN.getValue().equals(userType)) { + AdminUserDO user = adminUserService.getUser(userId); + if (user != null) { + return user.getEmail(); + } + } + if (UserTypeEnum.MEMBER.getValue().equals(userType)) { + return memberService.getMemberUserEmail(userId); + } + return null; + } + @Override public void doSendMail(MailSendMessage message) { // 1. 创建发送账号 @@ -213,14 +162,6 @@ public class MailSendServiceImpl implements MailSendService { return account; } - @VisibleForTesting - String validateMail(String mail) { - if (StrUtil.isEmpty(mail)) { - throw exception(MAIL_SEND_MAIL_NOT_EXISTS); - } - return mail; - } - /** * 校验邮件参数是否缺失 * diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java index 482aa5aeee..ec8aadacd9 100755 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java @@ -10,10 +10,12 @@ import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO; import cn.iocoder.yudao.module.system.dal.mysql.mail.MailLogMapper; import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum; +import org.assertj.core.util.Lists; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import jakarta.annotation.Resource; +import java.util.Collection; import java.util.Map; import static cn.hutool.core.util.RandomUtil.randomEle; @@ -43,7 +45,9 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { // 准备参数 Long userId = randomLongId(); Integer userType = randomEle(UserTypeEnum.values()).getValue(); - String toMail = randomEmail(); + Collection toMails = Lists.newArrayList(randomEmail(), randomEmail()); + Collection ccMails = Lists.newArrayList(randomEmail()); + Collection bccMails = Lists.newArrayList(randomEmail()); MailAccountDO account = randomPojo(MailAccountDO.class); MailTemplateDO template = randomPojo(MailTemplateDO.class); String templateContent = randomString(); @@ -52,14 +56,20 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { // mock 方法 // 调用 - Long logId = mailLogService.createMailLog(userId, userType, toMail, account, template, templateContent, templateParams, isSend); + Long logId = mailLogService.createMailLog(userId, userType, toMails, ccMails, bccMails, + account, template, templateContent, templateParams, isSend); // 断言 MailLogDO log = mailLogMapper.selectById(logId); assertNotNull(log); assertEquals(MailSendStatusEnum.INIT.getStatus(), log.getSendStatus()); assertEquals(userId, log.getUserId()); assertEquals(userType, log.getUserType()); - assertEquals(toMail, log.getToMail()); + assertEquals(toMails.size(), log.getToMails().size()); + assertTrue(log.getToMails().containsAll(toMails)); + assertEquals(ccMails.size(), log.getCcMails().size()); + assertTrue(log.getCcMails().containsAll(ccMails)); + assertEquals(bccMails.size(), log.getBccMails().size()); + assertTrue(log.getBccMails().containsAll(bccMails)); assertEquals(account.getId(), log.getAccountId()); assertEquals(account.getMail(), log.getFromMail()); assertEquals(template.getId(), log.getTemplateId()); @@ -136,7 +146,9 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到 o.setUserId(1L); o.setUserType(UserTypeEnum.ADMIN.getValue()); - o.setToMail("768@qq.com"); + o.setToMails(Lists.newArrayList("768@qq.com")); + o.setCcMails(Lists.newArrayList()); + o.setBccMails(Lists.newArrayList()); o.setAccountId(10L); o.setTemplateId(100L); o.setSendStatus(MailSendStatusEnum.INIT.getStatus()); @@ -148,8 +160,8 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L))); // 测试 userType 不匹配 mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue()))); - // 测试 toMail 不匹配 - mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMail("788@.qq.com"))); + // 测试 toMails 不匹配(特殊:find_in_set 无法单测) +// mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMails(Lists.newArrayList("788@qq.com")))); // 测试 accountId 不匹配 mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L))); // 测试 templateId 不匹配 @@ -162,7 +174,7 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { MailLogPageReqVO reqVO = new MailLogPageReqVO(); reqVO.setUserId(1L); reqVO.setUserType(UserTypeEnum.ADMIN.getValue()); - reqVO.setToMail("768"); +// reqVO.setToMail("768@qq.com"); reqVO.setAccountId(10L); reqVO.setTemplateId(100L); reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus()); diff --git a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java index 1ef1e12322..459568360d 100644 --- a/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java +++ b/yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java @@ -20,6 +20,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -65,14 +66,18 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testSendSingleMailToAdmin() { + public void testSendSingleMail_success() { // 准备参数 Long userId = randomLongId(); String templateCode = RandomUtils.randomString(); Map templateParams = MapUtil.builder().put("code", "1234") .put("op", "login").build(); + Collection toMails = Lists.newArrayList("admin@test.com"); + Collection ccMails = Lists.newArrayList("cc@test.com"); + Collection bccMails = Lists.newArrayList("bcc@test.com"); + // mock adminUserService 的方法 - AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setMobile("15601691300")); + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setEmail("admin@example.com")); when(adminUserService.getUser(eq(userId))).thenReturn(user); // mock MailTemplateService 的方法 @@ -93,61 +98,27 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); // mock MailLogService 的方法 Long mailLogId = randomLongId(); - when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.ADMIN.getValue()), eq(user.getEmail()), + when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.ADMIN.getValue()), + argThat(toMailSet -> toMailSet.contains(user.getEmail()) && toMailSet.contains("admin@test.com")), + argThat(ccMailSet -> ccMailSet.contains("cc@test.com")), + argThat(bccMailSet -> bccMailSet.contains("bcc@test.com")), eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId); // 调用 - Long resultMailLogId = mailSendService.sendSingleMailToAdmin(null, userId, templateCode, templateParams); + Long resultMailLogId = mailSendService.sendSingleMail(toMails, ccMails, bccMails, userId, + UserTypeEnum.ADMIN.getValue(), templateCode, templateParams); // 断言 assertEquals(mailLogId, resultMailLogId); // 断言调用 - verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(user.getEmail()), - eq(account.getId()), eq(template.getNickname()), eq(title), eq(content)); - } - - @Test - public void testSendSingleMailToMember() { - // 准备参数 - Long userId = randomLongId(); - String templateCode = RandomUtils.randomString(); - Map templateParams = MapUtil.builder().put("code", "1234") - .put("op", "login").build(); - // mock memberService 的方法 - String mail = randomEmail(); - when(memberService.getMemberUserEmail(eq(userId))).thenReturn(mail); - - // mock MailTemplateService 的方法 - MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> { - o.setStatus(CommonStatusEnum.ENABLE.getStatus()); - o.setContent("验证码为{code}, 操作为{op}"); - o.setParams(Lists.newArrayList("code", "op")); - }); - when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template); - String title = RandomUtils.randomString(); - when(mailTemplateService.formatMailTemplateContent(eq(template.getTitle()), eq(templateParams))) - .thenReturn(title); - String content = RandomUtils.randomString(); - when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams))) - .thenReturn(content); - // mock MailAccountService 的方法 - MailAccountDO account = randomPojo(MailAccountDO.class); - when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); - // mock MailLogService 的方法 - Long mailLogId = randomLongId(); - when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.MEMBER.getValue()), eq(mail), - eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId); - - // 调用 - Long resultMailLogId = mailSendService.sendSingleMailToMember(null, userId, templateCode, templateParams); - // 断言 - assertEquals(mailLogId, resultMailLogId); - // 断言调用 - verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(mail), + verify(mailProducer).sendMailSendMessage(eq(mailLogId), + argThat(toMailSet -> toMailSet.contains(user.getEmail()) && toMailSet.contains("admin@test.com")), + argThat(ccMailSet -> ccMailSet.contains("cc@test.com")), + argThat(bccMailSet -> bccMailSet.contains("bcc@test.com")), eq(account.getId()), eq(template.getNickname()), eq(title), eq(content)); } /** - * 发送成功,当短信模板开启时 + * 发送成功,当邮件模板开启时 */ @Test public void testSendSingleMail_successWhenMailTemplateEnable() { @@ -158,6 +129,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { String templateCode = RandomUtils.randomString(); Map templateParams = MapUtil.builder().put("code", "1234") .put("op", "login").build(); + Collection toMails = Lists.newArrayList(mail); + // mock MailTemplateService 的方法 MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> { o.setStatus(CommonStatusEnum.ENABLE.getStatus()); @@ -176,23 +149,29 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); // mock MailLogService 的方法 Long mailLogId = randomLongId(); - when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail), + when(mailLogService.createMailLog(eq(userId), eq(userType), + argThat(toMailSet -> toMailSet.contains(mail)), + argThat(Collection::isEmpty), + argThat(Collection::isEmpty), eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId); // 调用 - Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams); + Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams); // 断言 assertEquals(mailLogId, resultMailLogId); // 断言调用 - verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(mail), + verify(mailProducer).sendMailSendMessage(eq(mailLogId), + argThat(toMailSet -> toMailSet.contains(mail)), + argThat(Collection::isEmpty), + argThat(Collection::isEmpty), eq(account.getId()), eq(template.getNickname()), eq(title), eq(content)); } /** - * 发送成功,当短信模板关闭时 + * 发送成功,当邮件模板关闭时 */ @Test - public void testSendSingleMail_successWhenSmsTemplateDisable() { + public void testSendSingleMail_successWhenMailTemplateDisable() { // 准备参数 String mail = randomEmail(); Long userId = randomLongId(); @@ -200,6 +179,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { String templateCode = RandomUtils.randomString(); Map templateParams = MapUtil.builder().put("code", "1234") .put("op", "login").build(); + Collection toMails = Lists.newArrayList(mail); + // mock MailTemplateService 的方法 MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> { o.setStatus(CommonStatusEnum.DISABLE.getStatus()); @@ -218,15 +199,18 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); // mock MailLogService 的方法 Long mailLogId = randomLongId(); - when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail), + when(mailLogService.createMailLog(eq(userId), eq(userType), + argThat(toMailSet -> toMailSet.contains(mail)), + argThat(Collection::isEmpty), + argThat(Collection::isEmpty), eq(account), eq(template), eq(content), eq(templateParams), eq(false))).thenReturn(mailLogId); // 调用 - Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams); + Long resultMailLogId = mailSendService.sendSingleMail(toMails, null, null, userId, userType, templateCode, templateParams); // 断言 assertEquals(mailLogId, resultMailLogId); // 断言调用 - verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), anyString(), + verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), any(), any(), any(), anyLong(), anyString(), anyString(), anyString()); } @@ -255,12 +239,29 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testValidateMail_notExists() { + public void testSendSingleMail_noValidEmail() { // 准备参数 - // mock 方法 + Long userId = randomLongId(); + String templateCode = RandomUtils.randomString(); + Map templateParams = MapUtil.builder().put("code", "1234") + .put("op", "login").build(); + Collection toMails = Lists.newArrayList("invalid-email"); // 非法邮箱 + + // mock MailTemplateService 的方法 + MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setContent("验证码为{code}, 操作为{op}"); + o.setParams(Lists.newArrayList("code", "op")); + }); + when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template); + + // mock MailAccountService 的方法 + MailAccountDO account = randomPojo(MailAccountDO.class); + when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account); // 调用,并断言异常 - assertServiceException(() -> mailSendService.validateMail(null), + assertServiceException(() -> mailSendService.sendSingleMail(toMails, null, null, userId, + UserTypeEnum.ADMIN.getValue(), templateCode, templateParams), MAIL_SEND_MAIL_NOT_EXISTS); } @@ -286,7 +287,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { assertEquals(account.getPort(), mailAccount.getPort()); assertEquals(account.getSslEnable(), mailAccount.isSslEnable()); return true; - }), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true))) + }), eq(message.getToMails()), eq(message.getCcMails()), eq(message.getBccMails()), + eq(message.getTitle()), eq(message.getContent()), eq(true))) .thenReturn(messageId); // 调用 @@ -317,7 +319,8 @@ public class MailSendServiceImplTest extends BaseMockitoUnitTest { assertEquals(account.getPort(), mailAccount.getPort()); assertEquals(account.getSslEnable(), mailAccount.isSslEnable()); return true; - }), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true))).thenThrow(e); + }), eq(message.getToMails()), eq(message.getCcMails()), eq(message.getBccMails()), + eq(message.getTitle()), eq(message.getContent()), eq(true))).thenThrow(e); // 调用 mailSendService.doSendMail(message); diff --git a/yudao-module-system/src/test/resources/sql/create_tables.sql b/yudao-module-system/src/test/resources/sql/create_tables.sql index 4df039b8df..d8b68369aa 100644 --- a/yudao-module-system/src/test/resources/sql/create_tables.sql +++ b/yudao-module-system/src/test/resources/sql/create_tables.sql @@ -553,7 +553,9 @@ CREATE TABLE IF NOT EXISTS "system_mail_log" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "user_id" bigint, "user_type" varchar, - "to_mail" varchar NOT NULL, + "to_mails" varchar NOT NULL, + "cc_mails" varchar, + "bcc_mails" varchar, "account_id" bigint NOT NULL, "from_mail" varchar NOT NULL, "template_id" bigint NOT NULL, From d4357a747174f3c78603280ab5b7dd4ef9d52cae Mon Sep 17 00:00:00 2001 From: LesanOuO <1960681385@qq.com> Date: Wed, 6 Aug 2025 19:29:14 +0800 Subject: [PATCH 19/26] =?UTF-8?q?chore:=20=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/service/task/BpmTaskServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 133f05c76b..f53d86d515 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -1204,7 +1204,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (CollUtil.isEmpty(nextUserTaskKeys)) { throw exception(TASK_WITHDRAW_FAIL_NEXT_TASK_NOT_ALLOW); } - // TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以;包括 todo 和 done 那边的查询哇??? + // TODO @芋艿:是否选择升级flowable版本解决taskCreatedAfter、taskCreatedBefore问题,升级7.1.0可以;包括 todo 和 done 那边的查询哇??? 是的! long nextUserTaskFinishedCount = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstance.getProcessInstanceId()).taskDefinitionKeys(nextUserTaskKeys) .taskCreatedAfter(taskInstance.getEndTime()).finished().count(); From 0004f1f6fbdd9e0b4810e99c1c73021d49e46c85 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 9 Aug 2025 10:04:07 +0800 Subject: [PATCH 20/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90crm=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86=E3=80=91=E4=BF=AE=E5=A4=8D=E7=BA=BF?= =?UTF-8?q?=E7=B4=A2=E6=B1=A0=E6=93=8D=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98=20!1397?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java | 2 +- .../yudao/module/crm/service/clue/CrmClueServiceImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java index aeeed316dd..30d51cf472 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java @@ -14,7 +14,7 @@ public interface LogRecordConstants { String CRM_CLUE_CREATE_SUB_TYPE = "创建线索"; String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}"; String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索"; - String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}"; + String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReqVO}}"; String CRM_CLUE_DELETE_SUB_TYPE = "删除线索"; String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】"; String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索"; diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java index 0250fe14f5..fe84db1bdc 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java @@ -106,7 +106,7 @@ public class CrmClueServiceImpl implements CrmClueService { // 3. 记录操作日志上下文 updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class)); + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmClueSaveReqVO.class)); LogRecordContext.putVariable("clueName", oldClue.getName()); } From 3ca2981af34a4eb0884e8728beba9c967c2c4961 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 9 Aug 2025 10:14:49 +0800 Subject: [PATCH 21/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90system=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=8A=9F=E8=83=BD=E3=80=91=E4=BF=AE=E5=A4=8D=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E7=BB=84=E4=BB=B6=E5=90=8D=E9=87=8D=E5=A4=8D=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E4=B8=8D=E4=B8=A5=E8=B0=A8=E9=97=AE=E9=A2=98=20!201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/system/service/permission/MenuServiceImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java index 0d7536a1a7..80832e969f 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java @@ -255,9 +255,6 @@ public class MenuServiceImpl implements MenuService { return; } // 如果 id 为空,说明不用比较是否为相同 id 的菜单 - if (id == null) { - throw exception(MENU_NAME_DUPLICATE); - } if (!menu.getId().equals(id)) { throw exception(MENU_NAME_DUPLICATE); } From dfc92c94509436416a3912709e34604f4c5ece89 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 9 Aug 2025 10:17:31 +0800 Subject: [PATCH 22/26] =?UTF-8?q?fix=EF=BC=9A=E3=80=90system=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=8A=9F=E8=83=BD=E3=80=91=E4=BF=AE=E5=A4=8D=E9=98=BF?= =?UTF-8?q?=E9=87=8C=E4=BA=91sms=20api=E7=BC=BA=E5=B0=91=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=20!159?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/AliyunSmsClient.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index 3dd12491a9..d4ed95d9b2 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/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 { .map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))) .collect(Collectors.joining("&")); - // 2.1 请求 Header + // 2. 请求 Body + String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空 + String hashedRequestPayload = DigestUtil.sha256Hex(requestBody); + + // 3.1 请求 Header TreeMap headers = new TreeMap<>(); headers.put("host", HOST); headers.put("x-acs-version", VERSION); headers.put("x-acs-action", apiName); headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date())); headers.put("x-acs-signature-nonce", IdUtil.randomUUID()); + headers.put("x-acs-content-sha256", hashedRequestPayload); - // 2.2 构建签名 Header + // 3.2 构建签名 Header StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") @@ -157,13 +162,13 @@ public class AliyunSmsClient extends AbstractSmsClient { }); String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1); - // 3. 请求 Body - String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。 - String hashedRequestBody = DigestUtil.sha256Hex(requestBody); - // 4. 构建 Authorization 签名 - String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" - + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + String canonicalRequest = "POST" + "\n" + + "/" + "\n" + + queryString + "\n" + + canonicalHeaders + "\n" + + signedHeaders + "\n" + + hashedRequestPayload; String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest); String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 @@ -184,7 +189,7 @@ public class AliyunSmsClient extends AbstractSmsClient { @SneakyThrows private static String percentCode(String str) { Assert.notNull(str, "str 不能为空"); - return HttpUtils.encodeUtf8(str) + return URLEncoder.encode(str, StandardCharsets.UTF_8.name()) .replace("+", "%20") // 加号 "+" 被替换为 "%20" .replace("*", "%2A") // 星号 "*" 被替换为 "%2A" .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~" From 784a9964bac2b7a73aac2905019e4d515b4f80d9 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 9 Aug 2025 10:22:05 +0800 Subject: [PATCH 23/26] =?UTF-8?q?feat:=E3=80=90mp=20=E5=85=AC=E4=BC=97?= =?UTF-8?q?=E5=8F=B7=E3=80=91=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7=20ID=20?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E6=9D=A1=E4=BB=B6=E4=BB=A5=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/message/vo/message/MpMessagePageReqVO.java | 3 +++ .../yudao/module/mp/dal/mysql/message/MpMessageMapper.java | 1 + .../pay/core/client/impl/alipay/AbstractAlipayPayClient.java | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java index d9f7cc8761..9e01a5c338 100644 --- a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java +++ b/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 { @Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") private String openid; + @Schema(description = "公众号粉丝 UserId", example = "1") + private String userId; + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @Schema(description = "创建时间") private LocalDateTime[] createTime; diff --git a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java b/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java index 72ba566277..0f11bdfe55 100644 --- a/yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageMapper.java +++ b/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 { .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId()) .eqIfPresent(MpMessageDO::getType, reqVO.getType()) .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid()) + .eqIfPresent(MpMessageDO::getUserId, reqVO.getUserId()) .betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(MpMessageDO::getId)); } diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java index fee7cddba8..caf210fa66 100644 --- a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java +++ b/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 Date: Sat, 9 Aug 2025 12:26:47 +0800 Subject: [PATCH 24/26] =?UTF-8?q?fix:=20=E3=80=90infra=20=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=E3=80=91=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90=E4=BF=AE=E5=A4=8D=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=90=8E=20checkedIds=20=E6=9C=AA=E9=87=8D=E7=BD=AE=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/codegen/vue3/views/components/list_sub_erp.vue.vm | 1 + .../src/main/resources/codegen/vue3/views/index.vue.vm | 1 + .../resources/codegen/vue3_vben5_antd/general/views/index.vue.vm | 1 + .../vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm | 1 + .../resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm | 1 + .../vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm | 1 + .../resources/codegen/vue3_vben5_ele/general/views/index.vue.vm | 1 + .../vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm | 1 + .../resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm | 1 + .../vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm | 1 + 10 files changed, 10 insertions(+) diff --git a/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm index f9fbb9787b..a94cab5a59 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm @@ -209,6 +209,7 @@ const handleDeleteBatch = async () => { // 删除的二次确认 await message.delConfirm() await ${simpleClassName}Api.delete${subSimpleClassName}List(checkedIds.value); + checkedIds.value = []; message.success(t('common.delSuccess')) await getList(); } catch {} diff --git a/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm index 851bc2b5e4..dfb97804ce 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm @@ -366,6 +366,7 @@ const handleDeleteBatch = async () => { // 删除的二次确认 await message.delConfirm() await ${simpleClassName}Api.delete${simpleClassName}List(checkedIds.value); + checkedIds.value = []; message.success(t('common.delSuccess')) await getList(); } catch {} diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm index bb743305f8..6553ed0c87 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm @@ -168,6 +168,7 @@ async function handleDeleteBatch() { }); try { await delete${simpleClassName}List(checkedIds.value); + checkedIds.value = []; message.success( $t('ui.actionMessage.deleteSuccess') ); await getList(); } finally { diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm index cfd85589c9..999257d91d 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm +++ b/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() { }); try { await delete${subSimpleClassName}List(checkedIds.value); + checkedIds.value = []; message.success( $t('ui.actionMessage.deleteSuccess') ); await getList(); } finally { diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm index 635e12ac24..1e13de2e97 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm @@ -102,6 +102,7 @@ async function handleDeleteBatch() { }); try { await delete${simpleClassName}List(checkedIds.value); + checkedIds.value = []; message.success({ content: $t('ui.actionMessage.deleteSuccess'), key: 'action_key_msg', diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm index 4001ed3992..e046226efc 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm +++ b/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() { }); try { await delete${subSimpleClassName}List(checkedIds.value); + checkedIds.value = []; message.success({ content: $t('ui.actionMessage.deleteSuccess', [row.id]), key: 'action_key_msg', diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm index 9897ba6773..ae77cd4c7b 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/index.vue.vm @@ -163,6 +163,7 @@ async function handleDeleteBatch() { }); try { await delete${simpleClassName}List(checkedIds.value); + checkedIds.value = []; ElMessage.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm index e27965e4c1..ccad79a0d9 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/general/views/modules/list_sub_erp.vue.vm +++ b/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() { }); try { await delete${subSimpleClassName}List(checkedIds.value); + checkedIds.value = []; ElMessage.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm index f9232d6b50..c29beb9aa9 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/index.vue.vm @@ -99,6 +99,7 @@ async function handleDeleteBatch() { }); try { await delete${simpleClassName}List(checkedIds.value); + checkedIds.value = []; ElMessage.success($t('ui.actionMessage.deleteSuccess')); onRefresh(); } finally { diff --git a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm index 5afb9c7a0d..13a2415efd 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue3_vben5_ele/schema/views/modules/list_sub_erp.vue.vm +++ b/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() { }); try { await delete${subSimpleClassName}List(checkedIds.value); + checkedIds.value = []; ElMessage.success($t('ui.actionMessage.deleteSuccess')); onRefresh(); } finally { From ccfc5e18cc08158e88aac4b7bfe9f21a346be51a Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 9 Aug 2025 12:29:27 +0800 Subject: [PATCH 25/26] =?UTF-8?q?fix:=20=E3=80=90infra=20=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=E3=80=91=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90=E4=BF=AE=E5=A4=8D=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=90=8E=20checkedIds=20=E6=9C=AA=E9=87=8D=E7=BD=AE=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/codegen/vue/views/components/list_sub_erp.vue.vm | 1 + .../src/main/resources/codegen/vue/views/index.vue.vm | 1 + 2 files changed, 2 insertions(+) diff --git a/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm index e1305586cc..3f290ccff8 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm @@ -170,6 +170,7 @@ await this.#[[$modal]]#.confirm('是否确认删除?') try { await ${simpleClassName}Api.delete${subSimpleClassName}List(this.checkedIds); + this.checkedIds = []; await this.getList(); this.#[[$modal]]#.msgSuccess("删除成功"); } catch {} diff --git a/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm b/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm index 30014a8ff4..bbc913114a 100644 --- a/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm +++ b/yudao-module-infra/src/main/resources/codegen/vue/views/index.vue.vm @@ -338,6 +338,7 @@ export default { await this.#[[$modal]]#.confirm('是否确认删除?') try { await ${simpleClassName}Api.delete${simpleClassName}List(this.checkedIds); + this.checkedIds = []; await this.getList(); this.#[[$modal]]#.msgSuccess("删除成功"); } catch {} From 2cad35f3610a99566876c5bc35c94de8041af9ec Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 16 Aug 2025 15:57:47 +0800 Subject: [PATCH 26/26] =?UTF-8?q?feat=EF=BC=9A=E3=80=90framework=20?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E3=80=91=E5=A2=9E=E5=8A=A0=20api=20=E5=8A=A0?= =?UTF-8?q?=E8=A7=A3=E5=AF=86=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/enums/WebFilterOrderEnum.java | 2 + .../encrypt/config/ApiEncryptProperties.java | 70 ++++++++ .../YudaoApiEncryptAutoConfiguration.java | 34 ++++ .../encrypt/core/annotation/ApiEncrypt.java | 23 +++ .../core/filter/ApiDecryptRequestWrapper.java | 86 ++++++++++ .../encrypt/core/filter/ApiEncryptFilter.java | 152 ++++++++++++++++++ .../filter/ApiEncryptResponseWrapper.java | 109 +++++++++++++ .../yudao/framework/encrypt/package-info.java | 4 + .../core/filter/CacheRequestBodyWrapper.java | 17 +- ...ot.autoconfigure.AutoConfiguration.imports | 3 +- .../framework/encrypt/ApiEncryptTest.java | 86 ++++++++++ .../controller/admin/auth/AuthController.http | 18 +++ .../src/main/resources/application.yaml | 7 + 13 files changed, 606 insertions(+), 5 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/ApiEncryptProperties.java create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/YudaoApiEncryptAutoConfiguration.java create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/annotation/ApiEncrypt.java create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiDecryptRequestWrapper.java create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptFilter.java create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/package-info.java create mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/encrypt/ApiEncryptTest.java diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java index 11a5ee0782..497a213a2f 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java @@ -15,6 +15,8 @@ public interface WebFilterOrderEnum { int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500; + int API_ENCRYPT_FILTER = REQUEST_BODY_CACHE_FILTER + 1; + // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等 int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面 diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/ApiEncryptProperties.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/ApiEncryptProperties.java new file mode 100644 index 0000000000..135eb85bb0 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/ApiEncryptProperties.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.framework.encrypt.config; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * HTTP API 加解密配置 + * + * @author 芋道源码 + */ +@ConfigurationProperties(prefix = "yudao.api-encrypt") +@Validated +@Data +public class ApiEncryptProperties { + + /** + * 是否开启 + */ + @NotNull(message = "是否开启不能为空") + private Boolean enable; + + /** + * 请求头(响应头)名称 + * + * 1. 如果该请求头非空,则表示请求参数已被「前端」加密,「后端」需要解密 + * 2. 如果该响应头非空,则表示响应结果已被「后端」加密,「前端」需要解密 + */ + @NotEmpty(message = "请求头(响应头)名称不能为空") + private String header = "X-Api-Encrypt"; + + /** + * 对称加密算法,用于请求/响应的加解密 + * + * 目前支持 + * 【对称加密】: + * 1. {@link cn.hutool.crypto.symmetric.SymmetricAlgorithm#AES} + * 2. {@link cn.hutool.crypto.symmetric.SM4#ALGORITHM_NAME} (需要自己二次开发,成本低) + * 【非对称加密】 + * 1. {@link cn.hutool.crypto.asymmetric.AsymmetricAlgorithm#RSA} + * 2. {@link cn.hutool.crypto.asymmetric.SM2} (需要自己二次开发,成本低) + * + * @see 什么是公钥和私钥? + */ + @NotEmpty(message = "对称加密算法不能为空") + private String algorithm; + + /** + * 请求的解密密钥 + * + * 注意: + * 1. 如果是【对称加密】时,它「后端」对应的是“密钥”。对应的,「前端」也对应的也是“密钥”。 + * 2. 如果是【非对称加密】时,它「后端」对应的是“私钥”。对应的,「前端」对应的是“公钥”。(重要!!!) + */ + @NotEmpty(message = "请求的解密密钥不能为空") + private String requestKey; + + /** + * 响应的加密密钥 + * + * 注意: + * 1. 如果是【对称加密】时,它「后端」对应的是“密钥”。对应的,「前端」也对应的也是“密钥”。 + * 2. 如果是【非对称加密】时,它「后端」对应的是“公钥”。对应的,「前端」对应的是“私钥”。(重要!!!) + */ + @NotEmpty(message = "响应的加密密钥不能为空") + private String responseKey; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/YudaoApiEncryptAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/YudaoApiEncryptAutoConfiguration.java new file mode 100644 index 0000000000..03d0f1ac12 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/config/YudaoApiEncryptAutoConfiguration.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.framework.encrypt.config; + +import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; +import cn.iocoder.yudao.framework.encrypt.core.filter.ApiEncryptFilter; +import cn.iocoder.yudao.framework.web.config.WebProperties; +import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.createFilterBean; + +@AutoConfiguration +@Slf4j +@EnableConfigurationProperties(ApiEncryptProperties.class) +@ConditionalOnProperty(prefix = "yudao.api-encrypt", name = "enable", havingValue = "true") +public class YudaoApiEncryptAutoConfiguration { + + @Bean + public FilterRegistrationBean apiEncryptFilter(WebProperties webProperties, + ApiEncryptProperties apiEncryptProperties, + RequestMappingHandlerMapping requestMappingHandlerMapping, + GlobalExceptionHandler globalExceptionHandler) { + ApiEncryptFilter filter = new ApiEncryptFilter(webProperties, apiEncryptProperties, + requestMappingHandlerMapping, globalExceptionHandler); + return createFilterBean(filter, WebFilterOrderEnum.API_ENCRYPT_FILTER); + + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/annotation/ApiEncrypt.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/annotation/ApiEncrypt.java new file mode 100644 index 0000000000..7405111038 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/annotation/ApiEncrypt.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.framework.encrypt.core.annotation; + +import java.lang.annotation.*; + +/** + * HTTP API 加解密注解 + */ +@Documented +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiEncrypt { + + /** + * 是否对请求参数进行解密,默认 true + */ + boolean request() default true; + + /** + * 是否对响应结果进行加密,默认 true + */ + boolean response() default true; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiDecryptRequestWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiDecryptRequestWrapper.java new file mode 100644 index 0000000000..b9f015a7ec --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiDecryptRequestWrapper.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.framework.encrypt.core.filter; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.AsymmetricDecryptor; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.symmetric.SymmetricDecryptor; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 解密请求 {@link HttpServletRequestWrapper} 实现类 + * + * @author 芋道源码 + */ +public class ApiDecryptRequestWrapper extends HttpServletRequestWrapper { + + private final byte[] body; + + public ApiDecryptRequestWrapper(HttpServletRequest request, + SymmetricDecryptor symmetricDecryptor, + AsymmetricDecryptor asymmetricDecryptor) throws IOException { + super(request); + // 读取 body,允许 HEX、BASE64 传输 + String requestBody = StrUtil.utf8Str( + IoUtil.readBytes(request.getInputStream(), false)); + + // 解密 body + body = symmetricDecryptor != null ? symmetricDecryptor.decrypt(requestBody) + : asymmetricDecryptor.decrypt(requestBody, KeyType.PrivateKey); + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } + + @Override + public int getContentLength() { + return body.length; + } + + @Override + public long getContentLengthLong() { + return body.length; + } + + @Override + public ServletInputStream getInputStream() { + ByteArrayInputStream stream = new ByteArrayInputStream(body); + return new ServletInputStream() { + + @Override + public int read() { + return stream.read(); + } + + @Override + public int available() { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + }; + } +} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptFilter.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptFilter.java new file mode 100644 index 0000000000..e6d03ba32b --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptFilter.java @@ -0,0 +1,152 @@ +package cn.iocoder.yudao.framework.encrypt.core.filter; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.AsymmetricDecryptor; +import cn.hutool.crypto.asymmetric.AsymmetricEncryptor; +import cn.hutool.crypto.symmetric.SymmetricDecryptor; +import cn.hutool.crypto.symmetric.SymmetricEncryptor; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; +import cn.iocoder.yudao.framework.encrypt.config.ApiEncryptProperties; +import cn.iocoder.yudao.framework.encrypt.core.annotation.ApiEncrypt; +import cn.iocoder.yudao.framework.web.config.WebProperties; +import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter; +import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.io.IOException; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; + +/** + * API 加密过滤器,处理 {@link ApiEncrypt} 注解。 + * + * 1. 解密请求参数 + * 2. 加密响应结果 + * + * 疑问:为什么不使用 SpringMVC 的 RequestBodyAdvice 或 ResponseBodyAdvice 机制呢? + * 回答:考虑到项目中会记录访问日志、异常日志,以及 HTTP API 签名等场景,最好是全局级、且提前做解析!!! + * + * @author 芋道源码 + */ +@Slf4j +public class ApiEncryptFilter extends ApiRequestFilter { + + private final ApiEncryptProperties apiEncryptProperties; + + private final RequestMappingHandlerMapping requestMappingHandlerMapping; + + private final GlobalExceptionHandler globalExceptionHandler; + + private final SymmetricDecryptor requestSymmetricDecryptor; + private final AsymmetricDecryptor requestAsymmetricDecryptor; + + private final SymmetricEncryptor responseSymmetricEncryptor; + private final AsymmetricEncryptor responseAsymmetricEncryptor; + + public ApiEncryptFilter(WebProperties webProperties, + ApiEncryptProperties apiEncryptProperties, + RequestMappingHandlerMapping requestMappingHandlerMapping, + GlobalExceptionHandler globalExceptionHandler) { + super(webProperties); + this.apiEncryptProperties = apiEncryptProperties; + this.requestMappingHandlerMapping = requestMappingHandlerMapping; + this.globalExceptionHandler = globalExceptionHandler; + if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "AES")) { + this.requestSymmetricDecryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getRequestKey())); + this.requestAsymmetricDecryptor = null; + this.responseSymmetricEncryptor = SecureUtil.aes(StrUtil.utf8Bytes(apiEncryptProperties.getResponseKey())); + this.responseAsymmetricEncryptor = null; + } else if (StrUtil.equalsIgnoreCase(apiEncryptProperties.getAlgorithm(), "RSA")) { + this.requestSymmetricDecryptor = null; + this.requestAsymmetricDecryptor = SecureUtil.rsa(apiEncryptProperties.getRequestKey(), null); + this.responseSymmetricEncryptor = null; + this.responseAsymmetricEncryptor = SecureUtil.rsa(null, apiEncryptProperties.getResponseKey()); + } else { + // 补充说明:如果要支持 SM2、SM4 等算法,可在此处增加对应实例的创建,并添加相应的 Maven 依赖即可。 + throw new IllegalArgumentException("不支持的加密算法:" + apiEncryptProperties.getAlgorithm()); + } + } + + @Override + @SuppressWarnings("NullableProblems") + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + // 获取 @ApiEncrypt 注解 + ApiEncrypt apiEncrypt = getApiEncrypt(request); + boolean requestEnable = apiEncrypt != null && apiEncrypt.request(); + boolean responseEnable = apiEncrypt != null && apiEncrypt.response(); + String encryptHeader = request.getHeader(apiEncryptProperties.getHeader()); + if (!requestEnable && !responseEnable && StrUtil.isBlank(encryptHeader)) { + chain.doFilter(request, response); + return; + } + + // 1. 解密请求 + if (ObjectUtils.equalsAny(HttpMethod.valueOf(request.getMethod()), + HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE)) { + try { + if (StrUtil.isNotBlank(encryptHeader)) { + request = new ApiDecryptRequestWrapper(request, + requestSymmetricDecryptor, requestAsymmetricDecryptor); + } else if (requestEnable) { + throw invalidParamException("请求未包含加密标头,请检查是否正确配置了加密标头"); + } + } catch (Exception ex) { + CommonResult result = globalExceptionHandler.allExceptionHandler(request, ex); + ServletUtils.writeJSON(response, result); + return; + } + } + + // 2. 执行过滤器链 + if (responseEnable) { + // 特殊:仅包装,最后执行。目的:Response 内容可以被重复读取!!! + response = new ApiEncryptResponseWrapper(response); + } + chain.doFilter(request, response); + + // 3. 加密响应(真正执行) + if (responseEnable) { + ((ApiEncryptResponseWrapper) response).encrypt(apiEncryptProperties, + responseSymmetricEncryptor, responseAsymmetricEncryptor); + } + } + + /** + * 获取 @ApiEncrypt 注解 + * + * @param request 请求 + */ + private ApiEncrypt getApiEncrypt(HttpServletRequest request) { + try { + HandlerExecutionChain mappingHandler = requestMappingHandlerMapping.getHandler(request); + if (mappingHandler == null) { + return null; + } + Object handler = mappingHandler.getHandler(); + if (handler instanceof HandlerMethod handlerMethod) { + ApiEncrypt annotation = handlerMethod.getMethodAnnotation(ApiEncrypt.class); + if (annotation == null) { + annotation = handlerMethod.getBeanType().getAnnotation(ApiEncrypt.class); + } + return annotation; + } + } catch (Exception e) { + log.error("[getApiEncrypt][url({}/{}) 获取 @ApiEncrypt 注解失败]", + request.getRequestURI(), request.getMethod(), e); + } + return null; + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java new file mode 100644 index 0000000000..fed38917b9 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java @@ -0,0 +1,109 @@ +package cn.iocoder.yudao.framework.encrypt.core.filter; + +import cn.hutool.crypto.asymmetric.AsymmetricEncryptor; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.symmetric.SymmetricEncryptor; +import cn.iocoder.yudao.framework.encrypt.config.ApiEncryptProperties; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +/** + * 加密响应 {@link HttpServletResponseWrapper} 实现类 + * + * @author 芋道源码 + */ +public class ApiEncryptResponseWrapper extends HttpServletResponseWrapper { + + private final ByteArrayOutputStream byteArrayOutputStream; + private final ServletOutputStream servletOutputStream; + private final PrintWriter printWriter; + + public ApiEncryptResponseWrapper(HttpServletResponse response) { + super(response); + this.byteArrayOutputStream = new ByteArrayOutputStream(); + this.servletOutputStream = this.getOutputStream(); + this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream)); + } + + public void encrypt(ApiEncryptProperties properties, + SymmetricEncryptor symmetricEncryptor, + AsymmetricEncryptor asymmetricEncryptor) throws IOException { + // 1.1 清空 body + HttpServletResponse response = (HttpServletResponse) this.getResponse(); + response.resetBuffer(); + // 1.2 获取 body + this.flushBuffer(); + byte[] body = byteArrayOutputStream.toByteArray(); + + // 2. 加密 body + String encryptedBody = symmetricEncryptor != null ? symmetricEncryptor.encryptBase64(body) + : asymmetricEncryptor.encryptBase64(body, KeyType.PublicKey); + response.getWriter().write(encryptedBody); + + // 3. 添加加密 header 标识 + this.addHeader(properties.getHeader(), "true"); + // 特殊:特殊:https://juejin.cn/post/6867327674675625992 + this.addHeader("Access-Control-Expose-Headers", properties.getHeader()); + } + + @Override + public PrintWriter getWriter() { + return printWriter; + } + + @Override + public void flushBuffer() throws IOException { + if (servletOutputStream != null) { + servletOutputStream.flush(); + } + if (printWriter != null) { + printWriter.flush(); + } + } + + @Override + public void reset() { + byteArrayOutputStream.reset(); + } + + @Override + public ServletOutputStream getOutputStream() { + return new ServletOutputStream() { + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + } + + @Override + public void write(int b) { + byteArrayOutputStream.write(b); + } + + @Override + @SuppressWarnings("NullableProblems") + public void write(byte[] b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + @SuppressWarnings("NullableProblems") + public void write(byte[] b, int off, int len) { + byteArrayOutputStream.write(b, off, len); + } + + }; + } + +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/package-info.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/package-info.java new file mode 100644 index 0000000000..ca08197125 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/package-info.java @@ -0,0 +1,4 @@ +/** + * HTTP API 加密组件:支持 Request 和 Response 的加密、解密 + */ +package cn.iocoder.yudao.framework.encrypt; \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java index 8e80fa591f..b5f38d96fd 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyWrapper.java @@ -1,14 +1,13 @@ package cn.iocoder.yudao.framework.web.core.filter; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; - import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStreamReader; /** @@ -29,12 +28,22 @@ public class CacheRequestBodyWrapper extends HttpServletRequestWrapper { } @Override - public BufferedReader getReader() throws IOException { + public BufferedReader getReader() { return new BufferedReader(new InputStreamReader(this.getInputStream())); } @Override - public ServletInputStream getInputStream() throws IOException { + public int getContentLength() { + return body.length; + } + + @Override + public long getContentLengthLong() { + return body.length; + } + + @Override + public ServletInputStream getInputStream() { final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); // 返回 ServletInputStream return new ServletInputStream() { diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 9cdcd09c4e..07a7955c34 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/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 cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration -cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration \ No newline at end of file +cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration +cn.iocoder.yudao.framework.encrypt.config.YudaoApiEncryptAutoConfiguration \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/encrypt/ApiEncryptTest.java b/yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/encrypt/ApiEncryptTest.java new file mode 100644 index 0000000000..12d406e5f5 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/encrypt/ApiEncryptTest.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.framework.encrypt; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import cn.hutool.crypto.symmetric.SymmetricAlgorithm; +import org.junit.jupiter.api.Test; + +import java.util.Objects; + +/** + * 各种 API 加解密的测试类:不是单测,而是方便大家生成密钥、加密、解密等操作。 + * + * @author 芋道源码 + */ +@SuppressWarnings("ConstantValue") +public class ApiEncryptTest { + + @Test + public void testGenerateAsymmetric() { + String asymmetricAlgorithm = AsymmetricAlgorithm.RSA.getValue(); +// String asymmetricAlgorithm = "SM2"; +// String asymmetricAlgorithm = SM4.ALGORITHM_NAME; +// String asymmetricAlgorithm = SymmetricAlgorithm.AES.getValue(); + String requestClientKey = null; + String requestServerKey = null; + String responseClientKey = null; + String responseServerKey = null; + if (Objects.equals(asymmetricAlgorithm, AsymmetricAlgorithm.RSA.getValue())) { + // 请求的密钥 + RSA requestRsa = SecureUtil.rsa(); + requestClientKey = requestRsa.getPublicKeyBase64(); + requestServerKey = requestRsa.getPrivateKeyBase64(); + // 响应的密钥 + RSA responseRsa = new RSA(); + responseClientKey = responseRsa.getPrivateKeyBase64(); + responseServerKey = responseRsa.getPublicKeyBase64(); + } else if (Objects.equals(asymmetricAlgorithm, SymmetricAlgorithm.AES.getValue())) { + // AES 密钥可选 32、24、16 位 + // 请求的密钥(前后端密钥一致) + requestClientKey = RandomUtil.randomNumbers(32); + requestServerKey = requestClientKey; + // 响应的密钥(前后端密钥一致) + responseClientKey = RandomUtil.randomNumbers(32); + responseServerKey = responseClientKey; + } + + // 打印结果 + System.out.println("requestClientKey = " + requestClientKey); + System.out.println("requestServerKey = " + requestServerKey); + System.out.println("responseClientKey = " + responseClientKey); + System.out.println("responseServerKey = " + responseServerKey); + } + + @Test + public void testEncrypt_aes() { + String key = "52549111389893486934626385991395"; + String body = "{\n" + + " \"username\": \"admin\",\n" + + " \"password\": \"admin123\",\n" + + " \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" + + " \"code\": \"1024\"\n" + + "}"; + String encrypt = SecureUtil.aes(StrUtil.utf8Bytes(key)) + .encryptBase64(body); + System.out.println("encrypt = " + encrypt); + } + + @Test + public void testEncrypt_rsa() { + String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB"; + String body = "{\n" + + " \"username\": \"admin\",\n" + + " \"password\": \"admin123\",\n" + + " \"uuid\": \"3acd87a09a4f48fb9118333780e94883\",\n" + + " \"code\": \"1024\"\n" + + "}"; + String encrypt = SecureUtil.rsa(null, key) + .encryptBase64(body, KeyType.PublicKey); + System.out.println("encrypt = " + encrypt); + } + +} diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http index f42dfcd030..52a724bf35 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.http @@ -11,6 +11,24 @@ tag: Yunai.local "code": "1024" } +### 请求 /login 接口【加密 AES】 => 成功 +POST {{baseUrl}}/system/auth/login +Content-Type: application/json +tenant-id: {{adminTenantId}} +tag: Yunai.local +X-API-ENCRYPT: true + +WvSX9MOrenyGfBhEM0g1/hHgq8ocktMZ9OwAJ6MOG5FUrzYF/rG5JF1eMptQM1wT73VgDS05l/37WeRtad+JrqChAul/sR/SdOsUKqjBhvvQx1JVhzxr6s8uUP67aKTSZ6Psv7O32ELxXrzSaQvG5CInzz3w6sLtbNNLd1kXe6Q= + +### 请求 /login 接口【加密 RSA】 => 成功 +POST {{baseUrl}}/system/auth/login +Content-Type: application/json +tenant-id: {{adminTenantId}} +tag: Yunai.local +X-API-ENCRYPT: true + +e7QZTork9ZV5CmgZvSd+cHZk3xdUxKtowLM02kOha+gxHK2H/daU8nVBYS3+bwuDRy5abf+Pz1QJJGVAEd27wwrXBmupOOA/bhpuzzDwcRuJRD+z+YgiNoEXFDRHERxPYlPqAe9zAHtihD0ceub1AjybQsEsROew4C3Q602XYW0= + ### 请求 /login 接口 => 成功(无验证码) POST {{baseUrl}}/system/auth/login Content-Type: application/json diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index d82d3974a5..1decd79cda 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -245,6 +245,13 @@ yudao: security: permit-all_urls: - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 + api-encrypt: + enable: true # 是否开启 API 加密 + algorithm: AES # 加密算法,支持 AES、RSA 等 + request-key: 52549111389893486934626385991395 # 【AES 案例】请求加密的秘钥,,必须 16、24、32 位 + response-key: 96103715984234343991809655248883 # 【AES 案例】响应加密的秘钥,AES 案例,必须 16、24、32 位 +# request-key: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKWzasimcZ1icsWDPVdTXcZs1DkOWjI+m9bTQU8aOqflnomkr6QO1WWeSHBHzuJGsTlV/ZY2pFfq/NstKC94hBjx7yioYJvzb2bKN/Uy4j5nM3iCF//u0RiFkkY8j0Bt/EWoFTOb6RHf8cHIAjbYYtP3pYzbpCIwryfe0g//KIuzAgMBAAECgYADDjZrYcpZjR2xr7RbXmGtzYbyUGXwZEAqa3XaWBD51J2iSyOkAlQEDjGmxGQ3vvb4qDHHadWI+3/TKNeDXJUO+xTVJrnismK5BsHyC6dfxlIK/5BAuknryTca/3UoA1yomS9ZlF3Q0wcecaDoEnSmZEaTrp9T3itPAz4KnGjv5QJBAN5mNcfu6iJ5ktNvEdzqcxkKwbXb9Nq1SLnmTvt+d5TPX7eQ9fCwtOfVu5iBLhhZzb5PJ7pSN3Zt6rl5/jPOGv0CQQC+vETX9oe1wbxZSv6/RBGy0Xow6GndbJwvd89PcAJ2h+OJXWtg/rRHB3t9EQm7iis0XbZTapj19E4U6l8EibhvAkEA1CvYpRwmHKu1SqdM+GBnW/2qHlBwwXJvpoK02TOm674HR/4w0+YRQJfkd7LOAgcyxJuJgDTNmtt0MmzS+iNoFQJAMVSUIZ77XoDq69U/qcw7H5qaFcgmiUQr6QL9tTftCyb+LGri+MUnby96OtCLSdvkbLjIDS8GvKYhA7vSM2RDNQJBAKGyVVnFFIrbK3yuwW71yvxQEGoGxlgvZSezZ4vGgqTxrr9HvAtvWLwR6rpe6ybR/x8uUtoW7NRBWgpiIFwjvY4= # 【RSA 案例】请求解密的私钥 +# response-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDh/CHyBcS/zEfVyINVA7+c9Xxl0CPdxPMK1OIjxaLy/7BLfbkoEpI8onQtjuzfpuxCraDem9bu3BMF0pMH95HytI3Vi0kGjaV+WLIalwgc2w37oA2sbsmKzQOP7SDLO5s2QJNAD7kVwd+Q5rqaLu2MO0xVv+0IUJhn83hClC0L5wIDAQAB # 【RSA 案例】响应加密的公钥 websocket: enable: true # websocket的开关 path: /infra/ws # 路径