Pull to refresh
18
0
Send message
Спасибо за отличную статью, интересует чисто с технической точки зрения, сколько времени вам понадобилось на реализацию такого сложного механизма + эксперименты? Или над этим работала большая команда и они за месяц-два все реализовали, протестировали и выкатили в прод?
Полностью согласен на счет корпоративного почтового сервера, насколько это большое благо я понял только когда пришлось слать почту без него (что и описано выше). «В условиях ограниченных ресурсов» — замечательная формулировка полностью передающая суть описанного в статье, хотя это не домашняя машина.

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

Свой код для отправки писем с требующего авторизации почтового сервера привожу ниже. Скажу сразу, что этот код может слать письма в русской кодировке, например на Gmail. Успешность отсылки зависит от принимающего ящика, т.е. если ваше русскоязычное письмо пришло в неподобающем виде — попробуйте поменять строки с кодировкой.
Сам код:
create or replace procedure send_mail_with_authorization(p_mail_host varchar2, p_mail_port pls_integer:=587,
                                                         p_sender varchar2, p_password varchar2,
                                                         p_recipients varchar2, p_subject varchar2, 
                                                         p_message varchar2) is
  v_mail_conn utl_smtp.connection;
  raw_message	raw(32000);
  raw_subject raw(32000);
  
begin
  v_mail_conn := utl_smtp.open_connection(p_mail_host, p_mail_port);
  utl_smtp.ehlo(v_mail_conn, p_mail_host);
  utl_smtp.command(v_mail_conn, 'AUTH LOGIN');
  utl_smtp.command(v_mail_conn, utl_raw.cast_to_varchar2(utl_encode.base64_encode(
utl_raw.cast_to_raw(p_sender))));
  utl_smtp.command(v_mail_conn, utl_raw.cast_to_varchar2(utl_encode.base64_encode(
utl_raw.cast_to_raw(p_password))));
  utl_smtp.mail(v_mail_conn, p_sender);
  utl_smtp.rcpt(v_mail_conn, p_recipients);
  utl_smtp.open_data(v_mail_conn);
  utl_smtp.write_data(v_mail_conn, 'Date: ' || to_char(sysdate, 'dd-mm-yyyy hh24:mi:ss') || utl_tcp.CRLF);
  utl_smtp.write_data(v_mail_conn, 'From: ' || p_sender || utl_tcp.CRLF);
  utl_smtp.write_data(v_mail_conn, 'To: ' || p_recipients || utl_tcp.CRLF);
  
--  utl_smtp.write_raw_data(v_mail_conn, utl_raw.cast_to_raw('Content-Transfer-Encoding: base64'||
UTL_TCP.CRLF));
  utl_smtp.write_data(v_mail_conn,'Content-Type: text/html; charset=UTF-8'||UTL_TCP.CRLF);
  raw_subject := UTL_RAW.cast_to_raw(convert(p_subject,'AL32UTF8'));
  raw_message:=utl_raw.cast_to_raw(convert(p_message,'AL32UTF8'));


  utl_smtp.write_data(v_mail_conn, 'Subject: ');
  utl_smtp.write_raw_data(v_mail_conn, raw_subject);
  utl_smtp.write_data(v_mail_conn, utl_tcp.CRLF);
  utl_smtp.write_raw_data(v_mail_conn, raw_message);
  utl_smtp.write_data(v_mail_conn, utl_tcp.CRLF);  
  
  utl_smtp.close_data(v_mail_conn);
  utl_smtp.quit(v_mail_conn);
end;


Этот код у меня успешно слал письма с Рамблера и Yahoo. Вызов происходит так:
begin
  send_mail_with_authorization(p_mail_host => 'mail.rambler.ru',
                               p_mail_port => 587,
                               p_sender => 'my_mail@rambler.ru',
                               p_password => 'my_pass',
                               p_recipients => 'my_friend@gmail.com',
                               p_subject => 'Тема',
                               p_message => 'Текст письма.');

  send_mail_with_authorization(p_mail_host => 'smtp.mail.yahoo.com',
                               p_mail_port => 587,
                               p_sender => 'my_mail@yahoo.com',
                               p_password => 'my_pass',
                               p_recipients => 'my_friend@gmail.com',
                               p_subject => 'Тема',
                               p_message => 'Текст письма.');
end;


Теперь о ньюансах: их масса, и всех я не знаю, но то с чем столкнулся расскажу:
1. Иногда почта с Yahoo некорректно отображается на Gmail, т.е. я слал одно письмо с Yahoo на 3 разных почтовика, везде все ок, а на Gmail письмо приходит с пустым телом. Почему так — не знаю, но если то же самое письмо отправить с Рамблера, то Gmail его видит отлично.
2. Русский текст. Тут также «темна вода в облацеях» — вы може подобрать параметры, которые будут распознаваться большинством серверов, но на каком-то вы все-равно получите кроказябры. Таким образом идеал — когда вам нужно слать письма только пользователям одного почтового сервера, чтоб вы кастомизировали под него кодировку и тип сообщения (plain/html).
3. Рамблер странный, может мне просто не повезло с временем экспериментов, но суть такова: у меня есть ящик на Рамблере, я протестил указанную выше процедуру — через него она почту шлет отлично. Специально для нужн системы завел новый ящик, и через него почта не пошла: выпадают поочередно ошибки вида «Incorrect username/password» или «Internal server error». Работа с настройками ящиков ни к чему не привела — они одинаковы, возможно, на сервере стоит какой-то таймаут для использования smtp для новых ящиков, хотя звучит это как-то диковато.
На счет отсылки писем из СУБД — не вижу в этом ничего предосудительного (когда, конечно, все происходит в нормально сконфигурированной среде). Просто существует два диаметрально противоположных подхода: БД — только хранилище данных и БД — серверная часть приложения, как правило в живых проектах используется что-то среднее с перекосом в ту или другую сторону.
Если в ваших проектах привыкли не развертывать логику в БД, то да, отсылка оттуда писем, http-запросов и прочий не sql-функционал выглядит неуместно. Если наличие в БД логики для вас норма, все данные находятся и обрабатываются там же, почему бы не отослать письмо с отчетом? Тем более, что это выполняется стандартными средствами, без вызова «external subprograms».

