邮件工作流节点

This commit is contained in:
stageluo
2025-11-19 17:44:51 +08:00
parent f04db38d6c
commit cf9eca34d3
2 changed files with 208 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
package org.ruoyi.workflow.workflow.node.mailSend;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ruoyi.workflow.entity.WorkflowComponent;
import org.ruoyi.workflow.entity.WorkflowNode;
import org.ruoyi.workflow.workflow.NodeProcessResult;
import org.ruoyi.workflow.workflow.WfNodeState;
import org.ruoyi.workflow.workflow.WfState;
import org.ruoyi.workflow.workflow.WorkflowUtil;
import org.ruoyi.workflow.workflow.data.NodeIOData;
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
@Slf4j
public class MailSendNode extends AbstractWfNode {
public MailSendNode(WorkflowComponent wfComponent, WorkflowNode nodeDef, WfState wfState, WfNodeState nodeState) {
super(wfComponent, nodeDef, wfState, nodeState);
}
@Override
public NodeProcessResult onProcess() {
try {
MailSendNodeConfig config = checkAndGetConfig(MailSendNodeConfig.class);
List<NodeIOData> inputs = state.getInputs();
// 安全获取模板(使用 defaultString 避免 null
String subjectTemplate = StringUtils.defaultString(config.getSubject());
String contentTemplate = StringUtils.defaultString(config.getContent());
String toMailsTemplate = StringUtils.defaultString(config.getToMails());
String ccMailsTemplate = StringUtils.defaultString(config.getCcMails());
// 渲染收件人和主题
String toMails = WorkflowUtil.renderTemplate(toMailsTemplate, inputs);
String ccMails = WorkflowUtil.renderTemplate(ccMailsTemplate, inputs);
String subject = WorkflowUtil.renderTemplate(subjectTemplate, inputs);
// 内容:优先使用配置的内容模板,否则使用 output 或 input 参数
String content;
if (StringUtils.isNotBlank(contentTemplate)) {
content = WorkflowUtil.renderTemplate(contentTemplate, inputs);
} else {
// 优先使用 output如果没有则使用 input
content = inputs.stream()
.filter(item -> "output".equals(item.getName()))
.map(NodeIOData::valueToString)
.findFirst()
.orElseGet(() -> inputs.stream()
.filter(item -> "input".equals(item.getName()))
.map(NodeIOData::valueToString)
.findFirst()
.orElse(""));
}
// 将换行符转换为 HTML 换行
if (StringUtils.isNotBlank(content)) {
content = content.replace("\n", "<br>");
} else {
content = ""; // 安全兜底
}
// 校验必要字段
if (config.getSender() == null) {
throw new IllegalArgumentException("发件人配置sender不能为空");
}
if (StringUtils.isBlank(toMails)) {
throw new IllegalArgumentException("收件人邮箱to_mails不能为空或未解析出有效值");
}
// 创建邮件发送器
JavaMailSender mailSender = createMailSender(config);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(config.getSender().getMail(), config.getSender().getName());
// 设置收件人
String[] toArray = Arrays.stream(toMails.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.toArray(String[]::new);
if (toArray.length == 0) {
throw new IllegalArgumentException("收件人邮箱列表为空");
}
helper.setTo(toArray);
// 设置抄送(如有)
if (StringUtils.isNotBlank(ccMails)) {
String[] ccArray = Arrays.stream(ccMails.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.toArray(String[]::new);
if (ccArray.length > 0) {
helper.setCc(ccArray);
}
}
// 设置主题和内容(支持 HTML
helper.setSubject(subject);
helper.setText(content, true);
// 发送
mailSender.send(message);
log.info("Email sent successfully to: {}", toMails);
// 构造输出:统一输出为 output 参数
List<NodeIOData> outputs = new java.util.ArrayList<>();
// 优先使用 output如果没有则使用 input但重命名为 output
inputs.stream()
.filter(item -> "output".equals(item.getName()))
.findFirst()
.ifPresentOrElse(
outputs::add,
() -> inputs.stream()
.filter(item -> "input".equals(item.getName()))
.findFirst()
.ifPresent(inputParam -> {
String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null
? inputParam.getContent().getTitle() : "";
NodeIOData outputParam = NodeIOData.createByText("output", title, inputParam.valueToString());
outputs.add(outputParam);
})
);
return NodeProcessResult.builder().content(outputs).build();
} catch (Exception e) {
log.error("Failed to send email in node: {}", node.getId(), e);
// 异常时也统一输出为 output 参数,添加错误信息
List<NodeIOData> errorOutputs = new java.util.ArrayList<>();
state.getInputs().stream()
.filter(item -> "output".equals(item.getName()))
.findFirst()
.ifPresentOrElse(
errorOutputs::add,
() -> state.getInputs().stream()
.filter(item -> "input".equals(item.getName()))
.findFirst()
.ifPresent(inputParam -> {
String title = inputParam.getContent() != null && inputParam.getContent().getTitle() != null
? inputParam.getContent().getTitle() : "";
NodeIOData outputParam = NodeIOData.createByText("output", title, inputParam.valueToString());
errorOutputs.add(outputParam);
})
);
errorOutputs.add(NodeIOData.createByText("error", "mail", e.getMessage()));
return NodeProcessResult.builder().content(errorOutputs).build();
}
}
private JavaMailSender createMailSender(MailSendNodeConfig config) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(config.getSmtp().getHost());
sender.setPort(config.getSmtp().getPort());
sender.setUsername(config.getSender().getMail());
sender.setPassword(config.getSender().getPassword());
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true"); // QQ 邮箱 465 必须开 SSL
props.put("mail.smtp.ssl.protocols", "TLSv1.2");
sender.setJavaMailProperties(props);
return sender;
}
}

View File

@@ -0,0 +1,32 @@
package org.ruoyi.workflow.workflow.node.mailSend;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class MailSendNodeConfig {
private SmtpConfig smtp;
private SenderConfig sender;
private String subject;
private String content;
@JsonProperty("to_mails")
private String toMails; // 支持 "a@x.com,b@y.com"
@JsonProperty("cc_mails")
private String ccMails; // 可选
private Integer senderType;
@Data
public static class SmtpConfig {
private String host;
private Integer port; // 465 或 587
}
@Data
public static class SenderConfig {
private String mail;
private String name;
private String password; // 授权码
}
}