Spring 集成 IMAP:由于 Outlook 365 中的病毒扫描,无法从邮件中读取附件

Spring Integration IMAP : Failed to read attachment from mail due to virus scanning in Outlook 365

我正在使用 Spring 集成以使用 IMAP inbound-channel-adapter.

从 Outlook 365(云)读取电子邮件

场景: Outlook 365 中的目标邮箱正在对新电子邮件进行病毒扫描 一旦到达,在此扫描期间 outlook 会分离附件 并在病毒扫描后再次附加它已完成。

问题: 附件在极少数情况下丢失(大约 50 封邮件中有 1 封),这是因为当附件在 outlook 中 not 可用时,这些电子邮件由 inbound-channel-adapter 阅读(已分离通过病毒扫描程序)。

问题:

如何确保每次都阅读附件?如果我让线程在 handleMessage 方法中等待 2 分钟,那么 它会阻止 下一封电子邮件的阅读刚刚到达吗? 或者请让我知道处理这种情况的任何其他解决方案。

Spring-integration.xml:

<util:properties id="javaMailProperties">
    <prop key="mail.imap.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
    <prop key="mail.imap.socketFactory.fallback">false</prop>
    <prop key="mail.store.protocol">imaps</prop>
    <prop key="mail.debug">${imap.debug}</prop>
    <prop key="mail.imaps.partialfetch">false</prop>
    <prop key="mail.imaps.fetchsize">102400</prop>   <!-- 100KB, default is 16KB -->  
</util:properties>

<mail:inbound-channel-adapter id="imapAdapter" 
                                  store-uri="${imap.uri}"                                     
                                  channel="recieveEmailChannel"                                          
                                  should-delete-messages="false"
                                  should-mark-messages-as-read="true"                                      
                                  auto-startup="true"
                                  simple-content="true"
                                  auto-close-folder="true"
                                  java-mail-properties="javaMailProperties">
    <int:poller fixed-delay="${imap.polling.interval}" time-unit="SECONDS"/>
</mail:inbound-channel-adapter>

<int:channel id="recieveEmailChannel">        
    <int:interceptors>
        <int:wire-tap channel="logger"/>
    </int:interceptors>
</int:channel>

<int:logging-channel-adapter id="logger" level="DEBUG"/>

<int:service-activator input-channel="recieveEmailChannel" ref="emailReceiver" method="handleMessage"/>

是的,只要您不将工作转移到其他线程,SourcePollingChannelAdapter 就会在下一次轮询之前阻塞。默认情况下,它被配置为仅轮询一条消息。所以,到目前为止,你很好。

另一种方法可能是查看自定义 search-term-strategy:

                <xsd:attribute name="search-term-strategy" type="xsd:string">
                    <xsd:annotation>
                        <xsd:appinfo>
                            <tool:annotation kind="ref">
                                <tool:expected-type
                                        type="org.springframework.integration.mail.SearchTermStrategy"/>
                            </tool:annotation>
                        </xsd:appinfo>
                        <xsd:documentation>
                            Reference to a custom implementation of
                            org.springframework.integration.mail.SearchTermStrategy
                            to use when retrieving email. Only permitted with 'imap' protocol or an 'imap' uri.
                            By default, the ImapMailReceiver will search for Messages based on the default
                            SearchTerm
                            which is "All mails that are RECENT (if supported), that are NOT ANSWERED, that are NOT
                            DELETED, that are NOT SEEN and have not
                            been processed by this mail receiver (enabled by the use of the custom USER flag or
                            simply NOT FLAGGED if not supported)".
                        </xsd:documentation>
                    </xsd:annotation>
                </xsd:attribute>

因此,您不会轮询邮箱中的邮件,直到它们满足搜索条件。 在文档中查看更多信息:https://docs.spring.io/spring-integration/docs/current/reference/html/mail.html#mail-namespace

或者可能是 mail-filter-expressionhttps://docs.spring.io/spring-integration/docs/current/reference/html/mail.html#mail-filtering

无论如何,在对 Outlook 进行病毒扫描时在邮件上添加一些标记会很棒。

此解决方案基于@Artem Bilan 的回答和评论。感谢 Arten 的指导 :-).


问题:由于 Outlook 病毒扫描.

,无法间歇性读取附件

想法:延迟电子邮件阅读几秒钟,希望病毒扫描到那时完成。

解决方案: 使用自定义 SearchTermStrategy 和 OlderTerm.

此解决方案的副作用: 正如在@Artem 的回答评论中所讨论的,轮询新电子邮件时 JavaMail 和 IMAP 必须搜索ENTIRE INBOX for each 轮询并根据自定义 searchTermStatregy 查找匹配项。在我的例子中,这个邮箱每天接收 ~1000 封邮件,所以在一些 months/year INBOX 大小增加之后,电子邮件轮询器必须读取 million+ 每个民意调查的邮件。这将是一个巨大的性能打击。所以我们也需要解决这个问题。