На счет замечаний по моей настройке почты — то все замечания верные, и, в идеальном мире, я бы этим не занимался. Но в реальности бывают разные обстоятельства, разные люди, по-разному выполняющие свою работу. В общем, не займись я этим сам, слал бы я письма руками.

На счет паролей в открытом виде, то в данном случае пароли — не очень критичная информация, в том смысле, что это автоматически сгенерированные пароли для доступа к определенной функциональности в БД (потому их и нужно было рассылать). Хранятся они вместе с той функциональностью, доступ к которой обеспечивают. Другими словами, атака, способная достигнуть таблицы с паролями, равновероятно достигнет и защищаемой ими информации. Поэтому с точки зрения возможности кражи шифровать их бессмысленно, разве что для защиты от передачи по сети в открытом виде. Но тут уже встает вопрос соотношения цены информации к цене ее защиты. С точки зрения критичности такой перехват пароля позволит злоумышленнику всего лишь получить доступ к системе работы с данными, заточенной под весьма специфические операции, а не к самим данным.

Про автоматизированную отсылку через почтовый клиент типа Outlook'a даже не подумал — давно не пользовался, вот специфика мышления и определила однозначное направление поиска решения :)
Ну, скажем откровенно, роль приходящаяся на pls_integer в данном случае столь мала, что оно не повлияет сколь-нибудь существенно :)
Таким образом мы с вами вывели разницу в производительности Оракла в зависимости от машины (я запускал на сервере Intel Xeon 2 ядра по 3Ghz, 4 Gb RAM), который в тот момент был ничем не загружен.

У меня когда-то на ноутбуке Оракл стоял для домашних целей (AMD Turion 2 Ghz и 512 RAM), так ничего, работал, правда грузился долго. Но вот когда вместе с ним запустить NetBeans, например, то все жутко тормозило из-за нехватки памяти.

Пока писал, вспомнил, что у меня есть старая машина Athlon 1,41 Ghz, 256 RAM. Запустил запрос на ней, время выполнения 14 сек.
Спасибо, повеселили :)
Анонимный блок для Оракл (проверял, выполняется приблизительно одинаково с хранимой процедурой):
declare
  U number:=1;
  S number:=1;
  I pls_integer:=3;
begin
  WHILE (abs(U) >= 0.0000001) loop
    U:= -U*1*1*(I-2)/I;
    S:= S + U;
    I:= I + 2;
  END loop;
  dbms_output.put_line(4*S);
end;

Время выполнения около 4.5 сек.
Конечно, нужно на одинаковых машинах скорость замерять.
Да, спасибо за идею, добавил раздел «Особенности отображения дат в различных приложениях».
Имхо, для задания единого формата дат для всех сессий удобнее всего использовать ON_LOGON триггер, предложенный Томом, с указанием своего формата.
Явное указание масок для to_date и to_char, конечно, самый надежный вариант, но триггер тоже может обеспечить достаточную степень надежности (если никто сам не будет делать ALTER SESSION :)).
Да, действительно, фраза про union написана так, что может быть прочитана двояко. Я имел ввиду механику работы union, когда для построения объединения множеств они вначале сортируются, и не подразумевал, что union может вернуть отсортированный результат и заменить таким образом order by.

Про «union all гарантирует сохранение исходного порядка строк» я имел ввиду, что при объединении множества А с множеством В в результирующей выборке будут идти сначала строки из множества А, потом из множества В. Сам порядок выдачи строк начиная то ли с 10-ки, то ли даже раньше, Оракл не гарантирует (из обычного select), что уж говорить про union all.
Поскольку в примере, к которому относится эта фраза, каждое множество было представлено одной строкой, то union all гарантировал сохранение этого порядка строк :) В общем, тоже весьма двояко написал, впредь постараюсь точнее излагать.
Бывают разные специфические требования к порядку строк и выдаче данных. Предположим, что мы составляем отчет по клиентам, в отчете должно быть только ФИО и дата начала обслуживания, а отсортировать их нужно по «толщине кошелька», чтоб руководство сразу видело, кто им больше люб. «Толщина кошелька» вычисляется по нескольким полям с помощью формулы.
Тогда вы либо вводите «толщину кошелька» как еще одно поле в select и потом из полученных данных делаете еще одну выборку уже без этого поля (т.е. как писали выше, получаете дополнительный уровень вложенности), либо сразу используете формулу в order by.

На самом деле всяко бывает, например, может сложиться ситуация, когда ФИО целиком лежит в одной колонке и его нужно вывести, а сортировка должна быть по имени. Тогда вы в order by используете substr, не забивая результирующую выборку ненужным полем с именем.
Тоже часто использую такой формат условий для множеств состоящих из пар элементов.
Обычно либо в условиях:
select case when (1,2) in ((1,2),(2,3)) then 1 else 2 end as col1 from dual 
Либо, что более полезно, для обработки данных из вложенных подзапросов:
select * from dual
where (dummy, dummy) in (select dummy col1, dummy col2 from dual)

Information

Rating
Does not participate
Location
Киев, Киевская обл., Украина
Registered
Activity