作者 邓超

x

  1 +<?php
  2 +
  3 +namespace Event;
  4 +
  5 +/**
  6 + * 事件
  7 + * @author:dc
  8 + * @time 2024/9/30 16:33
  9 + * Class Event
  10 + * @package Event
  11 + */
  12 +class Event {
  13 +
  14 + /**
  15 + * 同步
  16 + * @var array
  17 + */
  18 + protected static $alias = [
  19 + // 列表同步成功后
  20 + 'mail_sync_list' => [
  21 + SyncMail::class,
  22 + MailBlack::class
  23 + ],
  24 +
  25 + ];
  26 +
  27 +
  28 + /**
  29 + * @param $name
  30 + * @param mixed ...$params
  31 + * @throws \Exception
  32 + * @author:dc
  33 + * @time 2024/9/30 16:55
  34 + */
  35 + public static function call($name, ...$params){
  36 + // 是否已配置
  37 + if(isset(self::$alias[$name])){
  38 + array_map(function ($v) use ($params){
  39 + new $v(...$params);
  40 + },
  41 + is_array(self::$alias[$name])? self::$alias[$name] : [is_array(self::$alias[$name])]
  42 + );
  43 + }else if (class_exists($name)){
  44 + new $name(...$params);
  45 + }else{
  46 + throw new \Exception('event '.$name.' not found');
  47 + }
  48 +
  49 + }
  50 +
  51 +
  52 +}
  1 +<?php
  2 +
  3 +namespace Event;
  4 +
  5 +use Model\emailSql;
  6 +use Model\folderSql;
  7 +use Model\listsSql;
  8 +use Swlib\Saber;
  9 +use Swlib\SaberGM;
  10 +
  11 +/**
  12 + * 黑名单
  13 + * @author:dc
  14 + * @time 2024/9/30 17:16
  15 + * Class MailBlack
  16 + * @package Event
  17 + */
  18 +class MailBlack {
  19 +
  20 + /**
  21 + * @var \Lib\Db|\Lib\DbPool
  22 + */
  23 + private $db;
  24 +
  25 + private $blacklist = [];
  26 +
  27 + private $blackFolder = '';
  28 +
  29 + private $data;
  30 +
  31 +
  32 + public function __construct($id,$data)
  33 + {
  34 + $data['id'] = $id;
  35 +
  36 + $this->data = $data;
  37 +
  38 +// 读取黑名单
  39 + $this->blacklist = redis()->get('blacklist:'.$data['email_id']);
  40 + if($this->blacklist){
  41 + $this->blackFolder = $this->db->cache(3600)->value(folderSql::originFolder($data['email_id'],'垃圾箱'));
  42 + }
  43 +
  44 +
  45 +
  46 +
  47 +
  48 +
  49 + }
  50 +
  51 +
  52 +}
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 namespace Event; 3 namespace Event;
4 4
  5 +use Lib\Imap\Parse\MessageItem;
