作者 邓超

1

@@ -199,7 +199,10 @@ function create_coroutine(array &$cid,int &$isRunMaxCNum,$worker_id){ @@ -199,7 +199,10 @@ function create_coroutine(array &$cid,int &$isRunMaxCNum,$worker_id){
199 sync(); 199 sync();
200 }catch (\Throwable $e){ 200 }catch (\Throwable $e){
201 _echo($e->getMessage()); 201 _echo($e->getMessage());
202 - logs($e->getMessage().PHP_EOL.$e->getTraceAsString(),LOG_PATH.'/'.$worker_id.'_'.co::getCid().'.log'); 202 + logs(
  203 + $e->getMessage().PHP_EOL.$e->getTraceAsString(),
  204 + LOG_PATH.'/'.$worker_id.'_'.co::getCid().'.log'
  205 + );
203 } 206 }
204 207
205 // 阻塞1秒 208 // 阻塞1秒
@@ -253,9 +256,38 @@ function sync(){ @@ -253,9 +256,38 @@ function sync(){
253 } 256 }
254 257
255 $mailServer->client->setId($id); 258 $mailServer->client->setId($id);
  259 +
  260 + // 文件夹间隔1天同步一次
  261 + if(empty($email['last_sync_time']) || time() > $email['last_sync_time']+86400){
256 // 同步文件夹 262 // 同步文件夹
257 - $mailServer->syncFolder($email['email'],db()); 263 + $mailServer->syncFolder(db());
  264 + }
  265 +
  266 + // 读取到邮箱中的文件夹
  267 + $folders = db()->all(\Model\folderSql::all($email['id']));
  268 + if(!$folders){
  269 + return 3;
  270 + }
  271 + $folders = list_to_tree($folders);
  272 + foreach ($folders as $folder){
  273 + try {
  274 + if(empty($folder['_child'])){
  275 + // 同步父文件夹
  276 + $mailServer->syncMail($id,$folder['id'],$folder['origin_name']);
  277 + }else{
  278 + foreach ($folder as $item){
  279 + // 同步子文件夹
  280 + $mailServer->syncMail($id,$item['id'],$folder['origin_name'].'/'.$item['origin_name']);
  281 + }
  282 + }
258 283
  284 + }catch (Throwable $e){
  285 + logs(
  286 + $e->getMessage(),
  287 + LOG_PATH.'/imap/'.$email.'.error.log'
  288 + );
  289 + }
  290 + }
