Работа рутинная, человеческий фактор провоцирует ошибки. Как автоматизировать процесс и избавить оператора от необходимости ручной обработки писем и их вложений? Мы нашли простое решение с использованием 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. Далее он забирает их через библиотеку, вытаскивает вложения, адрес отправителя и код отправителя (личный код агента компании).
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.
Наш постулат в таких условиях — не переставать совершенствование существующей системы, автоматизировать все рутины, используя доступные средства, смекалку и свои навыки в программировании.
ссылка на оригинал статьи http://habrahabr.ru/company/eastbanctech/blog/211669/
Добавить комментарий