作者 邓超

1

... ... @@ -199,7 +199,10 @@ function create_coroutine(array &$cid,int &$isRunMaxCNum,$worker_id){
sync();
}catch (\Throwable $e){
_echo($e->getMessage());
logs($e->getMessage().PHP_EOL.$e->getTraceAsString(),LOG_PATH.'/'.$worker_id.'_'.co::getCid().'.log');
logs(
$e->getMessage().PHP_EOL.$e->getTraceAsString(),
LOG_PATH.'/'.$worker_id.'_'.co::getCid().'.log'
);
}
// 阻塞1秒
... ... @@ -253,9 +256,38 @@ function sync(){
}
$mailServer->client->setId($id);
// 文件夹间隔1天同步一次
if(empty($email['last_sync_time']) || time() > $email['last_sync_time']+86400){
// 同步文件夹
$mailServer->syncFolder($email['email'],db());
$mailServer->syncFolder(db());
}
// 读取到邮箱中的文件夹
$folders = db()->all(\Model\folderSql::all($email['id']));
if(!$folders){
return 3;
}
$folders = list_to_tree($folders);
foreach ($folders as $folder){
try {
if(empty($folder['_child'])){
// 同步父文件夹
$mailServer->syncMail($id,$folder['id'],$folder['origin_name']);
}else{
foreach ($folder as $item){
// 同步子文件夹
$mailServer->syncMail($id,$item['id'],$folder['origin_name'].'/'.$item['origin_name']);
}
}
}catch (Throwable $e){
logs(
$e->getMessage(),
LOG_PATH.'/imap/'.$email.'.error.log'
);
}
}
$email = null;
... ...
... ... @@ -36,11 +36,18 @@ function db():\Lib\DbPool{
* @time 2023/2/10 14:58
*/
function logs($message,$filename=null){
$filename = $filename ? $filename : LOG_PATH.'/error.log';
if(!is_dir(dirname($filename))){
@mkdir(dirname($filename),0755,true);
}
@file_put_contents(
$filename ? $filename : LOG_PATH.'/error.log',
$filename,
date('Y-m-d H:i:s ').print_r($message,true).PHP_EOL,
FILE_APPEND
);
}
... ... @@ -195,7 +202,42 @@ function listsPage($item,$total,$page,$limit){
/**
* 把返回的数据集转换成Tree
* @param $list array 数据列表
* @param string|int $pk 主键|root
* @param string $pid 父id
* @param string $child 子键
* @param int $root 获取哪个id下面
* @param bool $empty_child 当子数据不存在,是否要返回空子数据
* @return array
*/
function list_to_tree($list, $pk='id',$pid = 'pid',$child = '_child',$root=0,$empty_child=true) {
// 创建Tree
$tree = array();
if(is_array($list)) {
// 创建基于主键的数组引用
$refer = array();
foreach ($list as $key => $data) {
if($empty_child){
$list[$key][$child] = [];
}
$refer[$data[$pk]] =& $list[$key];
}
foreach ($list as $key => $data) {
// 判断是否存在parent
$parentId = $data[$pid];
if ($root == $parentId) {
$tree[] =& $list[$key];
}else{
if (isset($refer[$parentId])) {
$refer[$parentId][$child][] = & $list[$key];
}
}
}
}
return $tree;
}
... ...
... ... @@ -3,6 +3,8 @@
namespace Lib\Mail;
use Lib\DbPool;
use Model\folderSql;
use Model\listsSql;
/**
* 操作邮件
... ... @@ -59,11 +61,14 @@ class Mail {
$pid = 0;
foreach ($folder['id'] as $k=>$item){
// 插入到数据库
$pid = Folder::_insert(
$this->client->getId(),
$folder['name'][$k],
$item,
$pid
$pid = $db->insert(
folderSql::$table,
[
'email_id' => $this->client->getId(),
'folder' => $folder['name'][$k],
'origin_folder' => $item,
'pid' => $pid
]
);
}
}
... ... @@ -74,32 +79,35 @@ class Mail {
/**
* 同步邮件
* @param $email
* @param $email_id
* @param $folder_id
* @param string $folder
* @param null|DbPool $db
* @return bool
* @throws \Exception
* @author:dc
* @time 2023/2/6 15:04
* @time 2023/2/18 9:54
*/
public static function syncMail($email,$email_id,$folder_id,$folder='INBOX'):bool {
public function syncMail($email_id,$folder_id,$folder='INBOX',$db = null):bool {
$db = $db ? $db : db();
// 选择文件夹
try {
$status = static::$client[$email]->selectFolder($folder);
}catch (\Throwable $e){
Log::error($email.' 选择文件夹错误:'.$e->getMessage());
return false;
}
$status = $this->client->selectFolder($folder);
// 是否有邮件
if (!isset($status['EXISTS']) || !$status['EXISTS']){
return true;
}
// 更新数量
Folder::_updateNum($folder_id,$status['EXISTS'], $status['UNSEEN']??null);
$db->update(
folderSql::$table,
['exsts'=>$status['EXISTS'],'unseen'=>$status['UNSEEN']??0],
dbWhere(['id'=>$folder_id])
);
// 最后拉取的时间,如果是第一次
$lastMsgno = EList::_lastMsgno($email_id, $folder_id);
// 最后拉取的msgno
$lastMsgno = $db->value(listsSql::lastMsgno($email_id,$folder_id));
$nu = 20;
... ... @@ -118,29 +126,19 @@ class Mail {
}
SYNCEMAILLIST:
// 是否有id
$dataids = EList::_getIdsByMsgno($email_id,$folder_id,$msgno);
// 循环
$results = static::$client[$email]->fetchHeader($msgno);
$results = $this->client->fetchHeader($msgno);
if($results){
DB::beginTransaction();
// 批量插入
foreach ($results as $key=>$result){
if($key == $status['EXISTS']){
$end = true;
}
$header = &$result['HEADER.FIELDS'];
$header = $result['HEADER.FIELDS'];
foreach ($result['FLAGS'] as $k=>$FLAG){
$result['FLAGS'][$k] = strtolower(str_replace('\\','',$FLAG));
}
try {
$file_header = &$result['BODYSTRUCTURE'];
$file_header = $result['BODYSTRUCTURE'];
// 没有收件人
if(!empty($header['To'])){
... ... @@ -149,11 +147,9 @@ class Mail {
$header['To'] = [];
}
$header['From'] = MailFun::toOrFrom($header['From']);
$data = [
'id' => $dataids[$key]??0,
'msgno' => $key,
'uid' => $result['UID'],
'subject' => $header['Subject'],
... ... @@ -166,7 +162,7 @@ class Mail {
'date' => isset($header['Date'])&&$header['Date'] ? strtotime(is_array($header['Date']) ? $header['Date'][0] : $header['Date']) : strtotime($result['INTERNALDATE']),
'message_id' => $header['Message-ID']??'',
'udate' => strtotime($result['INTERNALDATE']),
// 'size' => $result['RFC822.SIZE'],
'size' => $result['RFC822.SIZE']??0,
'recent' => in_array('recent',$result['FLAGS']),
'seen' => in_array('seen',$result['FLAGS']),
'draft' => in_array('draft',$result['FLAGS']),
... ... @@ -174,29 +170,50 @@ class Mail {
'answered' => in_array('answered',$result['FLAGS']),
'folder_id' => $folder_id,
'email_id' => $email_id,
'uuid' => $email_id.$folder_id.$result['UID'],
'uuid' => md5($email_id.$folder_id.$result['UID']),
'is_file' => MailFun::isFile($file_header[$key]['BODYSTRUCTURE']??[]) //是否附件
];
}catch (\Throwable $e){
Log::error('邮件解析失败:'.$e->getMessage().print_r($result,true));
logs(
'邮件解析失败:'.PHP_EOL.$e->getMessage().PHP_EOL.print_r($result,true),
LOG_PATH.'/imap/mail/'.$email_id.'/'.$result['UID'].'.log'
);
unset($results[$key]);
continue;
}
$results[$key] = $data;
}
EList::_insertAll(array_values($results));
// 提交
DB::commit();
// 保存数据,这里其实不用再次写循环的。我想写一个
$uuids = $db->all(listsSql::hasUuid(array_column($results,'uuid')));
$uuids = $uuids ? array_column($uuids,null,'uuid') : [];
$db->transaction();
foreach ($results as $insert){
if(empty($uuids[$insert['uuid']])){
// 新增
$db->insert(listsSql::$table,$insert);
}else{
// 修改
$db->update(
listsSql::$table,
$insert,
dbWhere(['id'=>$uuids[$insert['uuid']]['id']])
);
}
}
$db->commit();
// 结束操作了
}
// 再次调用
Mail::syncMail($email,$email_id,$folder_id,$folder);
$this->syncMail($email_id,$folder_id,$folder,$db);
return true;
}
... ...
<?php
namespace Helper\Mail;
namespace Lib\Mail;
use Illuminate\Support\Facades\Storage;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
... ...
... ... @@ -13,7 +13,16 @@ class folderSql {
public static $table = 'folders';
/**
* 所有文件夹
* @param int $email_id
* @return string
* @author:dc
* @time 2023/2/18 9:22
*/
public static function all(int $email_id):string {
return "select `folder`,`pid`,`origin_folder`,`last_sync_time` from `".static::$table."` where `email_id` = {$email_id} order by `id` asc";
}
... ...
... ... @@ -36,6 +36,45 @@ class listsSql {
}
/**
* 获取最后一条更新的msgno
* @param $email_id
* @param $folder_id
* @return string
* @author:dc
* @time 2023/2/18 10:01
*/
public static function lastMsgno($email_id,$folder_id):string{
return "select max(`msgno`) from `".self::$table."` where ".dbWhere(['email_id'=>$email_id,'folder_id'=>$folder_id])." limit 1";
}
/**
* 获取已存在的id
* @param $email_id
* @param $folder_id
* @param $msgno
* @return string
* @author:dc
* @time 2023/2/18 10:08
*/
public static function getIds($email_id,$folder_id,$msgno):string {
return "select `id`,`msgno` from `".static::$table."` where ".dbWhere([
'email_id' => $email_id,
'folder_id' => $folder_id,
'msgno' => $msgno,
]);
}
/**
* 通过uuid查询id和email_id
* @param $uuid
* @return string
* @author:dc
* @time 2023/2/18 10:44
*/
public static function hasUuid($uuid):string {
return "select `id`,`email_id`,`uuid` from `".self::$table."` where ".dbWhere(['uuid'=>$uuid]);
}
... ...