初始化前端目录
This commit is contained in:
@@ -13,4 +13,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
*/
|
||||
public interface MailAccountService extends IService<MailAccount> {
|
||||
|
||||
/**
|
||||
* 获取启用的邮件账户(默认取第一个,可扩展为多账户轮询)
|
||||
*/
|
||||
MailAccount getEnabledAccount();
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
*/
|
||||
public interface MailReceivedService extends IService<MailReceived> {
|
||||
|
||||
void receiveUnreadMail();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.mini.capi.biz.service;
|
||||
|
||||
import com.mini.capi.biz.domain.MailSent;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -13,4 +14,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
*/
|
||||
public interface MailSentService extends IService<MailSent> {
|
||||
|
||||
void sendMail(String[] toAddresses, String[] ccAddresses, String subject, String content, MultipartFile[] attachments);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.mini.capi.biz.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.mini.capi.biz.domain.MailAccount;
|
||||
import com.mini.capi.biz.mapper.MailAccountMapper;
|
||||
import com.mini.capi.biz.service.MailAccountService;
|
||||
@@ -17,4 +18,14 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class MailAccountServiceImpl extends ServiceImpl<MailAccountMapper, MailAccount> implements MailAccountService {
|
||||
|
||||
/**
|
||||
* 获取启用的账户(status=1,按创建时间倒序取第一个)
|
||||
*/
|
||||
@Override
|
||||
public MailAccount getEnabledAccount() {
|
||||
return getOne(new LambdaQueryWrapper<MailAccount>()
|
||||
.eq(MailAccount::getStatus, Boolean.TRUE) // 1-启用
|
||||
.orderByDesc(MailAccount::getCreateTime)
|
||||
.last("limit 1"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
package com.mini.capi.biz.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.mini.capi.config.JavaMailConfig;
|
||||
import com.mini.capi.biz.domain.MailAccount;
|
||||
import com.mini.capi.biz.domain.MailAttachment;
|
||||
import com.mini.capi.biz.domain.MailReceived;
|
||||
import com.mini.capi.biz.mapper.MailReceivedMapper;
|
||||
import com.mini.capi.biz.service.MailAccountService;
|
||||
import com.mini.capi.biz.service.MailAttachmentService;
|
||||
import com.mini.capi.biz.service.MailReceivedService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.mini.capi.utils.FileUtils;
|
||||
import com.mini.capi.utils.MailParseUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
import javax.mail.search.FlagTerm;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 接收邮件表 服务实现类
|
||||
@@ -17,4 +36,187 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class MailReceivedServiceImpl extends ServiceImpl<MailReceivedMapper, MailReceived> implements MailReceivedService {
|
||||
|
||||
@Resource
|
||||
private MailAccountService mailAccountService;
|
||||
|
||||
@Resource
|
||||
private MailAttachmentService mailAttachmentService;
|
||||
|
||||
@Qualifier("attachmentThreadPool")
|
||||
@Resource
|
||||
private ExecutorService attachmentThreadPool;
|
||||
|
||||
/**
|
||||
* 接收INBOX未读邮件:同步保存邮件基本信息,异步保存附件
|
||||
*/
|
||||
@Override
|
||||
public void receiveUnreadMail() {
|
||||
// 1. 获取启用的邮件账户
|
||||
MailAccount account = mailAccountService.getEnabledAccount();
|
||||
if (account == null) {
|
||||
throw new RuntimeException("无启用的邮件账户,无法接收邮件");
|
||||
}
|
||||
|
||||
// 2. 创建IMAP Session并连接邮箱
|
||||
Session session = JavaMailConfig.createImapSession(account);
|
||||
Store store = null;
|
||||
Folder inbox = null;
|
||||
|
||||
try {
|
||||
store = session.getStore("imap");
|
||||
store.connect(account.getHost(), account.getUsername(), account.getPassword());
|
||||
|
||||
// 3. 打开INBOX文件夹(READ_WRITE模式,支持设置已读)
|
||||
inbox = store.getFolder("INBOX");
|
||||
inbox.open(Folder.READ_WRITE);
|
||||
|
||||
// 4. 获取未读邮件(Flags.Flag.SEEN=false)
|
||||
Message[] unreadMessages = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
|
||||
if (unreadMessages == null || unreadMessages.length == 0) {
|
||||
System.out.print("INBOX无未读邮件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 遍历未读邮件,处理基本信息和附件
|
||||
for (Message message : unreadMessages) {
|
||||
MimeMessage mimeMsg = (MimeMessage) message;
|
||||
handleSingleMail(mimeMsg, account, inbox);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("接收邮件失败", e);
|
||||
} finally {
|
||||
// 6. 关闭资源
|
||||
try {
|
||||
if (inbox != null && inbox.isOpen()) {
|
||||
inbox.close(false); // false:不删除邮件
|
||||
}
|
||||
if (store != null && store.isConnected()) {
|
||||
store.close();
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
System.out.print(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单封邮件:同步存基本信息,异步存附件
|
||||
*/
|
||||
private void handleSingleMail(MimeMessage message, MailAccount account, Folder inbox) throws Exception {
|
||||
// -------------------------- 同步保存邮件基本信息 --------------------------
|
||||
MailReceived mailReceived = new MailReceived();
|
||||
// 基础字段
|
||||
mailReceived.setAccountId(account.getId());
|
||||
mailReceived.setMessageId(message.getMessageID());
|
||||
mailReceived.setFromAddress(MailParseUtils.parseFrom(message));
|
||||
mailReceived.setToAddresses(MailParseUtils.parseTo(message));
|
||||
mailReceived.setCcAddresses(MailParseUtils.parseCc(message));
|
||||
mailReceived.setSubject(MimeUtility.decodeText(message.getSubject())); // 处理中文主题乱码
|
||||
mailReceived.setContent(MailParseUtils.parseContent(message));
|
||||
// 时间字段(邮件发送时间 -> 转为LocalDateTime)
|
||||
mailReceived.setSendTime(LocalDateTime.ofInstant(message.getSentDate().toInstant(), java.time.ZoneId.systemDefault()));
|
||||
mailReceived.setReceiveTime(LocalDateTime.now());
|
||||
// 状态字段
|
||||
mailReceived.setIsRead(Boolean.FALSE); // 初始未读,附件处理后设为已读
|
||||
List<MailParseUtils.AttachmentInfo> attachments = MailParseUtils.extractAttachments(message);
|
||||
mailReceived.setHasAttachment(!attachments.isEmpty() ? Boolean.TRUE : Boolean.FALSE);
|
||||
// 公共字段
|
||||
mailReceived.setCreateTime(LocalDateTime.now());
|
||||
mailReceived.setUpdateTime(LocalDateTime.now());
|
||||
mailReceived.setFTenantId(account.getFTenantId()); // 继承账户的租户ID
|
||||
mailReceived.setFFlowId(account.getFFlowId());
|
||||
mailReceived.setFFlowTaskId(account.getFFlowTaskId());
|
||||
mailReceived.setFFlowState(account.getFFlowState());
|
||||
|
||||
// 保存到接收表,获取主键ID(用于附件关联)
|
||||
save(mailReceived);
|
||||
Long receivedId = mailReceived.getId();
|
||||
if (receivedId == null) {
|
||||
throw new RuntimeException("保存接收邮件失败,主键ID为空");
|
||||
}
|
||||
|
||||
// -------------------------- 异步保存附件(多线程) --------------------------
|
||||
if (!attachments.isEmpty()) {
|
||||
attachmentThreadPool.submit(() -> {
|
||||
try {
|
||||
saveAttachments(attachments, receivedId, account);
|
||||
// 附件保存完成后,设置邮件为已读
|
||||
message.setFlag(Flags.Flag.SEEN, true);
|
||||
// 更新接收表的已读状态
|
||||
MailReceived updateRead = new MailReceived();
|
||||
updateRead.setId(receivedId);
|
||||
updateRead.setIsRead(Boolean.TRUE);
|
||||
updateById(updateRead);
|
||||
} catch (Exception e) {
|
||||
System.out.print(e.getMessage());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 无附件,直接设为已读
|
||||
message.setFlag(Flags.Flag.SEEN, true);
|
||||
MailReceived updateRead = new MailReceived();
|
||||
updateRead.setId(receivedId);
|
||||
updateRead.setIsRead(Boolean.TRUE);
|
||||
updateById(updateRead);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存附件到本地目录和数据库
|
||||
*
|
||||
* @param attachments 附件信息列表
|
||||
* @param refId 关联的接收邮件ID
|
||||
* @param account 邮件账户(用于租户等公共字段)
|
||||
*/
|
||||
private void saveAttachments(List<MailParseUtils.AttachmentInfo> attachments, Long refId, MailAccount account) {
|
||||
for (MailParseUtils.AttachmentInfo attachment : attachments) {
|
||||
try (InputStream inputStream = attachment.getInputStream()) { // 自动关闭流
|
||||
// 1. 生成附件存储信息
|
||||
String originalFileName = attachment.getOriginalFileName();
|
||||
String randomFileName = FileUtils.generate32RandomFileName(originalFileName);
|
||||
String storagePath = FileUtils.ATTACHMENT_ROOT_DIR + "/" + randomFileName; // 完整存储路径
|
||||
String fileNo = randomFileName.substring(0, 16); // 文件编号(取32位随机名前16位)
|
||||
String directory = FileUtils.ATTACHMENT_ROOT_DIR; // 目录(根目录)
|
||||
// 2. 保存附件到本地目录
|
||||
FileUtils.saveFile(inputStream, storagePath);
|
||||
File savedFile = new File(storagePath);
|
||||
Long fileSize = FileUtils.getFileSize(savedFile);
|
||||
// 3. 保存附件信息到数据库(类型1:收件附件)
|
||||
MailAttachment mailAttachment = new MailAttachment();
|
||||
mailAttachment.setFileNo(fileNo);
|
||||
mailAttachment.setDirectory(directory);
|
||||
mailAttachment.setOriginalFileName(originalFileName);
|
||||
mailAttachment.setStoragePath(storagePath);
|
||||
mailAttachment.setFileSize(fileSize);
|
||||
mailAttachment.setType(Boolean.TRUE); // 1-收件附件
|
||||
mailAttachment.setRefId(refId); // 关联接收邮件ID
|
||||
mailAttachment.setContentType(getContentType(originalFileName)); // 简单判断文件类型
|
||||
mailAttachment.setDownloadCount(0); // 初始下载次数0
|
||||
// 公共字段
|
||||
mailAttachment.setCreateTime(LocalDateTime.now());
|
||||
mailAttachment.setUpdateTime(LocalDateTime.now());
|
||||
mailAttachment.setFTenantId(account.getFTenantId());
|
||||
mailAttachment.setFFlowId(account.getFFlowId());
|
||||
mailAttachment.setFFlowTaskId(account.getFFlowTaskId());
|
||||
mailAttachment.setFFlowState(account.getFFlowState());
|
||||
mailAttachmentService.save(mailAttachment);
|
||||
} catch (Exception e) {
|
||||
System.out.print(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单判断文件类型(可扩展为更精准的判断)
|
||||
*/
|
||||
private String getContentType(String fileName) {
|
||||
if (fileName.endsWith(".txt")) return "text/plain";
|
||||
if (fileName.endsWith(".pdf")) return "application/pdf";
|
||||
if (fileName.endsWith(".doc") || fileName.endsWith(".docx")) return "application/msword";
|
||||
if (fileName.endsWith(".xls") || fileName.endsWith(".xlsx")) return "application/vnd.ms-excel";
|
||||
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) return "image/jpeg";
|
||||
if (fileName.endsWith(".png")) return "image/png";
|
||||
return "application/octet-stream"; // 默认二进制流
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
package com.mini.capi.biz.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.mini.capi.config.JavaMailConfig;
|
||||
import com.mini.capi.biz.domain.MailAccount;
|
||||
import com.mini.capi.biz.domain.MailAttachment;
|
||||
import com.mini.capi.biz.domain.MailSent;
|
||||
import com.mini.capi.biz.mapper.MailSentMapper;
|
||||
import com.mini.capi.biz.service.MailAccountService;
|
||||
import com.mini.capi.biz.service.MailAttachmentService;
|
||||
import com.mini.capi.biz.service.MailSentService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.mini.capi.utils.FileUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.activation.DataHandler;
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -17,4 +32,221 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class MailSentServiceImpl extends ServiceImpl<MailSentMapper, MailSent> implements MailSentService {
|
||||
|
||||
@Autowired
|
||||
private MailAccountService mailAccountService;
|
||||
|
||||
@Autowired
|
||||
private MailAttachmentService mailAttachmentService;
|
||||
|
||||
/**
|
||||
* 发送邮件(支持多收件人、多抄送、多附件)
|
||||
*
|
||||
* @param toAddresses 收件人列表(数组)
|
||||
* @param ccAddresses 抄送列表(数组,可为null)
|
||||
* @param subject 邮件主题
|
||||
* @param content 邮件内容(支持HTML)
|
||||
* @param attachments 附件列表(可为null)
|
||||
*/
|
||||
@Override
|
||||
public void sendMail(String[] toAddresses, String[] ccAddresses, String subject, String content, MultipartFile[] attachments) {
|
||||
// 1. 校验参数
|
||||
if (toAddresses == null || toAddresses.length == 0) {
|
||||
throw new IllegalArgumentException("收件人不能为空");
|
||||
}
|
||||
if (subject == null || subject.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("邮件主题不能为空");
|
||||
}
|
||||
|
||||
// 2. 获取启用的邮件账户
|
||||
MailAccount account = mailAccountService.getEnabledAccount();
|
||||
if (account == null) {
|
||||
throw new RuntimeException("无启用的邮件账户,无法发送邮件");
|
||||
}
|
||||
|
||||
// 3. 创建SMTP Session和MimeMessage
|
||||
Session session = JavaMailConfig.createSmtpSession(account);
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
Transport transport = null;
|
||||
try {
|
||||
// -------------------------- 构建邮件内容 --------------------------
|
||||
// 3.1 设置发件人
|
||||
message.setFrom(new InternetAddress(account.getFromAddress()));
|
||||
|
||||
// 3.2 设置收件人
|
||||
InternetAddress[] toAddrs = Arrays.stream(toAddresses)
|
||||
.map(addr -> {
|
||||
try {
|
||||
return new InternetAddress(addr);
|
||||
} catch (AddressException e) {
|
||||
throw new RuntimeException("收件人地址格式错误:" + addr, e);
|
||||
}
|
||||
})
|
||||
.toArray(InternetAddress[]::new);
|
||||
message.setRecipients(Message.RecipientType.TO, toAddrs);
|
||||
|
||||
// 3.3 设置抄送(可选)
|
||||
if (ccAddresses != null && ccAddresses.length > 0) {
|
||||
InternetAddress[] ccAddrs = Arrays.stream(ccAddresses)
|
||||
.map(addr -> {
|
||||
try {
|
||||
return new InternetAddress(addr);
|
||||
} catch (AddressException e) {
|
||||
throw new RuntimeException("抄送地址格式错误:" + addr, e);
|
||||
}
|
||||
})
|
||||
.toArray(InternetAddress[]::new);
|
||||
message.setRecipients(Message.RecipientType.CC, ccAddrs);
|
||||
}
|
||||
|
||||
// 3.4 设置主题(处理中文)
|
||||
message.setSubject(subject, "UTF-8");
|
||||
|
||||
// 3.5 构建邮件内容(支持附件)
|
||||
Multipart multipart = new MimeMultipart();
|
||||
// 文本部分
|
||||
MimeBodyPart textPart = new MimeBodyPart();
|
||||
textPart.setContent(content, "text/html;charset=UTF-8"); // 支持HTML内容
|
||||
multipart.addBodyPart(textPart);
|
||||
|
||||
// 3.6 添加附件(可选)
|
||||
boolean hasAttachment = attachments != null && attachments.length > 0;
|
||||
if (hasAttachment) {
|
||||
for (MultipartFile file : attachments) {
|
||||
if (file.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
MimeBodyPart attachmentPart = new MimeBodyPart();
|
||||
// 附件流
|
||||
attachmentPart.setDataHandler(new DataHandler(file.getInputStream(), file.getContentType()));
|
||||
// 附件名(处理中文乱码)
|
||||
attachmentPart.setFileName(MimeUtility.encodeText(file.getOriginalFilename()));
|
||||
multipart.addBodyPart(attachmentPart);
|
||||
}
|
||||
}
|
||||
|
||||
// 3.7 设置邮件内容
|
||||
message.setContent(multipart);
|
||||
// 设置发送时间
|
||||
message.setSentDate(new java.util.Date());
|
||||
message.saveChanges();
|
||||
|
||||
// -------------------------- 发送邮件 --------------------------
|
||||
transport = session.getTransport("smtp");
|
||||
transport.connect(account.getHost(), account.getUsername(), account.getPassword());
|
||||
transport.sendMessage(message, message.getAllRecipients());
|
||||
|
||||
// -------------------------- 保存发送记录到数据库 --------------------------
|
||||
saveSentRecord(message, account, toAddresses, ccAddresses, subject, content, hasAttachment, null);
|
||||
|
||||
// -------------------------- 保存附件记录到数据库(类型2:发件附件) --------------------------
|
||||
if (hasAttachment) {
|
||||
saveSendAttachments(attachments, getSentIdByMessageId(message.getMessageID()), account);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 发送失败,保存失败记录
|
||||
saveSentRecord(null, account, toAddresses, ccAddresses, subject, content,
|
||||
attachments != null && attachments.length > 0, e.getMessage());
|
||||
throw new RuntimeException("发送邮件失败", e);
|
||||
} finally {
|
||||
// 关闭资源
|
||||
try {
|
||||
if (transport != null && transport.isConnected()) {
|
||||
transport.close();
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
log.error("关闭SMTP连接失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存发送记录到biz_mail_sent
|
||||
*/
|
||||
private void saveSentRecord(MimeMessage message, MailAccount account, String[] toAddresses,
|
||||
String[] ccAddresses, String subject, String content,
|
||||
boolean hasAttachment, String errorMsg) {
|
||||
try {
|
||||
MailSent mailSent = new MailSent();
|
||||
// 基础字段
|
||||
mailSent.setMessageId(message != null ? message.getMessageID() : null);
|
||||
mailSent.setAccountId(account.getId());
|
||||
mailSent.setFromAddress(account.getFromAddress());
|
||||
mailSent.setToAddresses(String.join(",", toAddresses));
|
||||
mailSent.setCcAddresses(ccAddresses != null ? String.join(",", ccAddresses) : "");
|
||||
mailSent.setSubject(subject);
|
||||
mailSent.setContent(content);
|
||||
mailSent.setSendTime(LocalDateTime.now());
|
||||
// 状态字段
|
||||
mailSent.setSendStatus(message != null ? Boolean.TRUE : Boolean.FALSE); // 1-成功,0-失败
|
||||
mailSent.setErrorMsg(errorMsg); // 失败时记录错误信息
|
||||
mailSent.setHasAttachment(hasAttachment ? Boolean.TRUE : Boolean.FALSE);
|
||||
// 公共字段
|
||||
mailSent.setCreateTime(LocalDateTime.now());
|
||||
mailSent.setUpdateTime(LocalDateTime.now());
|
||||
mailSent.setFTenantId(account.getFTenantId());
|
||||
mailSent.setFFlowId(account.getFFlowId());
|
||||
mailSent.setFFlowTaskId(account.getFFlowTaskId());
|
||||
mailSent.setFFlowState(account.getFFlowState());
|
||||
save(mailSent);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存发件附件到本地和数据库(类型2:发件附件)
|
||||
*/
|
||||
private void saveSendAttachments(MultipartFile[] multipartFiles, Long sentId, MailAccount account) {
|
||||
for (MultipartFile file : multipartFiles) {
|
||||
if (file.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// 1. 生成附件存储信息
|
||||
String originalFileName = file.getOriginalFilename();
|
||||
String randomFileName = FileUtils.generate32RandomFileName(originalFileName);
|
||||
String storagePath = FileUtils.ATTACHMENT_ROOT_DIR + "/" + randomFileName;
|
||||
String fileNo = randomFileName.substring(0, 16);
|
||||
String directory = FileUtils.ATTACHMENT_ROOT_DIR;
|
||||
|
||||
// 2. 保存附件到本地
|
||||
FileUtils.saveFile(file.getInputStream(), storagePath);
|
||||
Long fileSize = file.getSize();
|
||||
|
||||
// 3. 保存到附件表(类型2:发件附件)
|
||||
MailAttachment mailAttachment = new MailAttachment();
|
||||
mailAttachment.setFileNo(fileNo);
|
||||
mailAttachment.setDirectory(directory);
|
||||
mailAttachment.setOriginalFileName(originalFileName);
|
||||
mailAttachment.setStoragePath(storagePath);
|
||||
mailAttachment.setFileSize(fileSize);
|
||||
mailAttachment.setType(Boolean.FALSE); // 2-发件附件(注意:实体类type是Boolean,用false对应2)
|
||||
mailAttachment.setRefId(sentId); // 关联发送邮件ID
|
||||
mailAttachment.setContentType(file.getContentType());
|
||||
mailAttachment.setDownloadCount(0);
|
||||
// 公共字段
|
||||
mailAttachment.setCreateTime(LocalDateTime.now());
|
||||
mailAttachment.setUpdateTime(LocalDateTime.now());
|
||||
mailAttachment.setFTenantId(account.getFTenantId());
|
||||
mailAttachment.setFFlowId(account.getFFlowId());
|
||||
mailAttachment.setFFlowTaskId(account.getFFlowTaskId());
|
||||
mailAttachment.setFFlowState(account.getFFlowState());
|
||||
|
||||
mailAttachmentService.save(mailAttachment);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据messageId查询发送记录ID(用于附件关联)
|
||||
*/
|
||||
private Long getSentIdByMessageId(String messageId) {
|
||||
MailSent mailSent = getOne(new LambdaQueryWrapper<MailSent>()
|
||||
.eq(MailSent::getMessageId, messageId)
|
||||
.last("limit 1"));
|
||||
return mailSent != null ? mailSent.getId() : null;
|
||||
}
|
||||
}
|
||||
|
||||
69
src/main/java/com/mini/capi/config/JavaMailConfig.java
Normal file
69
src/main/java/com/mini/capi/config/JavaMailConfig.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package com.mini.capi.config;
|
||||
|
||||
import com.mini.capi.biz.domain.MailAccount;
|
||||
import javax.mail.Session;
|
||||
import java.util.Properties;
|
||||
|
||||
public class JavaMailConfig {
|
||||
|
||||
/**
|
||||
* 构建IMAP Session(用于接收邮件)
|
||||
*/
|
||||
public static Session createImapSession(MailAccount account) {
|
||||
Properties props = new Properties();
|
||||
// IMAP基础配置
|
||||
props.setProperty("mail.store.protocol", "imap");
|
||||
props.setProperty("mail.imap.host", account.getHost());
|
||||
props.setProperty("mail.imap.port", account.getImapPort().toString());
|
||||
// SSL配置
|
||||
if (Boolean.TRUE.equals(account.getSslEnable())) {
|
||||
props.setProperty("mail.imap.ssl.enable", "true");
|
||||
props.setProperty("mail.imap.ssl.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
}
|
||||
// 超时配置
|
||||
props.setProperty("mail.imap.connectiontimeout", "5000");
|
||||
props.setProperty("mail.imap.timeout", "5000");
|
||||
|
||||
// 创建Session(带认证)
|
||||
return Session.getInstance(props, new javax.mail.Authenticator() {
|
||||
@Override
|
||||
protected javax.mail.PasswordAuthentication getPasswordAuthentication() {
|
||||
return new javax.mail.PasswordAuthentication(
|
||||
account.getUsername(),
|
||||
account.getPassword()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建SMTP Session(用于发送邮件)
|
||||
*/
|
||||
public static Session createSmtpSession(MailAccount account) {
|
||||
Properties props = new Properties();
|
||||
// SMTP基础配置
|
||||
props.setProperty("mail.transport.protocol", "smtp");
|
||||
props.setProperty("mail.smtp.host", account.getHost());
|
||||
props.setProperty("mail.smtp.port", account.getSmtpPort().toString());
|
||||
// 认证和SSL配置
|
||||
props.setProperty("mail.smtp.auth", "true"); // 必须开启认证
|
||||
if (Boolean.TRUE.equals(account.getSslEnable())) {
|
||||
props.setProperty("mail.smtp.ssl.enable", "true");
|
||||
props.setProperty("mail.smtp.ssl.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
}
|
||||
// 超时配置
|
||||
props.setProperty("mail.smtp.connectiontimeout", "5000");
|
||||
props.setProperty("mail.smtp.timeout", "5000");
|
||||
|
||||
// 创建Session(带认证)
|
||||
return Session.getInstance(props, new javax.mail.Authenticator() {
|
||||
@Override
|
||||
protected javax.mail.PasswordAuthentication getPasswordAuthentication() {
|
||||
return new javax.mail.PasswordAuthentication(
|
||||
account.getUsername(),
|
||||
account.getPassword()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/mini/capi/config/ThreadPoolConfig.java
Normal file
25
src/main/java/com/mini/capi/config/ThreadPoolConfig.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.mini.capi.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
/**
|
||||
* 附件处理线程池:核心线程2,最大线程5,队列100,空闲60s回收
|
||||
*/
|
||||
@Bean("attachmentThreadPool")
|
||||
public ExecutorService attachmentThreadPool() {
|
||||
return new ThreadPoolExecutor(
|
||||
2,
|
||||
5,
|
||||
60L,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100),
|
||||
Executors.defaultThreadFactory(),
|
||||
new ThreadPoolExecutor.AbortPolicy() // 队列满时拒绝策略(可根据需求调整)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.mini.capi.exception;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<Map<String, String>> handleRuntimeException(RuntimeException e) {
|
||||
log.error("业务异常:", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("code", "500", "msg", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<Map<String, String>> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
log.error("参数异常:", e);
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("code", "400", "msg", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理附件过大异常
|
||||
*/
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
public ResponseEntity<Map<String, String>> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
|
||||
log.error("附件过大:", e);
|
||||
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
|
||||
.body(Map.of("code", "413", "msg", "附件大小超过限制"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理通用异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Map<String, String>> handleException(Exception e) {
|
||||
log.error("系统异常:", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("code", "500", "msg", "系统内部错误,请联系管理员"));
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.mini.capi.mail.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@Configuration
|
||||
public class MailConfig {
|
||||
|
||||
@Bean
|
||||
public JavaMailSenderImpl javaMailSender() {
|
||||
return new JavaMailSenderImpl();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 配置SMTP属性
|
||||
*/
|
||||
public Properties getSmtpProperties(String host, int port, boolean ssl, boolean auth) {
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.host", host);
|
||||
props.put("mail.smtp.port", port);
|
||||
props.put("mail.smtp.auth", auth);
|
||||
|
||||
if (ssl) {
|
||||
props.put("mail.smtp.ssl.enable", "true");
|
||||
} else {
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
}
|
||||
|
||||
props.put("mail.smtp.connectiontimeout", 5000);
|
||||
props.put("mail.smtp.timeout", 5000);
|
||||
props.put("mail.smtp.writetimeout", 5000);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置IMAP属性
|
||||
*/
|
||||
public Properties getImapProperties(String host, int port, boolean ssl) {
|
||||
Properties props = new Properties();
|
||||
props.put("mail.imap.host", host);
|
||||
props.put("mail.imap.port", port);
|
||||
|
||||
if (ssl) {
|
||||
props.put("mail.imap.ssl.enable", "true");
|
||||
}
|
||||
|
||||
props.put("mail.imap.connectiontimeout", 5000);
|
||||
props.put("mail.imap.timeout", 5000);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.mini.capi.mail.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
/**
|
||||
* 附件处理线程池
|
||||
*/
|
||||
@Bean(name = "attachmentExecutor")
|
||||
public Executor attachmentExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 核心线程数
|
||||
executor.setCorePoolSize(5);
|
||||
// 最大线程数
|
||||
executor.setMaxPoolSize(10);
|
||||
// 队列容量
|
||||
executor.setQueueCapacity(50);
|
||||
// 线程名称前缀
|
||||
executor.setThreadNamePrefix("attachment-");
|
||||
// 拒绝策略:由调用线程处理
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package com.mini.capi.mail.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/mail")
|
||||
public class MailController {
|
||||
|
||||
@Autowired
|
||||
private MailReceiveService mailReceiveService;
|
||||
|
||||
@Autowired
|
||||
private MailSendService mailSendService;
|
||||
|
||||
@Autowired
|
||||
private MailAccountMapper mailAccountMapper;
|
||||
|
||||
@Autowired
|
||||
private MailReceivedMapper mailReceivedMapper;
|
||||
|
||||
@Autowired
|
||||
private MailSentMapper mailSentMapper;
|
||||
|
||||
/**
|
||||
* 接收邮件
|
||||
*/
|
||||
@PostMapping("/receive/{accountId}")
|
||||
public ResponseVO<Integer> receiveEmails(@PathVariable Long accountId) {
|
||||
try {
|
||||
int count = mailReceiveService.syncNewEmails(accountId);
|
||||
return ResponseVO.success(count, "接收邮件成功");
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.error("接收邮件失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
*/
|
||||
@PostMapping("/send/{accountId}")
|
||||
public ResponseVO<Long> sendEmail(
|
||||
@PathVariable Long accountId,
|
||||
@ModelAttribute MailSendVO mailSendVO) {
|
||||
try {
|
||||
MailAccount account = mailAccountMapper.selectById(accountId);
|
||||
if (account == null) {
|
||||
return ResponseVO.error("未找到邮件账户");
|
||||
}
|
||||
|
||||
Long mailId = mailSendService.sendEmail(account, mailSendVO);
|
||||
return ResponseVO.success(mailId, "发送邮件成功");
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.error("发送邮件失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收件箱邮件
|
||||
*/
|
||||
@GetMapping("/received/{accountId}")
|
||||
public ResponseVO<List<MailReceived>> getReceivedEmails(
|
||||
@PathVariable Long accountId,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
try {
|
||||
QueryWrapper<MailReceived> query = new QueryWrapper<>();
|
||||
query.eq("account_id", accountId)
|
||||
.orderByDesc("received_date")
|
||||
.last("LIMIT " + page * size + "," + size);
|
||||
|
||||
List<MailReceived> emails = mailReceivedMapper.selectList(query);
|
||||
return ResponseVO.success(emails);
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.error("获取收件箱邮件失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已发送邮件
|
||||
*/
|
||||
@GetMapping("/sent/{accountId}")
|
||||
public ResponseVO<List<MailSent>> getSentEmails(
|
||||
@PathVariable Long accountId,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
try {
|
||||
QueryWrapper<MailSent> query = new QueryWrapper<>();
|
||||
query.eq("account_id", accountId)
|
||||
.orderByDesc("send_date")
|
||||
.last("LIMIT " + page * size + "," + size);
|
||||
|
||||
List<MailSent> emails = mailSentMapper.selectList(query);
|
||||
return ResponseVO.success(emails);
|
||||
} catch (Exception e) {
|
||||
return ResponseVO.error("获取已发送邮件失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.mini.capi.mail;
|
||||
|
||||
public class dc {
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.mini.capi.mail.service;
|
||||
|
||||
public interface MailReceiveService {
|
||||
|
||||
/**
|
||||
* 接收邮件
|
||||
* @param account 邮件账户
|
||||
* @return 接收成功的邮件数量
|
||||
*/
|
||||
int receiveEmails(MailAccount account);
|
||||
|
||||
/**
|
||||
* 同步接收最新的未读邮件
|
||||
* @param accountId 邮件账户ID
|
||||
* @return 接收成功的邮件数量
|
||||
*/
|
||||
int syncNewEmails(Long accountId);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.mini.capi.mail.service;
|
||||
|
||||
public interface MailSendService {
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
* @param account 邮件账户
|
||||
* @param mailSendVO 邮件发送参数
|
||||
* @return 发送成功的邮件ID
|
||||
*/
|
||||
Long sendEmail(MailAccount account, MailSendVO mailSendVO);
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
package com.mini.capi.mail.service.impl;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Service
|
||||
public class MailReceiveServiceImpl implements MailReceiveService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MailReceiveServiceImpl.class);
|
||||
|
||||
// 邮件存储目录
|
||||
private static final String MAIL_FILES_DIR = "/ogsapp/mailfiles";
|
||||
|
||||
@Autowired
|
||||
private MailAccountMapper mailAccountMapper;
|
||||
|
||||
@Autowired
|
||||
private MailReceivedMapper mailReceivedMapper;
|
||||
|
||||
@Autowired
|
||||
private MailAttachmentMapper mailAttachmentMapper;
|
||||
|
||||
@Autowired
|
||||
private MailConfig mailConfig;
|
||||
|
||||
@Autowired
|
||||
private Executor attachmentExecutor;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int receiveEmails(MailAccount account) {
|
||||
if (account == null) {
|
||||
logger.error("邮件账户不能为空");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Store store = null;
|
||||
Folder inbox = null;
|
||||
int receivedCount = 0;
|
||||
|
||||
try {
|
||||
// 初始化IMAP连接
|
||||
Properties props = mailConfig.getImapProperties(
|
||||
account.getImapHost(),
|
||||
account.getImapPort(),
|
||||
account.isImapSsl()
|
||||
);
|
||||
|
||||
Session session = Session.getInstance(props);
|
||||
store = session.getStore("imap");
|
||||
store.connect(
|
||||
account.getImapHost(),
|
||||
account.getUsername(),
|
||||
account.getPassword()
|
||||
);
|
||||
|
||||
// 打开收件箱,只获取未读邮件
|
||||
inbox = store.getFolder("INBOX");
|
||||
inbox.open(Folder.READ_WRITE);
|
||||
|
||||
// 搜索未读邮件
|
||||
Message[] messages = inbox.search(
|
||||
new FlagTerm(new Flags(Flags.Flag.SEEN), false)
|
||||
);
|
||||
|
||||
logger.info("找到 {} 封未读邮件", messages.length);
|
||||
|
||||
// 处理每封邮件
|
||||
for (Message message : messages) {
|
||||
if (message instanceof MimeMessage) {
|
||||
// 保存邮件基本信息
|
||||
MailReceived mailReceived = saveMailInfo((MimeMessage) message, account);
|
||||
if (mailReceived != null) {
|
||||
receivedCount++;
|
||||
|
||||
// 异步处理附件
|
||||
attachmentExecutor.execute(() -> {
|
||||
try {
|
||||
handleAttachments((MimeMessage) message, mailReceived.getId());
|
||||
// 标记为已读
|
||||
message.setFlag(Flags.Flag.SEEN, true);
|
||||
} catch (Exception e) {
|
||||
logger.error("处理邮件附件失败", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("接收邮件失败", e);
|
||||
} finally {
|
||||
try {
|
||||
if (inbox != null && inbox.isOpen()) {
|
||||
inbox.close(false);
|
||||
}
|
||||
if (store != null && store.isConnected()) {
|
||||
store.close();
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
logger.error("关闭邮件连接失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
return receivedCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int syncNewEmails(Long accountId) {
|
||||
if (accountId == null) {
|
||||
logger.error("账户ID不能为空");
|
||||
return 0;
|
||||
}
|
||||
|
||||
MailAccount account = mailAccountMapper.selectById(accountId);
|
||||
if (account == null) {
|
||||
logger.error("未找到ID为 {} 的邮件账户", accountId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return receiveEmails(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存邮件基本信息到数据库
|
||||
*/
|
||||
private MailReceived saveMailInfo(MimeMessage message, MailAccount account) throws MessagingException {
|
||||
try {
|
||||
MailReceived mailReceived = new MailReceived();
|
||||
|
||||
// 设置邮件基本信息
|
||||
mailReceived.setAccountId(account.getId());
|
||||
mailReceived.setSubject(message.getSubject());
|
||||
mailReceived.setSentDate(message.getSentDate());
|
||||
mailReceived.setReceivedDate(new Date());
|
||||
|
||||
// 设置发件人
|
||||
Address[] fromAddresses = message.getFrom();
|
||||
if (fromAddresses != null && fromAddresses.length > 0) {
|
||||
mailReceived.setFromAddress(((InternetAddress) fromAddresses[0]).getAddress());
|
||||
}
|
||||
|
||||
// 设置收件人
|
||||
Address[] toAddresses = message.getRecipients(Message.RecipientType.TO);
|
||||
if (toAddresses != null && toAddresses.length > 0) {
|
||||
List<String> toList = new ArrayList<>();
|
||||
for (Address addr : toAddresses) {
|
||||
toList.add(((InternetAddress) addr).getAddress());
|
||||
}
|
||||
mailReceived.setToAddresses(String.join(",", toList));
|
||||
}
|
||||
|
||||
// 设置抄送人
|
||||
Address[] ccAddresses = message.getRecipients(Message.RecipientType.CC);
|
||||
if (ccAddresses != null && ccAddresses.length > 0) {
|
||||
List<String> ccList = new ArrayList<>();
|
||||
for (Address addr : ccAddresses) {
|
||||
ccList.add(((InternetAddress) addr).getAddress());
|
||||
}
|
||||
mailReceived.setCcAddresses(String.join(",", ccList));
|
||||
}
|
||||
|
||||
// 获取邮件内容
|
||||
String content = MailUtil.getEmailContent(message);
|
||||
mailReceived.setContent(content);
|
||||
|
||||
// 保存到数据库
|
||||
mailReceivedMapper.insert(mailReceived);
|
||||
return mailReceived;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("保存邮件信息失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理并保存邮件附件
|
||||
*/
|
||||
@Async("attachmentExecutor")
|
||||
@Transactional
|
||||
public void handleAttachments(MimeMessage message, Long mailReceivedId) throws MessagingException {
|
||||
try {
|
||||
Object content = message.getContent();
|
||||
|
||||
if (content instanceof MimeMultipart) {
|
||||
MimeMultipart multipart = (MimeMultipart) content;
|
||||
|
||||
// 遍历所有部分寻找附件
|
||||
for (int i = 0; i < multipart.getCount(); i++) {
|
||||
BodyPart bodyPart = multipart.getBodyPart(i);
|
||||
|
||||
// 判断是否为附件
|
||||
if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) ||
|
||||
bodyPart.getFileName() != null) {
|
||||
|
||||
// 处理附件
|
||||
String originalFileName = bodyPart.getFileName();
|
||||
if (originalFileName != null) {
|
||||
// 生成存储文件名(32位随机字符+扩展名)
|
||||
String fileExt = originalFileName.contains(".") ?
|
||||
originalFileName.substring(originalFileName.lastIndexOf(".")) : "";
|
||||
String storedFileName = UUID.randomUUID().toString().replaceAll("-", "") + fileExt;
|
||||
|
||||
// 确保存储目录存在
|
||||
File dir = new File(MAIL_FILES_DIR);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// 保存附件
|
||||
String filePath = MAIL_FILES_DIR + File.separator + storedFileName;
|
||||
FileUtil.saveFile(bodyPart.getInputStream(), filePath);
|
||||
|
||||
// 获取文件大小
|
||||
File savedFile = new File(filePath);
|
||||
long fileSize = savedFile.length();
|
||||
|
||||
// 保存附件信息到数据库
|
||||
MailAttachment attachment = new MailAttachment();
|
||||
attachment.setFileNo(UUID.randomUUID().toString());
|
||||
attachment.setDirectory(MAIL_FILES_DIR);
|
||||
attachment.setFileName(originalFileName);
|
||||
attachment.setStoredPath(filePath);
|
||||
attachment.setFileSize(fileSize);
|
||||
attachment.setType(1); // 1表示收件
|
||||
attachment.setRelatedId(mailReceivedId);
|
||||
attachment.setCreateTime(new Date());
|
||||
|
||||
mailAttachmentMapper.insert(attachment);
|
||||
|
||||
logger.info("保存附件成功: {} -> {}", originalFileName, storedFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("处理邮件附件失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package com.mini.capi.mail.service.impl;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Service
|
||||
public class MailSendServiceImpl implements MailSendService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MailSendServiceImpl.class);
|
||||
|
||||
// 邮件存储目录
|
||||
private static final String MAIL_FILES_DIR = "/ogsapp/mailfiles";
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl mailSender;
|
||||
|
||||
@Autowired
|
||||
private MailConfig mailConfig;
|
||||
|
||||
@Autowired
|
||||
private MailSentMapper mailSentMapper;
|
||||
|
||||
@Autowired
|
||||
private MailAttachmentMapper mailAttachmentMapper;
|
||||
|
||||
@Autowired
|
||||
private Executor attachmentExecutor;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long sendEmail(MailAccount account, MailSendVO mailSendVO) {
|
||||
if (account == null || mailSendVO == null) {
|
||||
logger.error("邮件账户或发送参数不能为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mailSendVO.getTo() == null || mailSendVO.getTo().isEmpty()) {
|
||||
logger.error("收件人不能为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 配置邮件发送器
|
||||
mailSender.setHost(account.getSmtpHost());
|
||||
mailSender.setPort(account.getSmtpPort());
|
||||
mailSender.setUsername(account.getUsername());
|
||||
mailSender.setPassword(account.getPassword());
|
||||
mailSender.setJavaMailProperties(mailConfig.getSmtpProperties(
|
||||
account.getSmtpHost(),
|
||||
account.getSmtpPort(),
|
||||
account.isSmtpSsl(),
|
||||
true
|
||||
));
|
||||
|
||||
// 创建邮件消息
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
|
||||
|
||||
// 设置发件人
|
||||
helper.setFrom(account.getUsername());
|
||||
|
||||
// 设置收件人
|
||||
helper.setTo(mailSendVO.getTo().toArray(new String[0]));
|
||||
|
||||
// 设置抄送
|
||||
if (!CollectionUtils.isEmpty(mailSendVO.getCc())) {
|
||||
helper.setCc(mailSendVO.getCc().toArray(new String[0]));
|
||||
}
|
||||
|
||||
// 设置邮件主题和内容
|
||||
helper.setSubject(mailSendVO.getSubject());
|
||||
helper.setText(mailSendVO.getContent(), mailSendVO.isHtml());
|
||||
|
||||
// 设置发送时间
|
||||
Date sendDate = new Date();
|
||||
helper.setSentDate(sendDate);
|
||||
|
||||
// 处理附件
|
||||
List<MailAttachment> attachments = new ArrayList<>();
|
||||
if (mailSendVO.getAttachments() != null && !mailSendVO.getAttachments().isEmpty()) {
|
||||
for (MultipartFile file : mailSendVO.getAttachments()) {
|
||||
if (!file.isEmpty()) {
|
||||
// 保存附件到服务器
|
||||
String originalFileName = file.getOriginalFilename();
|
||||
String fileExt = originalFileName.contains(".") ?
|
||||
originalFileName.substring(originalFileName.lastIndexOf(".")) : "";
|
||||
String storedFileName = UUID.randomUUID().toString().replaceAll("-", "") + fileExt;
|
||||
|
||||
// 确保存储目录存在
|
||||
File dir = new File(MAIL_FILES_DIR);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
String filePath = MAIL_FILES_DIR + File.separator + storedFileName;
|
||||
file.transferTo(new File(filePath));
|
||||
|
||||
// 添加到邮件
|
||||
helper.addAttachment(originalFileName, new File(filePath));
|
||||
|
||||
// 记录附件信息
|
||||
MailAttachment attachment = new MailAttachment();
|
||||
attachment.setFileNo(UUID.randomUUID().toString());
|
||||
attachment.setDirectory(MAIL_FILES_DIR);
|
||||
attachment.setFileName(originalFileName);
|
||||
attachment.setStoredPath(filePath);
|
||||
attachment.setFileSize(file.getSize());
|
||||
attachment.setType(2); // 2表示发件
|
||||
attachment.setCreateTime(new Date());
|
||||
|
||||
attachments.add(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送邮件
|
||||
mailSender.send(message);
|
||||
logger.info("邮件发送成功,主题: {}", mailSendVO.getSubject());
|
||||
|
||||
// 保存发送记录
|
||||
MailSent mailSent = new MailSent();
|
||||
mailSent.setAccountId(account.getId());
|
||||
mailSent.setSubject(mailSendVO.getSubject());
|
||||
mailSent.setContent(mailSendVO.getContent());
|
||||
mailSent.setFromAddress(account.getUsername());
|
||||
mailSent.setToAddresses(String.join(",", mailSendVO.getTo()));
|
||||
|
||||
if (!CollectionUtils.isEmpty(mailSendVO.getCc())) {
|
||||
mailSent.setCcAddresses(String.join(",", mailSendVO.getCc()));
|
||||
}
|
||||
|
||||
mailSent.setSendDate(sendDate);
|
||||
mailSent.setIsHtml(mailSendVO.isHtml() ? 1 : 0);
|
||||
|
||||
mailSentMapper.insert(mailSent);
|
||||
|
||||
// 保存附件关联信息
|
||||
for (MailAttachment attachment : attachments) {
|
||||
attachment.setRelatedId(mailSent.getId());
|
||||
mailAttachmentMapper.insert(attachment);
|
||||
}
|
||||
|
||||
return mailSent.getId();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("发送邮件失败", e);
|
||||
throw new RuntimeException("发送邮件失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.mini.capi.mail.vo;
|
||||
|
||||
public class MailSendVO {
|
||||
|
||||
// 收件人列表
|
||||
private List<String> to;
|
||||
|
||||
// 抄送列表
|
||||
private List<String> cc;
|
||||
|
||||
// 邮件主题
|
||||
private String subject;
|
||||
|
||||
// 邮件内容
|
||||
private String content;
|
||||
|
||||
// 是否为HTML内容
|
||||
private boolean isHtml = false;
|
||||
|
||||
// 附件列表
|
||||
private List<MultipartFile> attachments;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.mini.capi.mail.vo;
|
||||
|
||||
|
||||
public class ResponseVO<T> {
|
||||
|
||||
// 状态码:0表示成功,其他表示失败
|
||||
private int code;
|
||||
|
||||
// 消息
|
||||
private String message;
|
||||
|
||||
// 数据
|
||||
private T data;
|
||||
|
||||
// 成功响应
|
||||
public static <T> ResponseVO<T> success(T data, String message) {
|
||||
ResponseVO<T> response = new ResponseVO<>();
|
||||
response.code = 0;
|
||||
response.message = message;
|
||||
response.data = data;
|
||||
return response;
|
||||
}
|
||||
|
||||
// 成功响应(默认消息)
|
||||
public static <T> ResponseVO<T> success(T data) {
|
||||
return success(data, "操作成功");
|
||||
}
|
||||
|
||||
// 错误响应
|
||||
public static <T> ResponseVO<T> error(String message) {
|
||||
ResponseVO<T> response = new ResponseVO<>();
|
||||
response.code = 1;
|
||||
response.message = message;
|
||||
response.data = null;
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.mini.capi.sys.controller;
|
||||
|
||||
import com.mini.capi.biz.service.MailReceivedService;
|
||||
import com.mini.capi.biz.service.MailSentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/mail")
|
||||
public class MailController {
|
||||
|
||||
@Autowired
|
||||
private MailReceivedService mailReceiveService;
|
||||
|
||||
@Autowired
|
||||
private MailSentService mailSendService;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 触发接收INBOX未读邮件
|
||||
*/
|
||||
@PostMapping("/receive")
|
||||
public ResponseEntity<Map<String, String>> receiveMail() {
|
||||
mailReceiveService.receiveUnreadMail();
|
||||
return ResponseEntity.ok(Map.of("code", "200", "msg", "接收邮件任务已触发"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件(支持多收件人、多抄送、多附件)
|
||||
* @param to 收件人(多个用逗号分隔,如:a@xxx.com,b@xxx.com)
|
||||
* @param cc 抄送(多个用逗号分隔,可选)
|
||||
* @param subject 主题
|
||||
* @param content 内容(支持HTML)
|
||||
* @param files 附件(可选)
|
||||
*/
|
||||
@PostMapping("/send")
|
||||
public ResponseEntity<Map<String, String>> sendMail(
|
||||
@RequestParam("to") String to,
|
||||
@RequestParam(value = "cc", required = false) String cc,
|
||||
@RequestParam("subject") String subject,
|
||||
@RequestParam("content") String content,
|
||||
@RequestParam(value = "files", required = false) MultipartFile[] files) {
|
||||
|
||||
// 解析收件人(逗号分隔转数组)
|
||||
String[] toAddresses = to.split(",");
|
||||
// 解析抄送(可选,空则为null)
|
||||
String[] ccAddresses = cc != null && !cc.trim().isEmpty() ? cc.split(",") : null;
|
||||
|
||||
// 调用发送服务
|
||||
mailSendService.sendMail(toAddresses, ccAddresses, subject, content, files);
|
||||
return ResponseEntity.status(HttpStatus.CREATED)
|
||||
.body(Map.of("code", "201", "msg", "邮件发送成功"));
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,78 @@
|
||||
package com.mini.capi.utils;
|
||||
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FileUtil {
|
||||
public class FileUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
|
||||
// 附件根目录(需求指定)
|
||||
public static final String ATTACHMENT_ROOT_DIR = "/ogsapp/mailFiles";
|
||||
|
||||
/**
|
||||
* 保存输入流到文件
|
||||
* 检查目录是否存在,不存在则创建
|
||||
*/
|
||||
public static void saveFile(InputStream inputStream, String filePath) {
|
||||
try (OutputStream outputStream = new FileOutputStream(filePath)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
public static void checkDirExists(String dirPath) {
|
||||
File dir = new File(dirPath);
|
||||
if (!dir.exists()) {
|
||||
boolean mkdirs = dir.mkdirs();
|
||||
if (!mkdirs) {
|
||||
throw new RuntimeException("创建目录失败:" + dirPath);
|
||||
}
|
||||
logger.info("文件保存成功: {}", filePath);
|
||||
} catch (Exception e) {
|
||||
logger.error("保存文件失败: " + filePath, e);
|
||||
throw new RuntimeException("保存文件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件
|
||||
* 保存文件到指定路径
|
||||
*
|
||||
* @param inputStream 输入流(附件流)
|
||||
* @param savePath 保存路径(含文件名)
|
||||
*/
|
||||
public static void copyFile(File source, File dest) {
|
||||
try {
|
||||
Files.copy(source.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
public static void saveFile(InputStream inputStream, String savePath) {
|
||||
checkDirExists(Paths.get(savePath).getParent().toString()); // 检查父目录
|
||||
try (OutputStream os = new FileOutputStream(savePath);
|
||||
BufferedInputStream bis = new BufferedInputStream(inputStream);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(os)) {
|
||||
|
||||
byte[] buffer = new byte[1024 * 8];
|
||||
int len;
|
||||
while ((len = bis.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, len);
|
||||
}
|
||||
bos.flush();
|
||||
} catch (IOException e) {
|
||||
logger.error("复制文件失败: {} -> {}", source.getPath(), dest.getPath(), e);
|
||||
throw new RuntimeException("复制文件失败", e);
|
||||
throw new RuntimeException("保存文件失败:" + savePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* 生成32位随机文件名(UUID去掉横线)+ 原始文件后缀
|
||||
*
|
||||
* @param originalFileName 原始文件名
|
||||
* @return 32位随机文件名(如:a1b2c3d4...1234.txt)
|
||||
*/
|
||||
public static boolean deleteFile(String filePath) {
|
||||
File file = new File(filePath);
|
||||
if (file.exists() && file.isFile()) {
|
||||
boolean deleted = file.delete();
|
||||
if (deleted) {
|
||||
logger.info("文件删除成功: {}", filePath);
|
||||
} else {
|
||||
logger.warn("文件删除失败: {}", filePath);
|
||||
}
|
||||
return deleted;
|
||||
public static String generate32RandomFileName(String originalFileName) {
|
||||
// 获取文件后缀(如.txt)
|
||||
String suffix = "";
|
||||
if (StringUtils.hasText(originalFileName) && originalFileName.contains(".")) {
|
||||
suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
|
||||
}
|
||||
return false;
|
||||
// 生成32位UUID(去掉横线)
|
||||
String random32Str = UUID.randomUUID().toString().replace("-", "");
|
||||
return random32Str + suffix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件大小(字节)
|
||||
*/
|
||||
public static long getFileSize(File file) {
|
||||
try {
|
||||
return Files.size(file.toPath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("获取文件大小失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/main/java/com/mini/capi/utils/MailParseUtils.java
Normal file
132
src/main/java/com/mini/capi/utils/MailParseUtils.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package com.mini.capi.utils;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MailParseUtils {
|
||||
|
||||
/**
|
||||
* 解析邮件发件人(名称+地址)
|
||||
*/
|
||||
public static String parseFrom(Message message) throws MessagingException {
|
||||
Address[] froms = message.getFrom();
|
||||
if (froms == null || froms.length == 0) {
|
||||
return "";
|
||||
}
|
||||
InternetAddress from = (InternetAddress) froms[0];
|
||||
String fromName = from.getPersonal(); // 发件人名称(可能为null)
|
||||
String fromAddr = from.getAddress(); // 发件人地址
|
||||
return StringUtils.hasText(fromName) ? fromName + "<" + fromAddr + ">" : fromAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析邮件收件人(多个用逗号分隔)
|
||||
*/
|
||||
public static String parseTo(Message message) throws MessagingException {
|
||||
return parseRecipients(message, Message.RecipientType.TO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析邮件抄送人(多个用逗号分隔)
|
||||
*/
|
||||
public static String parseCc(Message message) throws MessagingException {
|
||||
return parseRecipients(message, Message.RecipientType.CC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用解析收件人/抄送人
|
||||
*/
|
||||
private static String parseRecipients(Message message, Message.RecipientType type) throws MessagingException {
|
||||
Address[] recipients = message.getRecipients(type);
|
||||
if (recipients == null || recipients.length == 0) {
|
||||
return "";
|
||||
}
|
||||
List<String> addrList = new ArrayList<>();
|
||||
for (Address addr : recipients) {
|
||||
InternetAddress internetAddr = (InternetAddress) addr;
|
||||
String name = internetAddr.getPersonal();
|
||||
String address = internetAddr.getAddress();
|
||||
addrList.add(StringUtils.hasText(name) ? name + "<" + address + ">" : address);
|
||||
}
|
||||
return String.join(",", addrList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析邮件内容(支持文本/HTML)
|
||||
*/
|
||||
public static String parseContent(Part part) throws MessagingException, IOException {
|
||||
if (part.isMimeType("text/plain") || part.isMimeType("text/html")) {
|
||||
// 文本/HTML直接读取
|
||||
return (String) part.getContent();
|
||||
}
|
||||
// 多部分内容(含附件),递归解析文本部分
|
||||
if (part.isMimeType("multipart/*")) {
|
||||
MimeMultipart multipart = (MimeMultipart) part.getContent();
|
||||
for (int i = 0; i < multipart.getCount(); i++) {
|
||||
BodyPart bodyPart = multipart.getBodyPart(i);
|
||||
String content = parseContent(bodyPart);
|
||||
if (StringUtils.hasText(content)) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取邮件附件(返回附件流+原始文件名)
|
||||
*/
|
||||
public static List<AttachmentInfo> extractAttachments(Part part) throws MessagingException, IOException {
|
||||
List<AttachmentInfo> attachments = new ArrayList<>();
|
||||
// 多部分内容才可能有附件
|
||||
if (part.isMimeType("multipart/*")) {
|
||||
MimeMultipart multipart = (MimeMultipart) part.getContent();
|
||||
for (int i = 0; i < multipart.getCount(); i++) {
|
||||
BodyPart bodyPart = multipart.getBodyPart(i);
|
||||
// 判断是否为附件(Disposition为ATTACHMENT或INLINE)
|
||||
String disposition = bodyPart.getDisposition();
|
||||
if (disposition != null && (disposition.equals(Part.ATTACHMENT) || disposition.equals(Part.INLINE))) {
|
||||
// 获取原始文件名(处理中文乱码)
|
||||
String originalFileName = MimeUtility.decodeText(bodyPart.getFileName());
|
||||
// 获取附件输入流
|
||||
InputStream inputStream = bodyPart.getInputStream();
|
||||
attachments.add(new AttachmentInfo(originalFileName, inputStream));
|
||||
} else {
|
||||
// 递归处理嵌套的多部分内容
|
||||
attachments.addAll(extractAttachments(bodyPart));
|
||||
}
|
||||
}
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件信息封装(原始文件名+输入流)
|
||||
*/
|
||||
public static class AttachmentInfo {
|
||||
private String originalFileName;
|
||||
private InputStream inputStream;
|
||||
|
||||
public AttachmentInfo(String originalFileName, InputStream inputStream) {
|
||||
this.originalFileName = originalFileName;
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
// Getter
|
||||
public String getOriginalFileName() {
|
||||
return originalFileName;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user