正在显示
14 个修改的文件
包含
379 行增加
和
2723 行删除
@@ -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 | } |
app/Console/Commands/PushSync.php
0 → 100644
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 | +//} |
app/Console/Commands/SyncFolder.php
0 → 100644
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 | +//} |
app/Console/Commands/SyncMailList.php
0 → 100644
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 |
app/Mail/Jobs/ImapApi.php
已删除
100644 → 0
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 | -} |
app/Mail/Jobs/SendJob.php
已删除
100644 → 0
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 | -} |
app/Mail/Mail.php
已删除
100644 → 0
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 | -} |
app/Mail/lang/en.php
已删除
100644 → 0
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 | -]; |
app/Mail/lang/zh-cn.php
已删除
100644 → 0
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 | -]; |
app/Mail/lib/ImapUtf7.php
已删除
100644 → 0
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 | -} |
app/Mail/lib/Lang.php
已删除
100644 → 0
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 | -} |
app/Mail/lib/MailParse/Body.php
已删除
100644 → 0
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 | -} |
app/Mail/lib/MailParse/DeCoding.php
已删除
100644 → 0
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 | -} |
-
请 注册 或 登录 后发表评论