<?php namespace Lib\Mail; use Lib\DbPool; use Model\folderSql; use Model\listsSql; /** * 操作邮件 * @author:dc * @time 2023/2/5 10:10 * Class MailFun * @package Helper\Mail */ class Mail { /** * imap服务器连接实例 * @var Imap */ public Imap $client; /** * 登录imap服务器 * @param string $email * @param string $password * @param string $imap * @author:dc * @time 2023/2/5 10:46 */ public function login(string $email,string $password,string $imap) { $this->client = new Imap(); // 是否初始成功 $this->client->login("ssl://{$imap}:993",$email,$password); return true; } /** * 同步文件夹 * @param int $email_id * @param DbPool|null $db * @return mixed * @author:dc * @time 2023/2/5 10:58 */ public function syncFolder($email_id,$db=null){ $db = $db ? $db : db(); // 读取所有文件夹,未解密 $folders = $this->client->getFolder(); // $db->transaction(); foreach ($folders as $folder){ $pid = 0; $uuid = md5($email_id.$folder['folder']); // 处理子父文件夹 if(str_contains($folder['folder'], '/')){ // 子目录 $folder['name'] = explode('/',$folder['parseFolder']); // 查找pid $pid = $db->value(folderSql::has(['uuid'=>md5($email_id.explode('/',$folder['folder'])[0])])); // 去掉父目录名称 $folder['parseFolder'] = explode('/',$folder['parseFolder'])[1]; } try { $db->insert(folderSql::$table,[ 'email_id' => $email_id, 'folder' => $folder['parseFolder'], 'origin_folder' => $folder['folder'], 'uuid' => $uuid, 'pid' => $pid ],false); }catch (\Throwable $e){ // 这里就不处理失败了 } } // $db->commit(); } /** * 同步邮件 * @param $email_id * @param $folder_id * @param string $folder * @param null|DbPool $db * @return bool * @throws \Exception * @author:dc * @time 2023/2/18 9:54 */ public function syncMail($email_id,$folder_id,$folder='INBOX',$db = null):bool { // _echo('正在同步文件夹:'.$folder); $db = $db ? $db : db(); // 选择文件夹 $status = $this->client->selectFolder($folder); // 是否有邮件 if (!isset($status['EXISTS']) || !$status['EXISTS']){ return true; } // 更新数量 $db->update( folderSql::$table, ['exsts'=>$status['EXISTS'],'unseen'=>$status['UNSEEN']??0], dbWhere(['id'=>$folder_id]), false ); // 最后拉取的msgno $lastMsgno = $db->value(listsSql::lastMsgno($email_id,$folder_id)); $nu = 20; if(!$lastMsgno){ $msgno = range(1,$nu); }else{ $msgno = range($lastMsgno,$lastMsgno+$nu); if($lastMsgno > $status['EXISTS']){ $msgno = range($status['EXISTS'] > $nu ? $status['EXISTS'] - $nu : 1,$status['EXISTS']); } // 一样就不拉新的 if($lastMsgno == $status['EXISTS']){ return true; } } // 循环 $results = $this->client->fetchHeader($msgno); if($results){ // 批量插入 foreach ($results as $key=>$result){ $header = $result['HEADER.FIELDS']; foreach ($result['FLAGS'] as $k=>$FLAG){ $result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG)); } try { $file_header = $result['BODYSTRUCTURE']; // 没有收件人 if(!empty($header['To'])){ $header['To'] = MailFun::toOrFrom($header['To']); }else{ $header['To'] = []; } $header['From'] = MailFun::toOrFrom($header['From']); $data = [ 'msgno' => $key, 'uid' => $result['UID'], 'subject' => $header['Subject'], 'cc' => $header['Cc']??'', 'bcc' => $header['Bcc']??'', 'from' => $header['From'][0]['email']??'', 'from_name' => $header['From'][0]['name']??'', 'to' => $header['To']?implode(',',array_column($header['To'],'email')):'', 'to_name' => json_encode($header['To']), 'date' => isset($header['Date'])&&$header['Date'] ? strtotime(is_array($header['Date']) ? $header['Date'][0] : $header['Date']) : strtotime($result['INTERNALDATE']), 'message_id' => $header['Message-ID']??'', 'udate' => strtotime($result['INTERNALDATE']), 'size' => $result['RFC822.SIZE']??0, 'recent' => in_array('recent',$result['FLAGS']), 'seen' => in_array('seen',$result['FLAGS']), 'draft' => in_array('draft',$result['FLAGS']), 'flagged' => in_array('flagged',$result['FLAGS']), 'answered' => in_array('answered',$result['FLAGS']), 'folder_id' => $folder_id, 'email_id' => $email_id, 'uuid' => md5($email_id.$folder_id.$result['UID']), 'is_file' => MailFun::isFile($file_header[$key]['BODYSTRUCTURE']??[]) //是否附件 ]; }catch (\Throwable $e){ logs( '邮件解析失败:'.PHP_EOL.$e->getMessage().PHP_EOL.print_r($result,true), LOG_PATH.'/imap/mail/'.$email_id.'/'.$result['UID'].'.log' ); unset($results[$key]); continue; } $results[$key] = $data; } // 保存数据,这里其实不用再次写循环的。我想写一个 $uuids = $db->all(listsSql::hasUuid(array_column($results,'uuid'))); $uuids = $uuids ? array_column($uuids,null,'uuid') : []; // $db->transaction(); foreach ($results as $insert){ if(empty($uuids[$insert['uuid']])){ // 新增 $db->insert(listsSql::$table,$insert); }else{ // 修改 $db->update( listsSql::$table, $insert, dbWhere(['id'=>$uuids[$insert['uuid']]['id']]) ); } } // $db->commit(); // 更新数量 $db->update( folderSql::$table, ['last_sync_time' => time()], dbWhere(['id'=>$folder_id]), false ); // 结束操作了 // 再次调用 $this->syncMail($email_id,$folder_id,$folder,$db); } return true; } /** * 同步 邮件 内容 body * @param $id * @param $msgno * @param $email_id * @param $folder_name * @param $email * @return bool * @throws \Exception * @author:dc * @time 2023/2/9 10:29 */ public static function syncBody($id,$msgno, $email_id,$folder_name,$email):bool { // 选择文件夹 static::$client[$email]->selectFolder($folder_name); $body = static::$client[$email]->fetchBody([$msgno],storage_path('email/'.$email_id)); if(!empty($body[$msgno]['RFC822.TEXT'])){ \App\Models\Body::_insert($id,$body[$msgno]['RFC822.TEXT']); } return true; } }