作者 邓超

1

要显示太多修改。

为保证性能只显示 14 of 14+ 个文件。

@@ -29,9 +29,19 @@ class Demo extends Command @@ -29,9 +29,19 @@ class Demo extends Command
29 public function handle() 29 public function handle()
30 { 30 {
31 31
32 -// run(function (){  
33 -//  
34 -// }); 32 + $i = 0;
  33 + $n = 'a';
  34 + run(function () use (&$i,&$n){
  35 +
  36 + go(function () use (&$i,&$n){
  37 + $n = 'b';
  38 + $i = 2;
  39 + });
  40 +
  41 + });
  42 +
  43 + echo $i;
  44 + echo $n;
35 45
36 return Command::SUCCESS; 46 return Command::SUCCESS;
37 } 47 }
  1 +<?php
  2 +//
  3 +//namespace App\Console\Commands;
  4 +//
  5 +//use App\Models\Email;
  6 +//use App\Models\Folder;
  7 +//use Helper\Mail\Mail;
  8 +//use Illuminate\Console\Command;
  9 +//use Illuminate\Support\Facades\Cache;
  10 +//use Illuminate\Support\Facades\Log;
  11 +//use function Co\run;
  12 +//
  13 +//class PushSync extends Command
  14 +//{
  15 +// /**
  16 +// * The name and signature of the console command.
  17 +// *
  18 +// * @var string
  19 +// */
  20 +// protected $signature = 'PushSync';
  21 +//
  22 +// /**
  23 +// * The console command description.
  24 +// *
  25 +// * @var string
  26 +// */
  27 +// protected $description = '发布同步任务到队列中';
  28 +//
  29 +// /**
  30 +// * Execute the console command.
  31 +// *
  32 +// * @return int
  33 +// */
  34 +// public function handle()
  35 +// {
  36 +// // 最大协程 1000
  37 +// \co::set(['max_coroutine'=>1000]);
  38 +// // 开启协程池
  39 +// run(function (){
  40 +// // 统计邮箱总数量
  41 +// $size = Email::select(['id'])->count();
  42 +// if($size){
  43 +// // 开启协程的数量
  44 +// $i = ceil($size/100);
  45 +// while ($i >= 0){
  46 +// // 开启一个协程
  47 +// go(function () use (&$i){
  48 +// // 取得邮箱列表
  49 +// $emails = Email::getPushEmailList($i);
  50 +// if($emails){
  51 +// foreach ($emails as $email) {
  52 +//
  53 +// }
  54 +// }
  55 +// });
  56 +//
  57 +// $i--;
  58 +// }
  59 +// }
  60 +//
  61 +// });
  62 +//
  63 +// return Command::SUCCESS;
  64 +// }
  65 +//
  66 +// /**
  67 +// * 开始同步执行
  68 +// * @param int $id 邮箱id
  69 +// * @author:dc
  70 +// * @time 2023/2/5 17:21
  71 +// */
  72 +// private function push(int $id = 0){
  73 +//
  74 +// $email = Email::where(['status'=>1,'pwd_error'=>0])
  75 +// ->where('id','>',$id)
  76 +// ->orderBy('id','asc')
  77 +// ->select()
  78 +// ->first();
  79 +//
  80 +// if($email){
  81 +// // 登录imap服务器
  82 +// Mail::login($email->email,$email->password,$email->imap);
  83 +// // 设置id
  84 +// Mail::$client[$email]->setId($email->id);
  85 +// // 同步文件夹
  86 +// Mail::syncFolder($email->email);
  87 +//
  88 +// // 获取当前邮箱的所有文件夹
  89 +// $folders = Folder::_all($email->id);
  90 +// // 目前只发现最高2级
  91 +// foreach ($folders as $folder){
  92 +// if(empty($folder['_child'])){
  93 +// // 同步邮件
  94 +// Mail::syncMail($email->email,$folder['origin_folder']);
  95 +// }else{
  96 +// // 循环子级目录,有子级的情况,父级不可操作,且不会有邮件
  97 +// foreach ($folder['_child'] as $f){
  98 +// // 同步邮件
  99 +// Mail::syncMail($email->email,$folder['origin_folder'].'/'.$f['origin_folder']);
  100 +// }
  101 +// }
  102 +//
  103 +// }
  104 +//
  105 +// // 再次执行
  106 +// $this->push($email->id);
  107 +//
  108 +// }
  109 +//
  110 +// }
  111 +//
  112 +//
  113 +//
  114 +//}
  1 +<?php
  2 +//
  3 +//namespace App\Console\Commands;
  4 +//
  5 +//use App\Models\Email;
  6 +//use App\Models\Folder;
  7 +//use Helper\Mail\Mail;
  8 +//use Illuminate\Console\Command;
  9 +//use Illuminate\Support\Facades\Cache;
  10 +//use Illuminate\Support\Facades\Log;
  11 +//use function Co\run;
  12 +//
  13 +//class SyncFolder extends Command
  14 +//{
  15 +// /**
  16 +// * The name and signature of the console command.
  17 +// *
  18 +// * @var string
  19 +// */
  20 +// protected $signature = 'SyncFolder';
  21 +//
  22 +// /**
  23 +// * The console command description.
  24 +// *
  25 +// * @var string
  26 +// */
  27 +// protected $description = '同步邮箱的文件夹';
  28 +//
  29 +// /**
  30 +// * Execute the console command.
  31 +// *
  32 +// * @return int
  33 +// */
  34 +// public function handle()
  35 +// {
  36 +//
  37 +// // 这个是防止前一个任务没有完成,反而无法取得资源。
  38 +// $rand = date('dHi');
  39 +// // 携程数量越大,需要的内存越大
  40 +// $max_coroutine = 10000;
  41 +// // 最大携程数量
  42 +// \co::set(['max_coroutine'=>$max_coroutine]);
  43 +//
  44 +// run(function () use ($rand){
  45 +// // 获取邮箱总数量
  46 +// $size = Email::where(['status'=>1,'pwd_error'=>0])->count();
  47 +//// Cache::set('syncMail:total',$size);
  48 +// // 创建的携程数量
  49 +// $max_coroutine = $size > 3000 ? 3000 : $size;
  50 +// if ($max_coroutine){
  51 +// for ($i = $max_coroutine; $i > 0; $i--) {
  52 +// // 创建携程
  53 +// go(function () use ($size,$rand){
  54 +//// Log::error('携程:'.\co::getCid());
  55 +//
  56 +// $n = 0;
  57 +// while ($n <= $size){
  58 +// // 是否已存在
  59 +// if(Cache::add('syncMail'.$rand.':'.$n,$n,7200)) {
  60 +// $this->sync($n);
  61 +// }
  62 +// $n++;
  63 +// }
  64 +//
  65 +// });
  66 +// }
  67 +// }
  68 +//
  69 +// });
  70 +//
  71 +// return Command::SUCCESS;
  72 +// }
  73 +//
  74 +// /**
  75 +// * 开始同步执行
  76 +// * @param int $n 第几条数据
  77 +// * @author:dc
  78 +// * @time 2023/2/5 17:21
  79 +// */
  80 +// private function sync(int $n = 0){
  81 +//
  82 +// $email = Email::where(['status'=>1,'pwd_error'=>0])
  83 +// ->orderBy('id','asc')
  84 +// ->select()->limit($n,1)
  85 +// ->first();
  86 +//
  87 +// if($email){
  88 +// // 登录imap服务器
  89 +// Mail::login($email->email,$email->password,$email->imap);
  90 +// // 设置id
  91 +// Mail::$client[$email]->setId($email->id);
  92 +// // 同步文件夹
  93 +// Mail::syncFolder($email->email);
  94 +//
  95 +// // 获取当前邮箱的所有文件夹
  96 +// $folders = Folder::_all($email->id);
  97 +// // 目前只发现最高2级
  98 +// foreach ($folders as $folder){
  99 +// if(empty($folder['_child'])){
  100 +// // 同步邮件
  101 +// Mail::syncMail($email->email,$folder['origin_folder']);
  102 +// }else{
  103 +// // 循环子级目录,有子级的情况,父级不可操作,且不会有邮件
  104 +// foreach ($folder['_child'] as $f){
  105 +// // 同步邮件
  106 +// Mail::syncMail($email->email,$folder['origin_folder'].'/'.$f['origin_folder']);
  107 +// }
  108 +// }
  109 +//
  110 +// }
  111 +//
  112 +//
  113 +//
  114 +// }
  115 +//
  116 +// }
  117 +//
  118 +//
  119 +//
  120 +//}
  1 +<?php
  2 +
  3 +namespace App\Console\Commands;
  4 +
  5 +use App\Models\Email;
  6 +use App\Models\Folder;
  7 +use Helper\Mail\Mail;
  8 +use Illuminate\Console\Command;
  9 +use Illuminate\Support\Facades\Cache;
  10 +use Illuminate\Support\Facades\Log;
  11 +use function Co\run;
  12 +
  13 +class SyncMailList extends Command
  14 +{
  15 + /**
  16 + * The name and signature of the console command.
  17 + *
  18 + * @var string
  19 + */
  20 + protected $signature = 'SyncMailList';
  21 +
  22 + /**
  23 + * The console command description.
  24 + *
  25 + * @var string
  26 + */
  27 + protected $description = '同步邮箱的邮件到本地';
  28 +
  29 + /**
  30 + * Execute the console command.
  31 + *
  32 + * @return int
  33 + */
  34 + public function handle()
  35 + {
  36 +
  37 + // 这个是防止前一个任务没有完成,反而无法取得资源。
  38 + $rand = date('dHi');
  39 + // 携程数量越大,需要的内存越大
  40 + $max_coroutine = 10000;
  41 + // 最大携程数量
  42 + \co::set(['max_coroutine'=>$max_coroutine]);
  43 +
  44 + run(function () use ($rand,$max_coroutine){
  45 + // 获取邮箱总数量
  46 + $size = Email::where([])->count();
  47 + // 最后一条数据的id
  48 + $lastId = Email::where([])->orderBy('id','desc')->value('id');
  49 + // 创建的携程数量
  50 + $max_coroutine = $size > $max_coroutine ? $max_coroutine : $size;
  51 + if ($max_coroutine){
  52 + for ($i = $max_coroutine; $i > 0; $i--) {
  53 + // 创建携程
  54 + go(function () use ($size,$rand,$lastId){
  55 +
  56 + $n = 1;
  57 + while ($n <= $lastId){
  58 + // 是否已存在
  59 + if(Cache::add('syncMail'.$rand.':'.$n,$n,7200)) {
  60 + $this->sync($n);
  61 + }
  62 + $n++;
  63 + }
  64 +
  65 + });
  66 + }
  67 + }
  68 +
  69 + });
  70 +
  71 + return Command::SUCCESS;
  72 + }
  73 +
  74 + /**
  75 + * 开始同步执行
  76 + * @param int $n 第几条数据
  77 + * @author:dc
  78 + * @time 2023/2/5 17:21
  79 + */
  80 + private function sync(int $n = 0){
  81 +
  82 + $email = Email::where(['id'=>$n])->first();
  83 + // 密码没有错误,且状态正常的
  84 + if ($email && $email->pwd_error == 0 && $email->status == Email::STATUS_ACTIVE){
  85 + // 登录imap服务器
  86 + Mail::login($email->email,$email->password,$email->imap);
  87 + // 设置id
  88 + Mail::$client[$email]->setId($email->id);
  89 + // 同步文件夹
  90 + Mail::syncFolder($email->email);
  91 +
  92 + // 获取当前邮箱的所有文件夹
  93 + $folders = Folder::_all($email->id);
  94 + // 目前只发现最高2级
  95 + foreach ($folders as $folder){
  96 + if(empty($folder['_child'])){
  97 + // 同步邮件
  98 + Mail::syncMail([],$email->email,$email->id,$folder['id'],$folder['origin_folder']);
  99 + }else{
  100 + // 循环子级目录,有子级的情况,父级不可操作,且不会有邮件
  101 + foreach ($folder['_child'] as $f){
  102 + // 同步邮件
  103 + Mail::syncMail([],$email->email,$email->id,$f['id'],$folder['origin_folder'].'/'.$f['origin_folder']);
  104 + }
  105 + }
  106 +
  107 + }
  108 + }
  109 +
  110 + }
  111 +
  112 +
  113 +
  114 +}
@@ -4,8 +4,8 @@ namespace App\Http\Controllers; @@ -4,8 +4,8 @@ namespace App\Http\Controllers;
4 4
5 use App\Models\Email; 5 use App\Models\Email;
6 use App\Models\Host; 6 use App\Models\Host;
7 -use Helper\Fun;  
8 use Helper\Mail\Imap; 7 use Helper\Mail\Imap;
  8 +use Helper\Mail\Mail;
