diff --git a/app/Http/Controllers/Api/SystemController.php b/app/Http/Controllers/Api/SystemController.php
index e1e5fbacf..789866b56 100755
--- a/app/Http/Controllers/Api/SystemController.php
+++ b/app/Http/Controllers/Api/SystemController.php
@@ -99,7 +99,7 @@ class SystemController extends AbstractController
*
* @apiParam {String} type
* - get: 获取(默认)
- * - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice', 'task_remind_hours', 'task_remind_hours2'])
+ * - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice', 'task_remind_hours', 'task_remind_hours2', 'notice_msg', 'msg_unread_user_minute', 'msg_unread_group_minute'])
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
@@ -123,7 +123,10 @@ class SystemController extends AbstractController
'reg_verify',
'notice',
'task_remind_hours',
- 'task_remind_hours2'
+ 'task_remind_hours2',
+ 'notice_msg',
+ 'msg_unread_user_minute',
+ 'msg_unread_group_minute'
])) {
unset($all[$key]);
}
@@ -141,6 +144,9 @@ class SystemController extends AbstractController
$setting['notice'] = $setting['notice'] ?: 'open';
$setting['task_remind_hours'] = floatval($setting['task_remind_hours']) ?: 0;
$setting['task_remind_hours2'] = floatval($setting['task_remind_hours2']) ?: 0;
+ $setting['notice_msg'] = $setting['notice_msg'] ?: 'open';
+ $setting['msg_unread_user_minute'] = floatval($setting['msg_unread_user_minute']) ?: 0;
+ $setting['msg_unread_group_minute'] = floatval($setting['msg_unread_group_minute']) ?: 0;
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
diff --git a/app/Http/Controllers/IndexController.php b/app/Http/Controllers/IndexController.php
index 78465e6b4..71cd96023 100755
--- a/app/Http/Controllers/IndexController.php
+++ b/app/Http/Controllers/IndexController.php
@@ -5,7 +5,7 @@ namespace App\Http\Controllers;
use App\Module\Base;
use App\Tasks\AutoArchivedTask;
use App\Tasks\DeleteTmpTask;
-use App\Tasks\OverdueRemindEmailTask;
+use App\Tasks\EmailNoticeTask;
use Arr;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Redirect;
@@ -140,13 +140,13 @@ class IndexController extends InvokeController
// 限制内网访问
return "Forbidden Access";
}
+ // 自动归档
+ Task::deliver(new AutoArchivedTask());
+ // 邮件通知
+ Task::deliver(new EmailNoticeTask());
// 删除过期的临时表数据
Task::deliver(new DeleteTmpTask('wg_tmp_msgs', 1));
Task::deliver(new DeleteTmpTask('tmp', 24));
- // 自动归档任务
- Task::deliver(new AutoArchivedTask());
- // 任务到期邮件提醒
- Task::deliver(new OverdueRemindEmailTask());
return "success";
}
diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php
index 4167e88ea..465f516aa 100644
--- a/app/Models/WebSocketDialogMsg.php
+++ b/app/Models/WebSocketDialogMsg.php
@@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
*
* @property int $id
* @property int|null $dialog_id 对话ID
+ * @property string|null $dialog_type 对话类型
* @property int|null $userid 发送会员ID
* @property string|null $type 消息类型
* @property array|mixed $msg 详细消息
@@ -32,6 +33,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereRead($value)
@@ -198,6 +200,45 @@ class WebSocketDialogMsg extends AbstractModel
});
}
+ /**
+ * 预览消息
+ * @param bool $preserveHtml 保留html格式
+ * @return string
+ */
+ public function previewMsg($preserveHtml = false)
+ {
+ switch ($this->type) {
+ case 'text':
+ return $this->previewTextMsg($this->msg['text'], $preserveHtml);
+ case 'file':
+ if ($this->msg['type'] == 'img') {
+ return "[图片]";
+ }
+ return "[文件] {$this->msg['name']}";
+ default:
+ return "[未知的消息]";
+ }
+ }
+
+ /**
+ * 返回文本预览消息
+ * @param $text
+ * @param bool $preserveHtml 保留html格式
+ * @return string|string[]|null
+ */
+ private function previewTextMsg($text, $preserveHtml = false)
+ {
+ if (!$text) return '';
+ $text = preg_replace("/]*?alt=\"(\S+)\"[^>]*?>/", "[$1]", $text);
+ $text = preg_replace("/
]*?>/", "[表情]", $text);
+ $text = preg_replace("/
]*?>/", "[图片]", $text);
+ if ($preserveHtml) {
+ return $text;
+ } else {
+ return strip_tags($text);
+ }
+ }
+
/**
* 处理文本消息内容,用于发送前
* @param $text
@@ -288,6 +329,7 @@ class WebSocketDialogMsg extends AbstractModel
$dialog->save();
$dialogMsg->send = 1;
$dialogMsg->dialog_id = $dialog->id;
+ $dialogMsg->dialog_type = $dialog->type;
$dialogMsg->save();
});
Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id));
diff --git a/app/Models/WebSocketDialogMsgRead.php b/app/Models/WebSocketDialogMsgRead.php
index e0406bdb1..8a1a0c7c3 100644
--- a/app/Models/WebSocketDialogMsgRead.php
+++ b/app/Models/WebSocketDialogMsgRead.php
@@ -12,6 +12,7 @@ use Carbon\Carbon;
* @property int|null $msg_id 消息ID
* @property int|null $userid 发送会员ID
* @property int|null $mention 是否提及(被@)
+ * @property int|null $email 是否发了邮件
* @property int|null $after 在阅读之后才添加的记录
* @property string|null $read_at 阅读时间
* @property-read \App\Models\WebSocketDialogMsg|null $webSocketDialogMsg
@@ -20,6 +21,7 @@ use Carbon\Carbon;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereAfter($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereDialogId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMention($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMsgId($value)
diff --git a/app/Tasks/EmailNoticeTask.php b/app/Tasks/EmailNoticeTask.php
new file mode 100644
index 000000000..1976e91a9
--- /dev/null
+++ b/app/Tasks/EmailNoticeTask.php
@@ -0,0 +1,219 @@
+ 0) {
+ ProjectTask::whereNull("complete_at")
+ ->whereNull("archived_at")
+ ->whereBetween("end_at", [
+ Carbon::now()->addMinutes($hours * 60),
+ Carbon::now()->addMinutes($hours * 60 + 10)
+ ])->chunkById(100, function ($tasks) {
+ /** @var ProjectTask $task */
+ foreach ($tasks as $task) {
+ $this->overdueBeforeAfterEmail($task, true);
+ }
+ });
+ }
+ if ($hours2 > 0) {
+ ProjectTask::whereNull("complete_at")
+ ->whereNull("archived_at")
+ ->whereBetween("end_at", [
+ Carbon::now()->subMinutes($hours2 * 60 + 10),
+ Carbon::now()->subMinutes($hours2 * 60)
+ ])->chunkById(100, function ($tasks) {
+ /** @var ProjectTask $task */
+ foreach ($tasks as $task) {
+ $this->overdueBeforeAfterEmail($task, false);
+ }
+ });
+ }
+ }
+ // 消息通知
+ if ($setting['notice_msg'] === 'open') {
+ $userMinute = floatval($setting['msg_unread_user_minute']);
+ $groupMinute = floatval($setting['msg_unread_group_minute']);
+ if ($userMinute > 0) {
+ 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("web_socket_dialog_msgs.dialog_type", "user")
+ ->where("r.email", 0)
+ ->whereNull("r.read_at")
+ ->whereBetween("web_socket_dialog_msgs.created_at", [
+ Carbon::now()->subMinutes($userMinute + 10),
+ Carbon::now()->subMinutes($userMinute)
+ ])->chunkById(100, function ($rows) {
+ $this->unreadMsgEmail($rows);
+ });
+ }
+ if ($groupMinute > 0) {
+ 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("web_socket_dialog_msgs.dialog_type", "group")
+ ->where("r.email", 0)
+ ->whereNull("r.read_at")
+ ->whereBetween("web_socket_dialog_msgs.created_at", [
+ Carbon::now()->subMinutes($groupMinute + 10),
+ Carbon::now()->subMinutes($groupMinute)
+ ])->chunkById(100, function ($rows) {
+ $this->unreadMsgEmail($rows);
+ });
+ }
+ }
+ }
+
+ /**
+ * 任务过期前、超期后提醒
+ * @param ProjectTask $task
+ * @param $isBefore
+ * @return void
+ */
+ private function overdueBeforeAfterEmail(ProjectTask $task, $isBefore)
+ {
+ $userids = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
+ if (empty($userids)) {
+ return;
+ }
+ $users = User::whereIn('userid', $userids)->get();
+ if (empty($users)) {
+ return;
+ }
+
+ $setting = Base::setting('emailSetting');
+ $hours = floatval($setting['task_remind_hours']);
+ $hours2 = floatval($setting['task_remind_hours2']);
+
+ /** @var User $user */
+ foreach ($users as $user) {
+ $data = [
+ 'type' => $isBefore ? 1 : 2,
+ 'userid' => $user->userid,
+ 'task_id' => $task->id,
+ ];
+ $emailLog = ProjectTaskMailLog::where($data)->first();
+ if ($emailLog) {
+ continue;
+ }
+ try {
+ if (!Base::isEmail($user->email)) {
+ throw new \Exception("User email '{$user->email}' address error");
+ }
+ if ($isBefore) {
+ $subject = env('APP_NAME') . " 任务提醒";
+ $content = "
{$user->nickname} 您好:
您有一个任务【{$task->name}】还有{$hours}小时即将超时,请及时处理。
"; + } else { + $subject = env('APP_NAME') . " 任务过期提醒"; + $content = "{$user->nickname} 您好:
您的任务【{$task->name}】已经超时{$hours2}小时,请及时处理。
"; + } + 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($user->email) + ->subject($subject) + ->html($content)) + ->send(); + $data['is_send'] = 1; + } catch (\Exception $e) { + $data['send_error'] = $e->getMessage(); + } + $data['email'] = $user->email; + ProjectTaskMailLog::createInstance($data)->save(); + } + } + + /** + * 未读消息通知 + * @param \Illuminate\Database\Eloquent\Collection|EmailNoticeTask[] $rows + * @return void + */ + private function unreadMsgEmail($rows) + { + $array = $rows->groupBy('r_userid'); + foreach ($array as $userid => $data) { + $user = User::find($userid); + if (empty($user)) { + continue; + } + if (!Base::isEmail($user->email)) { + continue; + } + if (count($data) === 0) { + continue; + } + $setting = Base::setting('emailSetting'); + $subject = env('APP_NAME') . " 未读消息提醒(" . count($data) . ")条"; + $content = view('unread', [ + 'type' => 'head', + 'nickname' => $user->nickname, + 'count' => count($data), + ]); + $lists = $data->groupBy('dialog_id'); + /** @var WebSocketDialogMsg[] $items */ + foreach ($lists as $items) { + $dialogName = null; + foreach ($items as $item) { + $item->userInfo = User::userid2basic($item->userid); + $item->preview = $item->previewMsg(true); + if ($dialogName === null) { + switch ($item->dialog_type) { + case "user": + $dialogName = $item->userInfo['nickname']; + break; + case "group": + $dialogName = $item->webSocketDialog?->name; + break; + } + } + } + $content .= view('unread', [ + 'type' => 'content', + 'dialogName' => $dialogName, + 'unread' => count($items), + 'items' => $items, + ]); + } + try { + 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($user->email) + ->subject($subject) + ->html($content)) + ->send(); + } catch (\Exception $e) { + info("unreadMsgEmail: " . $e->getMessage()); + } + } + WebSocketDialogMsgRead::whereIn('id', $rows->pluck('r_id'))->update([ + 'email' => 1 + ]); + } +} diff --git a/database/migrations/2022_05_05_115726_add_web_socket_dialog_msgs_dialog_type.php b/database/migrations/2022_05_05_115726_add_web_socket_dialog_msgs_dialog_type.php new file mode 100644 index 000000000..41ecc86a9 --- /dev/null +++ b/database/migrations/2022_05_05_115726_add_web_socket_dialog_msgs_dialog_type.php @@ -0,0 +1,34 @@ +string('dialog_type', 50)->nullable()->default('')->after('dialog_id')->comment('对话类型'); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('web_socket_dialog_msgs', function (Blueprint $table) { + $table->dropColumn("dialog_type"); + }); + } +} diff --git a/database/migrations/2022_05_05_135535_add_web_socket_dialog_msg_reads_email.php b/database/migrations/2022_05_05_135535_add_web_socket_dialog_msg_reads_email.php new file mode 100644 index 000000000..0db49b85a --- /dev/null +++ b/database/migrations/2022_05_05_135535_add_web_socket_dialog_msg_reads_email.php @@ -0,0 +1,34 @@ +boolean('email')->default(0)->after('mention')->nullable()->comment('是否发了邮件'); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('web_socket_dialog_msg_reads', function (Blueprint $table) { + $table->dropColumn("email"); + }); + } +} diff --git a/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue b/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue index abfc10491..92cfedb24 100644 --- a/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue +++ b/resources/assets/js/pages/manage/setting/components/SystemEmailSetting.vue @@ -39,12 +39,12 @@ @@ -57,12 +57,12 @@ @@ -91,6 +91,9 @@ export default { notice: 'open', task_remind_hours: 0, task_remind_hours2: 0, + notice_msg: 'open', + msg_unread_user_minute: 0, + msg_unread_group_minute: 0, }, ruleData: {}, } diff --git a/resources/assets/sass/pages/page-setting.scss b/resources/assets/sass/pages/page-setting.scss index fc7fe020b..315c53765 100755 --- a/resources/assets/sass/pages/page-setting.scss +++ b/resources/assets/sass/pages/page-setting.scss @@ -211,7 +211,7 @@ } .email-setting-box { position: relative; - padding: 36px 24px 4px; + padding: 44px 24px 4px; margin: 24px 0 12px; border-radius: 8px; border: 1px solid #eeeeee; diff --git a/resources/views/unread.blade.php b/resources/views/unread.blade.php new file mode 100755 index 000000000..9274c191e --- /dev/null +++ b/resources/views/unread.blade.php @@ -0,0 +1,55 @@ +@if ($type === 'head') +|
+ {{ $nickname }} 您好: +您有({{ $count }})条未读消息,请及时处理。 ++ |
+
+ + {{ $dialogName }} +++ {{ $unread }}条未读信息 +++ @foreach($items as $item) +
+ + + |
+