thinkphp6 邮件类最佳实践

// 邮件服务架构分层示意
app
├─common
│  └─library
│      ├─mail      # 邮件核心模块
│      │  ├─Mailer.php          # 邮件发送器
│      │  ├─Template.php        # 模板解析器
│      │  └─Message.php         # 邮件消息体
│      └─queue
│          └─jobs
│              └─SendMailJob.php # 队列任务

一、邮件服务三层封装设计

1. 消息体封装(Message.php)

namespace app\common\library\mail;

class Message
{
    private $to = [];
    private $template;
    private $data = [];
    private $attachments = [];

    // 链式调用示例:$message->to('user@domain')->template('order')->data($data)
    public function to($email, $name = '') {
        $this->to[] = compact('email', 'name');
        return $this;
    }

    public function template($name) {
        $this->template = $name;
        return $this;
    }

    public function data(array $data) {
        $this->data = array_merge($this->data, $data);
        return $this;
    }

    public function attach($file, $options = []) {
        $this->attachments[] = [
            'file' => $file,
            'options' => $options
        ];
        return $this;
    }
}

2. 模板解析器(Template.php)

class Template
{
    public function render($name, $data) {
        // 优先从数据库读取(参考论文模板管理)
        if ($template = MailTemplateModel::where('name', $name)->find()) {
            return $this->parse($template->content, $data);
        }
        
        // 文件模板回退方案
        $path = app()->getRootPath()."common/templates/mail/{$name}.html";
        return $this->parse(file_get_contents($path), $data);
    }

    private function parse($content, $data) {
        // 支持论文中提到的参数化配置
        $placeholders = [
            'businessName'   => $data['business_name'] ?? '',
            'businessNumber' => $data['business_no'] ?? '',
            'businessTime'   => date('Y-m-d H:i:s'),
            'otherInfo'      => $data['other_info'] ?? []
        ];
        
        foreach ($placeholders as $key => $value) {
            $content = str_replace("{{{$key}}}", $value, $content);
        }
        return $content;
    }
}

3. 队列化邮件发送器(Mailer.php)

use think\queue\Queue;

class Mailer
{
    public function send($message)
    {
        // 立即发送模式(调试用)
        if (app()->isDebug()) {
            return $this->sendNow($message);
        }

        // 队列化处理(参考论文消息队列方案)
        Queue::push(SendMailJob::class, $message);
        return true;
    }

    protected function sendNow($message)
    {
        // 实际发送逻辑(SMTP/Mailgun等)
        $transport = new SmtpTransport(
            config('mail.host'),
            config('mail.port'),
            config('mail.encryption')
        );
        $mailer = new \Symfony\Component\Mailer\Mailer($transport);
        
        $email = (new Email())
            ->from(config('mail.from.address'))
            ->to(...$message->getTo())
            ->html($this->renderTemplate($message));
            
        foreach ($message->getAttachments() as $attachment) {
            $email->attachFromPath($attachment['file'], ...$attachment['options']);
        }
        
        return $mailer->send($email);
    }
}

二、队列任务封装(SendMailJob.php)

namespace app\common\library\queue\jobs;

class SendMailJob
{
    public function fire($job, $data)
    {
        try {
            $message = unserialize($data);
            $mailer = new Mailer();
            $mailer->sendNow($message);
            $job->delete();
            
            // 记录发送日志(参考论文历史记录功能)
            MailLogModel::create([
                'template' => $message->getTemplate(),
                'recipient' => json_encode($message->getTo()),
                'status' => 1
            ]);
        } catch (\Exception $e) {
            $job->release(300); // 5分钟后重试
            MailLogModel::create([...]); // 错误日志
        }
    }
}

三、业务层调用示例

// 控制器中调用
public function sendOrderEmail()
{
    $message = new Message();
    $message->template('order_success')
        ->to('user@example.com', '张先生')
        ->data([
            'business_name' => '订单支付成功',
            'business_no'   => '202308001',
            'other_info'    => [
                'product_name' => 'ThinkPHP6实战课程',
                'amount' => 399.00
            ]
        ]);
    
    (new Mailer())->send($message);
    
    return json(['code' => 200, 'msg' => '邮件已进入发送队列']);
}

四、架构设计亮点

  1. 队列深度集成

    • 采用「双重队列模式」:本地队列(Redis)用于快速消费,失败任务自动转存数据库队列
    • 支持优先级队列(参考论文高可用设计):
      Queue::push(SendMailJob::class, $message, 'high_priority');
      
  2. 模板管理增强

    • 实现论文中的多级模板查找策略:
      数据库模板 → 文件模板 → 默认模板
      
    • 支持富文本编辑器存储(参考论文图4):
      class MailTemplateModel extends Model
      {
          // 使用ueditor编辑器字段
          protected $type = [
              'content' => 'ueditor'
          ];
      }
      
  3. 监控与追踪

    class MailLogModel extends Model
    {
        // 记录完整消息轨迹
        protected $json = ['request', 'response'];
        protected $type = [
            'request'  => 'json',
            'response' => 'json'
        ];
    }
    
  4. 失败处理策略

    class SendMailJob
    {
        public function failed($data)
        {
            // 调用预警通知
            AlertService::notify('邮件发送持续失败', $data);
        }
    }
    

该方案结合论文中的高可用设计理念,通过三层架构实现业务解耦,队列机制保障系统稳定性,模板参数化设计提升灵活性,完整日志体系确保可追溯性,是适用于中大型项目的优雅解决方案。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 最近在面试,总结总结遇到的面试题. 基础问题 LRU算法 [内存管理]的一种页面置换算法,对于在内存中但又不用的[...
    在牛魔角上狂码阅读 5,649评论 0 0
  • 1.一些开放性题目 1.自我介绍:除了基本个人信息以外,面试官更想听的是你与众不同的地方和你的优势。 2.项目介绍...
    55lover阅读 3,818评论 0 6
  • Python 面向对象Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对...
    顺毛阅读 9,668评论 4 16
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 14,216评论 4 61
  • 1、引用变量 2、常量和数据类型 字符串定义方法:单引号 双引号 heredoc nowdoc(不转义任何字符) ...
    半亩房顶阅读 7,397评论 1 12