9 9
10 /** 10 /**
11 * 提供邮件各项数据 11 * 提供邮件各项数据
@@ -35,10 +35,10 @@ class MailApi @@ -35,10 +35,10 @@ class MailApi
35 ]); 35 ]);
36 36
37 if($validator->fails()){ 37 if($validator->fails()){
38 - Fun::response() 38 + return res()
39 ->message($validator->errors()->first()) 39 ->message($validator->errors()->first())
40 ->status(400) 40 ->status(400)
41 - ->throw(); 41 + ->toJson();
42 } 42 }
43 43
44 // host 44 // host
@@ -50,30 +50,33 @@ class MailApi @@ -50,30 +50,33 @@ class MailApi
50 50
51 $model->imap = $formData['imap']; 51 $model->imap = $formData['imap'];
52 $model->smtp = $formData['smtp']; 52 $model->smtp = $formData['smtp'];
53 - $model->password = encrypt($formData['password']); 53 + $model->password = @base64_encode($formData['password']);
54 54
55 - $imap = new Imap();  
56 - // 是否初始成功  
57 try { 55 try {
58 - $imap->login("ssl://{$formData['imap']}:993",$model->email,$model->password); 56 + Mail::login($model->email,$model->password,$model->imap);
59 }catch (\Throwable $e){ 57 }catch (\Throwable $e){
60 - Fun::response() 58 + return res()
61 ->message($e->getMessage()) 59 ->message($e->getMessage())
62 ->status(400) 60 ->status(400)
63 - ->throw(); 61 + ->toJson();
64 } 62 }
65 63
66 // 登录成功了,密码验证字段通过 64 // 登录成功了,密码验证字段通过
67 - $model->pass_error = 0; 65 + $model->pwd_error = 0;
68 // 保存好邮箱 66 // 保存好邮箱
69 $model->save(); 67 $model->save();
70 68
  69 + // 设置上id,方便后面使用
  70 + Mail::$client[$model->email]->setId($model->id);
  71 +
71 // 开始同步文件夹 72 // 开始同步文件夹
72 - $folder = $imap->getFolder(); 73 +// $folder = Mail::syncFolder($model->email);
73 74
74 - Fun::response()  
75 - ->data($folder)  
76 - ->throw(); 75 + return res()
  76 + ->data([
  77 + 'token' => token_en($model->id.','.$model->email.','.time())
  78 + ])
  79 + ->toJson();
77 } 80 }
78 81
79 82
@@ -86,7 +89,7 @@ class MailApi @@ -86,7 +89,7 @@ class MailApi
86 89
87 $host = Host::_all(); 90 $host = Host::_all();
88 91
89 - Fun::response()->data($host)->throw(); 92 + res()->data($host)->throw();
90 93
91 } 94 }
92 95
1 -<?php  
2 -  
3 -namespace App\Mail\Jobs;  
4 -  
5 -/**  
6 - *  
7 - * @time 2022/7/29 15:11  
8 - * Class ImapApi  
9 - * @package App\Mail\Jobs  
10 - */  
11 -class ImapApi {  
12 -  
13 - /**  
14 - * @var ImapApi  
15 - */  
16 - private static $Instance;  
17 -  
18 - /**  
19 - * 如 imap.qq.com  
20 - * @var string  
21 - */  
22 - private $host;  
23 -  
24 - /**  
25 - * 邮箱地址  
26 - * @var string  
27 - */  
28 - private $username;  
29 -  
30 - /**  
31 - * 密码  
32 - * @var string  
33 - */  
34 - private $password;  
35 -  
36 - /**  
37 - * 端口  
38 - * @var int  
39 - */  
40 - private $port = 993;  
41 -  
42 -  
43 - private function __construct(){}  
44 -  
45 -  
46 - /**  
47 - * @return ImapApi  
48 - */  
49 - public static function getInstance(): ImapApi  
50 - {  
51 - if(!self::$Instance){  
52 - self::$Instance = new static();  
53 - }  
54 -  
55 - return self::$Instance;  
56 - }  
57 -  
58 -  
59 -  
60 -}  
1 -<?php  
2 -namespace App\Mail\Jobs;  
3 -  
4 -  
5 -use App\Http\Mail\lib\MailFun;  
6 -use App\Http\Mail\Models\Email;  
7 -use App\Http\Mail\Models\EmailSendJob;  
8 -use App\Http\Mail\Models\EmailSendJobStatu;  
9 -use App\Http\Models\EmailSendTemplate;  
10 -use App\Sk;  
11 -use Illuminate\Bus\Queueable;  
12 -use Illuminate\Contracts\Queue\ShouldQueue;  
13 -use Illuminate\Foundation\Bus\Dispatchable;  
14 -use Illuminate\Queue\InteractsWithQueue;  
15 -use Illuminate\Queue\SerializesModels;  
16 -use Illuminate\Support\Facades\Cache;  
17 -  
18 -/**  
19 - * @author:dc  
20 - * @time 2022/11/9 11:52  
21 - * Class SendJob  
22 - */  
23 -class SendJob implements ShouldQueue  
24 -{  
25 - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;  
26 -  
27 - public $job_id;  
28 -  
29 - /**  
30 - * SendServiceMsg constructor.  
31 - * @param $id  
32 - */  
33 - public function __construct($id)  
34 - {  
35 - // 使用的链接  
36 - $this->connection = Sk::QUEUE_EMAIL_QUN;  
37 -  
38 - $this->job_id = $id;  
39 - }  
40 -  
41 - /**  
42 - * Execute the job.  
43 - *  
44 - * @return bool  
45 - */  
46 - public function handle()  
47 - {  
48 - // 查询邮件任务主体  
49 - $jobData = EmailSendJob::where('id',$this->job_id)  
50 - ->whereIn('status',[EmailSendJob::STATUS_WAIT,EmailSendJob::STATUS_RUNING])  
51 - ->first();  
52 -  
53 - if(!$jobData){  
54 - $this->log('任务不存在');  
55 - return false;  
56 - }  
57 -  
58 - // 发送时间  
59 - if(trim($jobData->send_time) != 'now'){  
60 - // 定时发送  
61 - list($start,$end) = explode(',',$jobData->send_time);  
62 - // 系统是中国时区,按照美国时区要慢13个小时  
63 - if(!(date('H') >= $start || date('H') < $end)){  
64 - // 也就是 美国早上 8点到晚上10点  
65 -// $this->log('休息中');  
66 - SendJob::dispatch($this->job_id)->delay(600);  
67 - return true;  
68 - }  
69 -  
70 - }  
71 -  
72 - $this->log('开始检查任务:'.$this->job_id);  
73 - if(!$jobData){  
74 - $this->log('没有找到任务');  
75 - return true;  
76 - }  
77 - // 查询需要发送的邮件  
78 - $jobStdata = EmailSendJobStatu::where(['job_id'=>$this->job_id,'status'=>EmailSendJobStatu::STATUS_WAIT])->first();  
79 - if(!$jobStdata){  
80 - $this->log('完成所有了');  
81 - // 是否完成  
82 - $jobData->status = EmailSendJob::STATUS_SUCCESS;  
83 - $jobData->save();  
84 - return true;  
85 - }  
86 - $this->log('找到任务:'.$jobStdata['id']);  
87 -  
88 - // 防止重复  
89 - $cachekey = 'email_send_job:'.$this->job_id.":".$jobStdata['id'];  
90 - // 存在  
91 - if(Cache::has($cachekey)){  
92 - $this->log("任务:{$jobStdata['id']}正在发送,跳过");  
93 - SendJob::dispatch($this->job_id)->delay(30);  
94 - return true;  
95 - }  
96 - // 占有2分钟  
97 - Cache::set($cachekey,$jobStdata['id'],120);  
98 -  
99 -  
100 - // 是否等待状态  
101 - if($jobData->status !== EmailSendJob::STATUS_RUNING){  
102 - $this->log('开始运行脚本了');  
103 - $jobData->status = EmailSendJob::STATUS_RUNING;;  
104 - $jobData->save();  
105 - }  
106 -  
107 - // 当前管理账号下所有绑定的邮件  
108 - $emailinfos = Email::_get($jobData->user_id,Email::STATUS_ACTIVE,['e.id','e.email','e.email_name','e.smtp','e.password','e.pwd_error']);  
109 -  
110 - $smtpErrorNum = 0;// 错误次数  
111 -  
112 - // 标签  
113 - $tags = $jobData->tags;  
114 - $tags = is_array($tags) ? $tags : explode(',',$tags);  
115 - // 模板列表  
116 - $tempLists = EmailSendTemplate::getAdminTagsList($tags);  
117 - if(!$tempLists){  
118 - // 是否完成  
119 - $jobData->status = EmailSendJob::STATUS_SUCCESS;  
120 - $jobData->save();  
121 - $this->log('没有可用的模板');  
122 - return false;  
123 - }  
124 - // 随机一个模板  
125 - $template = $tempLists[array_rand($tempLists->toArray())];  
126 -  
127 -  
128 - // 节点  
129 - EMAILRESETRANG:  
130 -  
131 - if(!$emailinfos){  
132 - $this->log('暂时没有可分配的账号');  
133 - // 再次发布任务,延时10分钟  
134 - SendJob::dispatch($this->job_id)->delay(120);  
135 - return true;  
136 - }  
137 -  
138 - // 随机一个邮件来当发送  
139 - $key = array_rand($emailinfos,1);  
140 - $emailinfo = $emailinfos ? $emailinfos[$key] : [];  
141 - if(!$emailinfo){  
142 - $this->log('没有分配到账号');  
143 - // 再次发布任务,延时10分钟  
144 - SendJob::dispatch($this->job_id)->delay(600);  
145 - return true;  
146 - }  
147 - unset($emailinfos[$key]);  
148 - $emailinfos = array_values($emailinfos);  
149 -  
150 - // 密码是否需要验证  
151 - if($emailinfo['pwd_error']){  
152 - $this->log('账号密码验证失败,需要修改密码 '.$emailinfo['email']);  
153 - // 重新随机一个,  
154 - goto EMAILRESETRANG;  
155 - }  
156 -  
157 - // 每个小时不能超过20  
158 - $cacheEmailSuccesshkey = 'email_job_email:'.$emailinfo['email'].":success_h";  
159 - if(Cache::get($cacheEmailSuccesshkey,0) >= 8){  
160 - $this->log('账号超过每小时8封了'.$emailinfo['email']);  
161 - // 重新随机一个,  
162 - goto EMAILRESETRANG;  
163 - }  
164 -  
165 - // 一天中是否超过100,  
166 -// $cacheEmailSuccessdkey = 'email_job_email:'.$emailinfo['email'].":success_d";  
167 -// if(Cache::get($cacheEmailSuccessdkey,0) >= 100){  
168 -// $this->log('账号超过每天100封了'.$emailinfo['email']);  
169 -// // 重新随机一个,  
170 -// goto EMAILRESETRANG;  
171 -// }  
172 -  
173 - $cachemailikey = 'email_job_email:'.$emailinfo['email'].":success_i";  
174 - if (Cache::has($cachemailikey)){  
175 - $this->log('账号没超过10分钟间隔 '.$emailinfo['email']);  
176 - // 重新随机一个,  
177 - goto EMAILRESETRANG;  
178 - }  
179 -  
180 - // 是否错误了  
181 - $cachemailierrordkey = 'email_job_email:'.$emailinfo['email'].":error_d";  
182 - if(Cache::has($cachemailierrordkey)){  
183 - $this->log('账号记录错误了,跳过 1小时'.$emailinfo['email']);  
184 - goto EMAILRESETRANG;  
185 - }  
186 -  
187 - Cache::set($cachemailikey,$jobStdata['id'],550);  
188 -  
189 - $this->log('找到账号:'.$emailinfo['email']);  
190 - // 数据发送的email  
191 - $data = json_decode($jobData->maildata,true);  
192 -  
193 - $data['body'] = $template['body'];  
194 - $data['subject'] = $template['subject'];  
195 -  
196 - try {  
197 -  
198 - // 替换邮件规则,  
199 - $data['body'] = str_replace('[read][/read]','<div style="opacity: 0;width: 1px;height: 1px;overflow: hidden;"><img src="https://king.shopk.com/_shopk_?mail='.base64_encode('logo|'.$jobStdata['id']).'" /></div>',$data['body']);  
200 - // 匹配链接规则  
201 - if (preg_match_all("/\[link:(.*)\](.*)\[\/link\]/U",$data['body'],$m)){  
202 - foreach ($m[0] as $mk=>$item){  
203 - $data['body'] = str_replace($item,'<a target="_blank" href="https://king.shopk.com/_shopk_?mail='.base64_encode('link|'.$jobStdata['id'].'|'.urlencode($m[1][$mk])).'">'.$m[2][$mk].'</a>',$data['body']);  
204 - }  
205 - }  
206 -  
207 - // 发送邮件  
208 - MailFun::sendEmail(  
209 - $emailinfo['smtp'],$emailinfo['email'],decrypt($emailinfo['password'])  
210 - ,$emailinfo['email_name'],$jobStdata['to_email'],$data['subject'],  
211 - $data['body'],$data['file']??[],false  
212 - ,($data['priority']??0) ? 1 : 3  
213 - );  
214 -  
215 - // 记录  
216 - $jobStdata->status = EmailSendJobStatu::STATUS_SUCCESS;  
217 - $jobStdata->send_email = $emailinfo['email'];  
218 - $jobStdata->time = date('Y-m-d H:i:s');  
219 - $this->log('成功了');  
220 - // 成功  
221 - $jobData->success = $jobData->success+1;  
222 -  
223 - // 24小时内不能超过100  
224 -// if(Cache::has($cacheEmailSuccessdkey)){  
225 -// // 加1  
226 -// Cache::increment($cacheEmailSuccessdkey);  
227 -// }else{  
228 -// Cache::set($cacheEmailSuccessdkey,1,86400);  
229 -// }  
230 -  
231 - // 每小时内不能超过20  
232 - if(Cache::has($cacheEmailSuccesshkey)){  
233 - // 加1  
234 - Cache::increment($cacheEmailSuccesshkey);  
235 - }else{  
236 - Cache::set($cacheEmailSuccesshkey,1,3600);  
237 - }  
238 -  
239 -  
240 -  
241 - } catch (\Exception $e) {  
242 - if($e->getMessage()=='SMTP Error: data not accepted.'){  
243 - // 下次跳过账号  
244 - if(!Cache::has($cachemailierrordkey)) {  
245 - Cache::set($cachemailierrordkey, $e->getMessage(), 3600);  
246 - }  
247 - }  
248 - // 无法验证,密码错误了,处理密码  
249 - if($e->getMessage()=='SMTP Error: Could not authenticate.'){  
250 - Email::_update(['id'=>$emailinfo['id']],['pwd_error'=>1]);  
251 - }  
252 -  
253 -  
254 - $smtpErrorNum++;  
255 - // 超过失败3次的  
256 - $error = $jobStdata->error;  
257 - $error[] = [  
258 - 'email' => $emailinfo['email'],  
259 - 'message' => $e->getMessage(),  
260 - 'file' => $e->getFile(),  
261 - 'line' => $e->getLine()  
262 - ];  
263 - $jobStdata->time = date('Y-m-d H:i:s');  
264 - $jobStdata->error = json_encode($error);  
265 -  
266 - // 如果失败了,重试3次  
267 - if($smtpErrorNum <= 20){  
268 - // 记录  
269 - $jobStdata->save();  
270 - $this->log('错误次数'.$smtpErrorNum);  
271 - goto EMAILRESETRANG;  
272 - }  
273 - $this->log('错误超过次数'.$smtpErrorNum);  
274 - // 记录  
275 - $jobStdata->status = EmailSendJobStatu::STATUS_ERROR;  
276 - $jobStdata->send_email = $emailinfo['email'];  
277 -  
278 - // 错误  
279 - $jobData->error = $jobData->error+1;  
280 - }  
281 -  
282 - // 保存  
283 - $jobStdata->save();  
284 - $jobData->save();  
285 - $this->log('完成');  
286 -  
287 - // 再次发布任务  
288 - SendJob::dispatch($this->job_id);  
289 -  
290 -  
291 - }  
292 -  
293 -  
294 - protected function log($content){  
295 - @file_put_contents(storage_path('logs/send_email_job_'.$this->job_id.'_.log'),date('Y-m-d H:i:s ').$content.PHP_EOL,FILE_APPEND);  
296 - }  
297 -  
298 -}  
1 -<?php  
2 -  
3 -namespace App\Mail;  
4 -  
5 -use App\Mail\Jobs\SendJob;  
6 -use App\Mail\lib\Lang;  
7 -use App\Mail\lib\MailFun;  
8 -use App\Mail\lib\MailParse\Body;  
9 -use App\Models\Email;  
10 -use App\Models\EmailBody;  
11 -use App\Models\EmailContact;  
12 -use App\Models\EmailContactGroup;  
13 -use App\Models\EmailFolder;  
14 -use App\Models\Host;  
15 -use App\Models\EmailList;  
16 -use App\Models\EmailLog;  
17 -use App\Models\EmailSendJob;  
18 -use Illuminate\Http\UploadedFile;  
19 -use Illuminate\Support\Facades\Config;  
20 -use Illuminate\Support\Facades\DB;  
21 -use Illuminate\Support\Facades\Log;  
22 -use Illuminate\Support\Facades\Validator;  
23 -  
24 -/**  
25 - * 邮件服务  
26 - * @time 2022/7/29 15:05  
27 - * Class Mail  
28 - * @package app\Mail  
29 - */  
30 -class Mail {  
31 -  
32 - /**  
33 - * @var Mail  
34 - */  
35 - private static $Instance;  
36 -  
37 - /**  
38 - * 如 imap.qq.com  
39 - * @var array  
40 - */  
41 - private $host = [  
42 - 'imap'=>'',  
43 - 'smtp'=>''  
44 - ];  
45 -  
46 - /**  
47 - * 用户email表的id  
48 - * @var int  
49 - */  
50 - private $id = 0;  
51 -  
52 - /**  
53 - * 邮箱地址  
54 - * @var string  
55 - */  
56 - private $username = '';  
57 - private $nickname = '';  
58 -  
59 - /**  
60 - * 密码  
61 - * @var string  
62 - */  
63 - private $password = '';  
64 -  
65 - /**  
66 - * 端口  
67 - * @var int 143 993  
68 - */  
69 - private $port = 993;  
70 - /**  
71 - * 协议  
72 - * @var string  
73 - */  
74 - private $ssl = 'ssl://';  
75 -  
76 - /**  
77 - * 用户id  
78 - * @var int  
79 - */  
80 - private $user_id = 0;  
81 -  
82 - /**  
83 - * 附件保存目录  
84 - * @var string  
85 - */  
86 - private $filePath;  
87 -  
88 -  
89 -  
90 - /**  
91 - * imap服务器连接  
92 - * @var \App\Http\Mail\lib\client\Imap[]  
93 - */  
94 - private $client;  
95 -  
96 -  
97 - /**  
98 - * 连接目录 INBOX  
99 - * @var string  
100 - */  
101 - private $folder = 'INBOX';  
102 -  
103 - /**  
104 - * 目录编号  
105 - * @var int  
106 - */  
107 - private $folderId = 0;  
108 -  
109 - /**  
110 - * @var array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Http\Request|string|null  
111 - */  
112 - private $request;  
113 -  
114 - /**  
115 - * 所有绑定邮箱的id  
116 - * @var array  
117 - */  
118 - private $email_ids = [];  
119 -  
120 -  
121 - private function __construct(){  
122 - $this->request = request();  
123 - }  
124 -  
125 -  
126 - /**  
127 - * @return Mail  
128 - */  
129 - public static function getInstance(int $user_id, string $email=''): Mail  
130 - {  
131 - $key = md5($user_id.$email);  
132 -  
133 - if(empty(self::$Instance[$key])){  
134 - $mail = self::$Instance[$key] = new static();  
135 -  
136 - $mail->user_id = $user_id;  
137 -  
138 - // 绑定的所有邮箱id  
139 - $mail->email_ids = array_column(Email::_getById($mail->user_id),'id');  
140 -  
141 - if($email){  
142 - $data = Email::_first($email);  
143 - // 是否存在并绑定了  
144 - if($data && in_array($data['id'],$mail->email_ids)){  
145 - $mail->id = $data['id'];  
146 - $mail->username = $email;  
147 - $mail->nickname = $data['email_name']??'';  
148 - $mail->password = $data['password'];  
149 - // 当前选择的邮箱  
150 - $mail->host = [  
151 - 'imap' => $data['imap'],  
152 - 'smtp' => $data['smtp'],  
153 - ];  
154 - // 设置目录  
155 - $mail->selectFolder($mail->folder);  
156 - }else{  
157 - throw new \Exception(Lang::__('email_not_bind',$email));  
158 - }  
159 - }  
160 - }  
161 -  
162 - // 设置邮件附件地址  
163 - self::$Instance[$key]->setFilePath(storage_path('/imap'));  
164 -  
165 - return self::$Instance[$key];  
166 - }  
167 -  
168 - /**  
169 - * 设置目录  
170 - * @param $path  
171 - * @time 2022/8/1 15:39  
172 - */  
173 - public function setFilePath($path){  
174 - $this->filePath = $path;  
175 -// if(!is_dir($this->filePath)){  
176 -// @mkdir($this->filePath,0775,true);  
177 -// }  
178 - }  
179 -  
180 - /**  
181 - * @return string  
182 - */  
183 - public function getFilePath(): string  
184 - {  
185 - $path = $this->filePath.'/'.$this->username.'/';  
186 -// if(is_dir($path)){  
187 -// mkdir($path,0775,true);  
188 -// }  
189 - return $path;  
190 - }  
191 -  
192 -  
193 - /**  
194 - * 选择使用的用户  
195 - * @param int $user_id  
196 - * @param string $email  
197 - * @return Mail  
198 - * @author:dc  
199 - * @time 2022/7/29 17:42  
200 - */  
201 - public static function use(int $user_id, string $email=''):Mail {  
202 - $mail = self::getInstance($user_id,$email);  
203 -  
204 -  
205 - return $mail;  
206 - }  
207 -  
208 - /**  
209 - * 选择文件夹  
210 - * @param string|int $folder  
211 - * @time 2022/8/4 14:54  
212 - */  
213 - public function selectFolder($folder){  
214 - if($folder) {  
215 - $lists = EmailFolder::_all($this->id, false);  
216 - // 找到目标文件的id,pid  
217 - foreach ($lists as $list) {  
218 - if ($list[is_numeric($folder)?'id':'folder'] == $folder) {  
219 - $id = $list['id'];// id  
220 - $pid = $list['pid']; // 上级id  
221 - $folder = $list['origin_folder'];  
222 - }  
223 - }  
224 -  
225 - // 不存在  
226 - if(!$lists){  
227 - $folder = 'INBOX';  
228 - $pid = 0;  
229 - // 这个名字是每个邮箱默认的,不可更改  
230 - $id = EmailFolder::_insert($this->user_id,$this->id,'INBOX','INBOX');  
231 - if($folder != 'INBOX'){  
232 - $id = 0;  
233 - }  
234 - }  
235 -  
236 - // 是否存  
237 - if ($id ?? 0) {  
238 - // 拿到上级,成为 dir/dir/dir  
239 - if ($pid ?? 0) {  
240 - EmailFolder::_firstTree($lists, $pid, $folder,'origin_folder');  
241 - }  
242 - $this->folderId = $id;  
243 - $this->folder = $folder;  
244 - $this->folderInfo = EmailFolder::_first($id);  
245 - } else {  
246 - throw new \Exception(Lang::__('email_folder_not', $folder));  
247 - }  
248 - }  
249 - }  
250 -  
251 - /**  
252 - * @return \App\Http\Mail\lib\client\Imap  
253 - * @throws \Exception  
254 - * @time 2022/8/5 17:22  
255 - */  
256 - public function client($email=''){  
257 -  
258 - $email = $email ? : $this->username;  
259 -  
260 - if(empty($this->client[$email])){  
261 - if($email == $this->username){  
262 - $this->login();  
263 - }else{  
264 - $data = Email::_first($email);  
265 - if($data){  
266 - $this->loginImap($data['imap'],$data['email'],$data['password']);  
267 - }  
268 - }  
269 - }  
270 - return $this->client[$email];  
271 - }  
272 -  
273 - /**  
274 - * 登录邮箱  
275 - * @param string $password  
276 - * @param string $imap  
277 - * @param string $smtp  
278 - * @return bool  
279 - * @throws \Exception  
280 - * @time 2022/8/5 17:21  
281 - */  
282 - public function login(string $password='',string $imap='',string $smtp=''):bool{  
283 - // host  
284 - if($imap && $this->username){  
285 - $this->hostAdd(explode('@',$this->username)[1],$imap,$smtp);  
286 - }  
287 -  
288 - if(!$password && !$this->password){  
289 - throw new \Exception(Lang::__('password_required'));  
290 - }  
291 -  
292 - if($password){  
293 - // 如果密码不一致,更新  
294 - if($password != $this->password){  
295 - Email::_changePwd($this->username, $password);  
296 - }  
297 -  
298 - $this->password = $password;  
299 - }  
300 -  
301 - if(!$this->hostMy()){  
302 - throw new \Exception(Lang::__('login_host'));  
303 - }  
304 -  
305 - // imap imap.qq.com  
306 - $this->loginImap($this->hostMy('imap'),$this->username,$this->password);  
307 - // 密码没验证成功  
308 -// if($this->id){  
309 -// Email::_update(['id'=>$this->id],['pwd_error'=>1]);  
310 -// }  
311 - return true;  
312 -  
313 - }  
314 -  
315 -  
316 - /**  
317 - * 公用链接  
318 - * @param $host  
319 - * @param $email  
320 - * @param $password  
321 - * @throws \Exception  
322 - * @author:dc  
323 - * @time 2022/12/7 9:35  
324 - */  
325 - public function loginImap($host,$email,$password):void {  
326 -  
327 - $this->client[$email] = new \App\Http\Mail\lib\client\Imap();  
328 - // 是否初始成功  
329 - $this->client[$email]->login($this->ssl.$host.':'.$this->port,$email,$password);  
330 -  
331 - }  
332 -  
333 - /**  
334 - * 自动路由  
335 - * @return \Illuminate\Http\JsonResponse|string|\Symfony\Component\HttpFoundation\BinaryFileResponse  
336 - * @throws \Illuminate\Contracts\Container\BindingResolutionException  
337 - * @throws \Psr\SimpleCache\InvalidArgumentException  
338 - * @throws \Throwable  
339 - * @author:dc  
340 - * @time 2022/8/1 11:07  
341 - */  
342 - public function autoRoute(){  
343 - $task = $this->request->get('_task');  
344 - $action = $this->request->get('_action');  
345 - $result = [];  
346 -  
347 - switch ($task){  
348 - case 'sync': {  
349 - // 同步操作  
350 - switch ($action){  
351 - case 'list': {  
352 - // 同步邮件列表  
353 - $msgno = $this->request->post('msgno',[]);  
354 - if(is_string($msgno)){  
355 - $msgno = explode(',',$msgno);  
356 - }  
357 - $result = $this->syncEmailList($msgno);  
358 - break;  
359 - }  
360 - // 同步文件夹  
361 - case 'folder': {  
362 - $folder = $this->request->post('folder');  
363 - $result = $this->syncFolder($folder);  
364 - break;  
365 - }  
366 - // 更新最新的  
367 - case 'new':{  
368 - $result = $this->syncEmailList();  
369 - break;  
370 - }  
371 - }  
372 - break;  
373 - }  
374 - // 设置标签,已读,未读,删除等  
375 - case 'flags':{  
376 - $ids = $this->request->post('ids');  
377 - $ids = is_string($ids) ? explode(',',$ids) : $ids;  
378 - $ids = is_array($ids) ? $ids : [$ids];  
379 - // 获取到邮件的uid  
380 - $uids = EmailList::_getUidsByIds($ids,$this->id);  
381 - $filed = '';  
382 - $value = 0;  
383 - $mod = '';  
384 - $flags = '';  
385 - // 标记操作  
386 - switch ($action){  
387 - // 标记已读  
388 - case 'setSeen':{  
389 - $filed = 'seen';  
390 - $value = 1;  
391 - $flags = 'seen';  
392 - $mod = '+';  
393 - break;  
394 - }  
395 - // 标记未读  
396 - case 'delSeen':{  
397 - $filed = 'seen';  
398 - $value = 0;  
399 - $flags = 'seen';  
400 - $mod = '-';  
401 - break;  
402 - }  
403 - case 'setFlagged':{  
404 - $filed = 'flagged';  
405 - $value = 1;  
406 - $flags = 'flagged';  
407 - $mod = '+';  
408 - break;  
409 - }  
410 - case 'delFlagged':{  
411 - $filed = 'flagged';  
412 - $value = 0;  
413 - $flags = 'flagged';  
414 - $mod = '-';  
415 - break;  
416 - }  
417 - }  
418 -  
419 - if($this->setflagged($uids,$flags,$mod)){  
420 - // 更新标签  
421 - if(EmailList::_setFlags(array_keys($uids),$this->id,$filed,$value)){  
422 - $result = array_keys($uids);  
423 - }  
424 - }  
425 - break;  
426 - }  
427 - case 'info': {  
428 - $id = (int) $this->request->get('id');  
429 - // 读取邮件  
430 - $result = $this->emailInfo($id);  
431 -  
432 - if(!$result){  
433 - $result = new static();  
434 - }else{  
435 - // 是否是数组  
436 - if(!is_array($result)) $result = $result->toArray();  
437 -  
438 - // 缓存一下  
439 - \Cache::set('app_email_info_'.$this->user_id.":".$result['id'],$result,3000);  
440 -  
441 - // 渲染视图  
442 - $result['body'] = view('admin/email/info',[  
443 - 'data' => $result  
444 - ])->render();  
445 - // 有邮件编码bug,必须要转  
446 - $result['body'] = base64_encode($result['body']);  
447 -  
448 - }  
449 - ///////////////////////// 这里是测试  
450 -// print_r($result['body']['text_html']);exit();  
451 -// foreach ($result['body']['text_html'] as $item){  
452 -// if(!empty($item['type']) && ($item['type'] == 'text/html' || $item['type'] == 'text/plain')){  
453 -// header("content-type:text/html;charset=".($item['charset']??'utf-8'));  
454 -// echo $item['body'];  
455 -// }  
456 -// }  
457 -// exit();  
458 - ///////////////////////  
459 -  
460 - break;  
461 - }  
462 - case 'list': {  
463 - if($this->id){  
464 - $eids = [$this->id];  
465 - }else{  
466 - $eids = array_column(Email::_get($this->user_id),'id');  
467 - }  
468 -  
469 - $total = 0;  
470 -  
471 - if($this->folder=='INBOX'){  
472 - $fids = EmailFolder::_user_folders($this->email_ids);  
473 -// $total = array_sum(array_column($fids,'exsts'));  
474 - $fids = array_column($fids,'id');  
475 -  
476 - $unseen = array_sum(array_column($fids,'unseen')); // 未读数量  
477 -// $unseen = EmailList::_getUnseenNum($eids); // 未读数量  
478 - }else{  
479 - $fids = [$this->folderId];  
480 -  
481 - $total = $this->folderInfo['exsts']??0;  
482 - }  
483 -  
484 - // 搜索  
485 - $search = [];  
486 - $search['search'] = $this->request->get('search');  
487 - $search['seen'] = $this->request->get('seen');  
488 -  
489 -  
490 - $result = $this->emailLists($eids,$fids,$total,$search)->toArray();  
491 -  
492 -  
493 -// if($this->folder == 'INBOX'){  
494 - $this->allemail = [];  
495 - foreach ($result['data'] as $k=>$datum){  
496 - if(!$datum['uid']){  
497 - // 邮件  
498 - if (empty($this->allemail[$datum['email_id']])){  
499 - $this->allemail[$datum['email_id']] = Email::_firstById($datum['email_id']);  
500 - }  
501 - if(empty($this->allemail[$datum['email_id']])){  
502 - continue;  
503 - }  
504 - // 同步  
505 - $id = $this->syncEmailList(  
506 - [$datum['msgno']],  
507 - $datum['email_id'],  
508 - $this->allemail[$datum['email_id']]['email'],  
509 - $datum['folder_id'],  
510 - $this->folder  
511 - );  
512 - if($id['ids']){  
513 - // 重新获取  
514 - $result['data'][$k] = EmailList::_first($id['ids'][0]);  
515 - }  
516 - }  
517 - // 干掉 -snv 和 (Failure)  
518 - if(preg_match("/(\-\ssnv)|(\(Failure\))|(\(Delay\))$/",$datum['subject'])){  
519 - unset($result['data'][$k]);  
520 - continue;  
521 - }  
522 - }  
523 - $result['data'] = array_values($result['data']);  
524 -// }  
525 -  
526 - // 未读邮件数量  
527 - $result['unseen'] = $unseen??0;  
528 - break;  
529 - }  
530 - case 'mail': {  
531 - $result = $this->emails();  
532 - break;  
533 - }  
534 - case 'add': {  
535 - $result = $this->emailAdd();  
536 - break;  
537 - }  
538 - case 'folder': {  
539 -  
540 - $email = $this->request->post('email');  
541 - if($email){  
542 - $ids = Email::_getIds($email);  
543 - }else{  
544 - $ids = $this->id;  
545 - }  
546 -  
547 - $result = $this->folders($ids);  
548 - break;  
549 - }  
550 - case 'contact': {  
551 - switch ($action){  
552 - case 'info': {  
553 - $id = $this->request->get('id');  
554 - $result = $this->contactInfo($id);  
555 - break;  
556 - }  
557 - case 'add': {  
558 - $result = $this->contactAdd();  
559 - break;  
560 - }  
561 - case 'del': {  
562 - $result = $this->contactDel();  
563 - break;  
564 - }  
565 - case 'group':{  
566 - $is_contact = $this->request->get('is_contact');  
567 - $result = $this->contactGroup($is_contact);  
568 - break;  
569 - }  
570 - case 'group_save': {  
571 - $name = $this->request->post('group_name');  
572 - $id = $this->request->post('id',0);  
573 - $result = $this->contactGroupSave($name,$id);  
574 - break;  
575 - }  
576 - case 'group_del': {  
577 - $group_id = (int) $this->request->get('group_id');  
578 - $result = $this->contactGroupDel($group_id);  
579 - break;  
580 - }  
581 - default: {  
582 - $is_group = $this->request->get('is_group',false);  
583 - $result = $this->contact($is_group);  
584 - break;  
585 - }  
586 - }  
587 - break;  
588 - }  
589 - // 下载附件  
590 - case 'download':{  
591 - $name = $this->request->get('name');  
592 - $originname = $this->request->get('originname');  
593 - return $this->download($name,$originname);  
594 - }  
595 - case 'contact_view':{  
596 - return $this->contact_view();  
597 - }  
598 - // 发送邮件  
599 - case 'send_email':{  
600 - $data = $this->request->post('to');  
601 -  
602 - // 文件收件人  
603 - $to = $this->request->file()['to']['to_email_file']??[];  
604 - if($to && $to instanceof UploadedFile){  
605 - $data['to'] = ($data['to']??'')."\n".$to->getContent();  
606 - }  
607 -  
608 -  
609 - // 附件  
610 - $data['file'] = $this->request->file()['to']['file']??[];  
611 - if($data['file']){  
612 -  
613 - $upconfig = [  
614 - // 上传文件的大小范围  
615 - 'size' => [  
616 - 'max' => 1024 * 1024 * 100, // 100M大了服务器不知道会发生什么  
617 - 'min' => 1, // 0k  
618 - ],  
619 - // 扩展名  
620 - 'ext' => [  
621 - 'xlsx', 'xltx', 'potx', 'ppsx', 'pptx', 'sldx', 'docx', 'dotx', 'xlam', 'xlsb',  
622 - 'apk', 'doc', 'pdf', 'xls', 'ppt', /*'jar', 'js', 'json', 'rpm',*/  
623 - 'swf', 'tar', 'zip', 'gif', 'png', 'flv', 'avi', 'ai', 'gz',  
624 - 'jpg', 'mov', 'mp3', 'mp4', 'txt', 'webm', 'webp',  
625 - ],  
626 - // mime类型  
627 - 'mime' => [  
628 - /*'xlsx' => */'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',  
629 - /*'xltx' => */'application/vnd.openxmlformats-officedocument.spreadsheetml.template',  
630 - /*'potx' => */'application/vnd.openxmlformats-officedocument.presentationml.template',  
631 - /*'ppsx' => */'application/vnd.openxmlformats-officedocument.presentationml.slideshow',  
632 - /*'pptx' => */'application/vnd.openxmlformats-officedocument.presentationml.presentation',  
633 - /*'sldx' => */'application/vnd.openxmlformats-officedocument.presentationml.slide',  
634 - /*'docx' => */'application/vnd.openxmlformats-officedocument.wordprocessingml.document',  
635 - /*'dotx' => */'application/vnd.openxmlformats-officedocument.wordprocessingml.template',  
636 - /*'xlam' => */'application/vnd.ms-excel.addin.macroEnabled.12',  
637 - /*'xlsb' => */'application/vnd.ms-excel.sheet.binary.macroEnabled.12',  
638 - /*'apk' => */'application/vnd.android.package-archive',  
639 - /*'doc' => */'application/msword',  
640 - /*'pdf' => */'application/pdf',  
641 - /*'xls' => */'application/vnd.ms-excel',  
642 - /*'ppt' => */'application/vnd.ms-powerpoint',  
643 -// /*'jar' => */'application/java-archive',  
644 -// /*'js' => */'application/javascript',  
645 -// /*'json' => */'application/json',  
646 -// /*'rpm' => */'application/x-rpm',  
647 - /*'swf' => */'application/x-shockwave-flash',  
648 - /*'tar' => */'application/x-tar',  
649 - /*'zip' => */'application/zip',  
650 - /*'gif' => */'image/gif',  
651 - /*'png' => */'image/png',  
652 - /*'flv' => */'video/x-flv',  
653 - /*'avi' => */'video/x-msvideo',  
654 - /*'ai' => */'application/postscript',  
655 - /*'gz' => */'application/x-gzip',  
656 - /*'jpg' => */'image/jpeg',  
657 - /*'mov' => */'video/quicktime',  
658 - /*'mp3' => */'audio/mpeg',  
659 - /*'mp4' => */'video/mp4',  
660 - /*'txt' => */'text/plain',  
661 - /*'webm' => */'video/webm',  
662 - /*'webp' => */'image/webp',  
663 -  
664 - ],  
665 - // 磁盘  
666 - 'disk' => 'local2',  
667 - // 目录  
668 - 'path' => '/email/'.$this->username.'/',  
669 - ];  
670 - // 设置配置  
671 - Config::set('upload.email_upload',$upconfig);  
672 -  
673 - foreach ($data['file'] as $k=>$file){  
674 - if($file instanceof UploadedFile){  
675 - $data['file'][$k] = [  
676 - 'path' => Upload::put($file,'email_upload'),  
677 - 'origin_name' => $file->getClientOriginalName(),  
678 - ];  
679 - }  
680 -  
681 - }  
682 -  
683 - // 保存的目录  
684 - $path = config('filesystems.disks.local2.root');  
685 - foreach ($data['file'] as &$f){  
686 - $f['path'] = realpath($path.'/'.$f['path']);  
687 - }  
688 -  
689 - }  
690 -  
691 -  
692 - if($this->send($data)){  
693 - return $this->echoJson(1,'邮件发送成功');  
694 - }  
695 - }  
696 - }  
697 -  
698 - return $this->echoJson($result);  
699 -  
700 - }  
701 -  
702 - /**  
703 - * 自动路由时  
704 - * @return array  
705 - * @time 2022/8/1 16:28  
706 - */  
707 - public function getRoute(){  
708 - return [  
709 - [  
710 - 'name' => '同步文件夹',  
711 - 'route' => '?_task=sync&_action=folder'  
712 - ],  
713 - [  
714 - 'name' => '同步邮件列表',  
715 - 'route' => '?_task=sync&_action=list'  
716 - ],  
717 - [  
718 - 'name' => '邮件详情',  
719 - 'route' => '?_task=info&id=1972'  
720 - ],  
721 - [  
722 - 'name' => '邮件列表',  
723 - 'route' => '?_task=list'  
724 - ],  
725 - [  
726 - 'name' => '邮箱',  
727 - 'route' => '?_task=mail'  
728 - ],  
729 - [  
730 - 'name' => '添加邮箱',  
731 - 'route' => '?_task=add'  
732 - ],  
733 - [  
734 - 'name' => '邮箱联系人',  
735 - 'route' => '?_task=contact'  
736 - ],  
737 - ];  
738 - }  
739 -  
740 -  
741 - /**  
742 - * 同步文件夹  
743 - * 缺点,远程修改文件名称后,同步回来,就会新建文件夹,会导致邮件混乱,  
744 - * 处理方法,删除本地的,重新同步该文件夹下面的所有邮件,虽然不友好,也只能这样做。  
745 - * @time 2022/8/1 16:09  
746 - */  
747 - public function syncFolder($folder=''){  
748 -  
749 - // 同步单个文件夹  
750 - if($folder){  
751 - $status = $this->client()->selectFolder($this->folder);  
752 - // 更新数量  
753 - EmailFolder::_updateNum($this->folderId,$status['EXISTS']??null, $status['UNSEEN']??null);  
754 -  
755 - return EmailFolder::_firstAndEmailId($this->folderId,$this->id);  
756 - }  
757 -  
758 -  
759 - // 读取所有文件夹,未解密  
760 - $folders = $this->client()->getFolder();  
761 -  
762 - DB::beginTransaction();  
763 - foreach ($folders as $folder){  
764 -  
765 - // 处理子父文件夹  
766 - $folder['id'] = explode('/',$folder['folder']);  
767 - $folder['name'] = explode('/',$folder['parseFolder']);  
768 - $pid = 0;  
769 - foreach ($folder['id'] as $k=>$item){  
770 - // 插入到数据库  
771 - $pid = EmailFolder::_insert(  
772 - $this->user_id,  
773 - $this->id,  
774 - $folder['name'][$k],  
775 - $item,  
776 - $pid  
777 - );  
778 - }  
779 - }  
780 - DB::commit();  
781 -  
782 - return EmailFolder::_all($this->id);  
783 - }  
784 -  
785 - /**  
786 - * 同步当前用户的邮件数量  
787 - * @return array  
788 - * [surplus_num] 剩余待拉取数量  
789 - * [ids] 本次拉取后保存后的id  
790 - * @time 2022/8/2 14:06  
791 - */  
792 - public function syncEmailList($msgno=[],$use_email_id=0,$use_email='',$use_folder_id=0,$use_folder=''){  
793 -  
794 - $use_email_id = $use_email_id ? : $this->id;  
795 - $use_email = $use_email ? : $this->username;  
796 - $use_folder_id = $use_folder_id ? : $this->folderId;  
797 - $use_folder = $use_folder ? : $this->folder;  
798 -  
799 - // 零时增加时长  
800 -// set_time_limit(180);  
801 - // 选择文件夹  
802 - $status = $this->client($use_email)->selectFolder($use_folder);  
803 - if (!isset($status['EXISTS']) || !$status['EXISTS']){  
804 - return [  
805 - 'ids' => [],  
806 - 'folder' => $status,  
807 - 'end' => true  
808 - ];  
809 - }  
810 - $end = false;  
811 -  
812 - if($msgno){  
813 - goto SYNCEMAILLIST;  
814 - }  
815 -  
816 - // 最后拉取的时间,如果是第一次  
817 - $lastMsgno = EmailList::_lastMsgno($use_email_id, $use_folder_id);  
818 -  
819 - $nu = 20;  
820 - if(!$msgno){  
821 - if(!$lastMsgno){  
822 - $msgno = range(1,$nu);  
823 - }else{  
824 - $msgno = range($lastMsgno,$lastMsgno+$nu);  
825 -  
826 - if($lastMsgno > $status['EXISTS']){  
827 - $msgno = range($status['EXISTS'] > $nu ? $status['EXISTS'] - $nu : 1,$status['EXISTS']);  
828 - }  
829 - // 一样就不拉新的  
830 - if($lastMsgno == $status['EXISTS']){  
831 - return [  
832 - 'ids' => [],  
833 - 'folder' => $status,  
834 - 'end' => true,  
835 - ];  
836 - }  
837 - }  
838 - }  
839 -  
840 - // 更新数量  
841 - EmailFolder::_updateNum($use_folder_id,$status['EXISTS'], $status['UNSEEN']??null);  
842 -  
843 - // 说明是第一次  
844 - if(!$lastMsgno){  
845 - $pgnu = 1000;  
846 - DB::beginTransaction();  
847 - for ($n=0;$n<$status['EXISTS']/$pgnu;$n++){  
848 -  
849 - $i = ($pgnu*$n)+1;  
850 - $max = $i+$pgnu;  
851 - $max = $max > $status['EXISTS'] ? $status['EXISTS']+1 : $max;  
852 - $sql = [];  
853 - while ($i<$max){  
854 - $sql[] = "(".$use_email_id.",{$i},".$use_folder_id.",1)";  
855 - $i++;  
856 - }  
857 -// bug 使用事务,无法插入数据  
858 - $ret = DB::insert("insert INTO email_lists (`email_id`,`msgno`,`folder_id`,`seen`) VALUES ".implode(',',$sql));  
859 - if(!$ret){  
860 - DB::rollBack();  
861 - abort(500,'同步失败');  
862 - }  
863 - }  
864 - DB::commit();  
865 - // 最新的数量  
866 - $msgno = range($status['EXISTS'] > $nu ? $status['EXISTS'] - $nu : 1,$status['EXISTS']);  
867 - // 不同步  
868 - return [  
869 - 'ids' => [1],  
870 - 'folder' => $status,  
871 - 'end' => $end,  
872 - ];  
873 - }  
874 - SYNCEMAILLIST:  
875 - // 是否有id  
876 - $dataids = EmailList::_getIdsByMsgno($use_email_id,$use_folder_id,$msgno);  
877 -  
878 - $this->client($use_email)->debug(false,storage_path('logs'));  
879 - // 循环  
880 - $results = $this->client($use_email)->fetchHeader($msgno);  
881 -  
882 - if($results){  
883 - DB::beginTransaction();  
884 - // 批量插入  
885 - foreach ($results as $key=>$result){  
886 - if($key == $status['EXISTS']){  
887 - $end = true;  
888 - }  
889 -// $header = $this->client($use_email)->getHeader($result['RFC822.HEADER']??'');  
890 - $header = &$result['HEADER.FIELDS'];  
891 -  
892 - foreach ($result['FLAGS'] as $k=>$FLAG){  
893 - $result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG));  
894 - }  
895 - try {  
896 -  
897 - $file_header = &$result['BODYSTRUCTURE'];  
898 -  
899 - // 没有收件人  
900 - if(!empty($header['To'])){  
901 - $header['To'] = MailFun::toOrFrom($header['To']);  
902 - }else{  
903 - $header['To'] = [];  
904 - }  
905 -  
906 -  
907 - $header['From'] = MailFun::toOrFrom($header['From']);  
908 -  
909 - $data = [  
910 - 'id' => $dataids[$key]??0,  
911 - 'msgno' => $key,  
912 - 'uid' => $result['UID'],  
913 - 'subject' => $header['Subject'],  
914 -// 'cc' => $header['Cc']??'',  
915 - 'from' => $header['From'][0]['email']??'',  
916 - 'from_name' => $header['From'][0]['name']??'',  
917 - 'to' => $header['To']?implode(',',array_column($header['To'],'email')):'',  
918 - 'to_name' => json_encode($header['To']),  
919 - 'date' => isset($header['Date'])&&$header['Date'] ? strtotime(is_array($header['Date']) ? $header['Date'][0] : $header['Date']) : strtotime($result['INTERNALDATE']),  
920 - 'message_id' => $header['Message-ID']??'',  
921 - 'udate' => strtotime($result['INTERNALDATE']),  
922 -// 'size' => $result['RFC822.SIZE'],  
923 - 'recent' => in_array('recent',$result['FLAGS']),  
924 - 'seen' => in_array('seen',$result['FLAGS']),  
925 - 'draft' => in_array('draft',$result['FLAGS']),  
926 - 'flagged' => in_array('flagged',$result['FLAGS']),  
927 - 'answered' => in_array('answered',$result['FLAGS']),  
928 - 'folder_id' => $use_folder_id,  
929 - 'email_id' => $use_email_id,  
930 - 'uuid' => $use_email_id.$use_folder_id.$result['UID'],  
931 - 'is_file' => MailFun::isFile($file_header[$key]['BODYSTRUCTURE']??[]) //是否附件  
932 - ];  
933 - }catch (\Throwable $e){  
934 - Log::error('邮件解析失败:'.$e->getMessage().print_r($result,true));  
935 - unset($results[$key]);  
936 - continue;  
937 - }  
938 -  
939 - $results[$key] = $data;  
940 - }  
941 - $ids = EmailList::_insertAll(array_values($results));  
942 - // 提交  
943 - DB::commit();  
944 - }else{  
945 - $end = true;  
946 - }  
947 -  
948 -  
949 -  
950 -  
951 - return [  
952 - 'ids' => $ids??[],  
953 - 'folder' => $status,  
954 - 'end' => $end,  
955 - ];  
956 -  
957 - }  
958 -  
959 -  
960 - /**  
961 - * 同步body  
962 - * @param int $uid  
963 - * @param int $id  
964 - * @return mixed  
965 - * @time 2022/8/2 15:11  
966 - */  
967 - public function syncEMailBody(int $uid, $id = 0 ){  
968 -  
969 -// $this->client()->debug(true,storage_path('logs'));  
970 -  
971 - $this->client()->selectFolder($this->folder);  
972 - $body = $this->client()->fetch([$uid],'body',true);  
973 -  
974 - /******* start ********/  
975 - // 记录原始数据,方便分析  
976 - $path = storage_path('logs/email/'.$this->username.'/');  
977 - if(!is_dir($path)){  
978 - mkdir($path,0775,true);  
979 - }  
980 - file_put_contents($path.$id.'.'.time().'.log',print_r($body,true));  
981 - /******** end *******/  
982 -  
983 - if($body && $id){  
984 - $body = array_values($body);  
985 -  
986 - $body = (new Body($body[0]['RFC822.TEXT'],$this->getFilePath()))->getItem();  
987 -  
988 -  
989 - EmailBody::_insert(  
990 - $id,  
991 - $body  
992 - );  
993 - }  
994 -  
995 - return EmailBody::_first($id);  
996 - }  
997 -  
998 -  
999 - /**  
1000 - * 设置标记  
1001 - * @param array $uids  
1002 - * @param string $flags  
1003 - * @param string $mod +|-  
1004 - * @return bool  
1005 - * @throws \Exception  
1006 - * @author:dc  
1007 - * @time 2022/10/27 14:22  
1008 - */  
1009 - public function setflagged(array $uids,string $flags,string $mod){  
1010 - // 选择目录  
1011 - $status = $this->client()->selectFolder($this->folder);  
1012 -  
1013 - return $this->client()->flags($uids,[$flags],$mod,true);  
1014 - }  
1015 -  
1016 - /**  
1017 - * 设置为已读  
1018 - * @param int|array $uids  
1019 - * @return bool  
1020 - * @throws \Exception  
1021 - * @author:dc  
1022 - * @time 2022/10/26 17:08  
1023 - */  
1024 - public function setSeen($uids):bool{  
1025 - // 选择目录  
1026 - $status = $this->client()->selectFolder($this->folder);  
1027 -  
1028 - return $this->client()->flags($uids,[\App\Http\Mail\lib\client\Imap::FLAGS_SEEN],'+',true);  
1029 - }  
1030 -  
1031 - /**  
1032 - * 设置为未读  
1033 - * @param $uids  
1034 - * @return bool  
1035 - * @throws \Exception  
1036 - * @author:dc  
1037 - * @time 2022/10/26 17:11  
1038 - */  
1039 - public function delSeen($uids):bool{  
1040 - // 选择目录  
1041 - $status = $this->client()->selectFolder($this->folder);  
1042 -  
1043 - return $this->client()->flags($uids,[\App\Http\Mail\lib\client\Imap::FLAGS_SEEN],'-',true);  
1044 - }  
1045 -  
1046 -  
1047 -  
1048 - /**  
1049 - * 邮件列表  
1050 - * @param array $mail_id  
1051 - * @param array $folder  
1052 - * @param int $total  
1053 - * @return mixed|object  
1054 - * @throws \Illuminate\Contracts\Container\BindingResolutionException  
1055 - * @time 2022/8/16 17:34  
1056 - */  
1057 - public function emailLists($mail_id = [], $folder = [], $total = 0, $search = []){  
1058 -  
1059 - return EmailList::_paginate(function ($query) use ($mail_id, $folder,$search){  
1060 - $query->whereIn('email_id',$mail_id)->whereIn('folder_id',$folder);  
1061 - // 搜索  
1062 - if(!empty($search['search'])){  
1063 - $search = htmlspecialchars($search);  
1064 - $query->where('subject','like',"%{$search}%");  
1065 - }  
1066 - // 已读  
1067 - if(!empty($search['seen']) && $search['seen']){  
1068 - $query->where('seen',0);  
1069 - }  
1070 - // 过滤  
1071 - $query->where([  
1072 - ['subject','not like','%- snv'],  
1073 - ['subject','not like','%(Failure)'],  
1074 - ['subject','not like','%(Delay)']  
1075 - ]);  
1076 - },20,$total);  
1077 - }  
1078 -  
1079 - /**  
1080 - * 读取邮件详情  
1081 - * @param $id  
1082 - * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null  
1083 - * @time 2022/8/2 15:13  
1084 - */  
1085 - public function emailInfo($id){  
1086 - $email = EmailList::_firstWithBody($id);  
1087 - // 是否存在,并且是否属于自己  
1088 - if($email && in_array($email->email_id,$this->email_ids)){  
1089 - if(!$email->body && $email['uid']){  
1090 - $email = $email->toArray();  
1091 - try {  
1092 - $email['body'] = $this->syncEMailBody($email['uid'],$email['id']);  
1093 - }catch (\Throwable $e){  
1094 - $email['body'] = $this->syncEMailBody($email['uid'],$email['id']);  
1095 - }  
1096 - }  
1097 - return $email;  
1098 - }  
1099 - return null;  
1100 - }  
1101 -  
1102 -  
1103 -  
1104 -  
1105 - /**  
1106 - * 获取邮件服务器  
1107 - * @param string $suffix  
1108 - * @return array  
1109 - * @time 2022/7/29 16:24  
1110 - */  
1111 - public function host(string $suffix=''):array {  
1112 - if($suffix){  
1113 - return EmailHost::_get($suffix);  
1114 - }  
1115 - return EmailHost::_all();  
1116 - }  
1117 -  
1118 - /**  
1119 - * 获取自己的服务器地址  
1120 - * @param null $name  
1121 - * @return array|mixed|string  
1122 - * @time 2022/8/1 15:46  
1123 - */  
1124 - public function hostMy($name=null) {  
1125 -  
1126 -// if(!$this->host){  
1127 -// $data = Email::_first($this->id);  
1128 -// $this->host = [  
1129 -// 'imap' => $data['imap'],  
1130 -// 'smtp' => $data['smtp'],  
1131 -// ];  
1132 -// }  
1133 -  
1134 - if($name){  
1135 - return $this->host[$name]??'';  
1136 - }  
1137 -  
1138 - return $this->host;  
1139 - }  
1140 -  
1141 - /**  
1142 - * 添加邮件服务器  
1143 - * @param string $suffix  
1144 - * @param string $imap  
1145 - * @param string $smtp  
1146 - * @return int email host 表的id  
1147 - * @time 2022/7/29 16:28  
1148 - */  
1149 - public function hostAdd(string $suffix, string $imap, string $smtp):int{  
1150 - return EmailHost::_insert($suffix, $imap, $smtp);  
1151 - }  
1152 -  
1153 -  
1154 - /**  
1155 - * 添加一个邮箱地址  
1156 - * @time 2022/7/29 16:51  
1157 - */  
1158 - public function emailAdd($data=[]){  
1159 - $data = $data ? $data : $this->request->post();  
1160 - if(!$data){  
1161 - throw new \Exception(Lang::__('empty_form'));  
1162 - }  
1163 -  
1164 - $validator = Validator::make($data,[  
1165 - 'email' => ['required','email'],  
1166 - 'password' => ['required'],  
1167 - ],[  
1168 - 'email.required' => 'email_required',  
1169 - 'email.email' => 'email_validator',  
1170 - 'password.required' => 'password_required',  
1171 - ]);  
1172 -  
1173 - if($validator->fails()){  
1174 - throw new \Exception(Lang::__($validator->errors()->first()));  
1175 - }  
1176 - $data['email'] = trim($data['email']);  
1177 - $data['password'] = trim($data['password']);  
1178 - $data['email_name'] = trim($data['email_name']??$data['email']);  
1179 -  
1180 - // 检查imap服务器  
1181 - if($data['host']??''){  
1182 - $host = [  
1183 - 'imap' => trim($data['host']),  
1184 - 'smtp' => trim($data['smtp']??'')  
1185 - ];  
1186 - }else{  
1187 - // 获取数据库中有的  
1188 - $host = $this->host(explode('@',$data['email'])[1]);  
1189 - }  
1190 -  
1191 - if(!$host){  
1192 - // 是否存在imap服务器  
1193 - throw new \Exception(Lang::__('host_required'));  
1194 - }  
1195 -  
1196 - // 登录名  
1197 - $this->username = $data['email'];  
1198 -  
1199 - // 进行远程登录  
1200 - // imap imap.qq.com  
1201 - $imap = new \App\Http\Mail\lib\client\Imap();  
1202 - $imap->login($this->ssl.$host['imap'].':'.$this->port,$data['email'],$data['password']);  
1203 -  
1204 - // 添加邮箱,并绑定user_id  
1205 - $model = Email::_add($this->user_id, $data['email'], $data['password'], $data['email_name'],$host);  
1206 -  
1207 - try {  
1208 - // 添加一个联系人的默认分组  
1209 - $this->contactGroupSave('默认分组');  
1210 - }catch (\Throwable $e){  
1211 -  
1212 - }  
1213 -  
1214 -  
1215 - // 退出imap登录  
1216 - $imap->loginOut();  
1217 -  
1218 - return $model;  
1219 -  
1220 -// throw new \Exception(Lang::__('email_insert_error'));  
1221 - }  
1222 -  
1223 - /**  
1224 - * 读取所有邮箱  
1225 - * @return array  
1226 - * @time 2022/8/1 9:34  
1227 - */  
1228 - public function emails():array {  
1229 - return Email::_get($this->user_id);  
1230 - }  
1231 -  
1232 - /**  
1233 - * 文件夹  
1234 - * @return array  
1235 - * @time 2022/8/4 17:34  
1236 - */  
1237 - public function folders($id){  
1238 -  
1239 - $result = EmailFolder::_all($id);  
1240 - if(!$result){  
1241 - $result = $this->syncFolder();  
1242 - }  
1243 - return $result;  
1244 - }  
1245 -  
1246 -  
1247 - /**  
1248 - * 联系人列表  
1249 - * @return mixed  
1250 - * @time 2022/8/4 9:05  
1251 - */  
1252 - public function contact($is_group=false){  
1253 -  
1254 - return EmailContact::_all($this->id,$is_group);  
1255 - }  
1256 -  
1257 - /**  
1258 - * 联系人管理视图  
1259 - * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View  
1260 - * @author:dc  
1261 - * @time 2022/11/2 16:37  
1262 - */  
1263 - public function contact_view(){  
1264 -  
1265 -  
1266 - return view('admin/email/contacts',[  
1267 -// 'emails' => $this->emails(),  
1268 - 'email' => $this->username,  
1269 -// 'groups' => $this->contactGroup(),  
1270 -// 'contacts' => $this->contact()  
1271 - ]);  
1272 - }  
1273 -  
1274 -  
1275 - /**  
1276 - * 添加联系人  
1277 - * @return EmailContact  
1278 - * @throws \Exception  
1279 - * @time 2022/8/4 9:46  
1280 - */  
1281 - public function contactAdd(){  
1282 -  
1283 - $data = $this->request->post();  
1284 -  
1285 - $validator = Validator::make($data,[  
1286 - 'group_id' => ['required'],  
1287 - 'email' => ['required','email'],  
1288 - 'email_name' => ['required','max:100'],  
1289 - 'remark' => ['max:200'],  
1290 - ],[  
1291 - 'group_id.required' => 'contact_group_exists',  
1292 - 'email.required' => 'contact_email_required',  
1293 - 'email.email' => 'contact_email_error',  
1294 - 'email_name.required' => 'contact_email_name_required',  
1295 - 'email_name.max' => 'contact_email_name_max',  
1296 - 'remark.max' => 'contact_remark_max',  
1297 - ]);  
1298 - // 验证数据  
1299 - if($validator->fails()){  
1300 -  
1301 - $p = '';  
1302 -  
1303 - switch ($validator->errors()->first()){  
1304 - case 'contact_email_name_max': $p = '100';break;  
1305 - case 'contact_remark_max': $p = '200';break;  
1306 - }  
1307 -  
1308 - throw new \Exception(  
1309 - Lang::__(  
1310 - $validator->errors()->first(),  
1311 - $p  
1312 - )  
1313 - );  
1314 - }  
1315 -  
1316 - $group = EmailContactGroup::_first($data['group_id']);  
1317 - if(!$group || $group['email_id']!=$this->id){  
1318 - abort(600,Lang::__('contact_group_exists'));  
1319 - }  
1320 -  
1321 -  
1322 - return EmailContact::_save($this->id,$data);  
1323 - }  
1324 -  
1325 -  
1326 - /**  
1327 - * 联系人详情  
1328 - * @param $id  
1329 - * @return array  
1330 - * @author:dc  
1331 - * @time 2022/11/4 17:02  
1332 - */  
1333 - public function contactInfo($id){  
1334 - return EmailContact::_first($this->id,$id);  
1335 - }  
1336 -  
1337 -  
1338 - /**  
1339 - * 删除联系人  
1340 - * @time 2022/8/4 9:52  
1341 - */  
1342 - public function contactDel(){  
1343 - $contact_id = (int) $this->request->get('contact_id');  
1344 - if(!$contact_id || EmailContact::_del($this->id,$contact_id)){  
1345 - abort(600,Lang::__('del_error'));  
1346 - }  
1347 - return true;  
1348 - }  
1349 -  
1350 - /**  
1351 - * 联系人分组  
1352 - * @return mixed  
1353 - * @time 2022/8/4 10:13  
1354 - */  
1355 - public function contactGroup($is_contact=false){  
1356 - return EmailContactGroup::_all($this->id,$is_contact);  
1357 - }  
1358 -  
1359 - /**  
1360 - * 添加联系人分组  
1361 - * @return EmailContactGroup  
1362 - * @throws \Exception  
1363 - * @time 2022/8/4 10:47  
1364 - */  
1365 - public function contactGroupSave($name,$id=0){  
1366 - if(!$name || mb_strlen($name)>100){  
1367 - throw new \Exception(Lang::__('contact_group_name_error',1,100));  
1368 - }  
1369 -  
1370 - $name = htmlspecialchars($name);  
1371 -  
1372 - $data = EmailContactGroup::_firstByName($this->id,$name);  
1373 - if(($data && !$id) || ($data && $id && $data['id'] != $id)){  
1374 - throw new \Exception(Lang::__('contact_group_name_unique'));  
1375 - }  
1376 -  
1377 - return EmailContactGroup::_save($this->id,$name,$id);  
1378 - }  
1379 -  
1380 - /**  
1381 - * 删除  
1382 - * @return false|mixed  
1383 - * @time 2022/8/4 10:49  
1384 - */  
1385 - public function contactGroupDel($group_id){  
1386 -  
1387 - if(!$group_id){  
1388 - GROUP_DEL_ABORT:  
1389 - abort(600,Lang::__('contact_group_del_id'));  
1390 - }  
1391 -  
1392 - // 是否有联系人在其中  
1393 - if(EmailContact::_count($group_id, $this->id)){  
1394 - abort(600,Lang::__('contact_group_del_contact'));  
1395 - }  
1396 -  
1397 - // 删除  
1398 - if(!EmailContactGroup::_del($group_id, $this->id)){  
1399 - goto GROUP_DEL_ABORT;  
1400 - }  
1401 -  
1402 - return true;  
1403 - }  
1404 -  
1405 -  
1406 -  
1407 -  
1408 -  
1409 - /**  
1410 - * 发送邮件  
1411 - * @time 2022/8/3 16:08  
1412 - */  
1413 - public function send($data=[]){  
1414 -  
1415 - $data = $data ? $data : $this->request->post();  
1416 -  
1417 - $data['is_text'] = intval($data['is_text']??0);//纯文本  
1418 -// $data['save_send'] = intval($data['save_send']??0);//保存到已发送  
1419 - $data['priority'] = intval($data['priority']??0);//紧急  
1420 - $data['receipt'] = intval($data['receipt']??0);//需要回执  
1421 - $data['encrypt'] = intval($data['encrypt']??0);//加密邮件  
1422 -  
1423 -  
1424 -  
1425 - // 是纯文本还是html  
1426 - $data['body'] = $data['is_text'] ? ($data['text']??'') : ($data['html']??'');  
1427 -  
1428 -// 是否是回复邮件  
1429 - if(!empty($data['reply']['id']) && !empty($data['reply']['email_id'])){  
1430 -  
1431 - // 标题,主题  
1432 - if(empty($data['subject'])){  
1433 - throw new \Exception(Lang::__('send_email_subject_error',500));  
1434 - }  
1435 -  
1436 - // 查询  
1437 - $originData = EmailList::_first($data['reply']['id']);  
1438 - // 是否存在邮件  
1439 - if($originData && $originData['email_id'] == $data['reply']['email_id'] && in_array($originData['email_id'],$this->email_ids)){  
1440 - $emaildata = Email::_firstById($originData['email_id']);  
1441 - if(!$emaildata){  
1442 - throw new \Exception('发件人异常',600);  
1443 - }  
1444 - try {  
1445 - // 立刻发送邮件  
1446 - MailFun::sendEmail(  
1447 - $emaildata['smtp'],$emaildata['email'],$emaildata['password'],  
1448 - $emaildata['email_name'],['email'=>$originData['from'],'name'=>$originData['from_name']]  
1449 - ,$data['subject'],$data['body'],$data['file']??[],$data['receipt'],$data['priority']?1:3  
1450 - );  
1451 - // 记录下  
1452 - EmailLog::error(print_r([  
1453 - 'manage_id'=>the_manage('id'),  
1454 - 'data' => $data,  
1455 - '回复邮件'  
1456 - ],true));  
1457 - return true;  
1458 - }catch (\Throwable $e){  
1459 - throw new \Exception($e->getMessage());  
1460 - }  
1461 - }  
1462 - }  
1463 -  
1464 -  
1465 - // 收件人  
1466 - if(empty($data['to'])){  
1467 - SEND_EMAIL_TO_ERROR:  
1468 - throw new \Exception(Lang::__('send_email_to_error'));  
1469 - }  
1470 - // 是否是数组  
1471 - $data['to'] = explode("\n",trim($data['to']));  
1472 - if(!$data['to']){  
1473 - goto SEND_EMAIL_TO_ERROR;  
1474 - }  
1475 -// $to = [  
1476 -// 'email' => 'xxx@qq.com',  
1477 -// 'name' => 'xxx'  
1478 -// ];  
1479 - foreach ($data['to'] as $k=>$to){  
1480 - $to = trim($to);  
1481 - $data['to'][$to] = [];  
1482 - $data['to'][$to]['email'] = $to;  
1483 - $data['to'][$to]['name'] = explode('@',$to)[0];  
1484 -  
1485 - // 是否是邮箱  
1486 - if(!preg_match('/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',$data['to'][$to]['email'])){  
1487 -// throw new \Exception(Lang::__('send_email_to_error',$data['to'][$to]['email']));  
1488 - unset($data['to'][$to]);  
1489 - }  
1490 -  
1491 - unset($data['to'][$k]);  
1492 - }  
1493 -  
1494 - $data['to'] = array_values($data['to']);  
1495 - if(!$data['to']){  
1496 - goto SEND_EMAIL_TO_ERROR;  
1497 - }  
1498 -  
1499 - // 时间  
1500 - if($data['send_time'] != 'now'){  
1501 - $data['send_time'] = explode(',',$data['send_time']);  
1502 - if($data['send_time'][0] < 24 && $data['send_time'][1] < 24){  
1503 - $data['send_time'] = implode(',',$data['send_time']);  
1504 - }else{  
1505 - $data['send_time'] = '21,11';  
1506 - }  
1507 - }  
1508 -  
1509 - $tags = get('tags');  
1510 - if (!$tags || !is_array($tags)){  
1511 - throw new \Exception('请选择tag标签');  
1512 - }  
1513 -  
1514 -  
1515 - // 加入任务  
1516 - $job_id = EmailSendJob::_insert([  
1517 - 'title' => $data['subject']??'',  
1518 - 'to' => $data['to'],  
1519 - 'the_manage_id' => the_manage('id'),  
1520 - 'email_id' => $this->id,  
1521 - 'user_id' => $this->user_id,  
1522 - 'data' => $data,  
1523 - 'send_time' => $data['send_time'],  
1524 - 'tags' => $tags  
1525 - ]);  
1526 - if($job_id){  
1527 - // 发送一个任务  
1528 - SendJob::dispatch($job_id);  
1529 - return $job_id;  
1530 - }  
1531 -  
1532 - throw new \Exception(Lang::__('email_send_error'));  
1533 - }  
1534 -  
1535 -  
1536 - /**  
1537 - * 下载附件  
1538 - * @param $name  
1539 - * @param string $originname  
1540 - * @return string|\Symfony\Component\HttpFoundation\BinaryFileResponse  
1541 - * @author:dc  
1542 - * @time 2022/11/2 10:59  
1543 - */  
1544 - public function download($name,$originname=''){  
1545 - $file = $this->getFilePath().$name;  
1546 - if(is_file($file)){  
1547 - return response()->download($file,$originname ? : $name);  
1548 - }  
1549 - return '';  
1550 - }  
1551 -  
1552 -  
1553 -  
1554 -  
1555 -  
1556 - /**  
1557 - * @param mixed $data  
1558 - * @param string $message  
1559 - * @param int $status  
1560 - * @return \Illuminate\Http\JsonResponse  
1561 - * @author:dc  
1562 - * @time 2022/8/1 11:05  
1563 - */  
1564 - private function echoJson($data='', $message='', $status=200):\Illuminate\Http\JsonResponse{  
1565 - $data = [  
1566 - 'data' => $data,  
1567 - 'message' => $message,  
1568 - 'status' => $status  
1569 - ];  
1570 - return response()->json($data,200,[]);  
1571 - }  
1572 -  
1573 -}  
1 -<?php  
2 -  
3 -  
4 -  
5 -return $message = [  
6 - 'imap_server_error' => 'IMAP server error: %s',  
7 - 'login_host' => 'Enter the IMAP server address.',  
8 - 'empty_form' => 'The form data is empty.',  
9 - 'password_required' => 'The password field is required.',  
10 - 'password_error' => 'wrong password.',  
11 - 'host_required' => 'The IMAP server address must be.',  
12 - 'email_required' => 'Email address must be.',  
13 - 'email_validator' => 'Email format error.',  
14 - 'email_insert_error' => 'Failed to Add Mailbox.',  
15 - 'contact_email_required' => 'Contact email address must be specified.',  
16 - 'contact_group_exists' => 'The selected group does not exist.',  
17 - 'contact_email_error' => 'The email format of the contact is incorrect.',  
18 - 'contact_email_name_required' => 'Contact name must be entered.',  
19 - 'contact_email_name_max' => 'Contact name Maximum character %s.',  
20 - 'contact_group_name_error' => 'The contact group must be longer than %s and smaller than %s characters.',  
21 - 'contact_group_name_unique' => 'The contact group already exists.',  
22 - 'contact_group_del_id' => 'Grouping does not exist.',  
23 - 'contact_remark_max' => 'The contact remarks must contain %s characters.',  
24 - 'contact_group_del_contact' => 'There are contacts in the group and cannot be deleted.',  
25 - 'email_folder_not' => 'Folder (%s) does not exist.',  
26 - 'email_not_bind' => 'Mailbox (%s) is not bound.',  
27 - 'email_send_error' => 'Failed to send message (%s).',  
28 - 'del_error' => 'fail to delete.',  
29 - 'send_email_subject_error' => 'The message subject must be within the %s character.',  
30 - 'send_email_to_error' => 'The recipient must or is formatted incorrectly, %s.',  
31 -];  
1 -<?php  
2 -  
3 -  
4 -  
5 -return $message = [  
6 - 'imap_server_error' => 'IMAP服务器错误: %s',  
7 - 'login_host' => '请输入imap服务器地址',  
8 - 'empty_form' => '表单数据为空',  
9 - 'password_required' => '密码必须',  
10 - 'password_error' => '密码错误',  
11 - 'host_required' => 'imap服务器地址必须',  
12 - 'email_required' => '邮箱必须',  
13 - 'email_validator' => '邮箱格式错误',  
14 - 'email_insert_error' => '添加邮箱失败',  
15 - 'contact_email_required' => '联系人邮箱必须填写',  
16 - 'contact_group_exists' => '选择的分组不存在',  
17 - 'contact_email_error' => '联系人邮箱格式错误',  
18 - 'contact_email_name_required' => '联系人姓名必须填写',  
19 - 'contact_email_name_max' => '联系人姓名最大字符%s',  
20 - 'contact_group_name_error' => '联系人分组必须大于%s且小于%s字符',  
21 - 'contact_group_name_unique' => '联系人分组已存在',  
22 - 'contact_group_del_id' => '分组不存在',  
23 - 'contact_group_del_contact' => '分组下还有联系人,无法删除',  
24 - 'contact_remark_max' => '联系人备注在%s字符内',  
25 - 'email_folder_not' => '文件夹(%s)不存在',  
26 - 'email_not_bind' => '邮箱(%s)未绑定',  
27 - 'email_send_error' => '邮件发送失败(%s)',  
28 - 'del_error' => '删除失败',  
29 - 'send_email_subject_error' => '邮件主题必须且在%s字符内',  
30 - 'send_email_to_error' => '收件人必须或格式错误,%s',  
31 -];  
1 -<?php  
2 -  
3 -namespace App\Mail\lib;  
4 -  
5 -/**  
6 - * 中文情况  
7 - * @time 2022/8/1 15:32  
8 - * Class ImapUtf7  
9 - * @package App\Mail\lib  
10 - */  
11 -class ImapUtf7 {  
12 - static $imap_base64 =  
13 - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,';  
14 - static private function encode_b64imap($s) {  
15 - $a=0; $al=0; $res=''; $n=strlen($s);  
16 - for($i=0;$i<$n;$i++) {  
17 - $a=($a<<8)|ord($s[$i]); $al+=8;  
18 - for(;$al>=6;$al-=6) $res.=self::$imap_base64[($a>>($al-6))&0x3F];  
19 - }  
20 - if ($al>0) { $res.=self::$imap_base64[($a<<(6-$al))&0x3F]; }  
21 - return $res;  
22 - }  
23 - static private function encode_utf8_char($w) {  
24 - if ($w&0x80000000) return '';  
25 - if ($w&0xFC000000) $n=5; else  
26 - if ($w&0xFFE00000) $n=4; else  
27 - if ($w&0xFFFF0000) $n=3; else  
28 - if ($w&0xFFFFF800) $n=2; else  
29 - if ($w&0xFFFFFF80) $n=1; else return chr($w);  
30 - $res=chr(( (255<<(7-$n)) | ($w>>($n*6)) )&255);  
31 - while(--$n>=0) $res.=chr((($w>>($n*6))&0x3F)|0x80);  
32 - return $res;  
33 - }  
34 - static private function decode_b64imap($s) {  
35 - $a=0; $al=0; $res=''; $n=strlen($s);  
36 - for($i=0;$i<$n;$i++) {  
37 - $k=strpos(self::$imap_base64,$s[$i]); if ($k===FALSE) continue;  
38 - $a=($a<<6)|$k; $al+=6;  
39 - if ($al>=8) { $res.=chr(($a>>($al-8))&255);$al-=8; }  
40 - }  
41 - $r2=''; $n=strlen($res);  
42 - for($i=0;$i<$n;$i++) {  
43 - $c=ord($res[$i]); $i++;  
44 - if ($i<$n) $c=($c<<8) | ord($res[$i]);  
45 - $r2.=self::encode_utf8_char($c);  
46 - }  
47 - return $r2;  
48 - }  
49 - static function encode($s) {  
50 - $n=strlen($s);$err=0;$buf='';$res='';  
51 - for($i=0;$i<$n;) {  
52 - $x=ord($s[$i++]);  
53 - if (($x&0x80)==0x00) { $r=$x;$w=0; }  
54 - else if (($x&0xE0)==0xC0) { $w=1; $r=$x &0x1F; }  
55 - else if (($x&0xF0)==0xE0) { $w=2; $r=$x &0x0F; }  
56 - else if (($x&0xF8)==0xF0) { $w=3; $r=$x &0x07; }  
57 - else if (($x&0xFC)==0xF8) { $w=4; $r=$x &0x03; }  
58 - else if (($x&0xFE)==0xFC) { $w=5; $r=$x &0x01; }  
59 - else if (($x&0xC0)==0x80) { $w=0; $r=-1; $err++; }  
60 - else { $w=0;$r=-2;$err++; }  
61 - for($k=0;$k<$w && $i<$n; $k++) {  
62 - $x=ord($s[$i++]); if ($x&0xE0!=0x80) { $err++; }  
63 - $r=($r<<6)|($x&0x3F);  
64 - }  
65 - if ($r<0x20 || $r>0x7E ) {  
66 - $buf.=chr(($r>>8)&0xFF); $buf.=chr($r&0xFF);  
67 - } else {  
68 - if (strlen($buf)) {  
69 - $res.='&'.self::encode_b64imap($buf).'-';  
70 - $buf='';  
71 - }  
72 - if ($r==0x26) { $res.='&-'; } else $res.=chr($r);  
73 - }  
74 - }  
75 - if (strlen($buf)) $res.='&'.self::encode_b64imap($buf).'-';  
76 - return $res;  
77 - }  
78 - static function decode($s) {  
79 - $res=''; $n=strlen($s); $h=0;  
80 - while($h<$n) {  
81 - $t=strpos($s,'&',$h); if ($t===false) $t=$n;  
82 - $res.=substr($s,$h,$t-$h); $h=$t+1; if ($h>=$n) break;  
83 - $t=strpos($s,'-',$h); if ($t===false) $t=$n;  
84 - $k=$t-$h;  
85 - if ($k==0) $res.='&';  
86 - else $res.=self::decode_b64imap(substr($s,$h,$k));  
87 - $h=$t+1;  
88 - }  
89 - return $res;  
90 - }  
91 -}  
1 -<?php  
2 -  
3 -namespace App\Mail\lib;  
4 -  
5 -/**  
6 - * @time 2022/8/3 9:42  
7 - * Class Lang  
8 - * @package App\Mail\lib  
9 - */  
10 -class Lang {  
11 -  
12 - public static $l;  
13 -  
14 - public static $data;  
15 -  
16 - /**  
17 - * @param $key  
18 - * @param mixed ...$var  
19 - * @return mixed  
20 - * @time 2022/8/3 9:41  
21 - */  
22 - public static function __($key,...$var){  
23 - // 是否加载语言  
24 - if (!isset(self::$data[self::$l])) self::load();  
25 - // 获取语言  
26 - $str = self::$data[self::$l][$key]??self::$data['en'][$key]??$key;  
27 -  
28 - // 数组填充到10个元素,避免出现元素不够而返回空字符  
29 - $var = array_pad($var,10,'');  
30 - // 替换  
31 - return @sprintf($str, ...$var);  
32 -  
33 - }  
34 -  
35 -  
36 - /**  
37 - * 加载  
38 - * @param string $l  
39 - * @time 2022/8/4 15:55  
40 - */  
41 - public static function load(string $l = ''){  
42 - self::$l = strtolower($l ? $l : app()->getLocale());  
43 -  
44 - self::$data[self::$l] = require_once __DIR__.'/../lang/'.self::$l.'.php';  
45 -  
46 - // default  
47 - if(!isset(self::$data['en'])){  
48 - self::$data['en'] = require_once __DIR__.'/../lang/en.php';  
49 - }  
50 -  
51 - }  
52 -  
53 -}  
1 -<?php  
2 -  
3 -namespace App\Mail\lib\MailParse;  
4 -  
5 -use App\Mail\lib\MailFun;  
6 -  
7 -/**  
8 - * 解析邮件body内容  
9 - * 通过 fetch msgno RFC822.text 获取到的内容  
10 - * 此内容包含html 文本 附件  
11 - * @author:dc  
12 - * @time 2022/8/12 9:15  
13 - * Class Body  
14 - * @package App\Mail\lib  
15 - */  
16 -class Body {  
17 -  
18 - /**  
19 - * @var string  
20 - */  
21 - private $body;  
22 -  
23 - /**  
24 - *  
25 - * @var array  
26 - */  
27 - private $item = [];  
28 -  
29 - /**  
30 - * 保存的目录  
31 - * @var string  
32 - */  
33 - private $fileSavePath;  
34 -  
35 -  
36 - /**  
37 - * Body constructor.  
38 - * @param string $body  
39 - * @param string $fileSavePath  
40 - */  
41 - public function __construct(string $body, string $fileSavePath='/')  
42 - {  
43 - $this->body = $body = trim($body);  
44 -  
45 - $this->fileSavePath = $fileSavePath;  
46 -  
47 - // 这个是描述特殊文本  
48 - if(strpos($body,'This is a multi-part message in MIME format.')===0){  
49 - $body = trim($body,'This is a multi-part message in MIME format.');  
50 - $body = trim($body);  
51 - }  
52 -  
53 - // 163 有  
54 - if(strpos($body,'------=_Part')!==false){  
55 - $this->parse($body,'------=_Part');  
56 - }  
57 - elseif (mb_strpos($body,'------=_NextPart')!==false){  
58 - $this->parse($body,'------=_NextPart');  
59 - }  
60 - elseif (mb_strpos($body,'----_NmP')!==false){  
61 - $this->parse($body,'----_NmP');  
62 - }  
63 - elseif (mb_strpos($body,'--_=_swift')!==false){  
64 - $this->parse($body,'--_=_swift');  
65 - }  
66 - elseif (mb_strpos($body,'----==_mimepart')!==false){  
67 - $this->parse($body,'----==_mimepart');  
68 - }  
69 - elseif (mb_strpos($body,'--------------Boundary')!==false){  
70 - $this->parse($body,'--------------Boundary');  
71 - }  
72 - elseif (mb_strpos($body,'--=-')!==false){  
73 - $this->parse($body,'--=-');  
74 - }  
75 - // 很多--开始的,且不规则  
76 - elseif(strpos($body,'--')===0){  
77 - // 获取第一行  
78 - $tag = $this->body_get_tag($body,'--');  
79 - // 以第一行为标准  
80 - $this->parse($body,trim($tag));  
81 - }  
82 - // 直接html  
83 - elseif (mb_strpos($body,'<')===0){  
84 - $body = quoted_printable_decode($body);  
85 -// preg_match("/<meta(?!\s*(?:name|value)\s*=)(?:[^>]*?content\s*=[\s\"']*)?([^>]*?)[\s\"';]*charset\s*=[\s\"']*([^\s\"'\/>]*)/",$body,$icon);  
86 -// if(!empty($icon[2])){  
87 -// // 解码  
88 -// $body = mb_convert_encoding($body,'utf-8',$icon[2]);  
89 -// }  
90 - $this->setItem(['type'=>'text/html','body'=>$body]);  
91 - }  
92 - else{  
93 - // qq的是base64  
94 - if(rtrim($body,'=') == rtrim(base64_encode(base64_decode($body)),'=')){  
95 - $this->setItem(['type'=>'text/plain','body'=>base64_decode($body)]);  
96 - }else{  
97 - $this->setItem(['type'=>'text/plain','body'=>$body]);  
98 - }  
99 -  
100 - }  
101 -  
102 -  
103 - }  
104 -  
105 - /**  
106 - * 获取标签  
107 - * @param $body  
108 - * @param $tag  
109 - * @return mixed|string  
110 - * @author:dc  
111 - * @time 2022/8/12 10:49  
112 - */  
113 - private function body_get_tag($body,$tag){  
114 - preg_match("/{$tag}[\w\W].*/i",$body,$result);  
115 - if(!empty($result[0])) {  
116 - return $result[0];  
117 - }  
118 - return '';  
119 - }  
120 -  
121 - /**  
122 - * @param $item  
123 - */  
124 - private function setItem($item): void  
125 - {  
126 - $this->item[] = $item;  
127 - }  
128 -  
129 -  
130 - /**  
131 - * @return []  
132 - */  
133 - public function getItem(): array  
134 - {  
135 - return $this->item;  
136 - }  
137 -  
138 -  
139 - /**  
140 - * 开始解析  
141 - * @param string $body  
142 - * @param string $tag  
143 - * @return array  
144 - * @author:dc  
145 - * @time 2022/8/12 9:50  
146 - */  
147 - private function parse(string $body, string $tag){  
148 -  
149 - // 删除第一个标签前面的数据,一般情况无用  
150 - $body = mb_substr($this->body,strpos($this->body,$tag),99999999999);  
151 -  
152 - // 有附件的情况  
153 - preg_match('/boundary="([-_A-Za-z0-9=\.]{1,})"/i',$body,$boundary);  
154 - if($boundary[0]??''){  
155 - $body = str_replace($boundary[0],'',$body);  
156 -// $body = mb_substr($body,mb_strpos($body,$boundary[0])+strlen($boundary[0]),99999999999);  
157 - }  
158 - // 附件情况  
159 - if(!empty($boundary[1])){  
160 - preg_match_all('/.*'.$boundary[1].'.*/i',$body,$boundary_tag);  
161 - $body = str_replace($boundary_tag[0],'{--tag--}',$body);  
162 - }  
163 - // 查找tag块  
164 - preg_match_all("/(".$tag.".*+\n)/i",$body."\r\n\r\n",$he);  
165 - // 把每个tag块分开成数组  
166 - if(!empty($he[0])){  
167 - foreach ($he[0] as $hk=>$h){  
168 - $he[0][$hk] = trim($h);  
169 - }  
170 - arsort($he[0]);  
171 - $body = str_replace($he[0],'{--tag--}',$body);  
172 - }  
173 - $body = explode('{--tag--}',$body);  
174 - // 处理  
175 - foreach ($body as $key=>$item){  
176 - $data = [];  
177 -  
178 - $item = trim($item);  
179 - // 附件的头  
180 - if(!$item) { continue; }  
181 -  
182 - // 邮件体包含邮件体  
183 - if(preg_match("/boundary=\"([-_a-z0-9]{5,})\"/Ui",$item,$bm)){  
184 - if (strpos($item,$bm[1].'--')!==false){  
185 - $data = (new self('--'.$bm[1]."\r\n".$item,$this->fileSavePath))->getItem();  
186 -// $this->setItem($data);  
187 - // 合并邮件体  
188 - $this->item = array_merge($this->item,$data);  
189 - }  
190 - continue;  
191 - }  
192 -  
193 - // 先解码解码  
194 - $encode = $this->body_match_tag('Content-Transfer-Encoding:',$item);  
195 - if($encode){  
196 - $data['encode'] = strtolower($encode['text']);  
197 - $item = str_replace($encode['origin'],'',$item);  
198 - }  
199 -  
200 - // 内容类型  
201 - $type = $this->preg_match_type($item);  
202 - if($type){  
203 - $data['type'] = strtolower($type['type']);  
204 - // 编码  
205 - if(isset($type['charset'])){  
206 - $data['charset'] = strtolower($type['charset']);  
207 - }  
208 - // nama。附件  
209 - if(isset($type['name'])){  
210 - $data['name'] = $type['name'];  
211 - }  
212 - // 删除  
213 - $item = str_replace($type['origin'],'',$item);  
214 - }  
215 -  
216 - //  
217 - if(empty($data['charset'])){  
218 - // 编码  
219 - $code = $this->preg_match_charset($item);  
220 - if($code){  
221 - $data['charset'] = strtolower($code['charset']);  
222 -  
223 - $item = str_replace($code['origin'],'',$item);  
224 - }  
225 - }  
226 -  
227 - // 先匹配留存文件名称  
228 - preg_match('/filename="(\w?.*)"/',$item,$filename);  
229 - if(!empty($filename[1])){  
230 - $filename = MailFun::decodeMimeStr($filename[1]);  
231 - }  
232 -  
233 -  
234 - // 删除不需要的tag属性,如果需要进进行解析  
235 - $item = $this->body_remove_tag($item,'Content-Description:');  
236 - $item = $this->body_remove_tag($item,'Content-Disposition:');  
237 - $item = $this->body_remove_tag($item,'Mime-Version:');  
238 -  
239 - $data['body'] = trim($item);  
240 -  
241 - if(!empty($data['type'])){  
242 - // 邮件头  
243 - if($data['type'] == 'multipart/alternative'){  
244 -  
245 - }  
246 - // 是文本还是附件  
247 - else if(strpos($data['type'],'text/') === 0 ){  
248 - // body解密  
249 - switch($data['encode']??''){  
250 - case 'base64': {  
251 - $data['body'] = base64_decode($data['body']);  
252 - break;  
253 - }  
254 - case 'quoted-printable': {  
255 - $data['body'] = quoted_printable_decode($data['body']);  
256 - break;  
257 - }  
258 - case '8bit': {  
259 - try {  
260 - $data['body'] = DeCoding::de8bit($data['body']);  
261 - $data['body'] = quoted_printable_decode($data['body']);  
262 - }catch (\Throwable $e){  
263 -  
264 - }  
265 -  
266 - break;  
267 - }  
268 - }  
269 -  
270 - // 转码  
271 -// if(isset($data['charset']) && $data['charset']){  
272 -// $debody = @mb_convert_encoding($data['body'],'utf-8',$data['charset']);  
273 -// if($debody){  
274 -// $data['body'] = $debody;  
275 -// $debody = null;  
276 -// }  
277 -// }  
278 -  
279 -  
280 - }  
281 - // 系统退信//里面包含了发送邮件所有内容,这里不记录  
282 - elseif (strpos($data['type'],'message') === 0){  
283 - $data['body'] = '';// 一般不需要这些内容,如有需要就要重新解析  
284 - }  
285 - elseif (!empty($data['type']) && $data['body']){  
286 - // 解析附件  
287 - $data = $this->parseFile($data,$filename);  
288 - }  
289 - }  
290 -  
291 - $this->setItem($data);  
292 -  
293 - }  
294 -  
295 - }  
296 -  
297 -  
298 - /**  
299 - * 解析文件  
300 - * @param $item  
301 - * @return array|mixed  
302 - * @author:dc  
303 - * @time 2022/8/12 10:40  
304 - */  
305 - private function parseFile($item,$filename=''){  
306 - $data = [];  
307 - // 查找文件名  
308 -  
309 - $data['filename'] = $this->file_save_name($item['body'],'filename');  
310 - $data['name'] = $this->file_save_name($item['body'],'name');  
311 - $data['name'] = $data['name'] ? : ($item['name']??$filename);  
312 - $data['filename'] = $data['filename'] ? : $data['name'];  
313 -  
314 - // 是否有文件名  
315 - if(empty($data['filename']) || strpos($data['filename'],'.')===false){  
316 - return $item;  
317 - }  
318 -  
319 - $ext = explode('.',$data['filename']);  
320 - $ext = end($ext);  
321 -  
322 -// if(!empty($item['type'])){  
323 -// // 文件类型来判断后缀  
324 -// // // download it from http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types  
325 -// if(is_readable(__DIR__.'/mime.types')){  
326 -// $f = fopen(__DIR__.'/mime.types','r');  
327 -// while(!feof($f)){  
328 -// $fext = fgets($f);  
329 -// if($fext){  
330 -// $fext = strtolower($fext);  
331 -// $item['type'] = strtolower($item['type']);  
332 -// // 找到了类型后缀  
333 -// if(strpos($fext,$item['type']) === 0){  
334 -// $ext = trim(str_replace($item['type'],'',$fext));  
335 -// break;//找到了要跳出循环  
336 -// }  
337 -// }  
338 -// }  
339 -// // 关闭文件  
340 -// fclose($f);  
341 -// }  
342 -// }  
343 -  
344 - // 找不到后缀,说明不是文件  
345 -// if(empty($ext)){  
346 - // 文件后缀  
347 -// $ext = explode('.',$data['filename']);  
348 -// $ext = count($ext) > 1 ? ($ext[count($ext)-1]??'') : '';  
349 - // 直接返回  
350 -// return $item['body'];  
351 -// }  
352 -  
353 -  
354 - // content id  
355 - preg_match("/Content-ID:[\s].*<[\w\W]{1,}>/i",$item['body'],$result);  
356 - if (!empty($result[0])){  
357 - $data['content-id'] = explode('<',$result[0]);  
358 - $data['content-id'] = $data['content-id'][1];  
359 - $data['content-id'] = trim($data['content-id']);  
360 - $data['content-id'] = trim($data['content-id'],'>');  
361 -  
362 - $item['body'] = str_replace($result[0],'',$item['body']);  
363 - }  
364 -  
365 - $item['body'] = str_replace($result,'',$item['body']);  
366 -  
367 -  
368 - $content = base64_decode(trim($item['body']));  
369 -  
370 - if($content){  
371 - // 目录  
372 - $data['path'] = $this->fileSavePath;  
373 - if(!is_dir($data['path'])){  
374 - mkdir($data['path'],0775,true);  
375 - }  
376 - $data['signName'] = md5($content).($ext ? '.'.$ext : '');  
377 - $data['path'] = $data['path'].'/'.$data['signName'];  
378 - // 保存文件  
379 - @file_put_contents($data['path'],$content);  
380 - }  
381 -  
382 -  
383 - return $data;  
384 - }  
385 -  
386 - // 获取文件名称  
387 - private function file_save_name(&$body,$tag){  
388 - preg_match('/'.$tag.'="[(\S\W.*\s.*)]{1,}"/i',$body,$result);  
389 - if($result[0]??''){  
390 - $body = str_replace($result[0],'',$body);  
391 - }  
392 - $val = trim(str_replace([$tag.'=','"',"'"],'',$result[0]??''));  
393 - if ($val && strpos($val,'=?')===0){  
394 -  
395 - $val = iconv_mime_decode($val,ICONV_MIME_DECODE_CONTINUE_ON_ERROR,'utf-8');  
396 - }  
397 -  
398 - return $val;  
399 - }  
400 -  
401 - /**  
402 - * 删除tag  
403 - * @param $body  
404 - * @param $tag  
405 - * @return mixed|string|string[]  
406 - * @author:dc  
407 - * @time 2022/8/12 10:34  
408 - */  
409 - private function body_remove_tag($body,$tag){  
410 - preg_match("/{$tag}[\w\W].*/i",$body,$result);  
411 - if(!empty($result[0])) {  
412 - $body = str_replace($result, '', $body);  
413 - }  
414 - return $body;  
415 - }  
416 -  
417 - /**  
418 - * 读取编码  
419 - * @param $item  
420 - * @return array  
421 - * @author:dc  
422 - * @time 2022/8/12 10:28  
423 - */  
424 - private static function preg_match_charset($item){  
425 - // 匹配内容 type  
426 - preg_match('/charset[ \t]{0,}=[ \t]{0,}"?[ \t0-9a-zA-Z-]{1,}"?/i',$item,$result);  
427 -  
428 - if(!empty($result[0])){  
429 - $ret['origin'] = trim($result[0]);  
430 - // charset  
431 - $ret['charset'] = trim(str_replace(['charset','=','"',"'"],'',$ret['origin']));  
432 -  
433 - return $ret;  
434 - }  
435 - return [];  
436 - }  
437 -  
438 - /**  
439 - * 解析type  
440 - * @param $item  
441 - * @return array  
442 - * @author:dc  
443 - * @time 2022/8/12 10:26  
444 - */  
445 - private function preg_match_type($item){  
446 - // 匹配内容 type  
447 - preg_match("/Content-Type:[\w\W].*/i",$item,$result);  
448 -  
449 - if(!empty($result[0])){  
450 - $ret['origin'] = trim($result[0]);  
451 - // type  
452 - $type = str_replace(['Content-Type:','"',"'"],'',$ret['origin']);  
453 - $type = explode(';',$type);  
454 - // 类型  
455 - $ret['type'] = trim($type[0]);  
456 - if(isset($type[1]) && $type[1]){  
457 - // 编码  
458 - $r = explode('=',$type[1]);  
459 - $ret[strtolower(trim($r[0]))] = trim($r[1]??'');  
460 - }  
461 - return $ret;  
462 - }  
463 - return [];  
464 - }  
465 -  
466 - /**  
467 - * 匹配tag  
468 - * @param $tag  
469 - * @param $item  
470 - * @return array  
471 - * @author:dc  
472 - * @time 2022/8/12 10:05  
473 - */  
474 - private function body_match_tag($tag,$item){  
475 - // tag Content-Transfer-Encoding:  
476 - preg_match("/".$tag."[\w\W].*/i",$item,$result);  
477 -  
478 - if(!empty($result[0])){  
479 - $ret['origin'] = trim($result[0]);  
480 - // charset  
481 - $ret['text'] = trim(str_replace([$tag,'"',"'"],'',$ret['origin']));  
482 -  
483 - return $ret;  
484 - }  
485 - return [];  
486 - }  
487 -  
488 -  
489 -}  
1 -<?php  
2 -  
3 -namespace App\Mail\lib\MailParse;  
4 -  
5 -/**  
6 - * 解码邮件内容  
7 - * @author:dc  
8 - * @time 2022/8/12 9:33  
9 - * Class DeCoding  
10 - * @package App\Mail\lib\MailParse  
11 - */  
12 -class DeCoding {  
13 -  
14 -  
15 - /**  
16 - * @param $sText  
17 - * @param bool $bEmulate_imap_8bit  
18 - * @return string  
19 - * @author:dc  
20 - * @time 2022/8/12 9:34  
21 - */  
22 - public static function de8bit($sText,$bEmulate_imap_8bit=true) {  
23 - // split text into lines  
24 - $aLines=explode(chr(13).chr(10),$sText);  
25 -  
26 - for ($i=0;$i<count($aLines);$i++) {  
27 - $sLine =& $aLines[$i];  
28 - if (strlen($sLine)===0) continue; // do nothing, if empty  
29 -  
30 - $sRegExp = '/[^\x09\x20\x21-\x3C\x3E-\x7E]/e';  
31 -  
32 - // imap_8bit encodes x09 everywhere, not only at lineends,  
33 - // for EBCDIC safeness encode !"#$@[\]^`{|}~,  
34 - // for complete safeness encode every character :)  
35 - if ($bEmulate_imap_8bit)  
36 - $sRegExp = '/[^\x20\x21-\x3C\x3E-\x7E]/e';  
37 -  
38 - $sReplmt = 'sprintf( "=%02X", ord ( "$0" ) ) ;';  
39 - $sLine = preg_replace( $sRegExp, $sReplmt, $sLine );  
40 -  
41 - // encode x09,x20 at lineends  
42 - {  
43 - $iLength = strlen($sLine);  
44 - $iLastChar = ord($sLine{$iLength-1});  
45 -  
46 - // !!!!!!!!  
47 - // imap_8_bit does not encode x20 at the very end of a text,  
48 - // here is, where I don't agree with imap_8_bit,  
49 - // please correct me, if I'm wrong,  
50 - // or comment next line for RFC2045 conformance, if you like  
51 - if (!($bEmulate_imap_8bit && ($i==count($aLines)-1)))  
52 -  
53 - if (($iLastChar==0x09)||($iLastChar==0x20)) {  
54 - $sLine{$iLength-1}='=';  
55 - $sLine .= ($iLastChar==0x09)?'09':'20';  
56 - }  
57 - } // imap_8bit encodes x20 before chr(13), too  
58 - // although IMHO not requested by RFC2045, why not do it safer :)  
59 - // and why not encode any x20 around chr(10) or chr(13)  
60 - if ($bEmulate_imap_8bit) {  
61 - $sLine=str_replace(' =0D','=20=0D',$sLine);  
62 - //$sLine=str_replace(' =0A','=20=0A',$sLine);  
63 - //$sLine=str_replace('=0D ','=0D=20',$sLine);  
64 - //$sLine=str_replace('=0A ','=0A=20',$sLine);  
65 - }  
66 -  
67 - // finally split into softlines no longer than 76 chars,  
68 - // for even more safeness one could encode x09,x20  
69 - // at the very first character of the line  
70 - // and after soft linebreaks, as well,  
71 - // but this wouldn't be caught by such an easy RegExp  
72 - preg_match_all( '/.{1,73}([^=]{0,2})?/', $sLine, $aMatch );  
73 - $sLine = implode( '=' . chr(13).chr(10), $aMatch[0] ); // add soft crlf's  
74 - }  
75 -  
76 - // join lines into text  
77 - return implode(chr(13).chr(10),$aLines);  
78 - }  
79 -}