u
This commit is contained in:
1 parent
6c057cbdca
commit
34deecb157
3 files changed
+530
-58
No files matched your search
@@ -2,6 +2,7 @@ package com.email;
|
||||
|
||||
import com.email.core.fetch.EmailFetcher;
|
||||
import com.email.core.fetch.ImapJavaMailEmailFetcher;
|
||||
import com.email.core.fetch.model.EmailFetchData;
|
||||
import com.email.domain.entity.UserAccount;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -31,9 +32,13 @@ public class Test {
|
||||
},
|
||||
// folderConsumer: 开始处理某个文件夹
|
||||
folder -> {
|
||||
log.info("folder:{}",folder.getName());
|
||||
},
|
||||
// fetchDataConsumer: 每封邮件拉取完成后处理
|
||||
emailData -> {
|
||||
for (EmailFetchData emailDatum : emailData) {
|
||||
log.info("subject:{}",emailDatum.getEmailSummary().getSubject());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.email.core;
|
||||
|
||||
import com.email.prop.EmailStorageProperties;
|
||||
import com.email.utils.entity.EmailUploadResult;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class EmailFileUtils {
|
||||
private static final ExecutorService ATTACHMENT_EXECUTOR =
|
||||
Executors.newFixedThreadPool(4); // 或使用 ThreadPoolExecutor 自定义
|
||||
|
||||
private static String LOCAL_BASE_PATH; // e.g. D:/data/email
|
||||
private static String LOCAL_SERVER_URL; // e.g. http://127.0.0.1:8081
|
||||
private static String LOCAL_STATIC_URL; // e.g. /static/email
|
||||
|
||||
private static final String BODY_PREFIX = "Body"; // 正文
|
||||
private static final String ATTACHMENT_PREFIX = "Attachment"; // 附件以及内联资源
|
||||
private static final String TEMPLATE_PREFIX = "Template"; // 模板
|
||||
|
||||
public static void init(EmailStorageProperties properties) {
|
||||
LOCAL_BASE_PATH = properties.getBasePath();
|
||||
LOCAL_SERVER_URL = properties.getServerUrl();
|
||||
LOCAL_STATIC_URL = properties.getStaticPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化邮件在存储时候的格式,将 @ 格式化为 _
|
||||
* @param email 邮件地址
|
||||
* @return 格式化后的邮件地址
|
||||
*/
|
||||
private static String normalizeEmail(String email) {
|
||||
if (email == null) return "unknown";
|
||||
return email.replace("@", "_").replace(".", "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过文件名称后去文件的后缀
|
||||
* @param fileName 文件名称
|
||||
* @return 文件后缀
|
||||
*/
|
||||
private static String getFileExtension(String fileName) {
|
||||
int idx = fileName.lastIndexOf('.');
|
||||
return (idx != -1 && idx < fileName.length() - 1) ? fileName.substring(idx + 1) : "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存邮件内容
|
||||
* @param email 邮件地址
|
||||
* @param bodyContent 邮件内容
|
||||
* @param charset 字符集
|
||||
* @return 保存后的信息
|
||||
*/
|
||||
public static EmailUploadResult uploadBody(String email, String bodyContent, String charset) {
|
||||
if (bodyContent == null) {
|
||||
bodyContent = "";
|
||||
}
|
||||
|
||||
InputStream inputStream = new ByteArrayInputStream(
|
||||
bodyContent.getBytes(java.nio.charset.Charset.forName(charset))
|
||||
);
|
||||
|
||||
return doUpload(email, "body.html", inputStream, BODY_PREFIX, "html");
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存邮件附件
|
||||
* @param email 邮件地址
|
||||
* @param originalFileName 附件名称
|
||||
* @param inputStream 文件流
|
||||
* @return 保存后的信息
|
||||
*/
|
||||
public static EmailUploadResult uploadAttachment(String email, String originalFileName, InputStream inputStream) {
|
||||
return doUpload(email, originalFileName, inputStream, ATTACHMENT_PREFIX, null);
|
||||
}
|
||||
|
||||
private static EmailUploadResult doUpload(String email, String originalFileName, InputStream inputStream,
|
||||
String typePrefix, String forcedExtension) {
|
||||
try {
|
||||
LocalDate today = LocalDate.now();
|
||||
String datePath = today.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
|
||||
String emailPath = normalizeEmail(email);
|
||||
|
||||
String extension = forcedExtension;
|
||||
if (StringUtils.isEmpty(forcedExtension)) {
|
||||
extension = getFileExtension(originalFileName);
|
||||
if(StringUtils.isEmpty(extension)){
|
||||
extension = "dat";
|
||||
}
|
||||
}
|
||||
|
||||
String fileName = System.currentTimeMillis() + "" + ThreadLocalRandom.current().nextInt(100,1000) + "." + extension;
|
||||
String relativePath = joinFilePath(emailPath,typePrefix,datePath,fileName);
|
||||
String fullPathStr = joinFilePath(LOCAL_BASE_PATH, relativePath);
|
||||
Path fullPath = Paths.get(fullPathStr);
|
||||
|
||||
Path parentDir = fullPath.getParent();
|
||||
if (parentDir != null && !Files.exists(parentDir)) {
|
||||
Files.createDirectories(parentDir);
|
||||
}
|
||||
|
||||
Files.copy(inputStream, fullPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
return new EmailUploadResult(
|
||||
originalFileName,
|
||||
extension,
|
||||
Files.size(fullPath),
|
||||
relativePath.replace("\\", "/")
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("文件上传失败,路径:" + LOCAL_BASE_PATH + ",错误:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建本地
|
||||
* @param relativePath 717406575_qq_com/Attachment/2025/06/03/1748927695653847.jpeg
|
||||
* @return D:/data/email/717406575_qq_com/Attachment/2025/06/03/1748927695653847.jpeg
|
||||
*/
|
||||
public static String buildLocalFilePath(String relativePath) {
|
||||
return joinFilePath(LOCAL_BASE_PATH, relativePath);
|
||||
}
|
||||
|
||||
public static String buildPublicFileUrl(String relativePath) {
|
||||
return joinUrl(LOCAL_SERVER_URL, LOCAL_STATIC_URL, relativePath).replace("\\", "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接文件路径,自动忽略空路径,并进行路径标准化。
|
||||
* 等价于 Paths.get(...).normalize().toString(),但支持多个参数并自动忽略空值。
|
||||
* 示例:
|
||||
* joinFilePath("D:/data", "email", "Body") => D:\data\email\Body
|
||||
*
|
||||
* @param paths 路径片段
|
||||
* @return 合并后的标准路径字符串
|
||||
*/
|
||||
private static String joinFilePath(String... paths) {
|
||||
if (paths == null || paths.length == 0) return "";
|
||||
|
||||
Path result = null;
|
||||
|
||||
for (String p : paths) {
|
||||
if (p == null || p.trim().isEmpty()) continue; // 跳过空路径
|
||||
|
||||
if (result == null) {
|
||||
result = Paths.get(p.trim()); // 初始化路径
|
||||
} else {
|
||||
result = result.resolve(p.trim()); // 追加路径
|
||||
}
|
||||
}
|
||||
|
||||
return (result == null) ? "" : result.normalize().toString(); // 返回标准化路径
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接 URL 路径,自动处理斜杠,确保不会出现重复或缺失的斜杠。
|
||||
* 示例:
|
||||
* joinUrl("http://localhost:8080/", "/static/email/", "/img.png") => http://localhost:8080/static/email/img.png
|
||||
*
|
||||
* @param parts URL 各部分片段
|
||||
* @return 合并后的完整 URL 字符串
|
||||
*/
|
||||
private static String joinUrl(String... parts) {
|
||||
if (parts == null || parts.length == 0) return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
String part = parts[i] == null ? "" : parts[i].trim();
|
||||
if (part.isEmpty()) continue;
|
||||
|
||||
if (i == 0) {
|
||||
// 第一个部分:去除尾部斜杠
|
||||
sb.append(part.replaceAll("/+$", ""));
|
||||
} else {
|
||||
sb.append("/");
|
||||
// 中间和结尾部分:去除前后多余的斜杠
|
||||
sb.append(part.replaceAll("^/+", "").replaceAll("/+$", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+329
-58
@@ -2,24 +2,35 @@ package com.email.core.fetch;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.email.constants.ResponseStatusConstants;
|
||||
import com.email.core.EmailFileUtils;
|
||||
import com.email.core.fetch.model.EmailFetchData;
|
||||
import com.email.core.fetch.model.EmailFetchProgress;
|
||||
import com.email.core.fetch.model.FolderWrapper;
|
||||
import com.email.domain.entity.*;
|
||||
import com.email.enums.ResponseEnum;
|
||||
import com.email.enums.email.EmailAddressTypeEnum;
|
||||
import com.email.enums.email.EmailBodyTypeEnum;
|
||||
import com.email.enums.email.EmailFileTypeEnum;
|
||||
import com.email.enums.email.EmailFolderTypeEnum;
|
||||
import com.email.exception.auth.AuthException;
|
||||
import com.email.utils.entity.EmailUploadResult;
|
||||
import com.sun.javaws.progress.Progress;
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.IdGenerator;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MailDateFormat;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
import javax.validation.constraints.Email;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -70,7 +81,7 @@ public class ImapJavaMailEmailFetcher implements EmailFetcher {
|
||||
try {
|
||||
threadStore = connect(userAccount);
|
||||
|
||||
processSingleFolder(threadStore, emailFolder, progressConsumer, folderConsumer, fetchDataConsumer);
|
||||
processSingleFolder(emailFolder,userAccount, threadStore, progressConsumer, folderConsumer, fetchDataConsumer);
|
||||
} catch (Exception e) {
|
||||
log.error("处理文件夹 [{}] 异常:{}", emailFolder.getFullName(), e.getMessage(), e);
|
||||
} finally {
|
||||
@@ -89,11 +100,13 @@ public class ImapJavaMailEmailFetcher implements EmailFetcher {
|
||||
folderLatch.await();
|
||||
}
|
||||
|
||||
private void processSingleFolder(Store store,
|
||||
private void processSingleFolder(
|
||||
EmailFolder emailFolder,
|
||||
UserAccount userAccount,
|
||||
Store store,
|
||||
Consumer<EmailFetchProgress> progressConsumer,
|
||||
Consumer<EmailFolder> folderConsumer,
|
||||
Consumer<List<EmailFetchData>> fetchDataConsumer) {
|
||||
Consumer<List<EmailFetchData>> emailConsumer) {
|
||||
IMAPFolder imapFolder = null;
|
||||
try {
|
||||
imapFolder = (IMAPFolder) store.getFolder(emailFolder.getFullName());
|
||||
@@ -105,10 +118,13 @@ public class ImapJavaMailEmailFetcher implements EmailFetcher {
|
||||
|
||||
int totalMessages = imapFolder.getMessageCount();
|
||||
if (totalMessages > 0) {
|
||||
processEmailsInFolder(imapFolder,emailFolder);
|
||||
try {
|
||||
processEmailsInFolder(emailFolder,userAccount,imapFolder,emailConsumer);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (MessagingException e) {
|
||||
log.error("打开或处理文件夹 [{}] 失败: {}", emailFolder.getFullName(), e.getMessage(), e);
|
||||
} finally {
|
||||
@@ -122,63 +138,163 @@ public class ImapJavaMailEmailFetcher implements EmailFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
private void processEmailsInFolder(IMAPFolder imapFolder,EmailFolder emailFolder) throws MessagingException {
|
||||
private void processEmailsInFolder(EmailFolder emailFolder,
|
||||
UserAccount userAccount,
|
||||
IMAPFolder imapFolder,
|
||||
Consumer<List<EmailFetchData>> emailConsumer) throws Exception {
|
||||
if (!imapFolder.isOpen()) {
|
||||
imapFolder.open(Folder.READ_ONLY);
|
||||
}
|
||||
|
||||
Message[] messages = imapFolder.getMessages();
|
||||
if (messages == null || messages.length == 0) {
|
||||
log.info("文件夹 [{}] 没有邮件,跳过", imapFolder.getFullName());
|
||||
return;
|
||||
}
|
||||
if (messages == null || messages.length == 0) return;
|
||||
|
||||
List<Message> messageList = Arrays.asList(messages);
|
||||
int totalMessages = messageList.size();
|
||||
log.info("文件夹 [{}] 邮件总数: {}", imapFolder.getFullName(), totalMessages);
|
||||
// ✅ 可选:预拉字段避免 getSubject 慢
|
||||
FetchProfile fetchProfile = new FetchProfile();
|
||||
fetchProfile.add(FetchProfile.Item.ENVELOPE);
|
||||
fetchProfile.add(FetchProfile.Item.FLAGS);
|
||||
fetchProfile.add(FetchProfile.Item.CONTENT_INFO);
|
||||
imapFolder.fetch(messages, fetchProfile);
|
||||
|
||||
int batchCount = (totalMessages + BATCH_SIZE - 1) / BATCH_SIZE;
|
||||
CountDownLatch latch = new CountDownLatch(batchCount);
|
||||
|
||||
for (int i = 0; i < totalMessages; i += BATCH_SIZE) {
|
||||
int end = Math.min(i + BATCH_SIZE, totalMessages);
|
||||
List<Message> batchMessages = messageList.subList(i, end);
|
||||
CountDownLatch latch = new CountDownLatch(messages.length);
|
||||
|
||||
for (Message message : messages) {
|
||||
emailExecutor.submit(() -> {
|
||||
try {
|
||||
for (Message message : batchMessages) {
|
||||
|
||||
}
|
||||
EmailFetchData data = processSingleEmail(imapFolder, message, emailFolder.getId(), userAccount.getEmail());
|
||||
log.info("subject:{}",data.getEmailSummary().getSubject());
|
||||
// 可选:收集并统一处理结果(如使用 synchronizedList)
|
||||
// emailConsumer.accept(Collections.singletonList(data));
|
||||
} catch (Exception e) {
|
||||
log.error("批量任务异常", e);
|
||||
log.error("处理邮件异常: {}", e.getMessage(), e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
latch.await(); // 等待所有任务完成
|
||||
|
||||
try {
|
||||
imapFolder.close(false);
|
||||
} catch (MessagingException e) {
|
||||
log.warn("关闭文件夹异常", e);
|
||||
}
|
||||
// if (!imapFolder.isOpen()) {
|
||||
// imapFolder.open(Folder.READ_ONLY);
|
||||
// }
|
||||
//
|
||||
// Message[] messages = imapFolder.getMessages();
|
||||
// for (Message message : messages) {
|
||||
// EmailFetchData data = processSingleEmail(imapFolder, message, emailFolder.getId(), userAccount.getEmail());
|
||||
// System.out.println(data.getEmailSummary().getSubject());
|
||||
// }
|
||||
|
||||
//
|
||||
// long uidNext = imapFolder.getUIDNext(); // 下一封邮件 UID(不包含)
|
||||
// long minUid = findMinUid(imapFolder); // 当前文件夹最小 UID
|
||||
//
|
||||
// if (minUid >= uidNext) {
|
||||
// log.info("📭 文件夹 {} 无可拉取邮件", emailFolder.getFullName());
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// List<long[]> uidRanges = new ArrayList<>();
|
||||
// for (long start = minUid; start < uidNext; start += BATCH_SIZE) {
|
||||
// long end = Math.min(start + BATCH_SIZE - 1, uidNext - 1);
|
||||
// uidRanges.add(new long[]{start, end});
|
||||
// }
|
||||
//
|
||||
// CountDownLatch latch = new CountDownLatch(uidRanges.size());
|
||||
//
|
||||
// for (long[] range : uidRanges) {
|
||||
// long startUid = range[0];
|
||||
// long endUid = range[1];
|
||||
//
|
||||
// emailExecutor.submit(() -> {
|
||||
// Store threadStore = null;
|
||||
// IMAPFolder threadFolder = null;
|
||||
// try {
|
||||
// threadStore = connect(userAccount); // 每线程自己的连接
|
||||
// threadFolder = (IMAPFolder) threadStore.getFolder(emailFolder.getFullName());
|
||||
// threadFolder.open(Folder.READ_ONLY);
|
||||
//
|
||||
// Message[] messages = threadFolder.getMessagesByUID(startUid, endUid);
|
||||
// if (messages == null || messages.length == 0) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // ✅ 预拉字段,避免后续每个字段都触发 IO
|
||||
// FetchProfile fp = new FetchProfile();
|
||||
// fp.add(FetchProfile.Item.ENVELOPE);
|
||||
// fp.add(FetchProfile.Item.FLAGS);
|
||||
// fp.add(FetchProfile.Item.CONTENT_INFO);
|
||||
// threadFolder.fetch(messages, fp);
|
||||
//
|
||||
// List<EmailFetchData> fetchDataList = new ArrayList<>(messages.length);
|
||||
// for (Message message : messages) {
|
||||
// try {
|
||||
// EmailFetchData data = processSingleEmail(threadFolder, message, emailFolder.getId(), userAccount.getEmail());
|
||||
// fetchDataList.add(data);
|
||||
// } catch (Exception e) {
|
||||
// log.error("处理邮件 UID={} 异常: {}", threadFolder.getUID(message), e.getMessage(), e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!fetchDataList.isEmpty()) {
|
||||
// emailConsumer.accept(fetchDataList);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("线程拉取 UID [{}-{}] 邮件异常: {}", startUid, endUid, e.getMessage(), e);
|
||||
// } finally {
|
||||
// try {
|
||||
// if (threadFolder != null && threadFolder.isOpen()) threadFolder.close(false);
|
||||
// if (threadStore != null && threadStore.isConnected()) threadStore.close();
|
||||
// } catch (MessagingException e) {
|
||||
// log.warn("关闭线程 IMAPFolder 或 Store 异常", e);
|
||||
// }
|
||||
// latch.countDown();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// latch.await();
|
||||
// } catch (InterruptedException e) {
|
||||
// Thread.currentThread().interrupt();
|
||||
// throw new RuntimeException("等待多线程拉取邮件中断", e);
|
||||
// }
|
||||
}
|
||||
|
||||
private long findMinUid(IMAPFolder folder) throws MessagingException {
|
||||
Message[] msgs = folder.getMessages();
|
||||
long min = Long.MAX_VALUE;
|
||||
for (Message msg : msgs) {
|
||||
long uid = folder.getUID(msg);
|
||||
if (uid > 0 && uid < min) {
|
||||
min = uid;
|
||||
}
|
||||
}
|
||||
return min == Long.MAX_VALUE ? 1L : min;
|
||||
}
|
||||
|
||||
private EmailFetchData processSingleEmail(Message message,EmailFolder emailFolder){
|
||||
private EmailFetchData processSingleEmail(Folder folder,Message message,Long folderId,String userEmail) throws Exception {
|
||||
EmailFetchData fetchData = new EmailFetchData();
|
||||
EmailSummary emailSummary = processSingleEmailSummary(folder, message, folderId);
|
||||
|
||||
Long emailId = emailSummary.getId();
|
||||
// emailAddress
|
||||
List<EmailAddress> addressList = processSingleEmailAddress(message,emailId);
|
||||
|
||||
// emailAttachments
|
||||
List<EmailFile> fileList = processSingleEmailAttachment(message, emailId, userEmail);
|
||||
|
||||
fetchData.setEmailSummary(emailSummary);
|
||||
fetchData.setEmailAddressList(addressList);
|
||||
fetchData.setEmailFileList(fileList);
|
||||
|
||||
return fetchData;
|
||||
}
|
||||
|
||||
private EmailSummary processSingleEmailSummary(Folder folder,Message message,Long folderId) throws Exception {
|
||||
EmailSummary emailSummary = new EmailSummary();
|
||||
// emailId
|
||||
long emailId = IdWorker.getId();
|
||||
// subject
|
||||
String subject = message.getSubject();
|
||||
// messageId
|
||||
@@ -191,8 +307,79 @@ public class ImapJavaMailEmailFetcher implements EmailFetcher {
|
||||
Date sentDate = resolveMailSentDate(message);
|
||||
// isSeen
|
||||
Integer isSeen = message.isSet(Flags.Flag.SEEN) ? 1 : 0;
|
||||
// emailId
|
||||
long emailId = IdWorker.getId();
|
||||
|
||||
// emailContent
|
||||
EmailSummary emailContent = processSingleEmailContent(message);
|
||||
|
||||
emailSummary.setId(emailId)
|
||||
.setSubject(subject)
|
||||
.setMessageId(messageId)
|
||||
.setImapUid(uid)
|
||||
.setSentTime(sentDate)
|
||||
.setIsSeen(isSeen)
|
||||
.setBodyEncoding(emailContent.getBodyEncoding())
|
||||
.setBodyCharset(emailContent.getBodyCharset())
|
||||
.setBodyType(emailContent.getBodyType())
|
||||
.setBodyPath(emailContent.getBodyPath())
|
||||
.setBodySize(emailContent.getBodySize())
|
||||
.setFolderId(folderId);
|
||||
|
||||
return emailSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个邮件的各种地址
|
||||
* @param message api
|
||||
* @param emailId 该邮件的id
|
||||
* @return 该封邮件返回的地址集合
|
||||
* @throws MessagingException 处理异常
|
||||
*/
|
||||
private List<EmailAddress> processSingleEmailAddress(Message message, Long emailId) throws MessagingException {
|
||||
List<EmailAddress> addressList = new ArrayList<>();
|
||||
|
||||
// 发件人
|
||||
addAddresses(addressList, message.getFrom(), EmailAddressTypeEnum.FROM, emailId);
|
||||
|
||||
// 回复地址
|
||||
addAddresses(addressList, message.getReplyTo(), EmailAddressTypeEnum.REPLY_TO, emailId);
|
||||
|
||||
// TO
|
||||
addAddresses(addressList, message.getRecipients(Message.RecipientType.TO), EmailAddressTypeEnum.TO, emailId);
|
||||
|
||||
// CC
|
||||
addAddresses(addressList, message.getRecipients(Message.RecipientType.CC), EmailAddressTypeEnum.CC, emailId);
|
||||
|
||||
// BCC
|
||||
addAddresses(addressList, message.getRecipients(Message.RecipientType.BCC), EmailAddressTypeEnum.BCC, emailId);
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
private void addAddresses(List<EmailAddress> list, Address[] addresses, EmailAddressTypeEnum type, Long emailId) {
|
||||
if (addresses == null) return;
|
||||
|
||||
for (Address address : addresses) {
|
||||
if (address instanceof InternetAddress) {
|
||||
InternetAddress ia = (InternetAddress) address;
|
||||
String email = ia.getAddress();
|
||||
String name = ia.getPersonal() != null ? ia.getPersonal() : email;
|
||||
|
||||
list.add(new EmailAddress()
|
||||
.setAddress(email)
|
||||
.setAddressName(name)
|
||||
.setEmailId(emailId)
|
||||
.setAddressType(type.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理邮件内容
|
||||
* @param message api
|
||||
* @throws Exception 处理邮件内容发生的异常
|
||||
*/
|
||||
private EmailSummary processSingleEmailContent(Message message) throws Exception {
|
||||
EmailSummary emailSummary = new EmailSummary();
|
||||
// 解析内容
|
||||
Part part = message;
|
||||
// bodyCharset
|
||||
@@ -210,34 +397,20 @@ public class ImapJavaMailEmailFetcher implements EmailFetcher {
|
||||
: "";
|
||||
// bodyContent
|
||||
String bodyContent = resolveMailContent(part);
|
||||
// 保存邮件到本地
|
||||
EmailUploadResult emailUploadResult = EmailFileUtils.uploadBody(String.valueOf(emailSummary.getId()), bodyContent, bodyCharset);
|
||||
|
||||
emailSummary.setBodyCharset(bodyContent);
|
||||
emailSummary.setBodyType(bodyType);
|
||||
emailSummary.setBodyEncoding(bodyEncoding);
|
||||
emailSummary.setBodyPath(emailUploadResult.getFilePath());
|
||||
emailSummary.setBodySize(emailUploadResult.getFileSize());
|
||||
|
||||
return emailSummary;
|
||||
}
|
||||
|
||||
private List<EmailAddress> processSingleEmailAddress(Message message) throws MessagingException {
|
||||
List<EmailAddress> addressList = new ArrayList<>();
|
||||
// 发件人
|
||||
Address[] froms = message.getFrom();
|
||||
// 回复地址
|
||||
Address[] replyTos = message.getReplyTo();
|
||||
// TO
|
||||
Address[] tos = message.getRecipients(Message.RecipientType.TO);
|
||||
// CC
|
||||
Address[] ccs = message.getRecipients(Message.RecipientType.CC);
|
||||
// BCC
|
||||
Address[] bccs = message.getRecipients(Message.RecipientType.BCC);
|
||||
|
||||
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
private List<EmailFile> processSingleEmailFile(){
|
||||
List<EmailFile> fileList = new ArrayList<>();
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
private String resolveMailContent(Part part) throws Exception {
|
||||
if (part.isMimeType("text/html")) {
|
||||
return (String) part.getContent();
|
||||
@@ -271,6 +444,104 @@ public class ImapJavaMailEmailFetcher implements EmailFetcher {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理邮件附件/内嵌资源
|
||||
* @param message api
|
||||
* @param emailId 邮件ID
|
||||
* @param userEmail 用户邮箱(保存附件时候需要)
|
||||
* @return 邮件文件集合
|
||||
* @throws MessagingException 异常
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
private List<EmailFile> processSingleEmailAttachment(Message message,Long emailId, String userEmail)
|
||||
throws MessagingException, IOException {
|
||||
List<EmailFile> attachments = new ArrayList<>();
|
||||
MimeMessage mimeMessage = (MimeMessage) message;
|
||||
Object content = mimeMessage.getContent();
|
||||
|
||||
if (content instanceof Multipart) {
|
||||
processMultipart((Multipart) content,attachments,emailId,userEmail);
|
||||
}
|
||||
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private void processMultipart(Multipart multipart, List<EmailFile> fileList, Long emailId, String userEmail)
|
||||
throws MessagingException, IOException {
|
||||
for (int i = 0; i < multipart.getCount(); i++) {
|
||||
BodyPart part = multipart.getBodyPart(i);
|
||||
Object partContent = part.getContent();
|
||||
|
||||
if (partContent instanceof Multipart) {
|
||||
processMultipart((Multipart) partContent, fileList,emailId,userEmail);
|
||||
continue;
|
||||
}
|
||||
|
||||
String disposition = part.getDisposition();
|
||||
String contentType = part.getContentType().toLowerCase();
|
||||
String[] cidHeaders = part.getHeader("Content-ID");
|
||||
|
||||
boolean isInline = cidHeaders != null && cidHeaders.length > 0;
|
||||
boolean isAttachment = Part.ATTACHMENT.equalsIgnoreCase(disposition)
|
||||
|| (disposition == null && part.getFileName() != null);
|
||||
|
||||
// 只处理内嵌图片或合法附件
|
||||
if (!(isAttachment || (isInline && contentType.startsWith("image")))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理文件名并解码
|
||||
String fileName = part.getFileName();
|
||||
if (StringUtils.isBlank(fileName)) {
|
||||
if (isInline && contentType.startsWith("image")) {
|
||||
String cid = cidHeaders[0].replaceAll("[<>]", "");
|
||||
fileName = "inline_" + cid + ".png";
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fileName = decodeMimeName(fileName); // 解码文件名
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// 如果解码失败,继续使用原始文件名
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 替换非法字符(如 < > 等)为下划线或其他合法字符
|
||||
fileName = fileName.replaceAll("[<>:\"/\\|?*]", "_");
|
||||
|
||||
EmailFile emailFile = new EmailFile();
|
||||
emailFile.setEmailId(emailId);
|
||||
|
||||
// 选择上传路径和文件
|
||||
InputStream inputStream = part.getInputStream();
|
||||
EmailUploadResult emailUploadResult = com.email.utils.EmailFileUtils.uploadAttachment(userEmail, fileName, inputStream);
|
||||
|
||||
emailFile.setFileName(emailUploadResult.getFileName());
|
||||
emailFile.setFileExtension(emailUploadResult.getFileExtension());
|
||||
emailFile.setFileSize(emailUploadResult.getFileSize());
|
||||
emailFile.setFilePath(emailUploadResult.getFilePath());
|
||||
|
||||
// 设置 CID(如果是内嵌图片)
|
||||
if (isInline) {
|
||||
String cid = cidHeaders[0].replaceAll("[<>]", "");
|
||||
emailFile.setEmailCid(cid);
|
||||
emailFile.setFileType(EmailFileTypeEnum.INLINE.getCode());
|
||||
}else{
|
||||
emailFile.setEmailCid("");
|
||||
emailFile.setFileType(EmailFileTypeEnum.ATTACHMENT.getCode());
|
||||
}
|
||||
|
||||
fileList.add(emailFile);
|
||||
}
|
||||
}
|
||||
|
||||
private String decodeMimeName(String encodedName) throws UnsupportedEncodingException {
|
||||
return MimeUtility.decodeText(encodedName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析邮件的 发送/接受 时间
|
||||
* @param message 消息
|
||||
|
||||
Reference in new issue
Block a user