Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Отчетные системы Новый топик    Ответить
 SSRS и рассылка по e-mail  [new]
ptr128
Member

Откуда: Moscow
Сообщений: 374
Имеется SSRS отчет квитанции на коммунальные услуги. Ну как положено, входящее и исходящее сальдо, начисления, детализация по перерасчетам, показаниям индивидуальных и общедомовых приборов учета и т.п.
В процессе оптимизации пришел к тому, что весь отчет сделал на одной таблице (Matrix), а источником данных стала хранимая процедура.
А вот тут я столкнулся с диллемой. С одной стороны, для того чтобы формировать эти квитанции быстрее, чем их печатает потоковый принтер, хранимая процедура была оптимизирована под формирование десятков тысяч квитанций при одном запуске (менее 40 миллисекунд на квитанцию). Скорость формирования отдельно одной квитанции при этом стала около 1 секунды.
Для потокового принтера все стало хорошо. А вот для рассылки одиночных квитанций по электронной почте - все плохо.

Соответственно, размышляю над следующими вариантами:
1. Каким-то образом сделать вариант, оптимизированный под одну квитанцию. Тогда можно будет рассылать средствами SSRS по подписке управляемой данными.
2. Сохранять результат в PDF вместе с какой-то скрытой информацией (e-mail адрес, тема, тело письма) и затем какой-то самописной утилитой разбивать этот PDF из десяти тысяч квитанций на десять тысяч файлов, которые уже отправлять по e-mail.

Сталкивался кто с такой задачей?
Какие еще возможны пути ее решения?
14 дек 17, 20:45    [21035003]     Ответить | Цитировать Сообщить модератору
 Re: SSRS и рассылка по e-mail  [new]
bbx1389
Member

Откуда: Русија
Сообщений: 23953
неочень понятно, что стало плохо
разделите 2 задачи и решайте их отдельно исходя
Можете пакет в SSIS написать или даже SQL Server через dbmail.
Только в черные списки не попадите:)
15 дек 17, 08:53    [21035661]     Ответить | Цитировать Сообщить модератору
 Re: SSRS и рассылка по e-mail  [new]
ptr128
Member

Откуда: Moscow
Сообщений: 374
bbx1389,
Плохо то, что по одной квитанции формировать на SSRS ОЧЕНЬ медленно. Даже если довести до 0.5 секунд на квитанцию, написанием отдельной процедуры (что уже не есть хорошо), то тогда даже 100 тыс квитанций я рассылать буду 14 часов, что ни в какие ворота не лезет.

И не понятно, при чем тут SSIS? У меня форма квитанции на SSRS со всеми причендалами, QR-кодом и прочей рекламной нечистью.
15 дек 17, 11:06    [21036132]     Ответить | Цитировать Сообщить модератору
 Re: SSRS и рассылка по e-mail  [new]
buser
Member

Откуда: Санкт-Петербург
Сообщений: 4109
ptr128, ssrs вам тогда мало чем поможет, если управляемые подписки негодны...
проблема в скорости рендеринга или получения данных?
попробуйте, как и было сказано разбить на задачи:
1. получение данных (всех),
2. используя локальный процессинг ReportViewer контрола подпихивайте данные и рендерите в отдельные pdf.
3. полученное добро шлите на почту.
P.S.: не знаю... как там с многопоточностью при использовании ReportViewer control, но можно попробовать распараллелить 2+3 (можно попробовать SSIS пакет сделать)
P.P.S.: а сколько занимает формирование и отсылка почтового сообщения? вас как спамера могут забанить на почтовике :)
15 дек 17, 11:49    [21036255]     Ответить | Цитировать Сообщить модератору
 Re: SSRS и рассылка по e-mail  [new]
ptr128
Member

Откуда: Moscow
Сообщений: 374
buser,

