feat: 消息会话支持免打扰

This commit is contained in:
kuaifan 2023-01-17 17:08:20 +08:00
parent d89377a8fe
commit e84961f20f
20 changed files with 210 additions and 59 deletions

View File

@ -48,7 +48,7 @@ class DialogController extends AbstractController
{
$user = User::auth();
//
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('u.userid', $user->userid);
if (Request::exists('at_after')) {
@ -88,7 +88,7 @@ class DialogController extends AbstractController
return Base::retError('请输入搜索关键词');
}
// 搜索会话
$dialogs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
$dialogs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.name', 'LIKE', "%{$key}%")
->where('u.userid', $user->userid)
@ -121,7 +121,7 @@ class DialogController extends AbstractController
}
// 搜索消息会话
if (count($list) < 20) {
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'm.id as search_msg_id'])
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'm.id as search_msg_id'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->join('web_socket_dialog_msgs as m', 'web_socket_dialogs.id', '=', 'm.dialog_id')
->where('u.userid', $user->userid)
@ -158,7 +158,7 @@ class DialogController extends AbstractController
//
$dialog_id = intval(Request::input('dialog_id'));
//
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.id', $dialog_id)
->where('u.userid', $user->userid)
@ -1060,6 +1060,69 @@ class DialogController extends AbstractController
return Base::retSuccess("success", $data);
}
/**
* @api {get} api/dialog/msg/silence 20. 消息免打扰
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__silence
*
* @apiParam {Number} dialog_id 会话ID
* @apiParam {String} type 类型
* - set
* - cancel
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__silence()
{
$user = User::auth();
$dialogId = intval(Request::input('dialog_id'));
$type = Request::input('type');
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
if (!$dialogUser) {
return Base::retError("会话不存在");
}
//
$dialogData = WebSocketDialog::find($dialogId);
if (empty($dialogData)) {
return Base::retError("会话不存在");
}
if ($dialogData->type === 'group' && $dialogData->group_type !== 'user') {
return Base::retError("此会话不允许设置免打扰");
}
//
switch ($type) {
case 'set':
$data['silence'] = 0;
WebSocketDialogMsgRead::whereUserid($user->userid)
->whereReadAt(null)
->whereDialogId($dialogId)
->chunkById(100, function ($list) {
WebSocketDialogMsgRead::onlyMarkRead($list);
});
$dialogUser->silence = 1;
$dialogUser->save();
break;
case 'cancel':
$dialogUser->silence = 0;
$dialogUser->save();
break;
default:
return Base::retError("参数错误");
}
$data = [
'id' => $dialogId,
'silence' => $dialogUser->silence,
];
return Base::retSuccess("success", $data);
}
/**
* @api {get} api/dialog/msg/forward 21. 转发消息给
*

View File

@ -5,6 +5,7 @@ namespace App\Models;
use App\Exceptions\ApiException;
use App\Module\Base;
use App\Tasks\PushTask;
use Cache;
use Carbon\Carbon;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -60,6 +61,13 @@ class WebSocketDialog extends AbstractModel
*/
public function formatData($userid, $hasData = false)
{
$dialogUserFun = function ($key, $default = null) use ($userid) {
$data = Cache::remember("Dialog::formatData", now()->addSeconds(10), function () use ($userid) {
return WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->first()?->toArray();
});
return $data[$key] ?? $default;
};
//
if (isset($this->search_msg_id)) {
// 最后消息 (搜索预览消息)
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
@ -76,7 +84,9 @@ class WebSocketDialog extends AbstractModel
$this->mention = $unreadBuilder->clone()->whereMention(1)->count();
$this->last_umid = intval($unreadBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
}
$this->mark_unread = $this->mark_unread ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('mark_unread');
$this->mark_unread = $this->mark_unread ?? $dialogUserFun('mark_unread');
// 是否免打扰
$this->silence = $this->silence ?? $dialogUserFun('silence');
// 对话人数
$builder = WebSocketDialogUser::whereDialogId($this->id);
$this->people = $builder->count();
@ -86,7 +96,7 @@ class WebSocketDialog extends AbstractModel
// 对方信息
$this->dialog_user = null;
$this->group_info = null;
$this->top_at = $this->top_at ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('top_at');
$this->top_at = $this->top_at ?? $dialogUserFun('top_at');
$this->bot = 0;
switch ($this->type) {
case "user":

View File

@ -541,8 +541,7 @@ class WebSocketDialogMsg extends AbstractModel
$text = preg_replace("/<img\s+class=\"browse\"[^>]*?>/", "[图片]", $text);
if (!$preserveHtml) {
$text = strip_tags($text);
$text = str_replace("&nbsp;", " ", $text);
$text = str_replace("&amp;", "&", $text);
$text = str_replace(["&nbsp;", "&amp;", "&lt;", "&gt;"], [" ", "&", "<", ">"], $text);
}
return $text;
}

View File

@ -10,6 +10,7 @@ namespace App\Models;
* @property int|null $userid 会员ID
* @property string|null $top_at 置顶时间
* @property int|null $mark_unread 是否标记为未读0否1是
* @property int|null $silence 是否免打扰0否1是
* @property int|null $inviter 邀请人
* @property int|null $important 是否不可移出(项目、任务、部门人员)
* @property \Illuminate\Support\Carbon|null $created_at
@ -23,6 +24,7 @@ namespace App\Models;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereImportant($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereInviter($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereMarkUnread($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereSilence($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)

View File

@ -192,7 +192,7 @@ class BotReceiveMsgTask extends AbstractTask
case '/dialog':
$data = $this->botManagerOne($array[1], $msg->userid);
if ($data) {
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('web_socket_dialogs.name', 'LIKE', "%{$array[2]}%")
->where('u.userid', $data->userid)

View File

@ -24,7 +24,7 @@ class WebSocketDialogMsgTask extends AbstractTask
protected $id;
protected $ignoreFd;
protected $msgNotExistRetry = false; // 推送失败后重试
protected $silence = false; // 静默推送(1:前端不通知、2:App不推送
protected $silence = false; // 静默推送(前端不通知、App不推送,如果会话设置了免打扰则强制静默
protected $endPush = [];
protected $endArray = [];
@ -85,6 +85,8 @@ class WebSocketDialogMsgTask extends AbstractTask
if (empty($dialog)) {
return;
}
$silences = $dialog->dialogUser->pluck('silence', 'userid')->toArray();
$userids = array_keys($silences);
// 提及会员
$mentions = [];
@ -96,7 +98,6 @@ class WebSocketDialogMsgTask extends AbstractTask
}
// 将会话以外的成员加入会话内
$userids = $dialog->dialogUser->pluck('userid')->toArray();
$diffids = array_values(array_diff($mentions, $userids));
if ($diffids) {
// 仅(群聊)且(是群主或没有群主)才可以@成员以外的人
@ -111,7 +112,11 @@ class WebSocketDialogMsgTask extends AbstractTask
$array = [];
foreach ($userids AS $userid) {
if ($userid == $msg->userid) {
$array[$userid] = false;
$array[$userid] = [
'userid' => $userid,
'silence' => $this->silence || $silences[$userid],
'mention' => false,
];
} else {
$mention = array_intersect([0, $userid], $mentions) ? 1 : 0;
WebSocketDialogMsgRead::createInstance([
@ -120,7 +125,11 @@ class WebSocketDialogMsgTask extends AbstractTask
'userid' => $userid,
'mention' => $mention,
])->saveOrIgnore();
$array[$userid] = $mention;
$array[$userid] = [
'userid' => $userid,
'silence' => $this->silence || $silences[$userid],
'mention' => $mention,
];
// 机器人收到消处理
$botUser = User::whereUserid($userid)->whereBot(1)->first();
if ($botUser) {
@ -132,40 +141,41 @@ class WebSocketDialogMsgTask extends AbstractTask
$msg->send = WebSocketDialogMsgRead::whereMsgId($msg->id)->count();
$msg->save();
// 开始推送消息
foreach ($array as $userid => $mention) {
$umengUserid = [];
foreach ($array as $item) {
$this->endPush[] = [
'userid' => $userid,
'userid' => $item['userid'],
'ignoreFd' => $this->ignoreFd,
'msg' => [
'type' => 'dialog',
'mode' => 'add',
'silence' => $this->silence ? 1 : 0,
'silence' => $item['silence'] ? 1 : 0,
'data' => array_merge($msg->toArray(), [
'mention' => $mention,
'mention' => $item['mention'],
]),
]
];
if ($item['userid'] != $msg->userid && !$item['silence'] && !$this->silence) {
$umengUserid[] = $item['userid'];
}
}
// umeng推送app
$setting = Base::setting('appPushSetting');
$pushMsg = $setting['push'] === 'open' && $setting['push_msg'] !== 'close';
if (!$this->silence && $pushMsg) {
$umengUserid = $array;
if (isset($umengUserid[$msg->userid])) {
unset($umengUserid[$msg->userid]);
if ($umengUserid) {
$setting = Base::setting('appPushSetting');
$pushMsg = $setting['push'] === 'open' && $setting['push_msg'] !== 'close';
if ($pushMsg) {
$umengTitle = User::userid2nickname($msg->userid);
if ($dialog->type == 'group') {
$umengTitle = "{$dialog->getGroupName()} ($umengTitle)";
}
$this->endArray[] = new PushUmengMsg($umengUserid, [
'title' => $umengTitle,
'body' => $msg->previewMsg(),
'description' => "MID:{$msg->id}",
'seconds' => 3600,
'badge' => 1,
]);
}
$umengUserid = array_keys($umengUserid);
$umengTitle = User::userid2nickname($msg->userid);
if ($dialog->type == 'group') {
$umengTitle = "{$dialog->getGroupName()} ($umengTitle)";
}
$this->endArray[] = new PushUmengMsg($umengUserid, [
'title' => $umengTitle,
'body' => $msg->previewMsg(),
'description' => "MID:{$msg->id}",
'seconds' => 3600,
'badge' => 1,
]);
}
// 推送目标②:正在打开这个任务会话的会员
@ -173,9 +183,9 @@ class WebSocketDialogMsgTask extends AbstractTask
$list = User::whereTaskDialogId($dialog->id)->pluck('userid')->toArray();
if ($list) {
$array = [];
foreach ($list as $uid) {
if (!in_array($uid, $userids)) {
$array[] = $uid;
foreach ($list as $item) {
if (!in_array($item, $userids)) {
$array[] = $item;
}
}
if ($array) {

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddWebSocketDialogUsersAddSilence extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_users', 'silence')) {
$table->boolean('silence')->default(0)->nullable()->after('mark_unread')->comment('是否免打扰0否1是');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
$table->dropColumn("silence");
});
}
}

View File

@ -97,7 +97,7 @@ export default {
let num = 0; //
let mention = 0; //
this.cacheDialogs.some(dialog => {
num += $A.getDialogUnread(dialog);
num += $A.getDialogUnread(dialog, false);
mention += $A.getDialogMention(dialog);
})
if (num > 99) {
@ -129,7 +129,7 @@ export default {
msgAllUnread() {
let num = 0;
this.cacheDialogs.some(dialog => {
num += $A.getDialogUnread(dialog);
num += $A.getDialogUnread(dialog, false);
})
return num;
},

View File

@ -350,10 +350,12 @@
/**
* 返回对话未读数量
* @param dialog
* @param containSilence
* @returns {*|number}
*/
getDialogUnread(dialog) {
return dialog ? (dialog.unread || dialog.mark_unread || 0) : 0
getDialogUnread(dialog, containSilence) {
const unread = containSilence || !dialog.silence ? dialog.unread : 0
return dialog ? (unread || dialog.mark_unread || 0) : 0
},
/**
@ -375,9 +377,12 @@
text = text.replace(/<img\s+class="emoticon"[^>]*?alt="(\S+)"[^>]*?>/g, "[$1]")
text = text.replace(/<img\s+class="emoticon"[^>]*?>/g, `[${$A.L('动画表情')}]`)
text = text.replace(/<img\s+class="browse"[^>]*?>/g, `[${$A.L('图片')}]`)
text = text.replace(/<[^>]+>/g,"")
text = text.replace(/&nbsp;/g," ")
text = text.replace(/&amp;/g,"&")
return text.replace(/<[^>]+>/g,"")
text = text.replace(/&lt;/g,"<")
text = text.replace(/&gt;/g,">")
return text
},
/**

View File

@ -448,7 +448,7 @@ export default {
let num = 0; //
let mention = 0; //
this.cacheDialogs.some(dialog => {
num += $A.getDialogUnread(dialog);
num += $A.getDialogUnread(dialog, false);
mention += $A.getDialogMention(dialog);
})
if (num > 99) {
@ -480,7 +480,7 @@ export default {
msgAllUnread() {
let num = 0;
this.cacheDialogs.some(dialog => {
num += $A.getDialogUnread(dialog);
num += $A.getDialogUnread(dialog, false);
})
return num;
},

View File

@ -703,7 +703,7 @@ export default {
msgUnreadOnly() {
let num = 0;
this.cacheDialogs.some(dialog => {
num += $A.getDialogUnread(dialog);
num += $A.getDialogUnread(dialog, false);
})
if (num <= 0) {
return '';

View File

@ -586,7 +586,7 @@ export default {
msgUnread() {
const {cacheDialogs, projectData} = this;
const dialog = cacheDialogs.find(({id}) => id === projectData.dialog_id);
return dialog ? $A.getDialogUnread(dialog) : 0;
return dialog ? $A.getDialogUnread(dialog, false) : 0;
},
panelTask() {

View File

@ -83,9 +83,10 @@
<em v-if="formatMsgEmojiDesc(dialog.last_msg)">{{formatMsgEmojiDesc(dialog.last_msg)}}</em>
<span>{{$A.getMsgSimpleDesc(dialog.last_msg)}}</span>
</div>
<div v-if="dialog.silence" class="taskfont last-silence">&#xe7d7;</div>
</div>
</div>
<Badge class="dialog-num" :count="$A.getDialogUnread(dialog)"/>
<Badge class="dialog-num" :type="dialog.silence ? 'normal' : 'error'" :count="$A.getDialogUnread(dialog, true)"/>
<div class="dialog-line"></div>
</li>
</ul>
@ -120,13 +121,13 @@
<div :style="{userSelect:operateVisible ? 'none' : 'auto', height: operateStyles.height}"></div>
<DropdownMenu slot="list">
<DropdownItem @click.native="handleTopClick">
{{ $L(operateItem.top_at ? '取消置顶' : '置顶该聊天') }}
{{ $L(operateItem.top_at ? '取消置顶' : '置顶') }}
</DropdownItem>
<DropdownItem @click.native="handleReadClick('read')" v-if="$A.getDialogUnread(operateItem) > 0">
{{ $L('标记已读') }}
<DropdownItem @click.native="handleReadClick">
{{ $L($A.getDialogUnread(operateItem, true) > 0 ? '标记已读' : '标记未读') }}
</DropdownItem>
<DropdownItem @click.native="handleReadClick('unread')" v-else>
{{ $L('标记未读') }}
<DropdownItem @click.native="handleSilenceClick" :disabled="silenceDisabled(operateItem)">
{{ $L(operateItem.silence ? '允许消息通知' : '消息免打扰') }}
</DropdownItem>
</DropdownMenu>
</Dropdown>
@ -351,7 +352,7 @@ export default {
return function (type) {
let num = 0;
this.cacheDialogs.some((dialog) => {
let unread = $A.getDialogUnread(dialog);
let unread = $A.getDialogUnread(dialog, false);
if (unread) {
switch (type) {
case 'project':
@ -480,7 +481,7 @@ export default {
onActive(type) {
if (this.dialogActive == type) {
//
const dialog = this.dialogList.find(dialog => $A.getDialogUnread(dialog) > 0)
const dialog = this.dialogList.find(dialog => $A.getDialogUnread(dialog, false) > 0)
if (dialog) {
$A.scrollIntoViewIfNeeded(this.$refs[`dialog_${dialog.id}`][0])
}
@ -531,7 +532,7 @@ export default {
},
filterDialog(dialog) {
if ($A.getDialogUnread(dialog) > 0 || dialog.id == this.dialogId || dialog.top_at || dialog.todo_num > 0) {
if ($A.getDialogUnread(dialog, false) > 0 || dialog.id == this.dialogId || dialog.top_at || dialog.todo_num > 0) {
return true
}
if (dialog.name === undefined || dialog.dialog_delete === 1) {
@ -718,12 +719,29 @@ export default {
});
},
handleReadClick(type) {
handleReadClick() {
this.$store.dispatch("call", {
url: 'dialog/msg/mark',
data: {
dialog_id: this.operateItem.id,
type: type
type: $A.getDialogUnread(this.operateItem, true) > 0 ? 'read' : 'unread'
},
}).then(({data}) => {
this.$store.dispatch("saveDialog", data);
}).catch(({msg}) => {
$A.modalError(msg);
});
},
handleSilenceClick() {
if (this.silenceDisabled(this.operateItem)) {
return
}
this.$store.dispatch("call", {
url: 'dialog/msg/silence',
data: {
dialog_id: this.operateItem.id,
type: this.operateItem.silence ? 'cancel' : 'set'
},
}).then(({data}) => {
this.$store.dispatch("saveDialog", data);
@ -747,7 +765,12 @@ export default {
$A.eeuiAppSendMessage({
action: 'gotoSetting',
});
}
},
silenceDisabled(data) {
const {type, group_type} = data
return type === 'group' && group_type !== 'user'
},
}
}
</script>

View File

@ -205,6 +205,7 @@
.dialog-text {
color: $primary-desc-color;
font-size: 12px;
min-height: 24px;
line-height: 24px;
display: flex;
align-items: center;
@ -248,6 +249,10 @@
text-overflow: ellipsis;
}
}
.last-silence {
padding-left: 4px;
color: rgba($primary-desc-color, 0.5);
}
}
}
.dialog-num {