# Conflicts:
#	yudao-gateway/src/main/resources/application.yaml
#	yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java
#	yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java
#	yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
#	yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
#	yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
This commit is contained in:
YunaiV
2026-01-18 16:58:19 +08:00
11 changed files with 58 additions and 88 deletions

View File

@@ -1,54 +0,0 @@
package cn.iocoder.yudao.gateway.filter.cors;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 解决 Spring Cloud Gateway 2.x 跨域时,出现重复 Origin 的 BUG
*
* 参考文档:<a href="https://blog.csdn.net/zimou5581/article/details/90043178" />
*
* @author 芋道源码
*/
@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// 指定此过滤器位于 NettyWriteResponseFilter 之后
// 即待处理完响应体后接着处理响应头
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.defer(() -> {
// https://gitee.com/zhijiantianya/yudao-cloud/pulls/177/
List<String> keysToModify = exchange.getResponse().getHeaders().entrySet().stream()
.filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
.filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
keysToModify.forEach(key->{
List<String> values = exchange.getResponse().getHeaders().get(key);
if (values != null && !values.isEmpty()) {
exchange.getResponse().getHeaders().put(key, Collections.singletonList(values.get(0)));
}
});
return chain.filter(exchange);
}));
}
}

View File

@@ -194,6 +194,8 @@ spring:
- RewritePath=/admin-api/iot/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - RewritePath=/admin-api/iot/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
x-forwarded: x-forwarded:
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
default-filters: # 全局过滤器,对应 GatewayFilterDefinition 数组
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
server: server:
port: 48080 port: 48080

View File

