diff --git a/server/pom.xml b/server/pom.xml
index ddcfcd85e..548b7c3db 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -305,6 +305,34 @@
+
+
+
+ org.simplejavamail
+ outlook-message-parser
+ 1.10.0
+
+
+ org.apache.commons
+ commons-lang3
+ 3.10
+
+
+ org.jsoup
+ jsoup
+ 1.8.3
+
+
+ jakarta.activation
+ jakarta.activation-api
+ 2.0.1
+
+
+
+ cn.hutool
+ hutool-core
+ 5.8.16
+
diff --git a/server/src/main/java/cn/keking/model/FileType.java b/server/src/main/java/cn/keking/model/FileType.java
index 22d69979d..bcee6598d 100644
--- a/server/src/main/java/cn/keking/model/FileType.java
+++ b/server/src/main/java/cn/keking/model/FileType.java
@@ -32,7 +32,8 @@ public enum FileType {
EPUB("epubFilePreviewImpl"),
BPMN("bpmnFilePreviewImpl"),
DCM("dcmFilePreviewImpl"),
- DRAWIO("drawioFilePreviewImpl");
+ DRAWIO("drawioFilePreviewImpl"),
+ MSG("msgFilePreviewImpl");
private static final String[] OFFICE_TYPES = {"docx", "wps", "doc", "docm", "xls", "xlsx", "csv" ,"xlsm", "ppt", "pptx", "vsd", "rtf", "odt", "wmf", "emf", "dps", "et", "ods", "ots", "tsv", "odp", "otp", "sxi", "ott", "vsdx", "fodt", "fods", "xltx","tga","psd","dotm","ett","xlt","xltm","wpt","dot","xlam","dotx","xla","pages", "eps"};
private static final String[] PICTURE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "ico", "jfif", "webp"};
@@ -112,6 +113,7 @@ public enum FileType {
FILE_TYPE_MAPPER.put("md", FileType.MARKDOWN);
FILE_TYPE_MAPPER.put("pdf", FileType.PDF);
FILE_TYPE_MAPPER.put("bpmn", FileType.BPMN);
+ FILE_TYPE_MAPPER.put("msg", FileType.MSG);
}
private static FileType to(String fileType) {
diff --git a/server/src/main/java/cn/keking/service/FilePreview.java b/server/src/main/java/cn/keking/service/FilePreview.java
index 388b251fa..2f67a3e53 100644
--- a/server/src/main/java/cn/keking/service/FilePreview.java
+++ b/server/src/main/java/cn/keking/service/FilePreview.java
@@ -34,5 +34,7 @@ public interface FilePreview {
String XLSX_FILE_PREVIEW_PAGE = "officeweb";
String CSV_FILE_PREVIEW_PAGE = "csv";
+ String MSG_FILE_PREVIEW_PAGE = "msg";
+
String filePreviewHandle(String url, Model model, FileAttribute fileAttribute);
}
diff --git a/server/src/main/java/cn/keking/service/FileVo.java b/server/src/main/java/cn/keking/service/FileVo.java
new file mode 100644
index 000000000..69b80665c
--- /dev/null
+++ b/server/src/main/java/cn/keking/service/FileVo.java
@@ -0,0 +1,51 @@
+package cn.keking.service;
+
+public class FileVo {
+ /**
+ * 文件名
+ */
+ private String fileName;
+ /**
+ * 文件路径
+ */
+ private String filePath;
+
+ /**
+ * UUID
+ */
+ private String contentId;
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+ public void setFilePath(String filePath) {
+ this.filePath = filePath;
+ }
+
+ public String getContentId() {
+ return contentId;
+ }
+
+ public void setContentId(String contentId) {
+ this.contentId = contentId;
+ }
+
+ public FileVo() {
+ }
+
+ public FileVo(String fileName, String filePath,String contentId) {
+ this.fileName = fileName;
+ this.filePath = filePath;
+ this.contentId = contentId;
+ }
+}
diff --git a/server/src/main/java/cn/keking/service/impl/MsgFilePreviewImpl.java b/server/src/main/java/cn/keking/service/impl/MsgFilePreviewImpl.java
new file mode 100644
index 000000000..634e1c8ea
--- /dev/null
+++ b/server/src/main/java/cn/keking/service/impl/MsgFilePreviewImpl.java
@@ -0,0 +1,28 @@
+package cn.keking.service.impl;
+
+import cn.keking.model.FileAttribute;
+import cn.keking.service.FilePreview;
+import org.springframework.stereotype.Component;
+import org.springframework.ui.Model;
+
+/**
+ * MSG 文件预览 impl
+ *
+ * @author albert.chen
+ * @date 2024/03/11
+ */
+@Component
+public class MsgFilePreviewImpl implements FilePreview {
+
+ private final OtherFilePreviewImpl otherFilePreview;
+
+
+ public MsgFilePreviewImpl(OtherFilePreviewImpl otherFilePreview) {
+ this.otherFilePreview = otherFilePreview;
+ }
+
+ @Override
+ public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
+ return null;
+ }
+}
diff --git a/server/src/main/java/cn/keking/utils/MsgUtil.java b/server/src/main/java/cn/keking/utils/MsgUtil.java
new file mode 100644
index 000000000..c9799eb0d
--- /dev/null
+++ b/server/src/main/java/cn/keking/utils/MsgUtil.java
@@ -0,0 +1,192 @@
+package cn.keking.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.keking.service.FileVo;
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.simplejavamail.outlookmessageparser.OutlookMessageParser;
+import org.simplejavamail.outlookmessageparser.model.*;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class MsgUtil {
+
+ /**
+ * 邮件转换为HTML
+ * @param file .msg文件
+ * @return {@link FileVo}
+ * @throws IOException IO异常
+ */
+ public static FileVo msgParseToPreview(File file) throws IOException {
+ OutlookMessageParser messageParser = new OutlookMessageParser();
+ OutlookMessage msg = messageParser.parseMsg(file.getAbsolutePath());
+ List attachList = new ArrayList<>();
+ List outlookAttachments = msg.getOutlookAttachments();
+ for (OutlookAttachment outlookattachment : outlookAttachments) {
+ if (outlookattachment instanceof OutlookMsgAttachment) {
+ attachList.add(msgAttachmentToFile((OutlookMsgAttachment) outlookattachment));
+ }else if(outlookattachment instanceof OutlookFileAttachment){
+ attachList.add(fileAttachToFile((OutlookFileAttachment) outlookattachment));
+ }
+ }
+ String html = messageToHtml(msg,attachList);
+ File tmpFile = createTmpFileWithName(file.getName().substring(0,file.getName().lastIndexOf("."))+".html");
+ Path path = Files.write(tmpFile.toPath(), html.getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
+ return new FileVo(file.getName(),path.toFile().getAbsolutePath(), cn.hutool.core.lang.UUID.randomUUID().toString());
+ }
+
+ /**
+ * 邮件内容转换为html
+ * @param msg 味精
+ * @param attachments
+ * @return {@link String}
+ * @throws IOException IO异常
+ */
+ private static String messageToHtml(OutlookMessage msg,List attachments) throws IOException {
+ String bodyText = msg.getBodyText();
+ //主题
+ String subject = msg.getSubject();
+ //主题即为文件名
+ String form = msg.getFromEmail();
+ String to = msg.getDisplayTo();
+ String cc = msg.getDisplayCc();
+ String content = StrUtil.EMPTY;
+ if(StrUtil.isNotEmpty(bodyText)){
+ Document doc = Jsoup.parse(msg.getConvertedBodyHTML());
+ //邮件中的图片转base64
+ Elements imgList = doc.select("img");
+ for (Element element : imgList) {
+ String src = element.attr("src");
+ if (!src.contains("cid:")) {
+ continue;
+ }
+ String contentId = src.substring(4);
+ FileVo fileVo = null;
+ for (FileVo tmp : attachments) {
+ if (contentId.equals(tmp.getContentId())) {
+ fileVo = tmp;
+ break;
+ }
+ }
+ if (fileVo == null) {
+ continue;
+ }
+ File attach = new File(fileVo.getFilePath());
+ String base64 = null;
+ try(InputStream in = Files.newInputStream(attach.toPath())){
+ byte[] bytes = new byte[(int) attach.length()];
+ in.read(bytes);
+ base64 = Base64.getEncoder().encodeToString(bytes);
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ if (StringUtils.isNotBlank(base64)) {
+ String srcBase64 = "data:image/png;base64," + base64;
+ element.attr("src", fileVo.getFilePath());
+ if (CollUtil.isNotEmpty(attachments) && attachments.contains(fileVo)) {
+ attachments.remove(fileVo);
+ }
+ }
+ }
+ // 内容
+ Elements bodyList = doc.select("body");
+ if (!bodyList.isEmpty()) {
+ Element bodyEle = bodyList.first();
+ if (!bodyEle.html().isEmpty()) {
+ content = bodyEle.html();
+ }
+ }
+ }
+ String html =
+ "发件人:" + form + "" +
+ "抄送:" + cc + "" +
+ "收件人:" + to + "" +
+ "主题:" + subject + ""+
+ "附件:" + attachments.stream()
+ .map(file -> ""+file.getFilePath()+"\">"+file.getFileName()+">")
+ .collect(Collectors.joining(",")) + ""+
+ content;
+ return html;
+ }
+
+ /**
+ * .msg文件转换为文件
+ *
+ * @param attachment 附件
+ * @return {@link FileVo}
+ * @throws IOException IO异常
+ */
+ private static FileVo msgAttachmentToFile(OutlookMsgAttachment attachment) throws IOException {
+ //附件
+ List outlookAttachments = attachment.getOutlookMessage().getOutlookAttachments();
+ List attachList = new ArrayList<>();
+ for (OutlookAttachment outlookAttachment : outlookAttachments) {
+ if(outlookAttachment instanceof OutlookMsgAttachment){
+ attachList.add(msgAttachmentToFile((OutlookMsgAttachment) outlookAttachment));
+ }else if(outlookAttachment instanceof OutlookFileAttachment){
+ attachList.add(fileAttachToFile((OutlookFileAttachment) outlookAttachment));
+ }
+ }
+ //主题
+ String subject = attachment.getOutlookMessage().getSubject();
+ //邮件内容
+ String html = messageToHtml(attachment.getOutlookMessage(), attachList);
+ File file = createTmpFileWithName(subject+".html");
+ Path path = Files.write(file.toPath(), html.getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
+ return new FileVo(subject,path.toFile().getAbsolutePath(),attachment.getOutlookMessage().getMessageId());
+ }
+
+ /**
+ * 附件下载到本地
+ *
+ * @param attachment 附件
+ * @return {@link FileVo}
+ * @throws IOException IO异常
+ */
+ private static FileVo fileAttachToFile(OutlookFileAttachment attachment) throws IOException {
+ String attachName = attachment.getLongFilename();
+ //存在没有命名的文件
+ if (StringUtils.isBlank(attachName)){
+ attachName= UUID.randomUUID().toString().replace("-", "");
+ }
+ //创建临时文件
+ File attachementFile = createTmpFileWithName(attachName);
+ InputStream is = new ByteArrayInputStream(attachment.getData());
+ Files.copy(is, attachementFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ if (ObjectUtil.isNotNull(attachementFile)) {
+ return new FileVo(attachName,attachementFile.getAbsolutePath(),attachment.getContentId());
+ }
+ return null;
+ }
+
+ private static File getTmpDir() {
+ String projectPath = System.getProperty("user.dir") + File.separator + "temp";
+ File file = new File(projectPath);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ return file;
+ }
+
+ private static File createTmpFileWithName(String fileName) throws IOException {
+ File file = new File(getTmpDir(), fileName);
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ return file;
+ }
+}