=========================================== ====================

解决方案-第一步:使用自定义searchTermStatregy延迟邮件阅读:

正在阅读 至少 10 分钟 旧的且 已读的电子邮件。

/**
 * 
 */
package com.abc.xyz.receiver;

import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.search.AndTerm;
import javax.mail.search.FlagTerm;
import javax.mail.search.SearchTerm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.mail.SearchTermStrategy;
import org.springframework.stereotype.Service;
import com.sun.mail.imap.OlderTerm;

/**
 * @author Sundararaj Govindasamy
 *
 */
@Service
public class MySearchTermStrategy implements SearchTermStrategy {
    
    Logger logger = LoggerFactory.getLogger(MySearchTermStrategy.class);

    @Override
    public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
        logger.info("entering MySearchTermStrategy");

        SearchTerm older10MinsTerm = new OlderTerm(600);// in seconds
        SearchTerm flagTerm  = new FlagTerm(new Flags(Flags.Flag.SEEN), false);

        SearchTerm[] searchTermArray = new SearchTerm[2];
        searchTermArray[0] = older10MinsTerm;
        searchTermArray[1] = flagTerm;

        SearchTerm andTerm = new AndTerm(searchTermArray);
        return andTerm;
    }
}

在spring-integration.xml中,如下配置自定义searchTermStatregy。

<mail:inbound-channel-adapter id="imapAdapter" 
                                  store-uri="${imap.uri}"                                     
                                  channel="recieveEmailChannel"                                          
                                  should-delete-messages="false"
                                  should-mark-messages-as-read="true"                                      
                                  auto-startup="true"
                                  simple-content="true"
                                  auto-close-folder="false"
                                  search-term-strategy="mySearchTermStrategy"
                                  java-mail-properties="javaMailProperties">
    <int:poller fixed-delay="${imap.polling.interval}" time-unit="SECONDS"/>
</mail:inbound-channel-adapter>

=========================================== =========================

解决方案-第 2 步: 移动 从收件箱读取的电子邮件到另一个文件夹 每天晚上 10 点,这样电子邮件轮询器的负载就会减少。

请注意,Spring 集成框架 支持邮件移动,当我尝试使用 Spring 集成时,它在 ClassCastException 下抛出如下在此线程中讨论:

所以我决定使用纯 JavaMail API 在文件夹之间移动邮件,如下所示。 此 Spring 调度程序 将每天 运行 一次并将所有已读邮件从 INBOX 文件夹移动到 SunTest 文件夹,如下所示。

/**
 * 
 */
package com.abc.xyz.receiver;

import java.util.Properties;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.search.FlagTerm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.sun.mail.imap.IMAPFolder;

/**
 * @author Sundararaj Govindasamy 
 * Move read email from INBOX to another folder
 *         to improve INBOX search performance.
 */
@Component
public class EmailMover {

    Logger logger = LoggerFactory.getLogger(EmailMover.class);

    /**
     * move all read mails everyday 10PM, after business hours.
     */
    @Scheduled(cron = "0 0 22 * * *")
    public void moveReadMails() {
        logger.info("entering: moveReadMails Scheduler");
        // create session object
        Session session = this.getImapSession();
        try {
            // connect to message store
            Store store = session.getStore("imaps");
            store.connect("outlook.office365.com", 993, "documents.local@mycompany.onmicrosoft.com",
                    "thisispassword");
            // open the inbox folder
            IMAPFolder inbox = (IMAPFolder) store.getFolder("INBOX");
            inbox.open(Folder.READ_WRITE);
            // fetch messages
            
            Message[] messageArr = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), true));
            logger.info("No. of read emails in INBOX:{}", messageArr.length);
            
            // read messages
            for (int i = 0; i < messageArr.length; i++) {
                Message msg = messageArr[i];
                String subject = msg.getSubject();
                logger.info("Subject:{}", subject);
            }
            
            Folder targetFolder = inbox.getStore().getFolder("SunTest");
            inbox.moveMessages(messageArr, targetFolder);
            logger.info("All read mails were moved successfully from INBOX to {} folder", targetFolder);
        } catch (Exception e) {
            logger.error("Exception while moving emails, exception:{}", e.getMessage());
        }
        logger.info("exiting: moveReadMails Scheduler");
    }
    /**
     * 
     * @return
     */
    private Session getImapSession() {
        Properties props = new Properties();
        props.setProperty("mail.store.protocol", "imaps");
        props.setProperty("mail.debug", "true");
        props.setProperty("mail.imap.host", "outlook.office365.com");
        props.setProperty("mail.imap.port", "993");
        props.setProperty("mail.imap.ssl.enable", "true");
        Session session = Session.getDefaultInstance(props, null);
        session.setDebug(true);
        return session;

    }
}