Pull to refresh
0
True Engineering
Лаборатория технологических инноваций

Как использовать функцию обработки входящей почты в SharePoint 2010 — пример из практики

Reading time 5 min
Views 6.8K
Нередко бизнес-процесс компании включает в себя необходимость обработки документации, содержащей штрих-коды, с последующим занесением их в некую учетную систему. Это актуально, например, для сферы ЖД-перевозок и авиакомпаний: в штрих-код купона, присылаемого на электронную почту агентами компании, зашиты номера билетов. Операционисты вынуждены ежедневно обрабатывать и заносить во внутреннюю учетную систему тысячи таких купонов.

Работа рутинная, человеческий фактор провоцирует ошибки. Как автоматизировать процесс и избавить оператора от необходимости ручной обработки писем и их вложений? Мы нашли простое решение с использованием MS SharePoint. По своему обыкновению, мы постарались по максимуму задействовать имеющийся функционал систем, существующую библиотеку и немного своей программистской магии.
Настройка Exchange

Если вы прочитали документацию и все настроили правильно, то дальше для вас все делается в несколько нажатий, в настройках библиотеки документов:





На сервере с SharePoint настраивается почтовый сервер на прием писем. Входящие письма в виде eml-файлов появляются в файловой системе в папке inbox. SharePoint каждые две минуты проверяет папку inbox, и если там есть файлы, он их обрабатывает и удаляет.

Стоит заметить, что все подряд файлы (с неправильным адресом получателя, с неправильными дополнительными тегами либо созданные с помощью программы Outlook Express) обрабатываться не будут — файл должен быть создан именно сервером SMTP из состава IIS.

В результате, все письма, приходящие на этот адрес, попадают в библиотеку документов:



В каждом письме присутствует одно или несколько вложений в форматах jpg, png, tiff, pdf, в которых может быть один или несколько документов, отмеченных штрих-кодом:



Для распознавания штрих-кодов мы использовали open source библиотеку zxing, с помощью которой можно сканировать и обрабатывать различные типы штрих-кодов. Здесь нам пришлось немного доработать возможности библиотеки, а точнее — алгоритм терминирующего символа, т.к. у заказчика используется своя специфическая кодировка, которая в zxing не поддерживается (хоть и похожа на codabar).

Далее мы реализовали Event Handler, с помощью zxing каждые 15 минут определяющий документы во вложениях писем, которые автоматически попадают на портал через Exchange. Далее он забирает их через библиотеку, вытаскивает вложения, адрес отправителя и код отправителя (личный код агента компании).

Осторожно! Дальше много рутинного кода о том, как вытащить данные мэйла через CDO и работать с его Stream-ами.
Код
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using ADODB;
using CDO;
using ETR.REBT.BarcodeReader;
using Stream = System.IO.Stream;

namespace ETR.BusinessLogic.EBTBlanks
{
    internal class EBTMailParser
    {
        private static readonly string[] SupportedExtensions =
        {
            "application/pdf"
            , "image/jpeg"
            , "image/bmp"
            , "image/png"
            , "image/tiff"
        };

        private static readonly Regex AgencyCodePattern = new Regex(@"{\w*}");
        public static EBTMailParseResult ParseEmail(Stream emlStream)
        {
            var result = new EBTMailParseResult
            {
                Success = true
            };
            Message msg = new MessageClass();
            ADODB.Stream stream = new StreamClass();
            try
            {
                CopyStream(emlStream, stream);

                msg.DataSource.OpenObject(stream, "_Stream");


                result.FromAddress = msg.From;
                result.SendDate = msg.SentOn;
                result.ReceivedDate = msg.ReceivedTime;
                string agentCode;
                if (TryParseAgencyCode(msg.Subject, out agentCode))
                {
                    result.AgencyCode = agentCode;
                }
                else
                {
                    result.Status = "Не удалось распознать код Агента. Код должен содержаться в теме письма, содержать только цифры и располагаться внутри фигурных скобок. Например, {12345}";
                    return result;
                }

                if (msg.Attachments.Count == 0)
                {
                    result.Status = "В письме не обнаружено ни одного вложения";
                    return result;
                }

                result.Attachments = ParseAttachments(msg.Attachments).ToList();
                return result;
            }
            catch (Exception ex)
            {
                //If we get unknown error - we don't mark letter as parsed an try next time
                result.Success = false;
                result.Status = "Внутренняя ошибка.";
                result.ExceptionMessage = ex.Message;
                return result;
            }
            finally
            {
                stream.Close();
            }

        }