@@ -240,6 +240,12 @@
<groupId>org.springframework.ai</groupId> <groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>${spring-ai.version}</version> <version>${spring-ai.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<!-- 客户端 --> <!-- 客户端 -->

View File

@@ -65,11 +65,17 @@ public class TradeOrderLogAspect {
public void doAfterReturning(JoinPoint joinPoint, TradeOrderLog orderLog) { public void doAfterReturning(JoinPoint joinPoint, TradeOrderLog orderLog) {
try { try {
// 1.1 操作用户 // 1.1 操作用户
Integer userType = getUserType(); Integer userType = USER_TYPE.get();
Long userId = getUserId(); if (ObjectUtil.isNull(userType)) {
userType = getUserType();
}
Long userId = USER_ID.get();
if (ObjectUtil.isNull(userId)) {
userId = getUserId();
}
// 1.2 订单信息 // 1.2 订单信息
Long orderId = ORDER_ID.get(); Long orderId = ORDER_ID.get();
if (orderId == null) { // 如果未设置,只有注解,说明不需要记录日志 if (ObjectUtil.isNull(orderId)) { // 如果未设置,只有注解,说明不需要记录日志
return; return;
} }
Integer beforeStatus = BEFORE_STATUS.get(); Integer beforeStatus = BEFORE_STATUS.get();

View File

@@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.system.api.mail.dto; package cn.iocoder.yudao.module.system.api.mail.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull; import java.io.File;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -34,7 +36,6 @@ public class MailSendSingleToUserReqDTO {
*/ */
private List<@Email String> bccMails; private List<@Email String> bccMails;
/** /**
* 邮件模板编号 * 邮件模板编号
*/ */
@@ -44,5 +45,9 @@ public class MailSendSingleToUserReqDTO {
* 邮件模板参数 * 邮件模板参数
*/ */
private Map<String, Object> templateParams; private Map<String, Object> templateParams;
/**
* 附件内容
*/
private File[] attachments;
} }

View File

@@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.system.api.mail;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.api.mail.dto.MailSendSingleToUserReqDTO; import cn.iocoder.yudao.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.service.mail.MailSendService; import cn.iocoder.yudao.module.system.service.mail.MailSendService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用 @RestController // 提供 RESTful API 接口,给 Feign 调用
@@ -21,14 +20,14 @@ public class MailSendApiImpl implements MailSendApi {
public CommonResult<Long> sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) { public CommonResult<Long> sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) {
return success(mailSendService.sendSingleMailToAdmin(reqDTO.getUserId(), return success(mailSendService.sendSingleMailToAdmin(reqDTO.getUserId(),
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(), reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); reqDTO.getTemplateCode(), reqDTO.getTemplateParams(), reqDTO.getAttachments()));
} }
@Override @Override
public CommonResult<Long> sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) { public CommonResult<Long> sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) {
return success(mailSendService.sendSingleMailToMember(reqDTO.getUserId(), return success(mailSendService.sendSingleMailToMember(reqDTO.getUserId(),
reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(), reqDTO.getToMails(), reqDTO.getCcMails(), reqDTO.getBccMails(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams())); reqDTO.getTemplateCode(), reqDTO.getTemplateParams(), reqDTO.getAttachments()));
} }
} }

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
@@ -32,6 +33,7 @@ public class AdminUserApiImpl implements AdminUserApi {
private DeptService deptService; private DeptService deptService;
@Override @Override
@DataPermission(enable = false) // 忽略数据权限避免因为过滤导致无法查询用户。类似https://github.com/YunaiV/ruoyi-vue-pro/issues/1051
public CommonResult<AdminUserRespDTO> getUser(Long id) { public CommonResult<AdminUserRespDTO> getUser(Long id) {
AdminUserDO user = userService.getUser(id); AdminUserDO user = userService.getUser(id);
return success(BeanUtils.toBean(user, AdminUserRespDTO.class)); return success(BeanUtils.toBean(user, AdminUserRespDTO.class));

View File

@@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.system.mq.message.mail; package cn.iocoder.yudao.module.system.mq.message.mail;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty; import java.io.File;
import javax.validation.constraints.NotNull;
import java.util.Collection; import java.util.Collection;
import java.util.List;
/** /**
* 邮箱发送消息 * 邮箱发送消息
@@ -54,5 +53,9 @@ public class MailSendMessage {
*/ */
@NotEmpty(message = "邮件内容不能为空") @NotEmpty(message = "邮件内容不能为空")
private String content; private String content;
/**
* 邮件附件
*/
private File[] attachments;
} }

View File

@@ -1,16 +1,13 @@
package cn.iocoder.yudao.module.system.mq.producer.mail; package cn.iocoder.yudao.module.system.mq.producer.mail;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage; import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import java.io.File;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import static java.util.Collections.singletonList;
/** /**
* Mail 邮件相关消息的 Producer * Mail 邮件相关消息的 Producer
@@ -39,12 +36,13 @@ public class MailProducer {
*/ */
public void sendMailSendMessage(Long sendLogId, public void sendMailSendMessage(Long sendLogId,
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails, Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
Long accountId, String nickname, String title, String content) { Long accountId, String nickname, String title, String content, File[] attachments) {
MailSendMessage message = new MailSendMessage() MailSendMessage message = new MailSendMessage()
.setLogId(sendLogId) .setLogId(sendLogId)
.setToMails(toMails).setCcMails(ccMails).setBccMails(bccMails) .setToMails(toMails).setCcMails(ccMails).setBccMails(bccMails)
.setAccountId(accountId).setNickname(nickname) .setAccountId(accountId).setNickname(nickname)
.setTitle(title).setContent(content); .setTitle(title).setContent(content)
.setAttachments(attachments);
applicationContext.publishEvent(message); applicationContext.publishEvent(message);
} }

View File

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.mail;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage; import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
import java.io.File;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@@ -27,9 +28,9 @@ public interface MailSendService {
*/ */
default Long sendSingleMailToAdmin(Long userId, default Long sendSingleMailToAdmin(Long userId,
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails, Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
String templateCode, Map<String, Object> templateParams) { String templateCode, Map<String, Object> templateParams, File... attachments) {
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(), return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.ADMIN.getValue(),
templateCode, templateParams); templateCode, templateParams, attachments);
} }
/** /**
@@ -45,9 +46,9 @@ public interface MailSendService {
*/ */
default Long sendSingleMailToMember(Long userId, default Long sendSingleMailToMember(Long userId,
Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails, Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
String templateCode, Map<String, Object> templateParams) { String templateCode, Map<String, Object> templateParams, File... attachments) {
return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(), return sendSingleMail(toMails, ccMails, bccMails, userId, UserTypeEnum.MEMBER.getValue(),
templateCode, templateParams); templateCode, templateParams, attachments);
} }
/** /**
@@ -64,7 +65,7 @@ public interface MailSendService {
*/ */
Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails, Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
Long userId, Integer userType, Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams); String templateCode, Map<String, Object> templateParams, File... attachments);
/** /**
* 执行真正的邮件发送 * 执行真正的邮件发送

View File

@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.system.service.mail;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
@@ -15,11 +13,14 @@ import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
import cn.iocoder.yudao.module.system.service.member.MemberService; import cn.iocoder.yudao.module.system.service.member.MemberService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.extra.mail.MailAccount;
import org.dromara.hutool.extra.mail.MailUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import java.io.File;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
@@ -56,7 +57,8 @@ public class MailSendServiceImpl implements MailSendService {
@Override @Override
public Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails, public Long sendSingleMail(Collection<String> toMails, Collection<String> ccMails, Collection<String> bccMails,
Long userId, Integer userType, Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams) { String templateCode, Map<String, Object> templateParams,
File... attachments) {
// 1.1 校验邮箱模版是否合法 // 1.1 校验邮箱模版是否合法
MailTemplateDO template = validateMailTemplate(templateCode); MailTemplateDO template = validateMailTemplate(templateCode);
// 1.2 校验邮箱账号是否合法 // 1.2 校验邮箱账号是否合法
@@ -94,7 +96,7 @@ public class MailSendServiceImpl implements MailSendService {
// 发送 MQ 消息,异步执行发送短信 // 发送 MQ 消息,异步执行发送短信
if (isSend) { if (isSend) {
mailProducer.sendMailSendMessage(sendLogId, toMailSet, ccMailSet, bccMailSet, mailProducer.sendMailSendMessage(sendLogId, toMailSet, ccMailSet, bccMailSet,
account.getId(), template.getNickname(), title, content); account.getId(), template.getNickname(), title, content, attachments);
} }
return sendLogId; return sendLogId;
} }
@@ -123,7 +125,7 @@ public class MailSendServiceImpl implements MailSendService {
// 2. 发送邮件 // 2. 发送邮件
try { try {
String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(), String messageId = MailUtil.send(mailAccount, message.getToMails(), message.getCcMails(), message.getBccMails(),
message.getTitle(), message.getContent(), true); message.getTitle(), message.getContent(), true, message.getAttachments());
// 3. 更新结果(成功) // 3. 更新结果(成功)
mailLogService.updateMailSendResult(message.getLogId(), messageId, null); mailLogService.updateMailSendResult(message.getLogId(), messageId, null);
} catch (Exception e) { } catch (Exception e) {
@@ -135,7 +137,7 @@ public class MailSendServiceImpl implements MailSendService {
private MailAccount buildMailAccount(MailAccountDO account, String nickname) { private MailAccount buildMailAccount(MailAccountDO account, String nickname) {
String from = StrUtil.isNotEmpty(nickname) ? nickname + " <" + account.getMail() + ">" : account.getMail(); String from = StrUtil.isNotEmpty(nickname) ? nickname + " <" + account.getMail() + ">" : account.getMail();
return new MailAccount().setFrom(from).setAuth(true) return new MailAccount().setFrom(from).setAuth(true)
.setUser(account.getUsername()).setPass(account.getPassword()) .setUser(account.getUsername()).setPass(account.getPassword().toCharArray())
.setHost(account.getHost()).setPort(account.getPort()) .setHost(account.getHost()).setPort(account.getPort())
.setSslEnable(account.getSslEnable()).setStarttlsEnable(account.getStarttlsEnable()); .setSslEnable(account.getSslEnable()).setStarttlsEnable(account.getStarttlsEnable());
} }