perf: 优化阅读消息

This commit is contained in:
kuaifan 2023-02-19 15:19:42 +08:00
parent 87d7be254a
commit aa5da2479c
8 changed files with 155 additions and 116 deletions

View File

@ -5,7 +5,6 @@ namespace App\Http\Controllers\Api;
use App\Models\AbstractModel;
use App\Models\File;
use App\Models\FileContent;
use App\Models\FileLink;
use App\Models\ProjectTask;
use App\Models\ProjectTaskFile;
use App\Models\User;
@ -49,11 +48,11 @@ class DialogController extends AbstractController
{
$user = User::auth();
//
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
->where('u.userid', $user->userid);
if (Request::exists('at_after')) {
$builder->where('web_socket_dialogs.last_at', '>', Carbon::parse(Request::input('at_after')));
$builder->where('u.updated_at', '>', Carbon::parse(Request::input('at_after')));
}
$list = $builder
->orderByDesc('u.top_at')
@ -102,7 +101,7 @@ class DialogController extends AbstractController
return Base::retError('请输入搜索关键词');
}
// 搜索会话
$dialogs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
$dialogs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
->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)
@ -135,7 +134,7 @@ class DialogController extends AbstractController
}
// 搜索消息会话
if (count($list) < 20) {
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'm.id as search_msg_id'])
$msgs = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at', '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)
@ -172,7 +171,7 @@ class DialogController extends AbstractController
//
$dialog_id = intval(Request::input('dialog_id'));
//
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence'])
$item = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.updated_at as user_at'])
->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)
@ -523,7 +522,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/read 11. 标记已读
* @api {get} api/dialog/msg/read 11. 已读聊天消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -543,45 +542,74 @@ class DialogController extends AbstractController
$id = Request::input('id');
$ids = Base::explodeInt($id);
//
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($user) {
$dialogIds = [];
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($user, &$dialogIds) {
/** @var WebSocketDialogMsg $item */
foreach ($list as $item) {
$item->readSuccess($user->userid);
$dialogIds[$item->dialog_id] = $item->dialog_id;
}
});
return Base::retSuccess('success');
//
$data = [];
$dialogUsers = WebSocketDialogUser::with(['webSocketDialog'])->whereUserid($user->userid)->whereIn('dialog_id', array_values($dialogIds))->get();
foreach ($dialogUsers as $dialogUser) {
if (!$dialogUser->webSocketDialog) {
continue;
}
$dialogUser->updated_at = Carbon::now();
$dialogUser->save();
//
$dialogUser->webSocketDialog->generateUnread($user->userid);
$data[] = [
'id' => $dialogUser->webSocketDialog->id,
'unread' => $dialogUser->webSocketDialog->unread,
'mention' => $dialogUser->webSocketDialog->mention,
'position_msgs' => $dialogUser->webSocketDialog->position_msgs,
'user_at' => $dialogUser->updated_at,
];
}
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/dialog/msg/unread 12. 获取未读消息数量
* @api {get} api/dialog/msg/unread 12. 获取未读消息数
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__unread
*
* @apiParam {Number} [dialog_id] 对话ID留空获取总未读消息数量
* @apiParam {Number} dialog_id 对话ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
* @apiSuccessExample {json} data:
{
"unread": 43, // 未读消息数
"last_umid": 308 // 最新的一条未读消息ID用于判断是否更新前端的未读数量
"id": 43,
"unread": 308,
"mention": 11,
"position_msgs": [],
"user_at": "2020-12-12 00:00:00",
}
*/
public function msg__unread()
{
$dialog_id = intval(Request::input('dialog_id'));
//
$builder = WebSocketDialogMsgRead::whereUserid(User::userid())->whereReadAt(null);
if ($dialog_id > 0) {
$builder->whereDialogId($dialog_id);
$dialogUser = WebSocketDialogUser::with(['webSocketDialog'])->whereDialogId($dialog_id)->whereUserid(User::userid())->first();
if (empty($dialogUser?->webSocketDialog)) {
return Base::retError('会话不存在');
}
$dialogUser->webSocketDialog->generateUnread($dialogUser->userid);
//
return Base::retSuccess('success', [
'unread' => $builder->count(),
'last_umid' => intval($builder->orderByDesc('msg_id')->value('msg_id')),
'id' => $dialogUser->webSocketDialog->id,
'unread' => $dialogUser->webSocketDialog->unread,
'mention' => $dialogUser->webSocketDialog->mention,
'position_msgs' => $dialogUser->webSocketDialog->position_msgs,
'user_at' => $dialogUser->updated_at,
]);
}
@ -1044,41 +1072,48 @@ class DialogController extends AbstractController
public function msg__mark()
{
$user = User::auth();
$dialogId = intval(Request::input('dialog_id'));
//
$dialog_id = intval(Request::input('dialog_id'));
$type = Request::input('type');
$afterMsgId = intval(Request::input('after_msg_id'));
$dialogUser = WebSocketDialogUser::whereUserid($user->userid)->whereDialogId($dialogId)->first();
if (!$dialogUser) {
return Base::retError("会话不存在");
$after_msg_id = intval(Request::input('after_msg_id'));
//
$dialogUser = WebSocketDialogUser::with(['webSocketDialog'])->whereDialogId($dialog_id)->whereUserid($user->userid)->first();
if (empty($dialogUser?->webSocketDialog)) {
return Base::retError('会话不存在');
}
$data = [
'id' => $dialogId,
];
switch ($type) {
case 'read':
$data['mark_unread'] = 0;
$data['unread'] = 0;
$data['mention'] = 0;
$builder = WebSocketDialogMsgRead::whereUserid($user->userid)->whereReadAt(null)->whereDialogId($dialogId);
if ($afterMsgId > 0) {
$unBuilder = $builder->clone()->where('msg_id', '<', $afterMsgId);
$data['unread'] = $unBuilder->count();
$data['mention'] = $data['unread'] > 0 ? $unBuilder->whereMention(1)->count() : 0;
$builder->where('msg_id', '>=', $afterMsgId);
$builder = WebSocketDialogMsgRead::whereDialogId($dialog_id)->whereUserid($user->userid)->whereReadAt(null);
if ($after_msg_id > 0) {
$builder->where('msg_id', '>=', $after_msg_id);
}
$builder->chunkById(100, function ($list) {
WebSocketDialogMsgRead::onlyMarkRead($list);
});
$data['position_msgs'] = WebSocketDialog::find($dialogId)?->getPositionMsgs($user->userid) ?: [];
//
$dialogUser->webSocketDialog->generateUnread($user->userid);
$data = [
'id' => $dialogUser->webSocketDialog->id,
'unread' => $dialogUser->webSocketDialog->unread,
'mention' => $dialogUser->webSocketDialog->mention,
'position_msgs' => $dialogUser->webSocketDialog->position_msgs,
'user_at' => Carbon::now()->toDateTimeString(),
'mark_unread' => 0,
];
break;
case 'unread':
$data['mark_unread'] = 1;
$data = [
'id' => $dialogUser->webSocketDialog->id,
'user_at' => Carbon::now()->toDateTimeString(),
'mark_unread' => 1,
];
break;
default:
return Base::retError("参数错误");
}
$dialogUser->updated_at = $data['user_at'];
$dialogUser->mark_unread = $data['mark_unread'];
$dialogUser->save();
return Base::retSuccess("success", $data);

View File

@ -68,6 +68,10 @@ class WebSocketDialog extends AbstractModel
return $data[$key] ?? $default;
};
//
$this->pinyin = Base::cn2pinyin($this->name);
$this->top_at = $this->top_at ?? $dialogUserFun('top_at');
$this->user_at = $this->user_at ?? $dialogUserFun('updated_at');
//
if (isset($this->search_msg_id)) {
// 最后消息 (搜索预览消息)
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
@ -76,31 +80,19 @@ class WebSocketDialog extends AbstractModel
// 最后消息
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
// 未读信息
$unBuilder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
$this->unread = $unBuilder->count();
$this->mention = 0;
$this->last_umid = 0;
$this->position_msgs = [];
if ($this->unread > 0) {
$this->mention = $unBuilder->clone()->whereMention(1)->count();
$this->last_umid = intval($unBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
if ($hasData === true) {
$this->position_msgs = $this->getPositionMsgs($userid);
}
}
$this->generateUnread($userid, $hasData);
// 未读标记
$this->mark_unread = $this->mark_unread ?? $dialogUserFun('mark_unread');
// 是否免打扰
$this->silence = $this->silence ?? $dialogUserFun('silence');
// 对话人数
$builder = WebSocketDialogUser::whereDialogId($this->id);
$this->people = $builder->count();
$this->people = WebSocketDialogUser::whereDialogId($this->id)->count();
// 有待办
$this->todo_num = WebSocketDialogMsgTodo::whereDialogId($this->id)->whereUserid($userid)->whereDoneAt(null)->count();
}
// 对方信息
$this->dialog_user = null;
$this->group_info = null;
$this->top_at = $this->top_at ?? $dialogUserFun('top_at');
$this->bot = 0;
switch ($this->type) {
case "user":
@ -152,36 +144,45 @@ class WebSocketDialog extends AbstractModel
$this->has_file = $msgBuilder->clone()->whereMtype('file')->exists();
$this->has_link = $msgBuilder->clone()->whereLink(1)->exists();
}
$this->pinyin = Base::cn2pinyin($this->name);
return $this;
}
/**
* 获取定位消息
* 生成未读数据
* @param $userid
* @return array[]
* @param $positionData
* @return $this
*/
public function getPositionMsgs($userid)
public function generateUnread($userid, $positionData = true)
{
$builder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
$array = [];
// @我的消息
$mention_id = intval($builder->clone()->whereMention(1)->orderByDesc('msg_id')->value('msg_id'));
if ($mention_id > 0) {
$array[] = [
'msg_id' => $mention_id,
'label' => Base::Lang('@我的消息'),
];
$this->unread = $builder->count();
$this->mention = 0;
if ($this->unread > 0) {
$this->mention = $builder->clone()->whereMention(1)->count();
}
// 最早一条未读消息
$first_id = intval($builder->clone()->orderBy('msg_id')->value('msg_id'));
if ($first_id > 0) {
$array[] = [
'msg_id' => $first_id,
'label' => 'unread'
];
if ($positionData) {
$array = [];
// @我的消息
if ($this->mention > 0
&& $mention_id = intval($builder->clone()->whereMention(1)->orderByDesc('msg_id')->value('msg_id'))) {
$array[] = [
'msg_id' => $mention_id,
'label' => Base::Lang('@我的消息'),
];
}
// 最早一条未读消息
if ($this->unread > 0
&& $first_id = intval($builder->clone()->orderBy('msg_id')->value('msg_id'))) {
$array[] = [
'msg_id' => $first_id,
'label' => '{UNREAD}'
];
}
//
$this->position_msgs = $array;
}
return $array;
return $this;
}
/**

View File

@ -426,8 +426,9 @@ class WebSocketDialogMsg extends AbstractModel
WebSocketDialogMsgTodo::whereIn('msg_id', $ids)->delete();
self::whereIn('id', $ids)->delete();
//
foreach ($dialogIds as $id) {
WebSocketDialog::find($id)?->updateMsgLastAt();
$dialogDatas = WebSocketDialog::whereIn('id', $dialogIds)->get();
foreach ($dialogDatas as $dialogData) {
$dialogData->updateMsgLastAt();
}
foreach ($replyIds as $id) {
self::whereId($id)->update(['reply_num' => self::whereReplyId($id)->count()]);
@ -453,9 +454,13 @@ class WebSocketDialogMsg extends AbstractModel
self::whereId($this->reply_id)->decrement('reply_num');
}
//
$dialog = $this->webSocketDialog;
if ($dialog) {
$userids = $dialog->dialogUser->pluck('userid')->toArray();
$dialogData = $this->webSocketDialog;
if ($dialogData) {
foreach ($dialogData->dialogUser as $dialogUser) {
$dialogUser->updated_at = Carbon::now();
$dialogUser->save();
}
$userids = $dialogData->dialogUser->pluck('userid')->toArray();
PushTask::push([
'userid' => $userids,
'msg' => [
@ -464,7 +469,7 @@ class WebSocketDialogMsg extends AbstractModel
'data' => [
'id' => $this->id,
'dialog_id' => $this->dialog_id,
'last_msg' => $dialog->updateMsgLastAt(),
'last_msg' => $dialogData->updateMsgLastAt(),
'update_read' => $deleteRead ? 1 : 0
],
]
@ -830,6 +835,7 @@ class WebSocketDialogMsg extends AbstractModel
$dialogMsg->send = 1;
$dialogMsg->key = $dialogMsg->generateMsgKey();
$dialogMsg->save();
WebSocketDialogUser::whereDialogId($dialog->id)->update(['updated_at' => $dialog->updated_at]);
});
//
$task = new WebSocketDialogMsgTask($dialogMsg->id);

View File

@ -15,6 +15,7 @@ namespace App\Models;
* @property int|null $important 是否不可移出(项目、任务、部门人员)
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser query()
@ -32,5 +33,11 @@ namespace App\Models;
*/
class WebSocketDialogUser extends AbstractModel
{
/**
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
}
}

View File

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

View File

@ -778,14 +778,14 @@ export default {
positionMsg() {
const {unread, position_msgs} = this.dialogData
if (unread === 0 || this.allMsgs.length === 0 ||position_msgs.length === 0) {
if (!position_msgs || position_msgs.length === 0 || unread === 0 || this.allMsgs.length === 0) {
return null
}
const item = position_msgs.sort((a, b) => {
return b.msg_id - a.msg_id
})[0]
if (this.allMsgs.findIndex(({id}) => id == item.msg_id) === -1) {
if (item.label === 'unread') {
if (item.label === '{UNREAD}') {
return Object.assign(item, {
'label': this.$L(`未读消息${unread}`)
})

View File

@ -2066,11 +2066,11 @@ export default {
} else if ($A.isJson(data)) {
const index = state.cacheDialogs.findIndex(({id}) => id == data.id);
if (index > -1) {
const original = state.cacheDialogs[index];
if (typeof data.unread === "number" && data.unread > original.unread && data.last_umid <= original.last_umid) {
// 增加未读数时新数据的最后一条未读消息id <= 原数据,则不更新未读数
data.unread = original.unread;
data.mention = original.mention;
const original = state.cacheDialogs[index]
if ($A.Time(data.user_at) < $A.Time(original.user_at)) {
typeof data.unread !== "undefined" && delete data.unread
typeof data.mention !== "undefined" && delete data.mention
typeof data.position_msgs !== "undefined" && delete data.position_msgs
}
state.cacheDialogs.splice(index, 1, Object.assign({}, original, data));
} else {
@ -2148,9 +2148,9 @@ export default {
callData.page = 1
if (state.cacheDialogs.length > 0) {
const tmpList = state.cacheDialogs.sort((a, b) => {
return $A.Date(b.last_at) - $A.Date(a.last_at);
return $A.Date(b.user_at) - $A.Date(a.user_at);
})
callData.at_after = tmpList[0].last_at;
callData.at_after = tmpList[0].user_at;
}
}
//
@ -2598,32 +2598,23 @@ export default {
if (data.read_at) return;
data.read_at = $A.formatDate();
//
const dialog = state.cacheDialogs.find(({id}) => id == data.dialog_id);
if (dialog && dialog.unread > 0) {
const newData = {
id: data.dialog_id,
mark_unread: 0,
}
newData.unread = dialog.unread - 1;
if (data.mention) {
newData.mention = dialog.mention - 1;
}
dispatch("saveDialog", newData)
}
//
state.wsReadWaitList.push(data.id);
state.wsReadWaitData[data.id] = data.id;
clearTimeout(state.wsReadTimeout);
state.wsReadTimeout = setTimeout(() => {
const id = $A.cloneJSON(state.wsReadWaitList);
state.wsReadWaitList = [];
state.wsReadTimeout = setTimeout(_ => {
const ids = Object.values(state.wsReadWaitData);
state.wsReadWaitData = {};
//
dispatch("call", {
url: 'dialog/msg/read',
data: {
id: id.join(",")
id: ids.join(",")
}
}).then(({data}) => {
dispatch("saveDialog", data)
}).catch(_ => {
state.wsReadWaitList.push(...id)
ids.some(id => {
state.wsReadWaitData[id] = id;
})
});
}, 50);
},
@ -2825,10 +2816,8 @@ export default {
dispatch("call", {
url: 'dialog/msg/unread',
data: {dialog_id}
}).then(result => {
newData.unread = result.data.unread
newData.last_umid = result.data.last_umid
dispatch("saveDialog", newData)
}).then(({data}) => {
dispatch("saveDialog", Object.assign(newData, data))
}).catch(() => {});
} else {
dispatch("saveDialog", newData)
@ -2852,11 +2841,12 @@ export default {
if (dialog) {
const newData = {
id: dialog_id,
last_umid: data.id,
unread: dialog.unread + 1,
mention: dialog.mention,
user_at: data.created_at,
}
newData.unread = dialog.unread + 1;
if (data.mention) {
newData.mention = dialog.mention + 1;
newData.mention++;
}
dispatch("saveDialog", newData)
}

View File

@ -63,7 +63,7 @@ export default {
wsOpenNum: 0,
wsListener: {},
wsReadTimeout: null,
wsReadWaitList: [],
wsReadWaitData: {},
// 会员信息
userInfo: {},