perf: 优化邮件通知

This commit is contained in:
kuaifan 2024-11-12 18:12:46 +08:00
parent 9f00047fdd
commit 1227a05e2d
6 changed files with 686 additions and 154 deletions

View File

@ -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) {

380
app/Module/Timer.php Normal file
View File

@ -0,0 +1,380 @@
<?php
namespace App\Module;
use App\Services\RequestContext;
use Carbon\Carbon;
class Timer
{
/**
* 获取时间戳
* @return int
*/
public static function time()
{
return intval(RequestContext::get("start_time", time()));
}
/**
* 获取毫秒时间戳
* @return float
*/
public static function msecTime()
{
list($msec, $sec) = explode(' ', microtime());
$time = explode(".", $sec . ($msec * 1000));
return $time[0];
}
/**
* 时间差(不够1个小时算一个小时)
* @param int $s 开始时间戳
* @param int $e 结束时间戳
* @return string
*/
public static function timeDiff($s, $e)
{
$time = $e - $s;
$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 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]);
}
}

View File

@ -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('<p>', '<p style="margin:0;padding:0">', $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('<p>', '<p style="margin:0;padding:0">', $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
]);
}
// 任务结束处理
}
}

View File

@ -49,6 +49,23 @@
<Radio label="close">{{ $L('关闭') }}</Radio>
</RadioGroup>
<Form v-if="formData.notice_msg == 'open'" @submit.native.prevent class="block-setting-msg-unread">
<FormItem :label="$L('通知时间')">
<div class="input-range-box">
<div
v-for="(_, index) in formData.msg_unread_time_ranges"
:key="index"
class="input-range-item">
<TimePicker
v-model="formData.msg_unread_time_ranges[index]"
type="timerange"
format="HH:mm"
:placeholder="$L('选择时间范围')"
transfer/>
</div>
<Button type="default" icon="md-add" @click="onAddTimeRange">{{ $L('添加时间范围') }}</Button>
<div class="form-tip">{{ $L('仅在通知时间内发送邮件。') }}</div>
</div>
</FormItem>
<FormItem :label="$L('未读个人消息')" prop="msg_unread_user_minute">
<div class="input-number-box">
<InputNumber v-model="formData.msg_unread_user_minute" :min="0" :step="1"/>
@ -104,6 +121,7 @@ export default {
msg_unread_user_minute: -1,
msg_unread_group_minute: -1,
ignore_addr: '',
msg_unread_time_ranges:[[]]
},
ruleData: {},
}
@ -130,6 +148,14 @@ export default {
this.formData = $A.cloneJSON(this.formDatum_bak);
},
onAddTimeRange() {
if (this.formData.msg_unread_time_ranges.length > 5) {
$A.messageError('最多添加5个时间范围');
return;
}
this.formData.msg_unread_time_ranges.push([])
},
systemSetting(save) {
this.loadIng++;
this.$store.dispatch("call", {

View File

@ -54,6 +54,12 @@ body {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
}
.input-range-box {
.input-range-item {
margin-bottom: 6px;
}
}
.input-number-box {
display: flex;
align-items: center;

View File

@ -3,9 +3,9 @@
<tbody>
<tr style="box-sizing:border-box;padding:0;vertical-align:top;text-align:left">
<td style="box-sizing:border-box;word-break:break-word;border-collapse:collapse;padding:0 0 10px;vertical-align:top;text-align:left;color:#202020;font-weight:normal;margin:0;line-height:19px;font-size:14px">
<p>{{ $nickname }} 您好:</p>
<p>您有({{ $count }})条未读{{ $msgType }}消息,请及时处理。</p>
<hr style='box-sizing:border-box;color:#d9d9d9;background-color:#d9d9d9;height:1px;border:none;margin-top:32px'>
<p>{{ $title }} </p>
<p>{{ $desc }}</p>
<hr style="box-sizing:border-box;color:#d9d9d9;background-color:#d9d9d9;height:1px;border:none;margin-top:32px">
</td>
</tr>
</tbody>
@ -19,7 +19,7 @@
{{ $dialogName }}
</h2>
<h4 style="box-sizing:border-box;color:#202020;font-weight:normal;padding:0;margin:0;text-align:left;line-height:1.3;word-break:normal;font-size:14px">
{{ $unread }}条未读信息
{{ $title }}
</h4>
<br style="box-sizing:border-box">
@foreach($items as $item)
@ -51,7 +51,7 @@
<tbody>
<tr style="box-sizing:border-box; padding:0; vertical-align:top; text-align:left">
<td style="box-sizing:border-box; word-break:break-word; border-collapse:collapse; padding:0 0 10px; vertical-align:top; text-align:left; color:#202020; font-weight:normal; margin:0; line-height:19px; font-size:14px">
<a style="text-decoration:none; box-sizing:border-box; color:white; background-color:#46bc99; padding:8px 16px; border-radius:4px; font-size:10px; text-transform:uppercase; font-weight:bold" href="{{ $dialogUrl }}" target="_blank">回复消息</a>
<a style="text-decoration:none; box-sizing:border-box; color:white; background-color:#46bc99; padding:8px 16px; border-radius:4px; font-size:10px; text-transform:uppercase; font-weight:bold" href="{{ $dialogUrl }}" target="_blank">{{ $button }}</a>
</td>
</tr>
</tbody>