laravel10中使用队列发送邮件,每一封邮件都需要从数据库读取单独的邮件服务配置,目前只能通过发送前设置config解决,寻求更优的解决方案

1. 运行环境

1). 当前使用的 Laravel 版本?

laravel 10

2). 当前使用的 php/php-fpm 版本?

php8.1

3). 当前系统

debian12

4). 业务环境

开发环境

5). 相关软件版本

nginx redis mysql docker

2. 问题描述?

laravel10中使用队列发送邮件,每一封邮件都需要从数据库读取单独的邮件服务配置,目前只能通过发送前设置config解决,寻求更优的解决方案。在laravel6版本解决过相同问题,重写了Illuminate\Mail\Mailable类解决了此问题。laravel10中同样的思路无法实现,寻求指点

<?php

namespace App\Mail;

use Illuminate\Container\Container;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\Mailable;
use Swift_Mailer;
use Swift_SmtpTransport;
use Exception;

class ConfigurableMailable extends Mailable
{
    public function send(Mailer $mailer)
    {
        try {
            // 配置邮件服务器
            $smtp_tls = $this->config['smtp_tls'] == 'none' ? null : $this->config['smtp_tls'];
            $transport = new Swift_SmtpTransport($this->config['smtp_server'], $this->config['smtp_port'], $smtp_tls);

            //设置认证方式
            $transport->setAuthMode($this->config['authentication']);
            if ($this->config['authentication'] == 'login') {
                if ($this->config['smtp_username']) {
                    $transport->setUsername($this->config['smtp_username']);
                    $transport->setPassword($this->config['smtp_password']);
                }
            }
            // 注意看,这里是修改为用户邮箱的配置,$this->config是用户传的数据。

            if ($this->config['smtp_tls'] == "tls") {
                $https['ssl']['verify_peer'] = FALSE;
                $https['ssl']['verify_peer_name'] = FALSE;
                $transport->setStreamOptions($https);
            }
            $mailer->setSwiftMailer(new Swift_Mailer($transport));
            // 重启Swift_SmtpTransport中ssl的连接要不然会报错
            $mailer->getSwiftMailer()->getTransport()->stop();

            Container::getInstance()->call([$this, 'build']);
            $mailer->send($this->buildView(), $this->buildViewData(), function ($message) {
                $this->buildFrom($message)
                    ->buildRecipients($message)
                    ->buildSubject($message)
                    ->buildAttachments($message)
                    ->runCallbacks($message);
            });
        } catch (\Swift_TransportException $swift_TransportException) {
//            $content = mb_convert_encoding( $swift_TransportException->getMessage(), 'UTF-8', 'UTF-8,GBK,GB2312,BIG5' );
            $content = iconv("gb2312", "utf-8//IGNORE", $swift_TransportException->getMessage());
            throw new Exception($content);
        }


    }
}

3. 您期望得到的结果?

4. 您实际得到的结果?

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

问题已解决,重写mailable类并重写send方法,具体代码如下:

    public function send($mailer)
    {
        $config['host'] = $this->config['smtp_server'];
        $config['username'] = $this->config['smtp_username'];
        $config['password'] = $this->config['smtp_password'];
        $config['port'] = $this->config['smtp_port'];
        $config['scheme'] = "smtp";
        $config['encryption'] = $this->config['smtp_tls'] == 'none' ? "" : $this->config['smtp_tls'];
        $config['verify_peer'] = false;
        $config['verify_peer_name'] = false;

        $factory = new EsmtpTransportFactory;

        $scheme = $config['scheme'] ?? null;

        if (!$scheme) {
            $scheme = !empty($config['encryption']) && $config['encryption'] === 'tls'
                ? (($config['port'] == 465) ? 'smtps' : 'smtp')
                : '';
        }

        $transport = $factory->create(new Dsn(
            $scheme,
            $config['host'],
            $config['username'] ?? null,
            $config['password'] ?? null,
            $config['port'] ?? null,
            $config
        ));

        $mailer->setSymfonyTransport($transport);
        return $this->withLocale($this->locale, function () use ($mailer) {
            $this->prepareMailableForDelivery();

            $mailer = $mailer instanceof MailFactory
                ? $mailer->mailer($this->mailer)
                : $mailer;

            return $mailer->send($this->buildView(), $this->buildViewData(), function ($message) {
                $this->buildFrom($message)
                    ->buildRecipients($message)
                    ->buildSubject($message)
                    ->buildTags($message)
                    ->buildMetadata($message)
                    ->runCallbacks($message)
                    ->buildAttachments($message);
            });
        });
    }
1年前 评论
讨论数量: 8

