mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-07-10 10:40:03 +08:00
完善直播聊天
This commit is contained in:
parent
51e88b53c0
commit
20b8ef8132
@ -14,6 +14,20 @@ class LiveController extends Controller
|
||||
|
||||
use ResponseTrait;
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/chats", name="web.live.chats")
|
||||
*/
|
||||
public function chatsAction($id)
|
||||
{
|
||||
$service = new LiveService();
|
||||
|
||||
$chats = $service->getRecentChats($id);
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('chapter/live_chats');
|
||||
$this->view->setVar('chats', $chats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/stats", name="web.live.stats")
|
||||
*/
|
||||
@ -29,9 +43,9 @@ class LiveController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/bind", name="web.live.bind")
|
||||
* @Post("/{id:[0-9]+}/user/bind", name="web.live.bind_user")
|
||||
*/
|
||||
public function bindAction($id)
|
||||
public function bindUserAction($id)
|
||||
{
|
||||
$service = new LiveService();
|
||||
|
||||
@ -41,15 +55,15 @@ class LiveController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/message", name="web.live.message")
|
||||
* @Post("/{id:[0-9]+}/msg/send", name="web.live.send_msg")
|
||||
*/
|
||||
public function messageAction($id)
|
||||
public function sendMessageAction($id)
|
||||
{
|
||||
$service = new LiveService();
|
||||
|
||||
$service->sendMessage($id);
|
||||
$response = $service->sendMessage($id);
|
||||
|
||||
return $this->jsonSuccess();
|
||||
return $this->jsonSuccess($response);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Web\Services;
|
||||
|
||||
use App\Repos\User as UserRepo;
|
||||
use App\Library\Cache\Backend\Redis as RedisCache;
|
||||
use App\Services\Frontend\ChapterTrait;
|
||||
use GatewayClient\Gateway;
|
||||
|
||||
@ -11,6 +11,25 @@ class Live extends Service
|
||||
|
||||
use ChapterTrait;
|
||||
|
||||
public function getRecentChats($id)
|
||||
{
|
||||
$redis = $this->getRedis();
|
||||
|
||||
$key = $this->getRedisListKey($id);
|
||||
|
||||
$items = $redis->lRange($key, 0, 10);
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($items) {
|
||||
foreach (array_reverse($items) as $item) {
|
||||
$result[] = json_decode($item, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getStats($id)
|
||||
{
|
||||
$chapter = $this->checkChapterCache($id);
|
||||
@ -23,14 +42,10 @@ class Live extends Service
|
||||
$userCount = Gateway::getUidCountByGroup($groupName);
|
||||
$guestCount = $clientCount - $userCount;
|
||||
|
||||
$userIds = Gateway::getUidListByGroup($groupName);
|
||||
|
||||
$users = $this->handleUsers($userIds);
|
||||
|
||||
return [
|
||||
'client_count' => $clientCount,
|
||||
'user_count' => $userCount,
|
||||
'guest_count' => $guestCount,
|
||||
'users' => $users,
|
||||
];
|
||||
}
|
||||
|
||||
@ -46,11 +61,23 @@ class Live extends Service
|
||||
|
||||
Gateway::$registerAddress = $this->getRegisterAddress();
|
||||
|
||||
if ($user->id > 0) {
|
||||
Gateway::bindUid($clientId, $user->id);
|
||||
}
|
||||
|
||||
Gateway::joinGroup($clientId, $groupName);
|
||||
|
||||
if ($user->id > 0) {
|
||||
|
||||
Gateway::bindUid($clientId, $user->id);
|
||||
|
||||
$message = kg_json_encode([
|
||||
'type' => 'new_user',
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'vip' => $user->vip,
|
||||
],
|
||||
]);
|
||||
|
||||
Gateway::sendToGroup($groupName, $message, $clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public function sendMessage($id)
|
||||
@ -59,52 +86,41 @@ class Live extends Service
|
||||
|
||||
$user = $this->getLoginUser();
|
||||
|
||||
$content = $this->request->getPost('content', ['trim', 'striptags']);
|
||||
|
||||
$content = kg_substr($content, 0, 150);
|
||||
|
||||
Gateway::$registerAddress = $this->getRegisterAddress();
|
||||
|
||||
$groupName = $this->getGroupName($chapter->id);
|
||||
|
||||
$excludeClientId = Gateway::getClientIdByUid($user->id);
|
||||
$clientId = Gateway::getClientIdByUid($user->id);
|
||||
|
||||
$message = json_encode([
|
||||
'type' => 'show_message',
|
||||
$message = [
|
||||
'type' => 'new_message',
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'avatar' => $user->avatar,
|
||||
'vip' => $user->vip,
|
||||
],
|
||||
]);
|
||||
'content' => $content,
|
||||
];
|
||||
|
||||
Gateway::sendToGroup($groupName, $message, $excludeClientId);
|
||||
$encodeMessage = kg_json_encode($message);
|
||||
|
||||
Gateway::sendToGroup($groupName, $encodeMessage, $clientId);
|
||||
|
||||
$redis = $this->getRedis();
|
||||
$key = $this->getRedisListKey($id);
|
||||
$redis->lPush($key, $encodeMessage);
|
||||
$redis->lTrim($key, 0, 10);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
protected function handleUsers($userIds)
|
||||
protected function getGroupName($id)
|
||||
{
|
||||
if (!$userIds) return [];
|
||||
|
||||
$userRepo = new UserRepo();
|
||||
|
||||
$users = $userRepo->findByIds($userIds);
|
||||
|
||||
$baseUrl = kg_ci_base_url();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($users->toArray() as $key => $user) {
|
||||
$user['avatar'] = $baseUrl . $user['avatar'];
|
||||
$result[] = [
|
||||
'id' => $user['id'],
|
||||
'name' => $user['name'],
|
||||
'vip' => $user['vip'],
|
||||
'avatar' => $user['avatar'],
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getGroupName($groupId)
|
||||
{
|
||||
return "live_{$groupId}";
|
||||
return "live_{$id}";
|
||||
}
|
||||
|
||||
protected function getRegisterAddress()
|
||||
@ -114,4 +130,19 @@ class Live extends Service
|
||||
return $config->websocket->register_address;
|
||||
}
|
||||
|
||||
protected function getRedisListKey($id)
|
||||
{
|
||||
return "live_recent_chat:{$id}";
|
||||
}
|
||||
|
||||
protected function getRedis()
|
||||
{
|
||||
/**
|
||||
* @var RedisCache $cache
|
||||
*/
|
||||
$cache = $this->getDI()->get('cache');
|
||||
|
||||
return $cache->getRedis();
|
||||
}
|
||||
|
||||
}
|
||||
|
9
app/Http/Web/Views/chapter/live_chats.volt
Normal file
9
app/Http/Web/Views/chapter/live_chats.volt
Normal file
@ -0,0 +1,9 @@
|
||||
{% for chat in chats %}
|
||||
<div class="chat">
|
||||
{% if chat.user.vip == 0 %}
|
||||
<span class="vip-icon layui-icon layui-icon-diamond"></span>
|
||||
{% endif %}
|
||||
<span class="user">{{ chat.user.name }}</span>
|
||||
<span class="content">{{ chat.content }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
@ -1,17 +1,6 @@
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">在线成员</div>
|
||||
<div class="layui-card-body live-stats">
|
||||
<div class="stats">
|
||||
用户:<span class="count">{{ stats.user_count }}</span>
|
||||
游客:<span class="count">{{ stats.guest_count }}</span>
|
||||
</div>
|
||||
<div class="live-user-list">
|
||||
{% for user in stats.users %}
|
||||
{% set vip_flag = user.vip ? '<span class="layui-badge">vip</span>' : '' %}
|
||||
<div class="live-user-card">
|
||||
<div class="name">{{ user.name }} {{ vip_flag }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="live-stats">
|
||||
<div class="stats">
|
||||
登录:<span class="count">{{ stats.user_count }} 人</span>
|
||||
游客:<span class="count">{{ stats.guest_count }} 人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -2,13 +2,16 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% set course_url = url({'for':'web.course.show','id':chapter.course.id}) %}
|
||||
{% set learning_url = url({'for':'web.chapter.learning','id':chapter.id}) %}
|
||||
{% set stats_url = url({'for':'web.live.stats','id':chapter.id}) %}
|
||||
{% set live_chats_url = url({'for':'web.live.chats','id':chapter.id}) %}
|
||||
{% set live_stats_url = url({'for':'web.live.stats','id':chapter.id}) %}
|
||||
{% set send_msg_url = url({'for':'web.live.send_msg','id':chapter.id}) %}
|
||||
{% set bind_user_url = url({'for':'web.live.bind_user','id':chapter.id}) %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
<span><i class="layui-icon layui-icon-return"></i> <a href="{{ course_url }}">返回课程主页</a></span>
|
||||
<a><cite>{{ chapter.course.title }}</cite></a>
|
||||
<a><cite>{{ chapter.title }}</cite></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -19,35 +22,35 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-sidebar">
|
||||
<div class="layui-tab layui-tab-brief user-tab">
|
||||
<ul class="layui-tab-title">
|
||||
<li class="layui-this">讨论</li>
|
||||
<li>成员</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div class="live-msg-list"></div>
|
||||
<div class="live-msg-form">
|
||||
<form class="layui-form" method="post" action="{{ url({'for':'web.live.message'}) }}">
|
||||
<input class="layui-input" type="text" name="content" placeholder="请输入内容..." lay-verify="required">
|
||||
<button class="layui-hide" type="submit" lay-submit="true" lay-filter="chat">发送</button>
|
||||
</form>
|
||||
<div class="chat-container">
|
||||
<div class="layui-tab layui-tab-brief user-tab">
|
||||
<ul class="layui-tab-title">
|
||||
<li class="layui-this">讨论</li>
|
||||
<li>统计</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div class="chat-msg-list" id="chat-msg-list" data-url="{{ live_chats_url }}"></div>
|
||||
<div class="chat-msg-form">
|
||||
<form class="layui-form" method="post" action="{{ send_msg_url }}">
|
||||
<input class="layui-input" type="text" name="content" maxlength="150" placeholder="快来和大家一起互动吧~" lay-verType="tips" lay-verify="required">
|
||||
<button class="layui-hide" type="submit" lay-submit="true" lay-filter="chat">发送</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-tab-item" id="tab-stats" data-url="{{ live_stats_url }}"></div>
|
||||
</div>
|
||||
<div class="layui-tab-item" id="tab-stats" data-url="{{ stats_url }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-hide">
|
||||
<input type="hidden" name="user.id" value="{{ auth_user.id }}">
|
||||
<input type="hidden" name="user.name" value="{{ auth_user.name }}">
|
||||
<input type="hidden" name="user.avatar" value="{{ auth_user.avatar }}">
|
||||
<input type="hidden" name="chapter.id" value="{{ chapter.id }}">
|
||||
<input type="hidden" name="chapter.plan_id" value="{{ chapter.me.plan_id }}">
|
||||
<input type="hidden" name="chapter.learning_url" value="{{ learning_url }}">
|
||||
<input type="hidden" name="chapter.play_urls" value='{{ chapter.play_urls|json_encode }}'>
|
||||
<input type="hidden" name="bind_user_url" value='{{ bind_user_url }}'>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
@ -7,7 +7,8 @@
|
||||
|
||||
<div class="breadcrumb">
|
||||
<span class="layui-breadcrumb">
|
||||
<span><i class="layui-icon layui-icon-return"></i> <a href="{{ course_url }}">返回课程主页</a></span>
|
||||
<a><cite>{{ chapter.course.title }}</cite></a>
|
||||
<a><cite>{{ chapter.title }}</cite></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -22,9 +23,6 @@
|
||||
</div>
|
||||
|
||||
<div class="layui-hide">
|
||||
<input type="hidden" name="user.id" value="{{ auth_user.id }}">
|
||||
<input type="hidden" name="user.name" value="{{ auth_user.name }}">
|
||||
<input type="hidden" name="user.avatar" value="{{ auth_user.avatar }}">
|
||||
<input type="hidden" name="chapter.id" value="{{ chapter.id }}">
|
||||
<input type="hidden" name="chapter.plan_id" value="{{ chapter.me.plan_id }}">
|
||||
<input type="hidden" name="chapter.learning_url" value="{{ learning_url }}">
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
<div class="search">
|
||||
<form class="layui-form" action="{{ url({'for':'web.search.list'}) }}">
|
||||
<input class="layui-input" type="text" name="query" value="{{ request.get('query')|striptags }}" autocomplete="off" placeholder="请输入课程关键字...">
|
||||
<input class="layui-input" type="text" name="query" maxlength="30" autocomplete="off" placeholder="请输入课程关键字..." value="{{ request.get('query')|striptags }}">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -757,7 +757,60 @@
|
||||
height: 428px;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
padding: 10px 20px 20px 20px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.vip-icon {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.chat-msg-list {
|
||||
height: 380px;
|
||||
margin-bottom: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.chat-msg-list::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.chat-msg-list::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chat-msg-list .chat, .chat-msg-list .chat-sys {
|
||||
margin-bottom: 10px;
|
||||
line-height: 25px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.chat-sys {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chat-sys span, .chat span {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.chat .user {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.chat .content {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.chat-msg-form .layui-input {
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.live-stats {
|
||||
height: 420px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@ -770,7 +823,8 @@
|
||||
}
|
||||
|
||||
.live-user-card {
|
||||
line-height: 30px;
|
||||
margin-bottom: 10px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.chat-login-tips {
|
||||
|
@ -1,9 +1,11 @@
|
||||
layui.use(['jquery', 'helper'], function () {
|
||||
layui.use(['jquery', 'form', 'helper'], function () {
|
||||
|
||||
var $ = layui.jquery;
|
||||
var form = layui.form;
|
||||
var helper = layui.helper;
|
||||
var socket = new WebSocket(window.koogua.socketUrl);
|
||||
|
||||
var bindUserUrl = $('input[name="bind_user_url"]').val();
|
||||
var $chatContent = $('input[name=content]');
|
||||
var $chatMsgList = $('#chat-msg-list');
|
||||
|
||||
socket.onopen = function () {
|
||||
@ -25,8 +27,10 @@ layui.use(['jquery', 'helper'], function () {
|
||||
socket.send('pong...');
|
||||
} else if (data.type === 'bind_user') {
|
||||
bindUser(data.client_id);
|
||||
} else if (data.type === 'show_message') {
|
||||
showMessage(data.content);
|
||||
} else if (data.type === 'new_message') {
|
||||
showNewMessage(data);
|
||||
} else if (data.type === 'new_user') {
|
||||
showLoginMessage(data);
|
||||
}
|
||||
};
|
||||
|
||||
@ -36,38 +40,64 @@ layui.use(['jquery', 'helper'], function () {
|
||||
url: data.form.action,
|
||||
data: data.field,
|
||||
success: function (res) {
|
||||
showMessage(res);
|
||||
showNewMessage(res);
|
||||
$chatContent.val('');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
loadRecentChats();
|
||||
|
||||
refreshLiveStats();
|
||||
|
||||
setInterval('refreshLiveStats()', 60000);
|
||||
setInterval(function () {
|
||||
refreshLiveStats();
|
||||
}, 300000);
|
||||
|
||||
function bindUser(clientId) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/live/bind',
|
||||
url: bindUserUrl,
|
||||
data: {client_id: clientId}
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(res) {
|
||||
function showNewMessage(res) {
|
||||
var html = '<div class="chat">';
|
||||
html += '<span class="user">' + res.user.name + '</span>';
|
||||
if (res.user.vip === 1) {
|
||||
html += '<span class="layui-badge">VIP</span>';
|
||||
if (res.user.vip === 0) {
|
||||
html += '<span class="vip-icon layui-icon layui-icon-diamond"></span>';
|
||||
}
|
||||
html += '<span class="user">' + res.user.name + ':</span>';
|
||||
html += '<span class="content">' + res.content + '</span>';
|
||||
html += '</div>';
|
||||
$chatMsgList.append(html);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function showLoginMessage(res) {
|
||||
var html = '<div class="chat chat-sys">';
|
||||
html += '<span>' + res.user.name + '</span>';
|
||||
html += '<span>进入了直播间</span>';
|
||||
html += '</div>';
|
||||
$chatMsgList.append(html);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
var $scrollTo = $chatMsgList.find('.chat:last');
|
||||
$chatMsgList.scrollTop(
|
||||
$scrollTo.offset().top - $chatMsgList.offset().top + $chatMsgList.scrollTop()
|
||||
);
|
||||
}
|
||||
|
||||
function refreshLiveStats() {
|
||||
var $liveStats = $('#live-stats');
|
||||
helper.ajaxLoadHtml($liveStats.data('url'), $liveStats.attr('id'));
|
||||
var $tabStats = $('#tab-stats');
|
||||
helper.ajaxLoadHtml($tabStats.data('url'), $tabStats.attr('id'));
|
||||
}
|
||||
|
||||
function loadRecentChats() {
|
||||
helper.ajaxLoadHtml($chatMsgList.data('url'), $chatMsgList.attr('id'));
|
||||
}
|
||||
|
||||
});
|
@ -6,9 +6,9 @@ layui.use(['jquery', 'helper'], function () {
|
||||
var interval = null;
|
||||
var intervalTime = 5000;
|
||||
var position = 0;
|
||||
var userId = window.koogua.user.id;
|
||||
var chapterId = $('input[name="chapter.id"]').val();
|
||||
var planId = $('input[name="chapter.plan_id"]').val();
|
||||
var userId = $('input[name="user.id"]').val();
|
||||
var learningUrl = $('input[name="chapter.learning_url"]').val();
|
||||
var playUrls = JSON.parse($('input[name="chapter.play_urls"]').val());
|
||||
var requestId = helper.getRequestId();
|
||||
|
@ -6,9 +6,9 @@ layui.use(['jquery', 'helper'], function () {
|
||||
var interval = null;
|
||||
var intervalTime = 5000;
|
||||
var position = 0;
|
||||
var userId = window.koogua.user.id;
|
||||
var chapterId = $('input[name="chapter.id"]').val();
|
||||
var planId = $('input[name="chapter.plan_id"]').val();
|
||||
var userId = $('input[name="user.id"]').val();
|
||||
var learningUrl = $('input[name="chapter.learning_url"]').val();
|
||||
var playUrls = JSON.parse($('input[name="chapter.play_urls"]').val());
|
||||
var requestId = helper.getRequestId();
|
||||
|
Loading…
x
Reference in New Issue
Block a user