配置Gmail API以替换PHP IMAP扩展名并使用OAuth2协议

从2021年2月15日起,对Gmail和其他产品的授权将仅通过OAuth进行,因此我一度感到很幸运,我读了文章“ Google埋藏了PHP IMAP扩展名”,然后悲伤地开始对替换PHP IMAP扩展名采取行动您在Google API上的项目。问题多于答案,所以我同时草拟了手册。



我将PHP IMAP用于以下任务:



  1. 从邮箱中删除旧信件不幸的是,在公司G Suite帐户的控制面板中,您只能配置在收到N天后从组织的所有邮箱中删除邮件的期限。但是,我只需要删除指定邮箱中以及收到后指定的不同天数后的字母。
  2. 过滤,解析和标记字母许多信件是从我们的网站自动发送的,其中一些没有到达收件人,据此报告。有必要收集这些报告,进行分解,通过电子邮件找到客户,并为经理形成易于阅读的信件,以便经理可以联系客户并阐明电子邮件地址的相关性。


我们将在本文中使用Gmail API解决这两个任务(同时在邮箱设置中禁用对不安全应用程序的访问,这使PHP IMAP能够正常工作,并且实际上将在2021年2月的糟糕一天停止工作)。我们将使用Gmail应用程序的所谓服务帐户,通过适当的配置,该帐户可以连接到组织的所有邮箱并在其中执行任何操作。



1.在Google API开发者控制台中创建一个项目



在该项目的帮助下,我们将与Gmail进行API交互,并在其中创建相同的服务帐户。



要创建一个项目:



  1. 转到Google API开发者控制台并以G Suite管理员身份登录(好吧,或者您的用户是谁,拥有所有权利)
  2. 我们正在寻找“创建项目”按钮。



    我在这里找到:
    image



    然后在这里:
    image



    填写项目名称并保存:



    项目创建
    image



  3. 转到项目,然后单击“启用API和服务”按钮:



    启用API和服务
    image



    选择Gmail API



2.创建和配置服务帐户



为此,您可以使用官方手册或继续阅读:



  1. 转到我们添加的Gmail API,单击“创建凭据”按钮,然后选择“服务帐户”:



    服务帐号创建
    image



    填写内容,然后单击“创建”:



    服务帐户详细信息
    image



    其他所有内容都可以保留为空白:



    服务帐户的访问权限
    image



    image



  2. , . G Suite, « — API».



    API
    image

    image



  3. « »:



    image



    «», « » , « OAuth» — :



    - https://mail.google.com/ -

    - https://www.googleapis.com/auth/gmail.modify -

    - https://www.googleapis.com/auth/gmail.readonly -

    - https://www.googleapis.com/auth/gmail.metadata -




    image

    image



  4. « G Suite»:



    image



    并在下面的字段中填写您的产品名称。

  5. 现在,您需要创建一个服务帐户密钥:这是一个应在应用程序中可用的文件。实际上,他将用于授权。



    为此,请从项目的“凭据”页面中,单击“管理服务帐户”链接:



    证书
    image



    并选择“操作-创建密钥”,输入:JSON:



    服务帐号管理
    image



    之后,将生成一个密钥文件并将其下载到您的计算机,该密钥文件必须放在您的项目中,并在您调用Gmail API时具有访问权限。



这样就完成了Gmail API的配置,然后会有一些我的可可代码,实际上,实现了到目前为止由IMAP PHP扩展解决的功能。



3.编写代码



我使用了相当不错Gmail API官方文档(单击单击)。但是,由于我开始编写详细的手册,因此我将附上自己的可可代码。



因此,首先,我们使用composer安装了Google客户端库(apiclient):(



composer require google/apiclient



首先,作为一名真正的文学专家,我安装了api客户端的2.0版,如PHP Quickstart所示,但是在开始时,各种折衷和警告都出现在PHP 7.4上,因此我不建议您这样做。)



然后,基于官方文档中的示例我们编写了自己的用于Gmail的类,不要忘记指定服务帐户密钥文件:



使用Gmail的课程
<?php
//     Gmail
class GmailAPI
{
    private $credentials_file = __DIR__ . '/../Gmail/credentials.json'; //   

