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; + } +}