hasOne(WebSocketDialog::class, 'id', 'dialog_id'); } /** * 阅读占比 * @return int|mixed */ public function getPercentageAttribute() { if (!isset($this->appendattrs['percentage'])) { $this->generatePercentage(); } return $this->appendattrs['percentage']; } /** * 消息格式化 * @param $value * @return array|mixed */ public function getMsgAttribute($value) { if (is_array($value)) { return $value; } $value = Base::json2array($value); if ($this->type === 'file') { $value['type'] = in_array($value['ext'], ['jpg', 'jpeg', 'png', 'gif']) ? 'img' : 'file'; $value['path'] = Base::fillUrl($value['path']); $value['thumb'] = Base::fillUrl($value['thumb'] ?: Base::extIcon($value['ext'])); } return $value; } /** * 获取占比 * @param bool|int $increment 是否新增阅读数 * @return int */ public function generatePercentage($increment = false) { if ($increment) { $this->increment('read', is_bool($increment) ? 1 : $increment); } if ($this->read > $this->send || empty($this->send)) { return $this->appendattrs['percentage'] = 100; } else { return $this->appendattrs['percentage'] = intval($this->read / $this->send * 100); } } /** * 标记已送达 同时 告诉发送人已送达 * @param $userid * @return bool */ public function readSuccess($userid) { if (empty($userid)) { return false; } self::transaction(function() use ($userid) { $msgRead = WebSocketDialogMsgRead::whereMsgId($this->id)->whereUserid($userid)->lockForUpdate()->first(); if (empty($msgRead)) { $msgRead = WebSocketDialogMsgRead::createInstance([ 'dialog_id' => $this->dialog_id, 'msg_id' => $this->id, 'userid' => $userid, 'after' => 1, ]); if ($msgRead->saveOrIgnore()) { $this->send = WebSocketDialogMsgRead::whereMsgId($this->id)->count(); $this->save(); } else { return; } } if (!$msgRead->read_at) { $msgRead->read_at = Carbon::now(); $msgRead->save(); $this->generatePercentage(true); PushTask::push([ 'userid' => $this->userid, 'msg' => [ 'type' => 'dialog', 'mode' => 'readed', 'data' => [ 'id' => $this->id, 'read' => $this->read, 'percentage' => $this->percentage, ], ] ]); } }); return true; } /** * 删除消息 * @return void */ public function deleteMsg() { $send_dt = Carbon::parse($this->created_at)->addDay(); if ($send_dt->lt(Carbon::now())) { throw new ApiException('已超过24小时,此消息不能撤回'); } AbstractModel::transaction(function() { $deleteRead = WebSocketDialogMsgRead::whereMsgId($this->id)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可 $this->delete(); // $last_msg = null; if ($this->webSocketDialog) { $last_msg = WebSocketDialogMsg::whereDialogId($this->dialog_id)->orderByDesc('id')->first(); $this->webSocketDialog->last_at = $last_msg->created_at; $this->webSocketDialog->save(); } // $dialog = WebSocketDialog::find($this->dialog_id); if ($dialog) { $userids = $dialog->dialogUser->pluck('userid')->toArray(); PushTask::push([ 'userid' => $userids, 'msg' => [ 'type' => 'dialog', 'mode' => 'delete', 'data' => [ 'id' => $this->id, 'dialog_id' => $this->dialog_id, 'last_msg' => $last_msg, 'update_read' => $deleteRead ? 1 : 0 ], ] ]); } }); } /** * 预览消息 * @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 * @param $dialog_id * @return mixed|string|string[] */ public static function formatMsg($text, $dialog_id) { @ini_set("pcre.backtrack_limit", 999999999); // 图片 [:IMAGE:className:width:height:src:alt:] preg_match_all("/(<\/img>)*/s", $text, $matchs); foreach ($matchs[2] as $key => $base64) { $tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/"; Base::makeDir(public_path($tmpPath)); $tmpPath .= md5s($base64) . "." . $matchs[1][$key]; if (file_put_contents(public_path($tmpPath), base64_decode($base64))) { $imagesize = getimagesize(public_path($tmpPath)); $text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text); } } // 表情图片 preg_match_all("//s", $text, $matchs); foreach ($matchs[1] as $key => $str) { preg_match("/data-asset=\"(.*?)\"/", $str, $matchAsset); preg_match("/data-name=\"(.*?)\"/", $str, $matchName); if (file_exists(public_path($matchAsset[1]))) { $imagesize = getimagesize(public_path($matchAsset[1])); $text = str_replace($matchs[0][$key], "[:IMAGE:emoticon:{$imagesize[0]}:{$imagesize[1]}:{$matchAsset[1]}:{$matchName[1]}:]", $text); } } // 其他网络图片 preg_match_all("/]*?src=([\"'])(.*?\.(png|jpg|jpeg|gif))\\1[^>]*?>/is", $text, $matchs); foreach ($matchs[2] as $key => $str) { $tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/"; Base::makeDir(public_path($tmpPath)); $tmpPath .= md5s($str) . "." . $matchs[3][$key]; if (file_exists(public_path($tmpPath))) { $imagesize = getimagesize(public_path($tmpPath)); $text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text); } else { $image = file_get_contents($str); if (empty($image)) { $text = str_replace($matchs[0][$key], "[:IMAGE:browse:90:90:images/other/imgerr.jpg::]", $text); } else if (file_put_contents(public_path($tmpPath), $image)) { $imagesize = getimagesize(public_path($tmpPath)); $text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text); } } } // @成员、#任务 preg_match_all("/.*?<\/span>.*?<\/span>.*?<\/span>/s", $text, $matchs); foreach ($matchs[1] as $key => $str) { preg_match("/data-denotation-char=\"(.*?)\"/", $str, $matchChar); preg_match("/data-id=\"(.*?)\"/", $str, $matchId); preg_match("/data-value=\"(.*?)\"/", $str, $matchValye); $text = str_replace($matchs[0][$key], "[:{$matchChar[1]}:{$matchId[1]}:{$matchValye[1]}:]", $text); } // 过滤标签 $text = strip_tags($text, '
 
    • '); $text = preg_replace("/\<(blockquote|strong|pre|ol|ul|li|em|p|s|u).*?\>/is", "<$1>", $text); $text = preg_replace("/\[:IMAGE:(.*?):(.*?):(.*?):(.*?):(.*?):\]/i", "\"$5\"/", $text); $text = preg_replace("/\[:@:(.*?):(.*?):\]/i", "@$2", $text); $text = preg_replace("/\[:#:(.*?):(.*?):\]/i", "#$2", $text); return preg_replace("/^(

      <\/p>)+|(

      <\/p>)+$/i", "", $text); } /** * 发送消息 * @param int $dialog_id 会话ID(即 聊天室ID) * @param string $type 消息类型 * @param array $msg 发送的消息 * @param int $sender 发送的会员ID(默认自己,0为系统) * @return array */ public static function sendMsg($dialog_id, $type, $msg, $sender = 0) { $dialogMsg = self::createInstance([ 'userid' => $sender ?: User::userid(), 'type' => $type, 'msg' => $msg, 'read' => 0, ]); AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) { $dialog = WebSocketDialog::find($dialog_id); if (empty($dialog)) { throw new ApiException('获取会话失败'); } $dialog->last_at = Carbon::now(); $dialog->save(); $dialogMsg->send = 1; $dialogMsg->dialog_id = $dialog->id; $dialogMsg->dialog_type = $dialog->type; $dialogMsg->save(); }); Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id)); return Base::retSuccess('发送成功', $dialogMsg); } }