返回

解决 Laravel 邮件中 Base64 图片显示难题

php

Laravel 邮件中 Base64 图片显示问题解析

在应用开发过程中,经常会遇到在邮件中嵌入图片的需求。当使用编辑器上传图片并以Base64编码格式保存时,有时会出现邮件中图片无法显示的问题。这是一个常见的难题,以下我们将详细探讨问题的原因,并提供多种解决方案。

问题原因

问题核心在于许多邮件客户端(尤其是网页版的,例如 Gmail)为了安全考虑,会对邮件中的 <img> 标签属性进行严格审查,这包含 src 属性中 data: 形式的 Base64 编码内容。客户端可能会直接删除该属性或者拒绝渲染图像。根本原因是防止潜在的安全漏洞和性能问题,毕竟大量或不恰当的Base64图片可能会使邮件变得非常大,也容易被恶意利用。

另一个问题点是部分邮件服务商会对较大的邮件进行过滤,过大的base64图片,加上原本的文本,可能导致邮件被服务商直接拒绝或者放入垃圾箱,即便显示了图片也会影响发送成功率。

解决方案

1. 使用外部链接

最直接且推荐的做法是放弃在 <img> 标签中直接使用Base64编码,将图片存储到服务器的可访问的地址上。这样一来,只需要在邮件中使用一个常规的<img>标签并设置src属性为该链接即可。

操作步骤:

  1. 图片存储: 将图片上传到服务器的公共目录,或者使用类似云存储服务(如 Amazon S3)。
  2. 获取链接: 取得图片在服务器或者云存储的公开 URL。
  3. 邮件内容修改: 修改HTML邮件模板中对应的<img>标签的 src 属性,将其指向第 2 步获取的图片 URL。

代码示例 (PHP/Laravel):

// 上传图片,这里用示例代码代替真实操作
$imagePath = public_path('images/my_uploaded_image.png');
//获取 URL (实际代码会更加复杂)
$imageUrl = url('images/my_uploaded_image.png');


$mailData['emailContent'] = str_replace(
    '<img src="data:image/png;base64,......" style="width:224px">',
     '<img src="'.$imageUrl.'" style="width:224px">',
    $templateData['email_body']
    );
// 或者更好的方法是用 DOMDocument
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML($mailData['emailContent']);
libxml_use_internal_errors(false);
$images = $doc->getElementsByTagName('img');
foreach ($images as $image) {
    if(str_contains($image->getAttribute('src'), 'data:image/')){
      //获取 base64 字符串 并解码
      $base64 = explode(',', $image->getAttribute('src'))[1];
       $decoded = base64_decode($base64);

      // 保存图片,假设这里会生成唯一文件名
        $filename = 'image-' .uniqid().'.png';
      $storagePath = public_path('images/' .$filename);
        file_put_contents($storagePath, $decoded);
         // 修改 src 属性为 URL
        $imageUrl = url('images/' .$filename);
        $image->setAttribute('src', $imageUrl);
        }
}
//保存内容回 mailContent
$mailData['emailContent'] = $doc->saveHTML();
\Event::fire(new SendMail($mailData));

2. 使用 Laravel Mailables 发送附件图片

如果不想使用外部链接,可以考虑将图片作为邮件附件发送,并在HTML邮件中通过 cid: (Content-ID) 方式引用。这个办法能保持邮件的自包含,不过对稍微复杂的邮件模板需要一定代码编写量。

操作步骤:

  1. 附件添加: 将 Base64 编码的图片转换为实际的文件 (例如 PNG 格式),并使用 Laravel 的 Mailables 添加到邮件附件。
  2. 获取Content-ID: 附件会被分配一个唯一 ID ,类似 content-id:xxxx,在html中使用 cid 引用。
  3. HTML 内容调整:<img> 标签的 src 属性中,使用 cid:xxx 的形式引用附件的Content-ID 。

代码示例(PHP/Laravel):

use Illuminate\Support\Facades\Mail;
use Illuminate\Mail\Mailable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;

class ImageMail extends Mailable
{
  use Queueable, SerializesModels;
  public $mailData;
  public $images;
    public function __construct($mailData, $images = []) {
        $this->mailData = $mailData;
        $this->images = $images;

    }
    public function build()
  {

      $mail = $this->from($this->mailData['fromEmail'], $this->mailData['fromName'])
                ->subject($this->mailData['emailSubject'])
               ->html($this->mailData['emailContent']);


        //add  inline images

        foreach ($this->images as $image ){
          $mail = $mail->attachData(base64_decode(explode(',', $image['base64'])[1]),  $image['filename'] ,  [ 'mime' =>  $image['mime'] ]);
        }
        return $mail;
  }
}

//发送邮件
$images = [];
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML($mailData['emailContent']);
libxml_use_internal_errors(false);
$htmlimages = $doc->getElementsByTagName('img');

//处理图片,附件
foreach ($htmlimages as $image) {
    if(str_contains($image->getAttribute('src'), 'data:image/')){
     $src = $image->getAttribute('src');
      $base64String = explode(';', $src)[1];
      $mime = explode(':', explode(';', $src)[0])[1];
        $ext = explode('/', $mime)[1];
         $base64 =  explode(',',  $base64String)[1];
      $filename  =  'inline_image_'. uniqid(). '.' .$ext ;
         $images[] = [
          'base64'=> $src,
             'filename'=>$filename,
          'mime'=>$mime,
        ];
          // 替换成cid 引用
           $cid= 'cid:'.  $filename;
      $image->setAttribute('src', $cid );


        }

}
//发送
$mailData['emailContent'] = $doc->saveHTML();

Mail::send(new ImageMail($mailData, $images ));

安全建议

使用外部链接,特别是公开的 URL,可能导致潜在安全问题。 务必对这些链接做好适当的保护。云存储服务提供私有链接功能,可以增加安全。
对图片附件也建议加密或采用其他的安全手段保护。

总结

解决邮件中Base64图片显示问题有多种方法。 综合考虑安全,维护便捷性,资源消耗等,使用外部链接或许是大多数场景中的首选。当然,使用 Laravel Mailable 的 cid 方式也可以在某些需要保持邮件独立的场景中运用。关键在于,了解每种方法的原理和优劣,才能选取最适合自己应用的方案。