        private static bool TryParseAgencyCode(string subject, out string result)
        {
            var allMatchResults = AgencyCodePattern.Matches(subject);
            if (allMatchResults.Count != 1 || false == allMatchResults[0].Success)
            {
                result = null;
                return false;
            }
            result = allMatchResults[0].Value.Substring(1, allMatchResults[0].Value.Length - 2);
            return true;
        }

        private static void CopyStream(Stream netStream, ADODB.Stream adoStream)
        {
            //adoStream.Open(Type.Missing, ADODB.ConnectModeEnum.adModeUnknown, ADODB.StreamOpenOptionsEnum.adOpenStreamUnspecified, String.Empty, String.Empty);
            adoStream.Type = StreamTypeEnum.adTypeBinary;
            adoStream.Open();

            netStream.Position = 0;
            var buffer = new byte[1024];
            while (netStream.Read(buffer, 0, buffer.Length) != 0)
            {
                adoStream.Write(buffer);
            }
            adoStream.Flush();
        }


        private static void CopyStream(ADODB.Stream adoStream, Stream netStream)
        {
            while (!adoStream.EOS)
            {
                var bytes = (byte[])adoStream.Read(1024);
                netStream.Write(bytes, 0, bytes.Length);
            }
            netStream.Flush();
        }


        private static IEnumerable<EBTAttachmentParseResult> ParseAttachments(IBodyParts attachments)
        {
            var barcodeReader = new BarcodeReader(true);

            for (var i = 1; i <= attachments.Count; i++)
            {
                var attachment = attachments[i];
                var fileResult = new EBTAttachmentParseResult
                {
                    FileName = attachment.FileName
                };

                if (false == SupportedExtensions.Any(ct => ct == attachment.ContentMediaType))
                {
                    fileResult.Status = String.Format("Файлы {0} не поддерживаются", attachment.ContentMediaType);
                    yield return fileResult;
                }

                var stream = attachment.GetDecodedContentStream();
                try
                {
                    var memoryStream = new MemoryStream();
                    CopyStream(stream, memoryStream);
                    memoryStream.Position = 0;
                    var parseResult = barcodeReader.Decode(memoryStream, attachment.FileName);

                    fileResult.Status = parseResult.AllPages > parseResult.RecognizedPages
                        ? String.Format("Распознано {0} из {1} страниц", parseResult.RecognizedPages,
                            parseResult.AllPages)
                        : fileResult.Status;
                    fileResult.Stream = memoryStream;

                    if (parseResult.ResultList != null && parseResult.ResultList.Count > 0)
                    {
                        fileResult.BarcodeNumbers = parseResult.ResultList.Select(b => ParseBarcode(b.Text)).ToList();
                    }
                }
                catch (Exception ex)
                {
                    fileResult.Status = "Внутренняя ошибка.";
                    fileResult.ExceptionMessage = ex.Message;
                }
                finally
                {
                    stream.Close();
                }
                yield return fileResult;
            }
        }

        private static EBTBarcodeParseResult ParseBarcode(string barCode)
        {
            if (barCode.Length != 15)
                return new EBTBarcodeParseResult
                {
                    BarcodeNumber = barCode,
                    Status = "Не удалось распознать код документа"
                };


            return new EBTBarcodeParseResult
            {
                BarcodeNumber = barCode,
                CouponNumber = barCode.Substring(1, 13).Insert(3, " ")
            };
        }
    }
}


Если хэндлер находит какую-то ошибку (неправильное расширение файлов, не распознается код агента и т.д.), в EventBus регистрируется событие с ее описанием.

Если ошибок нет, вложения разбираются через библиотеку, из них «вытаскиваются» штрих-коды, с которых считывается информация. На каждый определенный штрих-код создается бланк в SharePoint, который уже и обрабатывают операционисты, когда получают оригиналы документов.



В завершение, в EventBus отправляется событие с сообщением о том, что письмо разобрано, информацией сколько файлов в нем найдено, сколько купонов во вложении, сколько билетов в купонах. Все заинтересованные лица получают push-уведомление.



Данный бизнес-процесс — маленькая часть большой работы офиса крупной транспортной компании. Также его легко можно транслировать и на другие сферы, где необходимо много и рутинно обрабатывать входящую документацию, и где используются SharePoint и Exchange.

Наш постулат в таких условиях — не переставать совершенствование существующей системы, автоматизировать все рутины, используя доступные средства, смекалку и свои навыки в программировании.
Tags:
Hubs:
+5
Comments 1
Comments Comments 1

Articles

Information

Website
www.trueengineering.ru
Registered
Founded
Employees
101–200 employees
Location
Россия