diff --git a/app/Http/Controllers/Api/SystemController.php b/app/Http/Controllers/Api/SystemController.php index e789cdb5c..d668bff41 100755 --- a/app/Http/Controllers/Api/SystemController.php +++ b/app/Http/Controllers/Api/SystemController.php @@ -12,6 +12,7 @@ use Carbon\Carbon; use App\Module\Doo; use App\Models\User; use App\Module\Base; +use App\Module\Timer; use App\Models\Setting; use App\Module\Extranet; use LdapRecord\Container; @@ -180,11 +181,18 @@ class SystemController extends AbstractController 'notice_msg', 'msg_unread_user_minute', 'msg_unread_group_minute', + 'msg_unread_time_ranges', 'ignore_addr' ])) { unset($all[$key]); } } + $ranges = array_map(function ($item) { + return !is_array($item) ? explode(',', $item) : $item; + }, is_array($all['msg_unread_time_ranges']) ? $all['msg_unread_time_ranges'] : []); + $all['msg_unread_time_ranges'] = array_values(array_filter($ranges, function ($item) { + return count($item) == 2 && Timer::isTime($item[0]) && Timer::isTime($item[1]); + })); $setting = Base::setting('emailSetting', Base::newTrim($all)); } else { $setting = Base::setting('emailSetting'); @@ -198,6 +206,7 @@ class SystemController extends AbstractController $setting['notice_msg'] = $setting['notice_msg'] ?: 'close'; $setting['msg_unread_user_minute'] = intval($setting['msg_unread_user_minute'] ?? -1); $setting['msg_unread_group_minute'] = intval($setting['msg_unread_group_minute'] ?? -1); + $setting['msg_unread_time_ranges'] = is_array($setting['msg_unread_time_ranges']) ? $setting['msg_unread_time_ranges'] : [[]]; $setting['ignore_addr'] = $setting['ignore_addr'] ?: ''; // if ($type != 'save' && !in_array('admin', $user->identity)) { @@ -768,7 +777,7 @@ class SystemController extends AbstractController if ($data['info']['people'] > 0 && $data['user_count'] > $data['info']['people']) { $data['error'][] = '终端用户数超过License限制'; } - if ($data['info']['expired_at'] && strtotime($data['info']['expired_at']) <= Base::time()) { + if ($data['info']['expired_at'] && strtotime($data['info']['expired_at']) <= Timer::time()) { $data['error'][] = '终端License已过期'; } // @@ -1196,13 +1205,13 @@ class SystemController extends AbstractController if (count($userid) > 100) { return Base::retError('导出成员限制最多100个'); } - if (!(is_array($date) && Base::isDate($date[0]) && Base::isDate($date[1]))) { + if (!(is_array($date) && Timer::isDate($date[0]) && Timer::isDate($date[1]))) { return Base::retError('日期选择错误'); } if (Carbon::parse($date[1])->timestamp - Carbon::parse($date[0])->timestamp > 35 * 86400) { return Base::retError('日期范围限制最大35天'); } - if (!(is_array($time) && Base::isTime($time[0]) && Base::isTime($time[1]))) { + if (!(is_array($time) && Timer::isTime($time[0]) && Timer::isTime($time[1]))) { return Base::retError('时间选择错误'); } // @@ -1244,7 +1253,7 @@ class SystemController extends AbstractController $lastRecord = $sameCollect?->whereBetween("datetime", $lastBetween)->last(); $firstTimestamp = $firstRecord['timestamp'] ?: 0; $lastTimestamp = $lastRecord['timestamp'] ?: 0; - if (Base::time() < $startT + $secondStart) { + if (Timer::time() < $startT + $secondStart) { $firstResult = "-"; } else { $firstResult = Doo::translate("正常"); @@ -1256,7 +1265,7 @@ class SystemController extends AbstractController $styles["E{$index}"] = ["font" => ["color" => ["rgb" => "436FF6"]]]; } } - if (Base::time() < $startT + $secondEnd) { + if (Timer::time() < $startT + $secondEnd) { $lastResult = "-"; $lastTimestamp = 0; } else { @@ -1299,8 +1308,8 @@ class SystemController extends AbstractController } else { $fileName .= '的签到记录'; } - $fileName = Doo::translate($fileName) . '_' . Base::time() . '.xlsx'; - $filePath = "temp/checkin/export/" . date("Ym", Base::time()); + $fileName = Doo::translate($fileName) . '_' . Timer::time() . '.xlsx'; + $filePath = "temp/checkin/export/" . date("Ym", Timer::time()); $export = new BillMultipleExport($sheets); $res = $export->store($filePath . "/" . $fileName); if ($res != 1) { diff --git a/app/Module/Timer.php b/app/Module/Timer.php new file mode 100644 index 000000000..9503e599b --- /dev/null +++ b/app/Module/Timer.php @@ -0,0 +1,380 @@ += 86400) { // 如果大于1天 + $days = (int)($time / 86400); + $time = $time % 86400; // 计算天后剩余的毫秒数 + } + $hours = 0; + if ($time >= 3600) { // 如果大于1小时 + $hours = (int)($time / 3600); + $time = $time % 3600; // 计算小时后剩余的毫秒数 + } + $minutes = ceil($time / 60); // 剩下的毫秒数都算作分 + $daysStr = $days > 0 ? $days . '天' : ''; + $hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : ''; + $minuteStr = ($minutes > 0) ? $minutes . '分' : ''; + return $daysStr . $hoursStr . $minuteStr; + } + + /** + * 时间秒数格式化 + * @param int $time 时间秒数 + * @return string + */ + public static function timeFormat($time) + { + $days = 0; + if ($time >= 86400) { // 如果大于1天 + $days = (int)($time / 86400); + $time = $time % 86400; // 计算天后剩余的毫秒数 + } + $hours = 0; + if ($time >= 3600) { // 如果大于1小时 + $hours = (int)($time / 3600); + $time = $time % 3600; // 计算小时后剩余的毫秒数 + } + $minutes = ceil($time / 60); // 剩下的毫秒数都算作分 + $daysStr = $days > 0 ? $days . '天' : ''; + $hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : ''; + $minuteStr = ($minutes > 0) ? $minutes . '分' : ''; + return $daysStr . $hoursStr . $minuteStr; + } + + /** + * 检测日期格式 + * @param string $str 需要检测的字符串 + * @return bool + */ + public static function isDate($str) + { + $strArr = explode('-', $str); + if (empty($strArr) || count($strArr) != 3) { + return false; + } else { + list($year, $month, $day) = $strArr; + if (checkdate(intval($month), intval($day), intval($year))) { + return true; + } else { + return false; + } + } + } + + /** + * 检测时间格式 + * @param string $str 需要检测的字符串 + * @return bool + */ + public static function isTime($str) + { + $strArr = explode(':', $str); + $count = count($strArr); + if ($count < 2 || $count > 3) { + return false; + } + $hour = $strArr[0]; + if ($hour < 0 || $hour > 23) { + return false; + } + $minute = $strArr[1]; + if ($minute < 0 || $minute > 59) { + return false; + } + if ($count == 3) { + $second = $strArr[2]; + if ($second < 0 || $second > 59) { + return false; + } + } + return true; + } + + /** + * 检测 日期格式 或 时间格式 + * @param string $str 需要检测的字符串 + * @return bool + */ + public static function isDateOrTime($str) + { + return self::isDate($str) || self::isTime($str); + } + + /** + * 时间转毫秒时间戳 + * @param $time + * @return float|int + */ + public static function strtotimeM($time) + { + if (str_contains($time, '.')) { + list($t, $m) = explode(".", $time); + if (is_string($t)) { + $t = strtotime($t); + } + $time = $t . str_pad($m, 3, "0", STR_PAD_LEFT); + } + if (is_numeric($time)) { + return (int) str_pad($time, 13, "0"); + } else { + return strtotime($time) * 1000; + } + } + + /** + * 时间格式化 + * @param $date + * @return false|string + */ + public static function forumDate($date) + { + $dur = time() - $date; + if ($date > Carbon::now()->startOf('day')->timestamp) { + //今天 + if ($dur < 60) { + return max($dur, 1) . '秒前'; + } elseif ($dur < 3600) { + return floor($dur / 60) . '分钟前'; + } elseif ($dur < 86400) { + return floor($dur / 3600) . '小时前'; + } else { + return date("H:i", $date); + } + } elseif ($date > Carbon::now()->subDays()->startOf('day')->timestamp) { + //昨天 + return '昨天'; + } elseif ($date > Carbon::now()->subDays(2)->startOf('day')->timestamp) { + //前天 + return '前天'; + } elseif ($dur > 86400) { + //x天前 + return floor($dur / 86400) . '天前'; + } + return date("Y-m-d", $date); + } + + /** + * 获取(时间戳转)今天是星期几,只返回(几) + * @param string|number $unixTime + * @return string + */ + public static function getWeek($unixTime = '') + { + $unixTime = is_numeric($unixTime) ? $unixTime : time(); + $weekarray = ['日', '一', '二', '三', '四', '五', '六']; + return $weekarray[date('w', $unixTime)]; + } + + /** + * 获取(时间戳转)现在时间段:深夜、凌晨、早晨、上午..... + * @param string|number $unixTime + * @return string + */ + public static function getDayeSegment($unixTime = '') + { + $unixTime = is_numeric($unixTime) ? $unixTime : time(); + $H = date('H', $unixTime); + if ($H >= 19) { + return '晚上'; + } elseif ($H >= 18) { + return '傍晚'; + } elseif ($H >= 13) { + return '下午'; + } elseif ($H >= 12) { + return '中午'; + } elseif ($H >= 8) { + return '上午'; + } elseif ($H >= 5) { + return '早晨'; + } elseif ($H >= 1) { + return '凌晨'; + } elseif ($H >= 0) { + return '深夜'; + } else { + return ''; + } + } + + /** + * 秒 (转) 年、天、时、分、秒 + * @param $time + * @return array|bool + */ + public static function sec2time($time) + { + if (is_numeric($time)) { + $value = array( + "years" => 0, "days" => 0, "hours" => 0, + "minutes" => 0, "seconds" => 0, + ); + if ($time >= 86400) { + $value["days"] = floor($time / 86400); + $time = ($time % 86400); + } + if ($time >= 3600) { + $value["hours"] = floor($time / 3600); + $time = ($time % 3600); + } + if ($time >= 60) { + $value["minutes"] = floor($time / 60); + $time = ($time % 60); + } + $value["seconds"] = floor($time); + return (array)$value; + } else { + return (bool)FALSE; + } + } + + /** + * 年、天、时、分、秒 (转) 秒 + * @param $value + * @return int + */ + public static function time2sec($value) + { + $time = intval($value["seconds"]); + $time += intval($value["minutes"] * 60); + $time += intval($value["hours"] * 3600); + $time += intval($value["days"] * 86400); + $time += intval($value["years"] * 31536000); + return $time; + } + + /** + * 阿拉伯数字转化为中文 + * @param $num + * @return string + */ + public static function chinaNum($num) + { + $china = array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九'); + $arr = str_split($num); + $txt = ''; + for ($i = 0; $i < count($arr); $i++) { + $txt .= $china[$arr[$i]]; + } + return $txt; + } + + /** + * 阿拉伯数字转化为中文(用于星期,七改成日) + * @param $num + * @return string + */ + public static function chinaNumZ($num) + { + return str_replace("七", "日", Timer::chinaNum($num)); + } + + /** + * 时间是否在时间范围内 + * @param array $timeRanges 如:['08:00', '12:00'] 或 [['08:00', '12:00'], ['14:00', '18:00']] + * @param string|null $currentTime + * @return bool + */ + public static function isTimeInRanges(array $timeRanges, ?string $currentTime = null): bool + { + // 如果没有传入当前时间,使用当前时间 + $currentTime = $currentTime ?? date('H:i'); + + // 转换当前时间为分钟数,便于比较 + $currentMinutes = self::timeToMinutes($currentTime); + if ($currentMinutes === false) { + return false; + } + + // 将单个时间范围转换为数组格式 + if (isset($timeRanges[0]) && !is_array($timeRanges[0])) { + $timeRanges = [$timeRanges]; + } + + // 过滤并检查有效的时间范围 + foreach ($timeRanges as $range) { + if (!self::isValidTimeRange($range)) { + continue; + } + + $startMinutes = self::timeToMinutes($range[0]); + $endMinutes = self::timeToMinutes($range[1]); + + if ($startMinutes === false || $endMinutes === false) { + continue; + } + + if ($startMinutes <= $currentMinutes && $currentMinutes <= $endMinutes) { + return true; + } + } + + return false; + } + + /** + * 辅助函数:检查时间范围是否有效 + * @param $range + * @return bool + */ + private static function isValidTimeRange($range): bool + { + return is_array($range) + && count($range) === 2 + && is_string($range[0]) + && is_string($range[1]) + && !empty($range[0]) + && !empty($range[1]) + && preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $range[0]) + && preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $range[1]); + } + + /** + * 辅助函数:将时间转换为分钟数 + * @param string $time + * @return false|float|int + */ + private static function timeToMinutes(string $time) + { + if (!preg_match('/^([01]?[0-9]|2[0-3]):([0-5][0-9])$/', $time, $matches)) { + return false; + } + + return intval($matches[1]) * 60 + intval($matches[2]); + } + +} diff --git a/app/Tasks/EmailNoticeTask.php b/app/Tasks/EmailNoticeTask.php index 3c5ba372d..66805787a 100644 --- a/app/Tasks/EmailNoticeTask.php +++ b/app/Tasks/EmailNoticeTask.php @@ -8,165 +8,276 @@ use App\Models\WebSocketDialogMsg; use App\Models\WebSocketDialogMsgRead; use App\Module\Base; use App\Module\Doo; +use App\Module\Timer; use Carbon\Carbon; use Guanguans\Notify\Factory; use Guanguans\Notify\Messages\EmailMessage; -@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); - +/** + * 未读消息邮件通知任务 + * 根据设置的时间范围,将未读消息通过邮件发送给用户 + */ class EmailNoticeTask extends AbstractTask { + /** @var array 允许发送通知的消息类型 */ + private const ALLOWED_MSG_TYPES = ["text", "file", "record", "meeting"]; + + /** @var int 每批处理的数据量 */ + private const CHUNK_SIZE = 100; + + /** @var array 邮件相关设置 */ + private array $emailSetting; + public function __construct() { parent::__construct(); + $this->emailSetting = Base::setting('emailSetting'); } public function start() { - $setting = Base::setting('emailSetting'); - // 消息通知 - if ($setting['notice_msg'] === 'open') { - $userMinute = intval($setting['msg_unread_user_minute']); - $groupMinute = intval($setting['msg_unread_group_minute']); - \DB::statement("SET SQL_MODE=''"); - $builder = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid']) - ->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id') - ->whereNull("r.read_at") - ->where("r.silence", 0) - ->where("r.email", 0); - if ($userMinute > -1) { - $builder->clone() - ->where("web_socket_dialog_msgs.dialog_type", "user") - ->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"]) - ->whereBetween("web_socket_dialog_msgs.created_at", [ - Carbon::now()->subMinutes($userMinute + 10), - Carbon::now()->subMinutes($userMinute) - ]) - ->groupBy('r_userid') - ->chunkById(100, function ($rows) { - $this->unreadMsgEmail($rows, "user"); - }); - } - if ($groupMinute > -1) { - $builder->clone() - ->where("web_socket_dialog_msgs.dialog_type", "group") - ->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"]) - ->whereBetween("web_socket_dialog_msgs.created_at", [ - Carbon::now()->subMinutes($groupMinute + 10), - Carbon::now()->subMinutes($groupMinute) - ]) - ->groupBy('r_userid') - ->chunkById(100, function ($rows) { - $this->unreadMsgEmail($rows, "group"); - }); - } + // 检查是否可以发送邮件 + if (!$this->canSendEmails()) { + return; } + + \DB::statement("SET SQL_MODE=''"); + + // 分别处理用户消息和群组消息 + $this->processMessages('user'); + $this->processMessages('group'); + } + + /** + * 检查是否可以发送邮件通知 + * 需要开启通知功能且在指定的时间范围内 + */ + private function canSendEmails(): bool + { + if ($this->emailSetting['notice_msg'] !== 'open') { + return false; + } + + $timeRanges = is_array($this->emailSetting['msg_unread_time_ranges']) + ? $this->emailSetting['msg_unread_time_ranges'] + : []; + + return Timer::isTimeInRanges($timeRanges); + } + + /** + * 处理指定类型的未读消息 + * @param string $dialogType 对话类型:user|group + */ + private function processMessages(string $dialogType): void + { + // 获取未读时间限制(分钟) + $minute = $dialogType === 'user' + ? intval($this->emailSetting['msg_unread_user_minute']) + : intval($this->emailSetting['msg_unread_group_minute']); + + if ($minute <= -1) { + return; + } + + // 获取上次处理时间 + $lastProcessKey = 'time' . ucfirst($dialogType); + $startTime = Base::settingFind('emailLastNotice', $lastProcessKey); + $startTime = $startTime ? Carbon::parse($startTime) : Carbon::today(); + + // 计算本次处理的结束时间(当前时间减去未读时间限制) + $endTime = Carbon::now()->subMinutes($minute); + + // 如果开始时间晚于结束时间,则不处理 + if ($startTime->isAfter($endTime)) { + return; + } + + // 获取需要处理的用户列表 + $query = WebSocketDialogMsgRead::select('web_socket_dialog_msg_reads.userid') + ->join('web_socket_dialog_msgs as m', 'm.id', '=', 'web_socket_dialog_msg_reads.msg_id') + ->whereNull('web_socket_dialog_msg_reads.read_at') + ->where('web_socket_dialog_msg_reads.silence', 0) + ->where('web_socket_dialog_msg_reads.email', 0) + ->where('m.dialog_type', $dialogType) + ->whereBetween('m.created_at', [$startTime, $endTime]) + ->whereIn('m.type', self::ALLOWED_MSG_TYPES) + ->orderBy('web_socket_dialog_msg_reads.userid') + ->groupBy('web_socket_dialog_msg_reads.userid'); + + // 分批处理用户的未读消息 + $query->chunk(self::CHUNK_SIZE, function($users) use ($dialogType, $startTime, $endTime) { + foreach ($users as $userData) { + $this->sendUserEmail($userData->userid, $dialogType, $startTime, $endTime); + } + }); + + // 更新处理时间 + Base::setting('emailLastNotice', [ + $lastProcessKey => $endTime->toDateTimeString() + ]); + } + + /** + * 发送用户的未读消息邮件 + */ + private function sendUserEmail(int $userId, string $dialogType, Carbon $startTime, Carbon $endTime): void + { + // 验证用户 + $user = User::find($userId); + if (!$user || $user->bot || !is_null($user->disable_at) || !Base::isEmail($user->email)) { + return; + } + + // 获取未读消息 + $messages = $this->getUnreadMessages($userId, $dialogType, $startTime, $endTime); + if ($messages->isEmpty()) { + return; + } + + // 设置用户语言 + Doo::setLanguage($user->lang); + + // 按对话分组并生成邮件内容 + $messagesByDialog = $messages->groupBy('dialog_id'); + $emailContent = $this->generateEmailContent($user, $messagesByDialog, $dialogType); + + try { + // 发送邮件 + $this->sendEmail($user, $emailContent); + // 标记消息已发送邮件 + WebSocketDialogMsgRead::whereIn('id', $messages->pluck('r_id')) + ->update(['email' => 1]); + } catch (\Throwable $e) { + info("Email send failed for user {$userId}: " . $e->getMessage()); + } + } + + /** + * 获取用户的未读消息 + */ + private function getUnreadMessages($userId, $dialogType, Carbon $startTime, Carbon $endTime) + { + return WebSocketDialogMsg::select([ + 'web_socket_dialog_msgs.*', + 'r.id as r_id', + 'r.userid as r_userid' + ]) + ->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id') + ->where([ + 'r.userid' => $userId, + 'r.silence' => 0, + 'r.email' => 0, + 'web_socket_dialog_msgs.dialog_type' => $dialogType + ]) + ->whereNull('r.read_at') + ->whereBetween('web_socket_dialog_msgs.created_at', [$startTime, $endTime]) + ->whereIn('web_socket_dialog_msgs.type', self::ALLOWED_MSG_TYPES) + ->orderBy('web_socket_dialog_msgs.created_at') + ->limit(self::CHUNK_SIZE) + ->get(); + } + + /** + * 生成邮件内容 + */ + private function generateEmailContent($user, $messagesByDialog, $dialogType) + { + $msgType = $dialogType === "group" ? "群聊" : "单聊"; + + // 生成邮件头部 + $content = view('email.unread', [ + 'type' => 'head', + 'title' => Doo::translate(sprintf('%s,您好。', $user->nickname)), + 'desc' => Doo::translate(sprintf('您有%d条未读%s消息,请及时处理。', count($messagesByDialog), $msgType)), + ])->render(); + + $subject = null; + // 处理每个对话的消息 + foreach ($messagesByDialog as $items) { + $dialogId = 0; + $dialogName = null; + + foreach ($items as $item) { + $item->cancelAppend(); + $item->userInfo = User::userid2basic($item->userid, ['lang']); + Doo::setLanguage($item->userInfo->lang); + $item->preview = WebSocketDialogMsg::previewMsg($item, true); + $item->preview = str_replace('
', '
', $item->preview); + + if (empty($dialogId)) { + $dialogId = $item->dialog_id; + } + if ($dialogName === null) { + $dialogName = $this->getDialogName($item, $dialogType); + } + } + + // 生成邮件主题 + if ($subject === null) { + $subject = count($messagesByDialog) > 1 + ? sprintf('来自%d个%s未读消息提醒', count($messagesByDialog), $msgType) + : sprintf('来自%s未读消息提醒', $dialogName); + } + + // 添加对话内容 + $content .= view('email.unread', [ + 'type' => 'content', + 'dialogUrl' => config("app.url") . "/manage/messenger?dialog_id={$dialogId}", + 'dialogName' => $dialogName, + 'title' => Doo::translate(sprintf('%d条未读信息', count($items))), + 'button' => Doo::translate('回复消息'), + 'unread' => count($items), + 'items' => $items, + ])->render(); + } + + $content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content); + + return [ + 'subject' => Doo::translate($subject), + 'content' => $content + ]; + } + + /** + * 获取对话名称 + */ + private function getDialogName($message, $dialogType) + { + if ($dialogType === "user" && $message->userInfo) { + return $message->userInfo->profession + ? sprintf('%s (%s)', $message->userInfo->nickname, $message->userInfo->profession) + : $message->userInfo->nickname; + } + return $message->webSocketDialog?->getGroupName(); + } + + /** + * 发送邮件 + */ + private function sendEmail($user, $emailData): void + { + Setting::validateAddr($user->email, function($to) use ($emailData) { + Factory::mailer() + ->setDsn(sprintf( + 'smtp://%s:%s@%s:%s?verify_peer=0', + $this->emailSetting['account'], + $this->emailSetting['password'], + $this->emailSetting['smtp_server'], + $this->emailSetting['port'] + )) + ->setMessage(EmailMessage::create() + ->from(sprintf('%s <%s>', env('APP_NAME', 'Task'), $this->emailSetting['account'])) + ->to($to) + ->subject($emailData['subject']) + ->html($emailData['content'])) + ->send(); + }); } public function end() { - - } - - /** - * 未读消息通知 - * @param $rows - * @param $dialogType - * @return void - */ - private function unreadMsgEmail($rows, $dialogType) - { - $array = $rows->groupBy('r_userid'); - foreach ($array as $userid => $data) { - $data = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid']) - ->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id') - ->whereNull("r.read_at") - ->where("r.silence", 0) - ->where("r.email", 0) - ->where("r.userid", $userid) - ->where("web_socket_dialog_msgs.dialog_type", $dialogType) - ->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"]) - ->take(100) - ->get(); - if (empty($data)) { - continue; - } - $user = User::whereBot(0)->whereNull('disable_at')->find($userid); - if (empty($user)) { - continue; - } - if (!Base::isEmail($user->email)) { - continue; - } - $setting = Base::setting('emailSetting'); - $msgType = $dialogType === "group" ? "群聊" : "成员"; - $subject = null; - $content = view('email.unread', [ - 'type' => 'head', - 'nickname' => $user->nickname, - 'msgType' => $msgType, - 'count' => count($data), - ])->render(); - $lists = $data->groupBy('dialog_id'); - /** @var WebSocketDialogMsg[] $items */ - foreach ($lists as $items) { - $dialogId = 0; - $dialogName = null; - foreach ($items as $item) { - $item->cancelAppend(); - $item->userInfo = User::userid2basic($item->userid, ['lang']); - Doo::setLanguage($item->userInfo->lang); - $item->preview = WebSocketDialogMsg::previewMsg($item, true); - $item->preview = str_replace('
', '
', $item->preview);
- if (empty($dialogId)) {
- $dialogId = $item->dialog_id;
- }
- if ($dialogName === null) {
- if ($dialogType === "user" && $item->userInfo) {
- if ($item->userInfo->profession) {
- $dialogName = $item->userInfo->nickname . " ({$item->userInfo->profession})";
- } else {
- $dialogName = $item->userInfo->nickname;
- }
- } else {
- $dialogName = $item->webSocketDialog?->getGroupName();
- }
- }
- }
- if ($subject === null) {
- $count = count($lists);
- if ($count > 1) {
- $subject = "来自{$count}个{$msgType}未读消息提醒";
- } else {
- $subject = "来自{$dialogName}未读消息提醒";
- }
- }
- $content .= view('email.unread', [
- 'type' => 'content',
- 'dialogUrl' => config("app.url") . "/manage/messenger?dialog_id={$dialogId}",
- 'dialogName' => $dialogName,
- 'unread' => count($items),
- 'items' => $items,
- ])->render();
- $content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
- }
- try {
- Setting::validateAddr($user->email, function($to) use ($content, $subject, $setting) {
- Factory::mailer()
- ->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
- ->setMessage(EmailMessage::create()
- ->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
- ->to($to)
- ->subject($subject)
- ->html($content))
- ->send();
- });
- } catch (\Throwable $e) {
- info("unreadMsgEmail: " . $e->getMessage());
- }
- WebSocketDialogMsgRead::whereIn('id', $data->pluck('r_id'))->update([
- 'email' => 1
- ]);
- }
+ // 任务结束处理
}
}
diff --git a/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue b/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue
index 0b8be9e82..b5fed5e60 100644
--- a/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue
+++ b/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue
@@ -49,6 +49,23 @@