    // ---------------------------------------------------------------------------------------------
    /**
     *   Google_Service_Gmail Authorized Gmail API instance
     *
     * @param  string $strEmail  
     * @return Google_Service_Gmail Authorized Gmail API instance
     * @throws Exception
     */
    function getService(string $strEmail){
        //    
        try{
            $client = new Google_Client();
            $client->setAuthConfig($this->credentials_file);
            $client->setApplicationName('My Super Project');
            $client->setScopes(Google_Service_Gmail::MAIL_GOOGLE_COM);
            $client->setSubject($strEmail);
            $service = new Google_Service_Gmail($client);
        }catch (Exception $e) {
            throw new \Exception('   getService: '.$e->getMessage());
        }
        return $service;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *    ID    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrOptionalParams      
     *         Gmail  after: 2020/08/20 in:inbox label:
     *      q  $opt_param
     * @return array  ID     array('arrErrors' => $arrErrors),   
     * @throws Exception
     */
    function listMessageIDs(Google_Service_Gmail $service, string $strEmail, array $arrOptionalParams = array()) {
        $arrIDs = array(); //  ID 

        $pageToken = NULL; //     
        $messages = array(); //    

        //  
        $opt_param = array();
        //    ,       Gmail      q
        if (count($arrOptionalParams)) $opt_param['q'] = str_replace('=', ':', http_build_query($arrOptionalParams, null, ' '));

        //   ,   ,     
        do {
            try {
                if ($pageToken) {
                    $opt_param['pageToken'] = $pageToken;
                }
                $messagesResponse = $service->users_messages->listUsersMessages($strEmail, $opt_param);
                if ($messagesResponse->getMessages()) {
                    $messages = array_merge($messages, $messagesResponse->getMessages());
                    $pageToken = $messagesResponse->getNextPageToken();
                }
            } catch (Exception $e) {
                throw new \Exception('   listMessageIDs: '.$e->getMessage());
            }
        } while ($pageToken);

        //   ID  
        if (count($messages)) {
            foreach ($messages as $message) {
                $arrIDs[] = $message->getId();
            }
        }
        return $arrIDs;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *      ID  batchDelete
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrIDs  ID      listMessageIDs
     * @throws Exception
     */
    function deleteMessages(Google_Service_Gmail $service, string $strEmail, array $arrIDs){
        //      1000 ,      batchDelete
        $arrParts = array_chunk($arrIDs, 999);
        if (count($arrParts)){
            foreach ($arrParts as $arrPartIDs){
                try{
                    //     
                    $objBatchDeleteMessages = new Google_Service_Gmail_BatchDeleteMessagesRequest();
                    //   
                    $objBatchDeleteMessages->setIds($arrPartIDs);
                    //  
                    $service->users_messages->batchDelete($strEmail,$objBatchDeleteMessages);
                }catch (Exception $e) {
                    throw new \Exception('   deleteMessages: '.$e->getMessage());
                }
            }
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     get
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  string $strFormat The format to return the message in.
     * Acceptable values are:
     * "full": Returns the full email message data with body content parsed in the payload field; the raw field is not used. (default)
     * "metadata": Returns only email message ID, labels, and email headers.
     * "minimal": Returns only email message ID and labels; does not return the email headers, body, or payload.
     * "raw": Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.
     * @param  array $arrMetadataHeaders When given and format is METADATA, only include headers specified.
     * @return  object Message
     * @throws Exception
     */
    function getMessage(Google_Service_Gmail $service, string $strEmail, string $strMessageID, string $strFormat = 'full', array $arrMetadataHeaders = array()){
        $arrOptionalParams = array(
            'format' => $strFormat // ,    
        );
        //   - metadata,     
        if (($strFormat == 'metadata') and count($arrMetadataHeaders))
            $arrOptionalParams['metadataHeaders'] = implode(',',$arrMetadataHeaders);

        try{
            $objMessage = $service->users_messages->get($strEmail, $strMessageID,$arrOptionalParams);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   getMessage: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *   ,    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @return  object $objLabels -  -  
     * @throws Exception
     */
    function listLabels(Google_Service_Gmail $service, string $strEmail){
        try{
            $objLabels = $service->users_labels->listUsersLabels($strEmail);
            return $objLabels;
        }catch (Exception $e) {
            throw new \Exception('   listLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     ()  
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  array $arrAddLabelIds  ID ,     
     * @param  array $arrRemoveLabelIds  ID ,     
     * @return  object Message -  
     * @throws Exception
     */
    function modifyLabels(Google_Service_Gmail $service, string $strEmail, string $strMessageID, array $arrAddLabelIds = array(), array $arrRemoveLabelIds = array()){
        try{
            $objPostBody = new Google_Service_Gmail_ModifyMessageRequest();
            $objPostBody->setAddLabelIds($arrAddLabelIds);
            $objPostBody->setRemoveLabelIds($arrRemoveLabelIds);
            $objMessage = $service->users_messages->modify($strEmail,$strMessageID,$objPostBody);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   modifyLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

}




每当我们与Gmail进行交互时,我们要做的第一件事就是调用GmailAPI类的getService($ strEmail)函数,该函数返回用于处理$ strEmail邮箱的“授权”对象。此外,该对象已经传递给任何其他函数以直接执行我们需要的操作。GmailAPI类中的所有其他功能已经执行了特定任务:



  • listMessageIDs-根据指定的条件查找邮件并返回其ID(传递给listUsersMessages Gmail API函数的搜索字符串必须类似于邮箱Web界面中的搜索字符串),
  • deleteMessages-删除具有传递给它的ID的消息(batchDelete API Gmail函数一次删除最多不超过1000条消息,因此我不得不将传递给该函数的ID数组拆分为多个每个999个字母的数组,并多次执行删除操作),
  • getMessage-获取有关传递了ID的消息的所有信息,
  • listLabels-返回邮箱中的标志列表(我用它来获取最初在邮箱Web界面中创建并分配给所需消息的标志的ID)
  • ModifyLabels-在消息中添加或删除标志


接下来,我们的任务是删除各个邮箱中的旧字母。同时,我们认为每个邮箱在几天前收到的旧信件。为了完成此任务,我们编写了以下脚本,该脚本每天由cron运行:



删除旧电子邮件
<?php
/**
 *      Gmail
 *      
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

//       
$arrMailBoxesForClean = array(
    'a@domain.com' => 30,
    'b@domain.com' => 30,
    'c@domain.com' => 7,
    'd@domain.com' => 7,
    'e@domain.com' => 7,
    'f@domain.com' => 1
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail

//     ,      
foreach ($arrMailBoxesForClean as $strEmail => $intDays) {
    try{
        //    
        $service = $objGmailAPI->getService($strEmail);
        //       
        $arrParams = array('before' => date('Y/m/d', (time() - 60 * 60 * 24 * $intDays)));
        //   ,   
        $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail,$arrParams);
        //     ID   $arrIDs
        if (count($arrIDs)) $objGmailAPI->deleteMessages($service,$strEmail,$arrIDs);
        //    
        unset($service,$arrIDs);
    }catch (Exception $e) {
        $arrErrors[] = $e->getMessage();
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '       ';
    $strMessage = '         :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




该脚本连接到每个指定的邮箱,选择旧字母并将其删除。



通过以下脚本解决了为经理基于自动报告生成有关未送达电子邮件的报告的任务:



过滤和标记电子邮件
<?php
/*
 *    a@domain.com
 *      ,     : : mailer-daemon@googlemail.com
 *      .        ,   b@domain.com
 *   
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

$strEmail = 'a@domain.com';
$strLabelID = 'Label_2399611988534712153'; //  reportProcessed -    

//  
$arrParams = array(
    'from' => 'mailer-daemon@googlemail.com', //       
    'in' => 'inbox', //  
    'after' => date('Y/m/d', (time() - 60 * 60 * 24)), //   
    'has' => 'nouserlabels' //  
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail
$arrClientEmails = array(); //    ,      

try{
    //    
    $service = $objGmailAPI->getService($strEmail);
    //         ,    
    $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail, $arrParams);
    //      'X-Failed-Recipients',    ,      
    if (count($arrIDs)){
        foreach ($arrIDs as $strMessageID){
            //   
            $objMessage = $objGmailAPI->getMessage($service,$strEmail,$strMessageID,'metadata',array('X-Failed-Recipients'));
            //  
            $arrHeaders = $objMessage->getPayload()->getHeaders();
            //  
            foreach ($arrHeaders as $objMessagePartHeader){
                if ($objMessagePartHeader->getName() == 'X-Failed-Recipients'){
                    $strClientEmail = mb_strtolower(trim($objMessagePartHeader->getValue()), 'UTF-8');
                    if (!empty($strClientEmail)) {
                        if (!in_array($strClientEmail, $arrClientEmails)) $arrClientEmails[] = $strClientEmail;
                    }
                    //    reportProcessed,       
                    $objGmailAPI->modifyLabels($service,$strEmail,$strMessageID,array($strLabelID));
                }
            }
        }
    }
    unset($service,$arrIDs,$strMessageID);
}catch (Exception $e) {
    $arrErrors[] = $e->getMessage();
}

//     ,      ,    
if (count($arrClientEmails)) {
    $objClients = new clients();
    //   email  
    $arrAllClientsEmails = $objClients->getAllEmails();

    foreach ($arrClientEmails as $strClientEmail){
        $arrUsages = array();
        foreach ($arrAllClientsEmails as $arrRow){
            if (strpos($arrRow['email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['email2'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['site_user_settings_contact_email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
        }
        $intUsagesCnt = count($arrUsages);
        if ($intUsagesCnt > 0){
            $strMessage = '          <span style="color: #000099;">'.$strClientEmail.'</span><br/>
                  ';
            if ($intUsagesCnt == 1){
                $strMessage .= ' '.$arrUsages[0].'<br/>';
            }else{
                $strMessage .= ':<ul>';
                foreach ($arrUsages as $strUsage){
                    $strMessage .= '<li>'.$strUsage.'</li>';
                }
                $strMessage .= '</ul>';
            }
            $strMessage .= '<br/>,        .<br/><br/>
                    ,    ';
            if (empty($objMailSender)) $objMailSender = new mailSender();
            $objMailSender->sendMail('b@domain.com',' email ',$strMessage);
        }
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '      ';
    $strMessage = '        :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    if (empty($objMailSender)) $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




该脚本与第一个脚本一样,连接到指定的邮箱,从中选择所需的字母(未送达邮件的报告),不带标记,在字母中找到尝试发送该字母的电子邮件地址,并用“已处理”标记标记该字母... 然后,对找到的电子邮件地址进行操作,结果形成了对负责员工的可读信。



来源可在GitHub找到



这就是我在这篇文章中想要说的。谢谢阅读!如果我的代码ung在您的眼中,请卷起扰流板或发表您的评论-我将为建设性的批评感到高兴。



All Articles