diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index ab9a31945..ad50fb525 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -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); diff --git a/app/Models/WebSocketDialog.php b/app/Models/WebSocketDialog.php index 382d49865..8933d8b71 100644 --- a/app/Models/WebSocketDialog.php +++ b/app/Models/WebSocketDialog.php @@ -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; } /** diff --git a/app/Models/WebSocketDialogMsg.php b/app/Models/WebSocketDialogMsg.php index 4a92cc3fb..11eb42ab3 100644 --- a/app/Models/WebSocketDialogMsg.php +++ b/app/Models/WebSocketDialogMsg.php @@ -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); diff --git a/app/Models/WebSocketDialogUser.php b/app/Models/WebSocketDialogUser.php index 24d634158..017f8ab1a 100644 --- a/app/Models/WebSocketDialogUser.php +++ b/app/Models/WebSocketDialogUser.php @@ -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'); + } } diff --git a/app/Tasks/BotReceiveMsgTask.php b/app/Tasks/BotReceiveMsgTask.php index 950503969..9a2d16b39 100644 --- a/app/Tasks/BotReceiveMsgTask.php +++ b/app/Tasks/BotReceiveMsgTask.php @@ -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) diff --git a/resources/assets/js/pages/manage/components/DialogWrapper.vue b/resources/assets/js/pages/manage/components/DialogWrapper.vue index 74c7bdd3a..e011fed9e 100644 --- a/resources/assets/js/pages/manage/components/DialogWrapper.vue +++ b/resources/assets/js/pages/manage/components/DialogWrapper.vue @@ -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}条`) }) diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index e49050f5d..99ae5a1c7 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -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) } diff --git a/resources/assets/js/store/state.js b/resources/assets/js/store/state.js index 0d1b6f6d9..c52341970 100644 --- a/resources/assets/js/store/state.js +++ b/resources/assets/js/store/state.js @@ -63,7 +63,7 @@ export default { wsOpenNum: 0, wsListener: {}, wsReadTimeout: null, - wsReadWaitList: [], + wsReadWaitData: {}, // 会员信息 userInfo: {},