SendJob.php 9.9 KB
<?php
namespace App\Mail\Jobs;


use App\Http\Mail\lib\MailFun;
use App\Http\Mail\Models\Email;
use App\Http\Mail\Models\EmailSendJob;
use App\Http\Mail\Models\EmailSendJobStatu;
use App\Http\Models\EmailSendTemplate;
use App\Sk;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;

/**
 * @author:dc
 * @time 2022/11/9 11:52
 * Class SendJob
 */
class SendJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $job_id;

    /**
     * SendServiceMsg constructor.
     * @param $id
     */
    public function __construct($id)
    {
        // 使用的链接
        $this->connection   =   Sk::QUEUE_EMAIL_QUN;

        $this->job_id = $id;
    }

    /**
     * Execute the job.
     *
     * @return bool
     */
    public function handle()
    {
        // 查询邮件任务主体
        $jobData    =   EmailSendJob::where('id',$this->job_id)
            ->whereIn('status',[EmailSendJob::STATUS_WAIT,EmailSendJob::STATUS_RUNING])
            ->first();

        if(!$jobData){
            $this->log('任务不存在');
            return false;
        }

        // 发送时间
        if(trim($jobData->send_time) != 'now'){
            // 定时发送
            list($start,$end) = explode(',',$jobData->send_time);
            // 系统是中国时区,按照美国时区要慢13个小时
            if(!(date('H') >= $start || date('H') < $end)){
                // 也就是 美国早上 8点到晚上10点
//            $this->log('休息中');
                SendJob::dispatch($this->job_id)->delay(600);
                return true;
            }

        }

        $this->log('开始检查任务:'.$this->job_id);
        if(!$jobData){
            $this->log('没有找到任务');
            return true;
        }
        // 查询需要发送的邮件
        $jobStdata = EmailSendJobStatu::where(['job_id'=>$this->job_id,'status'=>EmailSendJobStatu::STATUS_WAIT])->first();
        if(!$jobStdata){
            $this->log('完成所有了');
            // 是否完成
            $jobData->status = EmailSendJob::STATUS_SUCCESS;
            $jobData->save();
            return true;
        }
        $this->log('找到任务:'.$jobStdata['id']);

        // 防止重复
        $cachekey = 'email_send_job:'.$this->job_id.":".$jobStdata['id'];
        // 存在
        if(Cache::has($cachekey)){
            $this->log("任务:{$jobStdata['id']}正在发送,跳过");
            SendJob::dispatch($this->job_id)->delay(30);
            return true;
        }
        // 占有2分钟
        Cache::set($cachekey,$jobStdata['id'],120);


        // 是否等待状态
        if($jobData->status !== EmailSendJob::STATUS_RUNING){
            $this->log('开始运行脚本了');
            $jobData->status = EmailSendJob::STATUS_RUNING;;
            $jobData->save();
        }

        // 当前管理账号下所有绑定的邮件
        $emailinfos = Email::_get($jobData->user_id,Email::STATUS_ACTIVE,['e.id','e.email','e.email_name','e.smtp','e.password','e.pwd_error']);

        $smtpErrorNum   =   0;// 错误次数

        // 标签
        $tags = $jobData->tags;
        $tags = is_array($tags) ? $tags : explode(',',$tags);
        // 模板列表
        $tempLists = EmailSendTemplate::getAdminTagsList($tags);
        if(!$tempLists){
            // 是否完成
            $jobData->status = EmailSendJob::STATUS_SUCCESS;
            $jobData->save();
            $this->log('没有可用的模板');
            return false;
        }
        // 随机一个模板
        $template = $tempLists[array_rand($tempLists->toArray())];


        // 节点
        EMAILRESETRANG:

        if(!$emailinfos){
            $this->log('暂时没有可分配的账号');
            // 再次发布任务,延时10分钟
            SendJob::dispatch($this->job_id)->delay(120);
            return true;
        }

        // 随机一个邮件来当发送
        $key = array_rand($emailinfos,1);
        $emailinfo  =   $emailinfos ? $emailinfos[$key] : [];
        if(!$emailinfo){
            $this->log('没有分配到账号');
            // 再次发布任务,延时10分钟
            SendJob::dispatch($this->job_id)->delay(600);
            return true;
        }
        unset($emailinfos[$key]);
        $emailinfos = array_values($emailinfos);

        // 密码是否需要验证
        if($emailinfo['pwd_error']){
            $this->log('账号密码验证失败,需要修改密码 '.$emailinfo['email']);
            // 重新随机一个,
            goto EMAILRESETRANG;
        }

        // 每个小时不能超过20
        $cacheEmailSuccesshkey = 'email_job_email:'.$emailinfo['email'].":success_h";
        if(Cache::get($cacheEmailSuccesshkey,0) >= 8){
            $this->log('账号超过每小时8封了'.$emailinfo['email']);
            // 重新随机一个,
            goto EMAILRESETRANG;
        }

        // 一天中是否超过100,
//        $cacheEmailSuccessdkey = 'email_job_email:'.$emailinfo['email'].":success_d";
//        if(Cache::get($cacheEmailSuccessdkey,0) >= 100){
//            $this->log('账号超过每天100封了'.$emailinfo['email']);
//            // 重新随机一个,
//            goto EMAILRESETRANG;
//        }

        $cachemailikey  = 'email_job_email:'.$emailinfo['email'].":success_i";
        if (Cache::has($cachemailikey)){
            $this->log('账号没超过10分钟间隔 '.$emailinfo['email']);
            // 重新随机一个,
            goto EMAILRESETRANG;
        }

        // 是否错误了
        $cachemailierrordkey = 'email_job_email:'.$emailinfo['email'].":error_d";
        if(Cache::has($cachemailierrordkey)){
            $this->log('账号记录错误了,跳过 1小时'.$emailinfo['email']);
            goto EMAILRESETRANG;
        }

        Cache::set($cachemailikey,$jobStdata['id'],550);

        $this->log('找到账号:'.$emailinfo['email']);
        // 数据发送的email
        $data = json_decode($jobData->maildata,true);

        $data['body'] = $template['body'];
        $data['subject'] = $template['subject'];

        try {

            // 替换邮件规则,
            $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']);
            // 匹配链接规则
            if (preg_match_all("/\[link:(.*)\](.*)\[\/link\]/U",$data['body'],$m)){
                foreach ($m[0] as $mk=>$item){
                    $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']);
                }
            }

            // 发送邮件
            MailFun::sendEmail(
                $emailinfo['smtp'],$emailinfo['email'],decrypt($emailinfo['password'])
                ,$emailinfo['email_name'],$jobStdata['to_email'],$data['subject'],
                $data['body'],$data['file']??[],false
                ,($data['priority']??0) ? 1 : 3
            );

            // 记录
            $jobStdata->status = EmailSendJobStatu::STATUS_SUCCESS;
            $jobStdata->send_email = $emailinfo['email'];
            $jobStdata->time = date('Y-m-d H:i:s');
            $this->log('成功了');
            // 成功
            $jobData->success = $jobData->success+1;

            // 24小时内不能超过100
//            if(Cache::has($cacheEmailSuccessdkey)){
//                // 加1
//                Cache::increment($cacheEmailSuccessdkey);
//            }else{
//                Cache::set($cacheEmailSuccessdkey,1,86400);
//            }

            // 每小时内不能超过20
            if(Cache::has($cacheEmailSuccesshkey)){
                // 加1
                Cache::increment($cacheEmailSuccesshkey);
            }else{
                Cache::set($cacheEmailSuccesshkey,1,3600);
            }



        } catch (\Exception $e) {
            if($e->getMessage()=='SMTP Error: data not accepted.'){
                // 下次跳过账号
                if(!Cache::has($cachemailierrordkey)) {
                    Cache::set($cachemailierrordkey, $e->getMessage(), 3600);
                }
            }
            // 无法验证,密码错误了,处理密码
            if($e->getMessage()=='SMTP Error: Could not authenticate.'){
                Email::_update(['id'=>$emailinfo['id']],['pwd_error'=>1]);
            }


            $smtpErrorNum++;
            // 超过失败3次的
            $error = $jobStdata->error;
            $error[] = [
                'email' =>  $emailinfo['email'],
                'message'   =>  $e->getMessage(),
                'file'  =>  $e->getFile(),
                'line'  =>  $e->getLine()
            ];
            $jobStdata->time = date('Y-m-d H:i:s');
            $jobStdata->error = json_encode($error);

            // 如果失败了,重试3次
            if($smtpErrorNum <= 20){
                // 记录
                $jobStdata->save();
                $this->log('错误次数'.$smtpErrorNum);
                goto EMAILRESETRANG;
            }
            $this->log('错误超过次数'.$smtpErrorNum);
            // 记录
            $jobStdata->status = EmailSendJobStatu::STATUS_ERROR;
            $jobStdata->send_email = $emailinfo['email'];

            // 错误
            $jobData->error = $jobData->error+1;
        }

        // 保存
        $jobStdata->save();
        $jobData->save();
        $this->log('完成');

        // 再次发布任务
        SendJob::dispatch($this->job_id);


    }


    protected function log($content){
        @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);
    }

}