259 291
260 292
261 $email = null; 293 $email = null;
@@ -36,11 +36,18 @@ function db():\Lib\DbPool{ @@ -36,11 +36,18 @@ function db():\Lib\DbPool{
36 * @time 2023/2/10 14:58 36 * @time 2023/2/10 14:58
37 */ 37 */
38 function logs($message,$filename=null){ 38 function logs($message,$filename=null){
  39 +
  40 + $filename = $filename ? $filename : LOG_PATH.'/error.log';
  41 + if(!is_dir(dirname($filename))){
  42 + @mkdir(dirname($filename),0755,true);
  43 + }
  44 +
39 @file_put_contents( 45 @file_put_contents(
40 - $filename ? $filename : LOG_PATH.'/error.log', 46 + $filename,
41 date('Y-m-d H:i:s ').print_r($message,true).PHP_EOL, 47 date('Y-m-d H:i:s ').print_r($message,true).PHP_EOL,
42 FILE_APPEND 48 FILE_APPEND
43 ); 49 );
  50 +
44 } 51 }
45 52
46 53
@@ -195,7 +202,42 @@ function listsPage($item,$total,$page,$limit){ @@ -195,7 +202,42 @@ function listsPage($item,$total,$page,$limit){
195 202
196 203
197 204
198 - 205 +/**
  206 + * 把返回的数据集转换成Tree
  207 + * @param $list array 数据列表
  208 + * @param string|int $pk 主键|root
  209 + * @param string $pid 父id
  210 + * @param string $child 子键
  211 + * @param int $root 获取哪个id下面
  212 + * @param bool $empty_child 当子数据不存在,是否要返回空子数据
  213 + * @return array
  214 + */
  215 +function list_to_tree($list, $pk='id',$pid = 'pid',$child = '_child',$root=0,$empty_child=true) {
  216 + // 创建Tree
  217 + $tree = array();
  218 + if(is_array($list)) {
  219 + // 创建基于主键的数组引用
  220 + $refer = array();
  221 + foreach ($list as $key => $data) {
  222 + if($empty_child){
  223 + $list[$key][$child] = [];
  224 + }
  225 + $refer[$data[$pk]] =& $list[$key];
  226 + }
  227 + foreach ($list as $key => $data) {
  228 + // 判断是否存在parent
  229 + $parentId = $data[$pid];
  230 + if ($root == $parentId) {
  231 + $tree[] =& $list[$key];
  232 + }else{
  233 + if (isset($refer[$parentId])) {
  234 + $refer[$parentId][$child][] = & $list[$key];
  235 + }
  236 + }
  237 + }
  238 + }
  239 + return $tree;
  240 +}
199 241
200 242
201 243
@@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
3 namespace Lib\Mail; 3 namespace Lib\Mail;
4 4
5 use Lib\DbPool; 5 use Lib\DbPool;
  6 +use Model\folderSql;
  7 +use Model\listsSql;
6 8
7 /** 9 /**
8 * 操作邮件 10 * 操作邮件
@@ -59,11 +61,14 @@ class Mail { @@ -59,11 +61,14 @@ class Mail {
59 $pid = 0; 61 $pid = 0;
60 foreach ($folder['id'] as $k=>$item){ 62 foreach ($folder['id'] as $k=>$item){
61 // 插入到数据库 63 // 插入到数据库
62 - $pid = Folder::_insert(  
63 - $this->client->getId(),  
64 - $folder['name'][$k],  
65 - $item,  
66 - $pid 64 + $pid = $db->insert(
  65 + folderSql::$table,
  66 + [
  67 + 'email_id' => $this->client->getId(),
  68 + 'folder' => $folder['name'][$k],
  69 + 'origin_folder' => $item,
  70 + 'pid' => $pid
  71 + ]
67 ); 72 );
68 } 73 }
69 } 74 }
@@ -74,32 +79,35 @@ class Mail { @@ -74,32 +79,35 @@ class Mail {
74 79
75 /** 80 /**
76 * 同步邮件 81 * 同步邮件
77 - * @param $email  
78 * @param $email_id 82 * @param $email_id
79 * @param $folder_id 83 * @param $folder_id
80 * @param string $folder 84 * @param string $folder
  85 + * @param null|DbPool $db
81 * @return bool 86 * @return bool
  87 + * @throws \Exception
82 * @author:dc 88 * @author:dc
83 - * @time 2023/2/6 15:04 89 + * @time 2023/2/18 9:54
84 */ 90 */
85 - public static function syncMail($email,$email_id,$folder_id,$folder='INBOX'):bool { 91 + public function syncMail($email_id,$folder_id,$folder='INBOX',$db = null):bool {
  92 + $db = $db ? $db : db();
86 // 选择文件夹 93 // 选择文件夹
87 - try {  
88 - $status = static::$client[$email]->selectFolder($folder);  
89 - }catch (\Throwable $e){  
90 - Log::error($email.' 选择文件夹错误:'.$e->getMessage());  
91 - return false;  
92 - } 94 + $status = $this->client->selectFolder($folder);
  95 +
93 // 是否有邮件 96 // 是否有邮件
94 if (!isset($status['EXISTS']) || !$status['EXISTS']){ 97 if (!isset($status['EXISTS']) || !$status['EXISTS']){
95 return true; 98 return true;
96 } 99 }
97 100
98 // 更新数量 101 // 更新数量
99 - Folder::_updateNum($folder_id,$status['EXISTS'], $status['UNSEEN']??null); 102 + $db->update(
  103 + folderSql::$table,
  104 + ['exsts'=>$status['EXISTS'],'unseen'=>$status['UNSEEN']??0],
  105 + dbWhere(['id'=>$folder_id])
  106 + );
  107 +
100 108
101 - // 最后拉取的时间,如果是第一次  
102 - $lastMsgno = EList::_lastMsgno($email_id, $folder_id); 109 + // 最后拉取的msgno
  110 + $lastMsgno = $db->value(listsSql::lastMsgno($email_id,$folder_id));
103 111
104 $nu = 20; 112 $nu = 20;
105 113
@@ -118,29 +126,19 @@ class Mail { @@ -118,29 +126,19 @@ class Mail {
118 } 126 }
119 127
120 128
121 -  
122 - SYNCEMAILLIST:  
123 - // 是否有id  
124 - $dataids = EList::_getIdsByMsgno($email_id,$folder_id,$msgno);  
125 -  
126 // 循环 129 // 循环
127 - $results = static::$client[$email]->fetchHeader($msgno);  
128 - 130 + $results = $this->client->fetchHeader($msgno);
129 if($results){ 131 if($results){
130 - DB::beginTransaction();  
131 // 批量插入 132 // 批量插入
132 foreach ($results as $key=>$result){ 133 foreach ($results as $key=>$result){
133 - if($key == $status['EXISTS']){  
134 - $end = true;  
135 - }  
136 - $header = &$result['HEADER.FIELDS']; 134 + $header = $result['HEADER.FIELDS'];
137 135
138 foreach ($result['FLAGS'] as $k=>$FLAG){ 136 foreach ($result['FLAGS'] as $k=>$FLAG){
139 $result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG)); 137 $result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG));
140 } 138 }
141 try { 139 try {
142 140
143 - $file_header = &$result['BODYSTRUCTURE']; 141 + $file_header = $result['BODYSTRUCTURE'];
144 142
145 // 没有收件人 143 // 没有收件人
146 if(!empty($header['To'])){ 144 if(!empty($header['To'])){
@@ -149,11 +147,9 @@ class Mail { @@ -149,11 +147,9 @@ class Mail {
149 $header['To'] = []; 147 $header['To'] = [];
150 } 148 }
151 149
152 -  
153 $header['From'] = MailFun::toOrFrom($header['From']); 150 $header['From'] = MailFun::toOrFrom($header['From']);
154 151
155 $data = [ 152 $data = [
156 - 'id' => $dataids[$key]??0,  
157 'msgno' => $key, 153 'msgno' => $key,
158 'uid' => $result['UID'], 154 'uid' => $result['UID'],
159 'subject' => $header['Subject'], 155 'subject' => $header['Subject'],
@@ -166,7 +162,7 @@ class Mail { @@ -166,7 +162,7 @@ class Mail {
166 'date' => isset($header['Date'])&&$header['Date'] ? strtotime(is_array($header['Date']) ? $header['Date'][0] : $header['Date']) : strtotime($result['INTERNALDATE']), 162 'date' => isset($header['Date'])&&$header['Date'] ? strtotime(is_array($header['Date']) ? $header['Date'][0] : $header['Date']) : strtotime($result['INTERNALDATE']),
167 'message_id' => $header['Message-ID']??'', 163 'message_id' => $header['Message-ID']??'',
168 'udate' => strtotime($result['INTERNALDATE']), 164 'udate' => strtotime($result['INTERNALDATE']),
169 -// 'size' => $result['RFC822.SIZE'], 165 + 'size' => $result['RFC822.SIZE']??0,
170 'recent' => in_array('recent',$result['FLAGS']), 166 'recent' => in_array('recent',$result['FLAGS']),
171 'seen' => in_array('seen',$result['FLAGS']), 167 'seen' => in_array('seen',$result['FLAGS']),
172 'draft' => in_array('draft',$result['FLAGS']), 168 'draft' => in_array('draft',$result['FLAGS']),
@@ -174,29 +170,50 @@ class Mail { @@ -174,29 +170,50 @@ class Mail {
174 'answered' => in_array('answered',$result['FLAGS']), 170 'answered' => in_array('answered',$result['FLAGS']),
175 'folder_id' => $folder_id, 171 'folder_id' => $folder_id,
176 'email_id' => $email_id, 172 'email_id' => $email_id,
177 - 'uuid' => $email_id.$folder_id.$result['UID'], 173 + 'uuid' => md5($email_id.$folder_id.$result['UID']),
178 'is_file' => MailFun::isFile($file_header[$key]['BODYSTRUCTURE']??[]) //是否附件 174 'is_file' => MailFun::isFile($file_header[$key]['BODYSTRUCTURE']??[]) //是否附件
179 ]; 175 ];
180 }catch (\Throwable $e){ 176 }catch (\Throwable $e){
181 - Log::error('邮件解析失败:'.$e->getMessage().print_r($result,true)); 177 + logs(
  178 + '邮件解析失败:'.PHP_EOL.$e->getMessage().PHP_EOL.print_r($result,true),
  179 + LOG_PATH.'/imap/mail/'.$email_id.'/'.$result['UID'].'.log'
  180 + );
182 unset($results[$key]); 181 unset($results[$key]);
183 continue; 182 continue;
184 } 183 }
185 184
186 $results[$key] = $data; 185 $results[$key] = $data;
187 } 186 }
188 - EList::_insertAll(array_values($results));  
189 - // 提交  
190 - DB::commit(); 187 +
  188 + // 保存数据,这里其实不用再次写循环的。我想写一个
  189 + $uuids = $db->all(listsSql::hasUuid(array_column($results,'uuid')));
  190 + $uuids = $uuids ? array_column($uuids,null,'uuid') : [];
  191 +
  192 + $db->transaction();
  193 + foreach ($results as $insert){
  194 + if(empty($uuids[$insert['uuid']])){
  195 + // 新增
  196 + $db->insert(listsSql::$table,$insert);
  197 + }else{
  198 + // 修改
  199 + $db->update(
  200 + listsSql::$table,
  201 + $insert,
  202 + dbWhere(['id'=>$uuids[$insert['uuid']]['id']])
  203 + );
  204 + }
  205 + }
  206 + $db->commit();
  207 +
  208 + // 结束操作了
191 } 209 }
192 210
193 211
194 // 再次调用 212 // 再次调用
195 - Mail::syncMail($email,$email_id,$folder_id,$folder); 213 + $this->syncMail($email_id,$folder_id,$folder,$db);
196 214
197 return true; 215 return true;
198 216
199 -  
200 } 217 }
201 218
202 219
1 <?php 1 <?php
2 -namespace Helper\Mail; 2 +namespace Lib\Mail;
3 3
4 4
5 -use Illuminate\Support\Facades\Storage;  
6 use PHPMailer\PHPMailer\PHPMailer; 5 use PHPMailer\PHPMailer\PHPMailer;
7 use PHPMailer\PHPMailer\SMTP; 6 use PHPMailer\PHPMailer\SMTP;
8 7
@@ -13,7 +13,16 @@ class folderSql { @@ -13,7 +13,16 @@ class folderSql {
13 13
14 public static $table = 'folders'; 14 public static $table = 'folders';
15 15
16 - 16 + /**
  17 + * 所有文件夹
  18 + * @param int $email_id
  19 + * @return string
  20 + * @author:dc
  21 + * @time 2023/2/18 9:22
  22 + */
  23 + public static function all(int $email_id):string {
  24 + return "select `folder`,`pid`,`origin_folder`,`last_sync_time` from `".static::$table."` where `email_id` = {$email_id} order by `id` asc";
  25 + }
17 26
18 27
19 28
@@ -36,6 +36,45 @@ class listsSql { @@ -36,6 +36,45 @@ class listsSql {
36 36
37 } 37 }
38 38
  39 + /**
  40 + * 获取最后一条更新的msgno
  41 + * @param $email_id
  42 + * @param $folder_id
  43 + * @return string
  44 + * @author:dc
  45 + * @time 2023/2/18 10:01
  46 + */
  47 + public static function lastMsgno($email_id,$folder_id):string{
  48 + return "select max(`msgno`) from `".self::$table."` where ".dbWhere(['email_id'=>$email_id,'folder_id'=>$folder_id])." limit 1";
  49 + }
  50 +
  51 + /**
  52 + * 获取已存在的id
  53 + * @param $email_id
  54 + * @param $folder_id
  55 + * @param $msgno
  56 + * @return string
  57 + * @author:dc
  58 + * @time 2023/2/18 10:08
  59 + */
  60 + public static function getIds($email_id,$folder_id,$msgno):string {
  61 + return "select `id`,`msgno` from `".static::$table."` where ".dbWhere([
  62 + 'email_id' => $email_id,
  63 + 'folder_id' => $folder_id,
  64 + 'msgno' => $msgno,
  65 + ]);
  66 + }
  67 +
  68 + /**
  69 + * 通过uuid查询id和email_id
  70 + * @param $uuid
  71 + * @return string
  72 + * @author:dc
  73 + * @time 2023/2/18 10:44
  74 + */
  75 + public static function hasUuid($uuid):string {
  76 + return "select `id`,`email_id`,`uuid` from `".self::$table."` where ".dbWhere(['uuid'=>$uuid]);
  77 + }
39 78
40 79
41 80