У меня не может быть локального процессинга, потому что нет локала. У меня есть только SQL сервер с SQL Server Agent и сервер для SSRS.
Задачи и так разделены. Хранимая процедура на SQL формирует в одном выходном потоке неограниченное (в пределах размера tempdb) количество квитанций. 10 тыс. без проблем формируются менее, чем за 400 секунд.
Проблема в скорости рендеринга одиночной квитанции. В одном потоке SSRS 10 тыс. квитанций рендерит в PDF за полчаса. Запустив 8 потоков на 4 ядрах я получаю время рендеринга менее, чем 40 мс на квитанцию. Естественно, пока SSRS их рендерит я могу на SQL спокойно готовить очередную порцию в хранимой процедуре.
А вот одиночную квитанцию в PDF SSRS, хоть убейся, быстрее чем за 500 мс (0.5 сек) не рендерит.
То есть, формируя восемь PDF, каждый из которых содержит 10 тыс квитанций, я трачу 6 минут на хранимую процедуру и 6 минут на рендеринг в восемь потоков на SSRS. А вот если я из этого потока начну редерить по одной квитанции в PDF, то 8 потоков SSRS у меня будут рендерить больше часа, что ни в какие ворота уже не лезет.

Ладно, начал уже изучать iTextSharp. Если получится им быстро разбивать один большой PDF на множество мелких, отправляя их аттачами по SMTP, то на том и остановлюсь.
15 дек 17, 15:50    [21037316]     Ответить | Цитировать Сообщить модератору
 Re: SSRS и рассылка по e-mail  [new]
ptr128
Member

Откуда: Moscow
Сообщений: 374
buser
P.P.S.: а сколько занимает формирование и отсылка почтового сообщения? вас как спамера могут забанить на почтовике :)

Мне это по фигу. Это уже проблема того поставщика коммунальных услуг, который эти квитанции рассылает )
SMTP у него на хорошем сервере стоит. Должен с такими потоками легко справляться.
Про время сказать пока ничего не могу, так как exim справляется явно быстрее, чем SSRS
15 дек 17, 16:00    [21037354]     Ответить | Цитировать Сообщить модератору
 Re: SSRS и рассылка по e-mail  [new]
ptr128
Member

Откуда: Moscow
Сообщений: 374
Короче так. Глаза боятся - руки делают. Тему можно отмечать [SOLVED]

За два часа написал утилиту на C#, которая разбивает один огромный PDF с кучей квитанций, на множество мелких, содержащих по одной квитанции и сразу же их отправляет по электронной почте.

