laravel框架 + php-ews(Exchange)库实现EWS邮件服务功能流程步骤
PHP Exchange Web 服务库 (PHP-ews) 旨在使用 Exchange Web 服务简化与 Microsoft Exchange 服务器的通信。
它处理使用 SOAP 服务所需的 NTLM 身份验证,并为形成请求所需的复杂类型提供面向对象的接口。
github:
https://github.com/jamesiarmes/php-ews
composer安装 - 必需开启soap扩展
composer require php-ews/php-ews "~1.0"
配置.env文件
# Laravel-exchange EWS邮件服务
# 邮件发送服务地址
EWS_HOST=smtp.office365.com
# 发送邮件邮箱
[email protected]
# 发送邮件密码
EWS_PASSWORD=xxxxx
# 回复邮件的地址
[email protected]
# 回复邮件者姓名
MAIL_FROM_NAME=xxx
创建EWS服务类
App\Handlers\EWS.php
<?php
namespace App\Handlers;
use Illuminate\Support\Facades\Storage;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseFolderIdsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfRequestAttachmentIdsType;
use jamesiarmes\PhpEws\Client;
use App\Exceptions\EWSException;
use jamesiarmes\PhpEws\Enumeration\DefaultShapeNamesType;
use jamesiarmes\PhpEws\Enumeration\IndexBasePointType;
use jamesiarmes\PhpEws\Enumeration\ItemQueryTraversalType;
use jamesiarmes\PhpEws\Request\FindItemType;
use jamesiarmes\PhpEws\Request\GetAttachmentType;
use jamesiarmes\PhpEws\Request\GetItemType;
use jamesiarmes\PhpEws\Type\BodyType;
use jamesiarmes\PhpEws\Type\IndexedPageViewType;
use jamesiarmes\PhpEws\Type\ItemIdType;
use jamesiarmes\PhpEws\Type\ItemResponseShapeType;
use jamesiarmes\PhpEws\Type\MessageType;
use jamesiarmes\PhpEws\Request\SendItemType;
use jamesiarmes\PhpEws\Type\EmailAddressType;
use jamesiarmes\PhpEws\Request\CreateItemType;
use jamesiarmes\PhpEws\Type\RequestAttachmentIdType;
use jamesiarmes\PhpEws\Type\TargetFolderIdType;
use jamesiarmes\PhpEws\Type\FileAttachmentType;
use jamesiarmes\PhpEws\Enumeration\BodyTypeType;
use jamesiarmes\PhpEws\Type\SingleRecipientType;
use jamesiarmes\PhpEws\Request\CreateAttachmentType;
use jamesiarmes\PhpEws\Enumeration\ResponseClassType;
use jamesiarmes\PhpEws\Type\DistinguishedFolderIdType;
use jamesiarmes\PhpEws\ArrayType\ArrayOfRecipientsType;
use jamesiarmes\PhpEws\Enumeration\MessageDispositionType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfAllItemsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfAttachmentsType;
use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseItemIdsType;
use jamesiarmes\PhpEws\Enumeration\DistinguishedFolderIdNameType;
class EWS
{
/**
* [EWS client]
*
* @var [Object]
*/
public $client;
/**
* [邮箱地址]
*
* @var [String]
*/
public $emails;
/**
* [邮件标题]
*
* @var [String]
*/
public $subject;
/**
* [邮件内容]
*
* @var [String]
*/
public $body;
/**
* [附件的绝对路径]
*
* @var [String]
*/
public $attachment;
/**
* [构造方法]
*
* @return [Object]
*/
public function __construct()
{
$host = env('EWS_HOST', null);
$username = env('EWS_USERNAME', null);
$password = env('EWS_PASSWORD', null);
$version = Client::VERSION_2016;
$this->client = new Client($host, $username, $password, $version);
}
/**
* 发送邮件
*
* @param [Array] $data [数据]
*
* @return [Json] [结果]
*/
public function send($data)
{
if(empty($data['emails'])){
throw new EWSException('The email is invalid.', 422);
}
if(!empty($data['attachments'])){
foreach ($data['attachments'] as $path) {
if(!file_exists($path)){
throw new EWSException("The file:{$path} is not exists.", 404);
}
}
}
//初始化
$this->emails = $data['emails'] ?? '';
$this->subject = $data['subject'] ?? '';
$this->body = $data['body'] ?? '';
$this->attachments = $data['attachments'] ?? '';
//创建所需要参数
$message = $this->createMessage();
$request = $this->createRequest($message);
//创建草稿并返回
$item = $this->createItem($request);
$send = $this->doSend($item);
return $send;
}
/**
* [创建草稿并且返回]
*
* @param [Request] $request [message]
*
* @return [Array] [返回创建信息]
*/
protected function createItem($request)
{
$response = $this->client->CreateItem($request);
//分析结果
$response_messages = $response->ResponseMessages->CreateItemResponseMessage;
$data = [];
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException($response_message->MessageText, $response_message->ResponseCode);
}
// Iterate over the created messages, printing the id for each.
foreach ($response_message->Items->Message as $item) {
$data['code'] = 0;
$data['item_id'] = $item->ItemId->Id;
$data['change_key'] = $item->ItemId->ChangeKey;
}
}
return $data;
}
/**
* [执行发送]
*
* @param [Array] $item [草稿]
*
* @return [Array] [结果]
*/
protected function doSend($item)
{
//判断是否有附件添加
if(!empty($this->attachments) && $item['code'] == 0){
$item = $this->addAttachment($item);
}
$message_id = $item['item_id'];
$change_key = $item['change_key'];
// Build the request.
$request = new SendItemType();
$request->SaveItemToFolder = true;
$request->ItemIds = new NonEmptyArrayOfBaseItemIdsType();
// Add the message to the request.
$item = new ItemIdType();
$item->Id = $message_id;
$item->ChangeKey = $change_key;
$request->ItemIds->ItemId[] = $item;
// Configure the folder to save the sent message to.
$send_folder = new TargetFolderIdType();
$send_folder->DistinguishedFolderId = new DistinguishedFolderIdType();
$send_folder->DistinguishedFolderId->Id = DistinguishedFolderIdNameType::SENT;
$request->SavedItemFolderId = $send_folder;
$response = $this->client->SendItem($request);
// Iterate over the results, printing any error messages.
$response_messages = $response->ResponseMessages->SendItemResponseMessage;
$data = [];
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException($response_message->MessageText, $response_message->ResponseCode);
}
$data['code'] = 0;
$data['message'] = 'Message sent successfully.';
}
return $data;
}
/**
* [添加附件]
*
* @param [Item] $item [草稿]
*
* @return [Array] [附件结果]
*/
protected function addAttachment($item)
{
// Build the request,
$request = new CreateAttachmentType();
$request->ParentItemId = new ItemIdType();
$request->ParentItemId->Id = $item['item_id'];
$request->Attachments = new NonEmptyArrayOfAttachmentsType();
// Build the file attachment.
foreach ($this->attachments as $path) {
// Open file handlers.
$file = new \SplFileObject($path);
$finfo = finfo_open();
$attachment = new FileAttachmentType();
$attachment->Content = $file->openFile()->fread($file->getSize());
$attachment->Name = $file->getBasename();
$attachment->ContentType = finfo_file($finfo, $path);
$request->Attachments->FileAttachment[] = $attachment;
}
$response = $this->client->CreateAttachment($request);
$response_messages = $response->ResponseMessages->CreateAttachmentResponseMessage;
$item = [];
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException($response_message->MessageText, $response_message->ResponseCode);
}
// Iterate over the created attachments, printing the id of each.
foreach ($response_message->Attachments->FileAttachment as $attachment) {
$item['code'] = 0;
$item['item_id'] = $attachment->AttachmentId->RootItemId;
$item['change_key'] = $attachment->AttachmentId->RootItemChangeKey;
$item['attachment_id'] = $attachment->AttachmentId->Id;
}
}
return $item;
}
/**
* 创建草稿箱
*
* @param [Message] $message [要发送的文本信息]
*
* @return [request] []
*/
protected function createRequest($message)
{
// Build the request,
$request = new CreateItemType();
$request->Items = new NonEmptyArrayOfAllItemsType();
// Add the message to the request.
$request->Items->Message[] = $message;
// Save the message, but do not send it.
$request->MessageDisposition = MessageDispositionType::SAVE_ONLY;
return $request;
}
/**
* 创建 发件人 标题 内容
*
* @param [Client] $client [EWS客户端]
* @param [EmailAddress] $email [邮箱地址]
* @param [Subject] $subject [标题]
* @param [Body] $body [内容]
*
* @return [Message] [message]
*/
protected function createMessage()
{
// Create the message.
$message = new MessageType();
$message->Subject = $this->subject;
$message->ToRecipients = new ArrayOfRecipientsType();
// Set the sender.
$message->From = new SingleRecipientType();
$message->From->Mailbox = new EmailAddressType();
$message->From->Mailbox->EmailAddress = env('EWS_USERNAME', null);
foreach ($this->emails as $key => $email) {
// Set the recipient.
$to = $email['to'] ?? '';
$name = $email['name'] ?? $to;
$recipient = new EmailAddressType();
$recipient->Name = $name;
$recipient->EmailAddress = $to;
$message->ToRecipients->Mailbox[] = $recipient;
}
// Set the message body.
$message->Body = new BodyType();
$message->Body->BodyType = BodyTypeType::HTML;
$message->Body->_ = $this->body;
return $message;
}
/**
* 获取邮件ID
* @param int $page_size
* @return array
* @throws EWSException
*/
public function getEmailIds(int $page_size = 10): array
{
// Build the request.
$request = new FindItemType();
$request->ParentFolderIds = new NonEmptyArrayOfBaseFolderIdsType();
$request->Traversal = ItemQueryTraversalType::SHALLOW;
// Return all message properties.
$request->ItemShape = new ItemResponseShapeType();
$request->ItemShape->BaseShape = DefaultShapeNamesType::ALL_PROPERTIES;
// Search in the user's inbox.
$folder_id = new DistinguishedFolderIdType();
$folder_id->Id = DistinguishedFolderIdNameType::INBOX;
$request->ParentFolderIds->DistinguishedFolderId[] = $folder_id;
// Limits the number of items retrieved
$request->IndexedPageItemView = new IndexedPageViewType();
$request->IndexedPageItemView->BasePoint = IndexBasePointType::BEGINNING;
$request->IndexedPageItemView->Offset = 0;
$request->IndexedPageItemView->MaxEntriesReturned = $page_size;
$response = $this->client->FindItem($request);
$email_ids = [];
// Iterate over the results, printing any error messages or message subjects.
$response_messages = $response->ResponseMessages->FindItemResponseMessage;
foreach ($response_messages as $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException("Failed to search for messages with :".$response_message->MessageText, $response_message->ResponseCode);
}
foreach ($response_message->RootFolder->Items->Message as $message) {
$email_ids[] = $message->ItemId->Id;
}
}
return $email_ids;
}
/**
* 根据邮件ID 获取文本内容和下载附件
* @param array $email_ids
* @return array
* @throws EWSException
*/
public function getBodyContent(array $email_ids): array
{
// Build the request.
$request = new GetItemType();
$request->ItemShape = new ItemResponseShapeType();
$request->ItemShape->BaseShape = DefaultShapeNamesType::ALL_PROPERTIES;
$request->ItemIds = new NonEmptyArrayOfBaseItemIdsType();
// Iterate over the message ids, setting each one on the request.
foreach ($email_ids as $email_id) {
$item = new ItemIdType();
$item->Id = $email_id;
$request->ItemIds->ItemId[] = $item;
}
$response = $this->client->GetItem($request);
$bodyContent = [];
// Iterate over the results, printing any error messages or message subjects.
$response_messages = $response->ResponseMessages->GetItemResponseMessage;
foreach ($response_messages as $key => $response_message) {
// Make sure the request succeeded.
if ($response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException("Failed to get message with :".$response_message->MessageText, $response_message->ResponseCode);
}
$attachments = array();
// Iterate over the messages, printing the subject for each.
foreach ($response_message->Items->Message as $item) {
$body = str_replace(array("\r\n", "\r", "\n"), "", strip_tags($item->Body->_));
preg_match("/:(\d+)]/", $body, $passwordMatches);
$bodyContent[$key] = [
'message_id' => $item->InternetMessageId,
'subject' => $item->Subject,
'email_id' => $item->ItemId->Id,
'body' => $body,
'password' => last($passwordMatches),
];
// If there are no attachments for the item, move on to the next
// message.
if (empty($item->Attachments)) {
continue;
}
// Iterate over the attachments for the message.
foreach ($item->Attachments->FileAttachment as $attachment) {
$attachments[] = $attachment->AttachmentId->Id;
}
// Build the request to get the attachments.
$request = new GetAttachmentType();
$request->AttachmentIds = new NonEmptyArrayOfRequestAttachmentIdsType();
// Iterate over the attachments for the message.
foreach ($attachments as $attachment_id) {
$id = new RequestAttachmentIdType();
$id->Id = $attachment_id;
$request->AttachmentIds->AttachmentId[] = $id;
}
$response = $this->client->GetAttachment($request);
// Iterate over the response messages, printing any error messages or
// saving the attachments.
$attachment_response_messages = $response->ResponseMessages->GetAttachmentResponseMessage;
foreach ($attachment_response_messages as $attachment_response_message) {
// Make sure the request succeeded.
if ($attachment_response_message->ResponseClass != ResponseClassType::SUCCESS) {
throw new EWSException("Failed to get attachment with :".$response_message->MessageText, $response_message->ResponseCode);
}
// Iterate over the file attachments, saving each one.
$attachments = $attachment_response_message->Attachments->FileAttachment;
foreach ($attachments as $attachment) {
$bodyContent[$key]['attachments'] = [
'AttachmentId' => $attachment->AttachmentId->Id,
'AttachmentName' => $attachment->Name,
];
Storage::disk('public')->put($attachment->Name, $attachment->Content);
}
}
}
}
return $bodyContent;
}
}
获取邮件内容及附件:
$exchange = new EWS();
try {
$emailIds = $exchange->getEmailIds(5);
} catch (EWSException $e) {
throw new EWSException("获取邮件ID失败 :".$e->getMessage());
}
try {
$bodyContents = $exchange->getBodyContent($emailIds);
} catch (EWSException $e) {
throw new EWSException("获取文本内容和下载附件失败 :".$e->getMessage());
}
获取附件解压后的内容 (有密码):
public function getZipBodyContent($bodyContents): array
{
foreach ($bodyContents as $bodyContent) {
// 判断主题是否包含 Tencentflex 或 文本是否有密码
if (!Str::contains($bodyContent['subject'], 'Tencentflex') || !$bodyContent['password']) {
continue;
}
$zip = new ZipArchive();
// 打开压缩包
if (!$zip->open(config('filesystems.disks.public.root').'/'.$bodyContent['attachments']['AttachmentName'])) {
logger()->error("打开文件失败。文件名: ".$bodyContent['attachments']['AttachmentName']);
return [];
}
// 解压密码
$zip->setPassword($bodyContent['password']);
// 设置解压到新的目录
if (!$zip->extractTo(config('filesystems.disks.public.root'))) {
logger()->error("密码错误, 解压失败。文件名: ".$bodyContent['attachments']['AttachmentName']);
return [];
}
$rows = [];
for ($i = 0; $i < $zip->numFiles; $i++) {
$filename = $zip->getNameIndex($i);
$rows += Excel::toArray(new VolunteerServiceInfosImport(), config('filesystems.disks.public.root').'/'.$filename)[0];
Storage::disk('public')->delete($filename); // 删除ZIP附件解压后的文件
Storage::disk('public')->delete($bodyContent['attachments']['AttachmentName']); // 删除ZIP附件
}
$zip->close();
logger()->info("[下载附件成功, No: ".$bodyContent['email_id'].'][文件内容]['.json_encode($rows, JSON_UNESCAPED_UNICODE)."]");
return [
'rows' => $rows,
'subject' => $bodyContent['subject'], // 邮件主题
'zipFileName' => $bodyContent['attachments']['AttachmentName'], // 附件压缩包
'message_id' => $bodyContent['message_id'], // 邮件ID
];
}
return [];
}
发送邮件:
// 要发送给的人的姓名和邮箱地址
$emails = [
[
'to' => env("MAIL_TO_ADDRESS"),
'name' => env("MAIL_FROM_NAME")
],
];
// 附件路径
$attachments = [
storage_path('1.pdf'),
storage_path('2.pdf')
];
// 发送
$exchange = new EWS();
$send = $exchange->send([
'emails' => $emails,
'subject' => 'Laravel-exchange EWS服务库',
'body' => response(view('emails.pdf', ['name' => '邮箱测试']))->getContent(), //这里可以使用blade模板自定义你的模板样式
'attachments' => $attachments
]);
return response()->json($send);
自定义异常
App\Exceptions\EWSException.php
<?php
namespace App\Exceptions;
use Exception;
class EWSException extends Exception
{
}
自定义异常处理:
App\Exceptions\Handler.php
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception $exception
* @return void
*/
public function report(Exception $exception)
{
if ($exception instanceof EWSException) {
//如果是邮件发送异常,这里做一些记录
}
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if($exception instanceof EWSException) {
$result = [
"code" => $exception->getCode(),
"message" => $exception->getMessage()
];
return response()->json($result, $exception->getCode());
}
return parent::render($request, $exception);
}
}
相关文章