去看了一下10的文档,跟之前的方式差别很多,我的猜测是,之所以每次都需要重新获取配置是因为这个:
laravel10中文文档-邮件-备用配置

有时,已经配置好用于发送应用程序邮件的外部服务可能已关闭。在这种情况下,定义一个或多个备份邮件传递配置非常有用,这些配置将在主传递驱动程序关闭时使用。
为此,应该在应用程序的 mail 配置文件中定义一个使用 failover 传输的邮件程序。应用程序的 failover 邮件程序的配置数组应包含一个 mailers 数组,该数组引用选择邮件驱动程序进行传递的顺序:


'mailers' => [
    'failover' => [
        'transport' => 'failover',
        'mailers' => [
            'postmark',
            'mailgun',
            'sendmail',
        ],
    ],

    // ...
],

可能是为了确保所有的驱动的配置能用性提高吧。

1年前 评论
huxiuhang (楼主) 1年前

问题已解决,重写mailable类并重写send方法,具体代码如下:

    public function send($mailer)
    {
        $config['host'] = $this->config['smtp_server'];
        $config['username'] = $this->config['smtp_username'];
        $config['password'] = $this->config['smtp_password'];
        $config['port'] = $this->config['smtp_port'];
        $config['scheme'] = "smtp";
        $config['encryption'] = $this->config['smtp_tls'] == 'none' ? "" : $this->config['smtp_tls'];
        $config['verify_peer'] = false;
        $config['verify_peer_name'] = false;

        $factory = new EsmtpTransportFactory;

        $scheme = $config['scheme'] ?? null;

        if (!$scheme) {
            $scheme = !empty($config['encryption']) && $config['encryption'] === 'tls'
                ? (($config['port'] == 465) ? 'smtps' : 'smtp')
                : '';
        }

        $transport = $factory->create(new Dsn(
            $scheme,
            $config['host'],
            $config['username'] ?? null,
            $config['password'] ?? null,
            $config['port'] ?? null,
            $config
        ));

        $mailer->setSymfonyTransport($transport);
        return $this->withLocale($this->locale, function () use ($mailer) {
            $this->prepareMailableForDelivery();

            $mailer = $mailer instanceof MailFactory
                ? $mailer->mailer($this->mailer)
                : $mailer;

            return $mailer->send($this->buildView(), $this->buildViewData(), function ($message) {
                $this->buildFrom($message)
                    ->buildRecipients($message)
                    ->buildSubject($message)
                    ->buildTags($message)
                    ->buildMetadata($message)
                    ->runCallbacks($message)
                    ->buildAttachments($message);
            });
        });
    }
1年前 评论

我们自用的配置 数据库取出配置然后发送邮件

public function __construct(string $email,string $nickname, string $smtp, int $port, string $username, string $password, bool $is_ssl)
    {
        $this->email = $email;
        $this->nickname = $nickname;
        $this->smtp = $smtp;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
        $this->is_ssl = $is_ssl;
        $this->initTransport();

    }

    private function initTransport()
    {
        $transport = $transport = new Swift_SmtpTransport($this->smtp, $this->port, $this->is_ssl ? 'ssl' : '');
        $transport->setUsername($this->username);
        $transport->setPassword($this->password);
        $this->transport = $transport;
        return $this;
    }

    /**
     * @param       $to
     * @param       $subject
     * @param       $content
     * @param array $cc
     * @param array $attach
     */
    public function send($to, $subject, $content, $attach = [], $cc = [])
    {
        // 备份初始设置
        $backup = \Mail::getSwiftMailer();
        $mailer = new \Swift_Mailer($this->transport);
        \Mail::setSwiftMailer($mailer);
        \Mail::send('mail', ['html' => $content], function (Message $message) use ($to, $subject, $attach, $cc) {
            $message->from($this->email,$this->nickname);
            $message->subject($subject);
            $message->cc($cc);
            $message->to($to);
            if (isset($attach['file'])) {
                $message->attach($attach['file'], $attach['options'] ? $attach['options'] : []);
            }
        });
        // 还原初始配置
        \Mail::setSwiftMailer($backup);
    }
1年前 评论
huxiuhang (楼主) 1年前
yekern (作者) 1年前
huxiuhang (楼主) 1年前

可以把这部分代码替换掉就支持队列了

 \Mail::send('mail', ['html' => $content], function (Message $message) use ($to, $subject, $attach, $cc) {
            $message->from($this->email,$this->nickname);
            $message->subject($subject);
            $message->cc($cc);
            $message->to($to);
            if (isset($attach['file'])) {
                $message->attach($attach['file'], $attach['options'] ? $attach['options'] : []);
            }
        });

替换成

Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->queue(new OrderShipped($order));

正常使用Mail就行了

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!