Теперь подробности.
1. В источнике данных должны быть:
- уникальный номер документа
- электронный адрес получателя или список получателей, разделенных запятой (все, что допустимо в поле To: e-mail)
- тема сообщения
- текст сообщения
2. В SSRS отчете размещаем, например, первой строкой поле с белым шрифтом на белом поле (чтобы видно не было) и размером шрифта в 1 пункт следующего содержимого:
- маркер начала поля из четырех символов "&$#!"
- уникальный номер документа (только цифры)
- разделитель "%"
- электронный адрес получателя, закодированный в base64
- разделитель "%"
- тема письма, закодированная в base64
- разделитель "%"
- тело письма, закодированнное в base64
- маркер конца поля из четырех символов "&$#!"
Кодирование в base64 пришлось использовать, так как это оказалось простейшим способом совладать с кириллицей в PDF.
У меня это получилось так:
=iif(Max(Fields!customeremail.Value, "proc_id_group")="","","&$#!"+Str(Fields!proc_id.Value)+"%"+
System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(Max(Fields!customeremail.Value, "proc_id_group")))+"%"+
System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(Iif(Max(Fields!emailsubj.Value, "proc_id_group")="","(без темы)",
Replace(
Replace(
Replace(
Replace(
Replace(
Replace(
Replace(
Replace(
Max(Fields!emailsubj.Value, "proc_id_group"),
"%к",Max(Fields!customername.Value, "proc_id_group"),
),"%п",Max(Fields!companyname.Value, "proc_id_group"),
), "%л",Max(Fields!externalcontractid.Value, "proc_id_group"),
), "%р",Choose(Max(Fields!repmonth.Value, "proc_id_group"),
"январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"
) + " " + CStr(Max(Fields!repyear.Value, "proc_id_group")),
), "%о",FormatDateTime(Max(Fields!duedate.Value, "proc_id_group"), DateFormat.ShortDate),
), "%н",FormatNumber(Max(Fields!dr_amt.Value, "proc_id_group")+Max(Fields!rc_amt.Value, "proc_id_group"),2)
), "%з",FormatNumber(Max(Fields!ou_amt.Value, "proc_id_group"),2)
), "%%", "%")
)))+"%"+System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(Iif(IsNothing(Max(Fields!emailbody.Value, "proc_id_group")),"(без тела)",
Replace(
Replace(
Replace(
Replace(
Replace(
Replace(
Replace(
Replace(
Max(Fields!emailbody.Value, "proc_id_group"),
"%к",Max(Fields!customername.Value, "proc_id_group"),
),"%п",Max(Fields!companyname.Value, "proc_id_group"),
), "%л",Max(Fields!externalcontractid.Value, "proc_id_group"),
), "%р",Choose(Max(Fields!repmonth.Value, "proc_id_group"),
"январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"
) + " " + CStr(Max(Fields!repyear.Value, "proc_id_group")),
), "%о",FormatDateTime(Max(Fields!duedate.Value, "proc_id_group"), DateFormat.ShortDate),
), "%н",FormatNumber(Max(Fields!dr_amt.Value, "proc_id_group")+Max(Fields!rc_amt.Value, "proc_id_group"),2)
), "%з",FormatNumber(Max(Fields!ou_amt.Value, "proc_id_group"),2)
), "%%", "%")
)))+"&$#!")
Множество Replace() нужны для замены плейсхолдеров в теме или теле письма значениями из отчета.

Утилита имеет три параметра:
1. адрес или доменное имя SMTP сервера для отправки писем и через двоеточие - номер порта на сервере; если номер порта указан, то отправка писем будет производиться по STARTTLS, а в противном случае - на 25 порт без SSL
2. e-mail отправителя сообщения;
3. путь к PDF файлу

