Grails: Как буферизовать исходящие письма, когда SMTP-сервер временно отключен? - PullRequest
2 голосов
/ 15 марта 2011

Grails использует mailService из Spring. Эта служба является синхронной, что означает, что если SMTP временно отключается, это сильно влияет на работу приложения (HTTP 500).

Я хочу отделить приложение от SMTP-сервера.

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

Интересно, как я могу заменить / обернуть определение mailService, чтобы весь код, мой собственный и плагины, прозрачно использовали мой собственный сервис?

1009 * Т.е. *

  • Код плагина внедряет mailService
  • Но вместо почтового сервиса Spring по умолчанию вводится мой собственный код
  • Когда плагин отправляет электронное письмо, объект электронной почты сохраняется в БД
  • По таймеру работа просыпается, получает следующие N писем и пытается отправить их

Есть идеи, как подойти к этой проблеме?

P.S. Я знаю о плагине AsynchronousMail. К сожалению, его служба должна вызываться явно, т. Е. Она не является заменой для службы почты.

Ответы [ 3 ]

2 голосов
/ 12 января 2012

Плагин асинхронной почты теперь поддерживает переопределение почтового плагина. Просто добавьте

asynchronous.mail.override=true

к вашей конфигурации. Смотри http://grails.org/plugin/asynchronous-mail

2 голосов
/ 15 марта 2011

Простым решением для этого является использование локально установленного почтового сервера.Доступны хорошо известные и полнофункциональные MTA, такие как Postfix, Sendmail или Exim, а также легкие замены, такие как http://packages.qa.debian.org/s/ssmtp.html.

. Настройте используемый пакет MTA так, чтобы он передавал все свои электронные письма на реальный SMTP-сервер вашего домена.Приложение Grails будет тогда просто использовать 127.0.0.1 в качестве SMTP-хоста.

Это также имеет преимущество в улучшенном времени отклика в вашем приложении, поскольку отправка электронной почты больше не требует никакого нелокального IP-трафика на первом месте.

0 голосов
/ 18 марта 2011

ОК, это было не так сложно, в конце концов. Сначала легкие шаги:

Шаг первый : подготовить таблицу базы данных для хранения ожидающих записей электронной почты:

class PendingEmail {
    Date sentAt = new Date()
    String fileName

    static constraints = {
        sentAt nullable: false
        fileName nullable: false, blank:false
    }
}

Шаг второй : создайте периодическое задание для отправки ожидающих писем. Примечание mailSender инъекция - это часть оригинального почтового плагина Grails, поэтому отправка (и его настройка!) Осуществляется через почтовый плагин:

import javax.mail.internet.MimeMessage

class BackgroundEmailSenderJob {

    def concurrent = false
    def mailSender

    static triggers = {
        simple startDelay:15000l, repeatInterval: 30000l, name: "Background Email Sender"
    }

    def execute(context){
        log.debug("sending pending emails via ${mailSender}")

        // 100 at a time only
        PendingEmail.list(max:100,sort:"sentAt",order:"asc").each { pe ->

            // FIXME: do in transaction
            try {
                log.info("email ${pe.id} is to be sent")

                // try to send
                MimeMessage mm = mailSender.createMimeMessage(new FileInputStream(pe.fileName))
                mailSender.send(mm)

                // delete message
                log.info("email ${pe.id} has been sent, deleting the record")
                pe.delete(flush:true)

                // delete file too
                new File(pe.fileName).delete();
            } catch( Exception ex ) {
                log.error(ex);
            }
        }
    }
}

Шаг третий : создать замену mailService, которая может использоваться любым кодом Grails, включая плагины. Обратите внимание на инъекцию mmbf: это mailMessageBuilderFactory из почтового плагина. Служба использует фабрику для сериализации входящих вызовов Closure в действительное сообщение MIME, а затем сохраняет его в файловой системе:

import java.io.File;

import org.springframework.mail.MailMessage
import org.springframework.mail.javamail.MimeMailMessage

class MyMailService {
    def mmbf

    MailMessage sendMail(Closure callable) {
        log.info("sending mail using ${mmbf}")

        if (isDisabled()) {
            log.warn("No mail is going to be sent; mailing disabled")
            return
        } 

        def messageBuilder = mmbf.createBuilder(mailConfig)
        callable.delegate = messageBuilder
        callable.resolveStrategy = Closure.DELEGATE_FIRST
        callable.call()
        def m = messageBuilder.finishMessage()

        if( m instanceof MimeMailMessage ) {
            def fil = File.createTempFile("mail", ".mime")
            log.debug("writing content to ${fil.name}")
            m.mimeMessage.writeTo(new FileOutputStream(fil))

            def pe = new PendingEmail(fileName: fil.absolutePath)
            assert pe.save(flush:true)
            log.debug("message saved for sending later: id ${pe.id}")
        } else {
            throw new IllegalArgumentException("expected MIME")
        }
    }

    def getMailConfig() {
        org.codehaus.groovy.grails.commons.ConfigurationHolder.config.grails.mail
    }

    boolean isDisabled() {
        mailConfig.disabled
    }
}

Шаг четвертый : замените почтовый плагин MailService на модифицированную версию, добавив его на завод. В grails-app/conf/spring/resources.groovy:

beans = {
    mailService(MyMailService) {
        mmbf = ref("mailMessageBuilderFactory")
    }
}

Готово!

С этого момента любой плагин или код Grails, который использует / внедряет mailService, будет получать ссылку на MyMailService. Служба будет принимать запросы на отправку электронной почты, но вместо отправки она будет сериализовать ее на диск, сохраняя запись в базе данных. Периодическая задача будет загружать кучу таких записей каждые 30 секунд и пытаться отправить их, используя оригинальные сервисы Mail Plugin.

Я проверил это, и, кажется, работает нормально. Мне нужно сделать очистку здесь и там, добавить область транзакций для отправки, сделать параметры настраиваемыми и так далее, но скелет уже является работоспособным кодом.

Надеюсь, это кому-нибудь поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...