5 use Model\emailSql; 6 use Model\emailSql;
6 use Model\folderSql; 7 use Model\folderSql;
7 use Model\listsSql; 8 use Model\listsSql;
@@ -15,15 +16,16 @@ use Swlib\SaberGM; @@ -15,15 +16,16 @@ use Swlib\SaberGM;
15 * Class syncMail 16 * Class syncMail
16 * @package Event 17 * @package Event
17 */ 18 */
18 -class syncMail { 19 +class SyncMail {
19 /** 20 /**
20 * @var \Lib\Db|\Lib\DbPool 21 * @var \Lib\Db|\Lib\DbPool
21 */ 22 */
22 private $db; 23 private $db;
23 24
24 25
25 - public function __construct($id,$header,$data) 26 + public function __construct(int $id, array $data)
26 { 27 {
  28 + if(php_sapi_name() == 'cli'){
27 $this->db = db(); 29 $this->db = db();
28 30
29 // 是否是预热邮件 aicc专用 31 // 是否是预热邮件 aicc专用
@@ -86,6 +88,10 @@ class syncMail { @@ -86,6 +88,10 @@ class syncMail {
86 88
87 } 89 }
88 90
  91 +
  92 +
  93 + }
  94 +
89 private function hot($id){ 95 private function hot($id){
90 return $this->db->update(listsSql::$table,['is_hots'=>1],dbWhere(['id'=>$id])); 96 return $this->db->update(listsSql::$table,['is_hots'=>1],dbWhere(['id'=>$id]));
91 } 97 }
@@ -254,6 +254,9 @@ trait DbQuery { @@ -254,6 +254,9 @@ trait DbQuery {
254 * @time 2023/2/17 11:03 254 * @time 2023/2/17 11:03
255 */ 255 */
256 public function value(string|array $sql){ 256 public function value(string|array $sql){
  257 +
  258 + return $this->getCacheData($sql,null,\PDO::FETCH_COLUMN);
  259 +
257 $query = $this->query($sql); 260 $query = $this->query($sql);
258 if($query){ 261 if($query){
259 return $query->fetch(\PDO::FETCH_COLUMN); 262 return $query->fetch(\PDO::FETCH_COLUMN);
@@ -37,6 +37,8 @@ class Imap { @@ -37,6 +37,8 @@ class Imap {
37 $this->config = $config; 37 $this->config = $config;
38 38
39 $this->client = new ImapClient($this->config->getHost()); 39 $this->client = new ImapClient($this->config->getHost());
  40 +
  41 + $this->debug($config->isDebug());
40 } 42 }
41 43
42 /** 44 /**
@@ -6,6 +6,7 @@ class ImapConfig { @@ -6,6 +6,7 @@ class ImapConfig {
6 protected string $host = ''; 6 protected string $host = '';
7 protected string $password = ''; 7 protected string $password = '';
8 protected string $email = ''; 8 protected string $email = '';
  9 + protected bool $debug = false;
9 10
10 11
11 /** 12 /**
@@ -68,4 +69,21 @@ class ImapConfig { @@ -68,4 +69,21 @@ class ImapConfig {
68 return $this->password; 69 return $this->password;
69 } 70 }
70 71
  72 +
  73 + /**
  74 + * @param bool $debug
  75 + */
  76 + public function debug(bool $debug = true): static
  77 + {
  78 + $this->debug = $debug;
  79 + return $this;
  80 + }
  81 +
  82 + /**
  83 + * @return bool
  84 + */
  85 + public function isDebug(): bool
  86 + {
  87 + return $this->debug;
  88 + }
71 } 89 }
@@ -347,6 +347,14 @@ class Body { @@ -347,6 +347,14 @@ class Body {
347 } 347 }
348 348
349 349
  350 + /**
  351 + * @return DataArray[]
  352 + */
  353 + public function getItems(): array
  354 + {
  355 + return $this->items;
  356 + }
  357 +
350 } 358 }
351 359
352 360
@@ -44,6 +44,7 @@ class Folder{ @@ -44,6 +44,7 @@ class Folder{
44 $this->folder = $folder; 44 $this->folder = $folder;
45 $this->flags = $flags; 45 $this->flags = $flags;
46 $this->isSelect = $isSelect; 46 $this->isSelect = $isSelect;
  47 + $this->parent = $parent;
47 } 48 }
48 49
49 /** 50 /**
@@ -58,7 +58,23 @@ class Msg extends Request{ @@ -58,7 +58,23 @@ class Msg extends Request{
58 * @time 2024/9/14 14:06 58 * @time 2024/9/14 14:06
59 */ 59 */
60 public function forPage(int $p=1, int $limit=20):static { 60 public function forPage(int $p=1, int $limit=20):static {
61 - $this->msgno(range(($p-1)*$limit+1,$p*$limit)); 61 + $start = ($p-1)*$limit+1;
  62 + $end = $p*$limit;
  63 +
  64 + // 如果开始大于 总数 就不要查询了
  65 + if($start <= $this->folder->getTotal()){
  66 + // 如果结束大于 总数 以总数为结束
  67 + if($end > $this->folder->getTotal()){
  68 + // 防止 有些邮件服务商 查询没有的邮件会异常
  69 + $end = $this->folder->getTotal();
  70 + }
  71 + }else{
  72 + // 如果 开始 大于总数 就始终 查询总数后的limit
  73 + $start = $this->folder->getTotal()+1;
  74 + $end = $start+$limit;
  75 + }
  76 +
  77 + $this->msgno(range($start,$end));
62 return $this; 78 return $this;
63 } 79 }
64 80
@@ -2,11 +2,15 @@ @@ -2,11 +2,15 @@
2 2
3 namespace Service; 3 namespace Service;
4 4
  5 +use Event\Event;
5 use Lib\Imap\ImapConfig; 6 use Lib\Imap\ImapConfig;
6 use Lib\Imap\ImapPool; 7 use Lib\Imap\ImapPool;
7 use Lib\Imap\Parse\Folder\Folder; 8 use Lib\Imap\Parse\Folder\Folder;
  9 +use Lib\Imap\Parse\MessageItem;
  10 +use Model\bodySql;
8 use Model\emailSql; 11 use Model\emailSql;
9 use Model\folderSql; 12 use Model\folderSql;
  13 +use Model\listsSql;
10 14
11 /** 15 /**
12 * 同步邮件 16 * 同步邮件
@@ -32,6 +36,9 @@ class SyncMail { @@ -32,6 +36,9 @@ class SyncMail {
32 */ 36 */
33 protected $email; 37 protected $email;
34 38
  39 +
  40 + protected $isStop = false;
  41 +
35 /** 42 /**
36 * SyncMail constructor. 43 * SyncMail constructor.
37 * @param int|string|array $email 44 * @param int|string|array $email
@@ -54,12 +61,18 @@ class SyncMail { @@ -54,12 +61,18 @@ class SyncMail {
54 ->setHost($email['imap']) 61 ->setHost($email['imap'])
55 ->setEmail($email['email']) 62 ->setEmail($email['email'])
56 ->setPassword(base64_decode($email['password'])) 63 ->setPassword(base64_decode($email['password']))
  64 + ->debug()
57 ); 65 );
58 66
59 $this->login(); 67 $this->login();
60 68
61 } 69 }
62 70
  71 + public function stop(){
  72 + $this->isStop = true;
  73 + }
  74 +
  75 +
63 protected function emailId(){ 76 protected function emailId(){
64 return $this->email['id']; 77 return $this->email['id'];
65 } 78 }
@@ -78,16 +91,16 @@ class SyncMail { @@ -78,16 +91,16 @@ class SyncMail {
78 } 91 }
79 92
80 /** 93 /**
81 - * 同步文件夹 94 + * @param $folder
  95 + * @param int $pid
  96 + * @return array
  97 + * @throws \Exception
82 * @author:dc 98 * @author:dc
83 - * @time 2024/9/26 10:22 99 + * @time 2024/9/26 10:46
84 */ 100 */
85 - protected function folders(){ 101 + protected function folder($folder,$pid = 0){
86 $uuids = []; 102 $uuids = [];
87 - // 获取文件夹  
88 - $folders = $this->imap->getFolders();  
89 103
90 - $tmp = function ($folder) use ($folders,&$uuids){  
91 foreach ($folder as $item){ 104 foreach ($folder as $item){
92 /** @var Folder $item*/ 105 /** @var Folder $item*/
93 106
@@ -95,21 +108,13 @@ class SyncMail { @@ -95,21 +108,13 @@ class SyncMail {
95 $uuids[$uuid] = $uuid; 108 $uuids[$uuid] = $uuid;
96 109
97 $folder_name = ''; 110 $folder_name = '';
98 - // 已发送  
99 - if(in_array('Send',$item->flags)){  
100 - $folder_name = folderAlias('Send');  
101 - }  
102 - // 草稿  
103 - elseif(in_array('Drafts',$item->flags)){  
104 - $folder_name = folderAlias('Drafts'); 111 + if($item->flags){
  112 + // 有些邮箱是把公共的文件夹标记在flag里面的,识别出来
  113 + foreach ($item->flags as $flag){
  114 + if(in_array($flag,['Send','Drafts','Junk','Trash'])){
  115 + $folder_name = folderAlias($flag);
105 } 116 }
106 - // 垃圾  
107 - elseif(in_array('Junk',$item->flags)){  
108 - $folder_name = folderAlias('Junk');  
109 } 117 }
110 - // 回收站  
111 - elseif(in_array('Trash',$item->flags)){  
112 - $folder_name = folderAlias('Trash');  
113 } 118 }
114 if(!$folder_name){ 119 if(!$folder_name){
115 $fn = explode('/',$item->getParseFolder()); 120 $fn = explode('/',$item->getParseFolder());
@@ -120,29 +125,191 @@ class SyncMail { @@ -120,29 +125,191 @@ class SyncMail {
120 $id = $this->db->value(folderSql::has(['email_id'=>$this->emailId(),'uuid'=>$uuid])); 125 $id = $this->db->value(folderSql::has(['email_id'=>$this->emailId(),'uuid'=>$uuid]));
121 $data = [ 126 $data = [
122 'email_id' => $this->emailId(), 127 'email_id' => $this->emailId(),
123 - 'folder' => folderAlias($folder_name),  
124 - 'origin_folder' => $folder['folder'], 128 + 'folder' => $folder_name,
  129 + 'origin_folder' => $item->folder,
125 'uuid' => $uuid, 130 'uuid' => $uuid,
126 'pid' => $pid 131 'pid' => $pid
127 ]; 132 ];
128 if ($id){ 133 if ($id){
129 - 134 + $this->db->update(folderSql::$table,$data,dbWhere(['id'=>$id]),false);
  135 + }else{
  136 + $id = $this->db->insert(folderSql::$table,$data,false);
  137 + if(!$id) abort('文件夹写入异常 '.json_encode($data,JSON_UNESCAPED_UNICODE));
  138 + }
  139 + // 是否有子级目录
  140 + if($id && $item->getChild()){
  141 + $uuids = array_merge($uuids,$this->folder($item->getChild(),$id));
130 } 142 }
131 } 143 }
132 - };  
133 144
134 - $tmp($folders->getTopFolder()); 145 + return $uuids;
  146 + }
135 147
  148 + /**
  149 + * 同步
  150 + * @throws \Exception
  151 + * @author:dc
  152 + * @time 2024/9/26 10:46
  153 + */
  154 + public function sync(){
  155 + $this->isStop = false;
  156 + /*********************************** 同步文件夹 ***************************************/
  157 + // 获取文件夹
  158 + $folders = $this->imap->getFolders();
  159 + $uuids = $this->folder($folders->getTopFolder());
136 if($uuids){ 160 if($uuids){
137 // 删除以前的 161 // 删除以前的
138 $this->db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$this->emailId()]); 162 $this->db->delete(folderSql::$table,['uuid.notin'=>$uuids,'email_id'=>$this->emailId()]);
139 } 163 }
140 164
141 - return $folders; 165 + if($this->isStop) return;
  166 +
  167 +
  168 + /********************* 同步邮件 **********************/
  169 +
  170 + // 循环文件夹
  171 + foreach ($folders->all() as $f){
  172 + if($this->isStop) return;
  173 +
  174 + if($f->isSelect){ // 是否可以选择 只有可以选中的文件夹才有邮件
  175 + $folder = $this->imap->folder($f); // 选择文件夹后,有状态
  176 +
  177 + // 这个暂时不要
  178 +// $this->db->update(folderSql::$table,[
  179 +// 'exsts' => $folder->getTotal(),
  180 +// 'unseen' => $folder->getUnseen(),
  181 +// 'last_sync_time' => time()
  182 +// ],dbWhere(['email_id'=>$this->emailId(),'origin_folder'=>$folder->getName()]),false);
  183 +
  184 + // 是否有邮件 有邮件才继续
  185 + if ($folder->getTotal()){
  186 + $this->mail($folder);
  187 + }
  188 + }
  189 + }
  190 +
  191 +
142 } 192 }
143 193
144 - public function sync(){  
145 - $folders = $this->folders(); 194 + /**同步邮件
  195 + *
  196 + * @param string|\Lib\Imap\Request\Folder $folder
  197 + * @param array $uids 固定的uid
  198 + * @param false $isBody 是否同时同步body
  199 + * @author:dc
  200 + * @time 2024/9/26 11:10
  201 + */
  202 + public function mail(string|\Lib\Imap\Request\Folder $folder, array $uids = [],$isBody = false){
  203 + if(is_string($folder)){
  204 + $folder = $this->imap->folder($folder);
  205 + }
  206 +
  207 + $folder_id = $this->db->value(folderSql::first([
  208 + 'email_id'=>$this->emailId(),
  209 + 'uuid' => md5($this->emailId().$folder->getName())
  210 + ],'`id`'));
  211 +
  212 + // 选择成功
  213 + if($folder->isOk()){
  214 + $msg = $folder->msg();
  215 + if($uids){
  216 + $this->saveMail($folder_id,$msg->uid($uids)->get()->all(),$isBody);
  217 + }else{
  218 + $p=1;
  219 + while (1){
  220 + if($this->isStop) return;
  221 +
  222 + $lists = $msg->forPage($p)->get()->all();
  223 + // 没有数据就跳出
  224 + if(!$lists){
  225 + break;
  226 + }else{
  227 + $p++;
  228 + $this->saveMail($folder_id,$lists,$isBody);
  229 + }
  230 + }
  231 + }
  232 +
  233 + }
  234 +
  235 + }
  236 +
  237 + /**
  238 + * 保存邮件列表
  239 + * @param int $folder_id
  240 + * @param MessageItem[] $lists
  241 + * @param bool $isBody
  242 + * @author:dc
  243 + * @time 2024/9/29 15:14
  244 + */
  245 + protected function saveMail(int $folder_id, array $lists, bool $isBody=false){
  246 + foreach ($lists as $item){
  247 +
  248 + $data = [
  249 + 'uid' => $item->uid,
  250 + 'subject' => mb_substr($item->header->getSubject(),0,1000),// 控制下,有的蛋疼,整tm多长
  251 + 'cc' => $item->header->getCc(true),
  252 + 'bcc' => $item->header->getBcc(true),
  253 + 'from' => $item->header->getFrom()->email,
  254 + 'from_name' => $item->header->getFrom()->name,
  255 + 'to' => implode(',',array_column($item->header->getTo(true),'email')),
  256 + 'to_name' => $item->header->getTo(true),
  257 + // 这个是 邮件的时间 就是header里面带的 一般情况就是发件时间
  258 +// 'date' => strtotime($item->header->getDate()),
  259 + 'udate' => strtotime($item->date), // 有这个时间就够了,内部时间,就是收到邮件的时间
  260 + 'size' => $item->size,
  261 + 'recent' => $item->isRecent() ? 1 : 0,
  262 + 'seen' => $item->isSeen() ? 1 : 0,
  263 + 'draft' => $item->isDraft() ? 1 : 0,
  264 + 'flagged' => $item->isFlagged() ? 1 : 0,
  265 + 'answered' => $item->isAnswered() ? 1 : 0,
  266 + 'folder_id' => $folder_id,
  267 + 'email_id' => $this->emailId(),
  268 + 'is_file' => $item->isAttachment() ? 1: 0 //是否附件
  269 + ];
  270 +
  271 + // 查询是否存在
  272 + $id = $this->db->value(listsSql::first(dbWhere([
  273 + 'email_id'=> $data['email_id'],
  274 + 'folder_id' => $data['folder_id'],
  275 + 'uid' => $data['uid']
  276 + ]),'`id`'));
  277 +
  278 +
  279 + if(!$id){
  280 + $id = $this->db->insert(listsSql::$table,$data);
  281 + // 新邮件标记
  282 + if($item->getFolderName() == 'INBOX')
  283 + redis()->incr('have_new_mail_'.$this->emailId(),120);
  284 + // 执行事件
  285 + $data['Aicc-Hot-Mail'] = $item->header->get('Aicc-Hot-Mail');
  286 + Event::call('mail_sync_list',$id, $data);
  287 +
  288 + }else{
  289 + $this->db->update(listsSql::$table,$data,dbWhere(['id'=> $id]));
  290 + }
  291 +
  292 +
  293 + // 是否同步body内容
  294 + if($isBody){
  295 +
  296 + $body = [
  297 + 'lists_id' => $id,
  298 + 'text_html' => $item->getBody()->getItems()
  299 + ];
  300 +
  301 + if($this->db->count(bodySql::has($id))){
  302 + $this->db->update(bodySql::$table,$body,'`lists_id` = '.$id,false);
  303 + }else{
  304 + $this->db->insert(bodySql::$table,$body,false);
  305 + }
  306 +
  307 + }
  308 +
  309 +
  310 + }
  311 +
  312 +
146 } 313 }
147 314
148 315