За саму утилиту просьба сильно ногами не бить, так как это первый в жизни мой код на C#. Главное - работает )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int rc = 0;
            if (args.Length != 3)
            {
                Console.WriteLine("Usage: pdf_to_e-mail_splitter.exe smtp-server[:port] sender-e-mail PDF-file-to-parse");
                Environment.Exit(12);
            }

            try
            {
                rc=parse_file(args[0], args[1], args[2]);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error parsing input PDF file {0}. {1}", args[2], ex.ToString());
                Environment.Exit(16);
            }
            Environment.Exit(rc);
        }

        static int parse_file(string smtpServer, string emailFrom, string fname)
        {
            int rc = 0;
            PdfReader reader = new PdfReader(fname);
            Document document = null;
            PdfCopy copy = null;
            string pagePath = null;
            string rawPage;
            SmtpClient client;
            string[] decoded = null;
            string proc_id = "";

            ServicePointManager.ServerCertificateValidationCallback =
                delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)  { return true; };
            
            string[] smtpName = smtpServer.Split(':');
            if (smtpName.Length > 1)
            {
                client = new SmtpClient(smtpName[0], Convert.ToInt32(smtpName[1]));
                client.EnableSsl = true;
                client.Credentials = CredentialCache.DefaultNetworkCredentials;
                client.UseDefaultCredentials = true;
                Console.WriteLine("Connecting to SMTP with SSL");
            }
            else
            {
                client = new SmtpClient(smtpName[0]);
                client.EnableSsl = false;
                Console.WriteLine("Connecting to SMTP without SSL and authentication");
            }
            client.DeliveryFormat = SmtpDeliveryFormat.International;

            for (int pageNo = 1; pageNo <= reader.NumberOfPages; pageNo++)
            {
                rawPage = PdfTextExtractor.GetTextFromPage(reader, pageNo);
                try
                {
                    decoded = parse_page(rawPage);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("[Page {0} skipped: {1}] ", pageNo, ex.Message);
                    continue;
                }

                if (decoded[0] != proc_id)
                {
                    Console.WriteLine("Page {0} decoded as document No{1} for e-mail address {2}", pageNo, decoded[0], decoded[1]);
                    if (proc_id != "")
                    {
                        document.Close();
                        document.Dispose();
                        MailMessage message = new MailMessage(emailFrom, decoded[1], decoded[2], decoded[3]);
                        Attachment data = new Attachment(pagePath, MediaTypeNames.Application.Pdf);
                        message.Attachments.Add(data);
                        try
                        {
                            client.Send(message);
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("Exception caught in SmtpClient.Send: {0}", ex.ToString());
                            rc = 4;
                        }
                        data.Dispose();
                        message.Dispose();
                        File.Delete(pagePath);
                    }

                    pagePath = System.IO.Path.GetTempPath() + System.IO.Path.GetFileNameWithoutExtension(fname) + "_page.PDF";
                    document = new Document();
                    copy = new PdfCopy(document, new FileStream(pagePath, FileMode.Create));
                    document.Open();
                    copy.AddPage(copy.GetImportedPage(reader, pageNo));
                }
                else
                {
                    Console.WriteLine("Page {0} decoded as document No{1} and appended for e-mail address {2}", pageNo, decoded[0], decoded[1]);
                    copy.AddPage(copy.GetImportedPage(reader, pageNo));
                }
                proc_id = decoded[0];
            }
            if (proc_id != "")
            {
                document.Close();
                document.Dispose();
                MailMessage message = new MailMessage(emailFrom, decoded[1], decoded[2], decoded[3]);
                Attachment data = new Attachment(pagePath, MediaTypeNames.Application.Pdf);
                message.Attachments.Add(data);
                try
                {
                    client.Send(message);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception caught in SmtpClient.Send: {0}", ex.ToString());
                    rc = 4;
                }
                data.Dispose();
                message.Dispose();
                File.Delete(pagePath);
            }
            return (rc);
        }

        static string[] parse_page(string page_raw)
        {
            string[] decoded = new string[4];;
            int from = page_raw.IndexOf("&$#!");
            if (from < 0) throw new ArgumentException("Label &$#! not found");
            int to = page_raw.IndexOf("&$#!",from+4);
            if (to < 0) throw new ArgumentException("Label &$#! found only once");
            string found = page_raw.Substring(from + 4, to - 4);
            string[] parsed = found.Split('%');
            if (parsed.Length != 4) throw new ArgumentException("Delimiter % must exist only two times");
            for (int i=0; i<4; i++ )
            {
                decoded[i] = (i==0) ? parsed[i] : Encoding.UTF8.GetString(Convert.FromBase64String(parsed[i]));
            }
            return decoded;
        }
    }
}


Если e-mail для документа определен, то в начале каждого листа у него наша скрытая строка будет заполнена. Если не определен - строка будет пустая.
Если документ занимает несколько листов, то утилита определит это по повторению номера документа на последующих листах.

Со скоростью все очень хорошо. Дробит PDF библиотека iTextSharp очень быстро )
15 дек 17, 21:22    [21038350]     Ответить | Цитировать Сообщить модератору
 Re: SSRS и рассылка по e-mail  [new]
buser
Member

Откуда: Санкт-Петербург
Сообщений: 4109
ptr128, годный вариант.
15 дек 17, 22:36    [21038445]     Ответить | Цитировать Сообщить модератору
Все форумы / Отчетные системы Ответить