mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-06-28 13:21:37 +08:00
完善直播聊天
This commit is contained in:
parent
20b8ef8132
commit
401ec0793a
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Web\Controllers;
|
||||
|
||||
use App\Services\Pay\Alipay as AlipayService;
|
||||
use App\Traits\Response as ResponseTrait;
|
||||
|
||||
class AlipayController extends \Phalcon\Mvc\Controller
|
||||
{
|
||||
|
||||
use ResponseTrait;
|
||||
|
||||
/**
|
||||
* @Post("/alipay/notify", name="web.alipay.notify")
|
||||
*/
|
||||
public function notifyAction()
|
||||
{
|
||||
$alipayService = new AlipayService();
|
||||
|
||||
$response = $alipayService->notify();
|
||||
|
||||
if (!$response) exit;
|
||||
|
||||
$response->send();
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
@ -14,6 +14,20 @@ class LiveController extends Controller
|
||||
|
||||
use ResponseTrait;
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/preview", name="web.live.preview")
|
||||
*/
|
||||
public function previewAction($id)
|
||||
{
|
||||
$service = new LiveService();
|
||||
|
||||
$stats = $service->getStats($id);
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->pick('chapter/live_stats');
|
||||
$this->view->setVar('stats', $stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/chats", name="web.live.chats")
|
||||
*/
|
||||
|
@ -4,6 +4,8 @@ namespace App\Http\Web\Controllers;
|
||||
|
||||
use App\Library\CsrfToken as CsrfTokenService;
|
||||
use App\Models\ContentImage as ContentImageModel;
|
||||
use App\Services\Pay\Alipay as AlipayService;
|
||||
use App\Services\Pay\Wxpay as WxpayService;
|
||||
use App\Services\Storage as StorageService;
|
||||
use App\Traits\Response as ResponseTrait;
|
||||
use App\Traits\Security as SecurityTrait;
|
||||
@ -67,4 +69,44 @@ class PublicController extends \Phalcon\Mvc\Controller
|
||||
return $this->jsonSuccess(['token' => $token]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/alipay/notify", name="web.alipay_notify")
|
||||
*/
|
||||
public function alipayNotifyAction()
|
||||
{
|
||||
$alipayService = new AlipayService();
|
||||
|
||||
$response = $alipayService->notify();
|
||||
|
||||
if (!$response) exit;
|
||||
|
||||
$response->send();
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/wxpay/notify", name="web.wxpay_notify")
|
||||
*/
|
||||
public function wxpayNotifyAction()
|
||||
{
|
||||
$wxpayService = new WxpayService();
|
||||
|
||||
$response = $wxpayService->notify();
|
||||
|
||||
if (!$response) exit;
|
||||
|
||||
$response->send();
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/live/notify", name="web.live_notify")
|
||||
*/
|
||||
public function liveNotifyAction()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Web\Controllers;
|
||||
|
||||
use App\Services\Pay\Wxpay as WxpayService;
|
||||
use App\Traits\Response as ResponseTrait;
|
||||
|
||||
class WxpayController extends \Phalcon\Mvc\Controller
|
||||
{
|
||||
|
||||
use ResponseTrait;
|
||||
|
||||
/**
|
||||
* @Post("/wxpay/notify", name="web.wxpay.notify")
|
||||
*/
|
||||
public function notifyAction()
|
||||
{
|
||||
$wxpayService = new WxpayService();
|
||||
|
||||
$response = $wxpayService->notify();
|
||||
|
||||
if (!$response) exit;
|
||||
|
||||
$response->send();
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
@ -17,7 +17,9 @@ class Live extends Service
|
||||
|
||||
$key = $this->getRedisListKey($id);
|
||||
|
||||
$items = $redis->lRange($key, 0, 10);
|
||||
$redis->expire($key, 3 * 3600);
|
||||
|
||||
$items = $redis->lRange($key, 0, 15);
|
||||
|
||||
$result = [];
|
||||
|
||||
@ -88,7 +90,7 @@ class Live extends Service
|
||||
|
||||
$content = $this->request->getPost('content', ['trim', 'striptags']);
|
||||
|
||||
$content = kg_substr($content, 0, 150);
|
||||
$content = kg_substr($content, 0, 80);
|
||||
|
||||
Gateway::$registerAddress = $this->getRegisterAddress();
|
||||
|
||||
@ -111,9 +113,14 @@ class Live extends Service
|
||||
Gateway::sendToGroup($groupName, $encodeMessage, $clientId);
|
||||
|
||||
$redis = $this->getRedis();
|
||||
|
||||
$key = $this->getRedisListKey($id);
|
||||
|
||||
$redis->lPush($key, $encodeMessage);
|
||||
$redis->lTrim($key, 0, 10);
|
||||
|
||||
if ($redis->lLen($key) % 20 == 0) {
|
||||
$redis->lTrim($key, 0, 15);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% for chat in chats %}
|
||||
<div class="chat">
|
||||
{% if chat.user.vip == 0 %}
|
||||
{% if chat.user.vip == 1 %}
|
||||
<span class="vip-icon layui-icon layui-icon-diamond"></span>
|
||||
{% endif %}
|
||||
<span class="user">{{ chat.user.name }}</span>
|
||||
|
@ -31,12 +31,16 @@
|
||||
<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>
|
||||
{% if auth_user.id > 0 %}
|
||||
<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">
|
||||
<input class="layui-input" type="text" name="content" maxlength="80" placeholder="快来和大家一起互动吧~" lay-verType="tips" lay-verify="required">
|
||||
<button class="layui-hide" type="submit" lay-submit="true" lay-filter="chat">发送</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="chat-login-tips">登录后才可以发言哦~</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="layui-tab-item" id="tab-stats" data-url="{{ live_stats_url }}"></div>
|
||||
</div>
|
||||
|
@ -68,19 +68,8 @@
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block inline_js %}
|
||||
{% block include_js %}
|
||||
|
||||
<script>
|
||||
layui.use(['carousel', 'flow'], function () {
|
||||
var carousel = layui.carousel;
|
||||
var flow = layui.flow;
|
||||
carousel.render({
|
||||
elem: '#carousel',
|
||||
width: '100%',
|
||||
height: '270px'
|
||||
});
|
||||
flow.lazyimg();
|
||||
});
|
||||
</script>
|
||||
{{ js_include('web/js/index.js') }}
|
||||
|
||||
{% endblock %}
|
@ -2,6 +2,7 @@
|
||||
<html lang="zh-CN-Hans">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="keywords" content="{{ site_seo.getKeywords() }}">
|
||||
<meta name="description" content="{{ site_seo.getDescription() }}">
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html lang="zh-CN-Hans">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrfToken.getToken() }}">
|
||||
{{ icon_link('favicon.ico') }}
|
||||
|
@ -34,15 +34,21 @@ class CsrfToken
|
||||
|
||||
public function checkToken($token)
|
||||
{
|
||||
if (!$token) return false;
|
||||
|
||||
$text = $this->crypt->decryptBase64($token);
|
||||
|
||||
list($time, $fixed, $random) = explode($this->delimiter, $text);
|
||||
$params = explode($this->delimiter, $text);
|
||||
|
||||
if ($time != intval($time) || $fixed != $this->fixed || strlen($random) != 8) {
|
||||
if (!isset($params[0]) || !isset($params[1]) || !isset($params[2])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (time() - $time > $this->lifetime) {
|
||||
if ($params[0] != intval($params[0]) || $params[1] != $this->fixed || strlen($params[2]) != 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (time() - $params[0] > $this->lifetime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -130,11 +130,6 @@ class ChapterInfo extends FrontendService
|
||||
|
||||
$playUrls = $service->getPlayUrls($chapter->id);
|
||||
|
||||
/**
|
||||
* @var array $attrs
|
||||
*/
|
||||
$attrs = $chapter->attrs;
|
||||
|
||||
return [
|
||||
'id' => $chapter->id,
|
||||
'title' => $chapter->title,
|
||||
@ -150,22 +145,36 @@ class ChapterInfo extends FrontendService
|
||||
|
||||
protected function formatChapterLive(ChapterModel $chapter)
|
||||
{
|
||||
$liveService = new LiveService();
|
||||
$service = new LiveService();
|
||||
|
||||
$playUrls = $liveService->getPullUrls("chapter_{$chapter->id}");
|
||||
$streamName = $this->getLiveStreamName($chapter->id);
|
||||
|
||||
/**
|
||||
* @var array $attrs
|
||||
*/
|
||||
$attrs = $chapter->attrs;
|
||||
$chapterRepo = new ChapterRepo();
|
||||
|
||||
$live = $chapterRepo->findChapterLive($chapter->id);
|
||||
|
||||
$playUrls = [];
|
||||
|
||||
if ($live->start_time - time() > 1800) {
|
||||
$status = 'pending';
|
||||
} elseif (time() - $live->end_time > 1800) {
|
||||
$status = 'finished';
|
||||
} else {
|
||||
$status = $service->getStreamState($streamName);
|
||||
}
|
||||
|
||||
if ($status == 'active') {
|
||||
$playUrls = $service->getPullUrls($streamName);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $chapter->id,
|
||||
'title' => $chapter->title,
|
||||
'summary' => $chapter->summary,
|
||||
'model' => $chapter->model,
|
||||
'start_time' => $attrs['start_time'],
|
||||
'end_time' => $attrs['end_time'],
|
||||
'status' => $status,
|
||||
'start_time' => $live->start_time,
|
||||
'end_time' => $live->end_time,
|
||||
'play_urls' => $playUrls,
|
||||
'user_count' => $chapter->user_count,
|
||||
'agree_count' => $chapter->agree_count,
|
||||
@ -180,11 +189,6 @@ class ChapterInfo extends FrontendService
|
||||
|
||||
$read = $chapterRepo->findChapterRead($chapter->id);
|
||||
|
||||
/**
|
||||
* @var array $attrs
|
||||
*/
|
||||
$attrs = $chapter->attrs;
|
||||
|
||||
return [
|
||||
'id' => $chapter->id,
|
||||
'title' => $chapter->title,
|
||||
@ -259,4 +263,9 @@ class ChapterInfo extends FrontendService
|
||||
$this->eventsManager->fire('chapterCounter:incrUserCount', $this, $chapter);
|
||||
}
|
||||
|
||||
protected function getLiveStreamName($id)
|
||||
{
|
||||
return "chapter_{$id}";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,32 +2,191 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Phalcon\Logger\Adapter\File as FileLogger;
|
||||
use TencentCloud\Common\Credential;
|
||||
use TencentCloud\Common\Exception\TencentCloudSDKException;
|
||||
use TencentCloud\Common\Profile\ClientProfile;
|
||||
use TencentCloud\Common\Profile\HttpProfile;
|
||||
use TencentCloud\Live\V20180801\LiveClient;
|
||||
use TencentCloud\Live\V20180801\Models\DescribeLiveStreamStateRequest;
|
||||
use TencentCloud\Live\V20180801\Models\ForbidLiveStreamRequest;
|
||||
use TencentCloud\Live\V20180801\Models\ResumeLiveStreamRequest;
|
||||
|
||||
class Live extends Service
|
||||
{
|
||||
|
||||
const END_POINT = 'live.tencentcloudapi.com';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var LiveClient
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
/**
|
||||
* @var FileLogger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->settings = $this->getSectionSettings('live');
|
||||
|
||||
$this->logger = $this->getLogger('live');
|
||||
|
||||
$this->client = $this->getLiveClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流的状态
|
||||
*
|
||||
* @param string $streamName
|
||||
* @param string $appName
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getStreamState($streamName, $appName = 'live')
|
||||
{
|
||||
try {
|
||||
|
||||
$request = new DescribeLiveStreamStateRequest();
|
||||
|
||||
$params = json_encode([
|
||||
'DomainName' => $this->settings['push_domain'],
|
||||
'AppName' => $appName ?: 'live',
|
||||
'StreamName' => $streamName,
|
||||
]);
|
||||
|
||||
$request->fromJsonString($params);
|
||||
|
||||
$this->logger->debug('Describe Live Stream State Request ' . $params);
|
||||
|
||||
$response = $this->client->DescribeLiveStreamState($request);
|
||||
|
||||
$this->logger->debug('Describe Live Stream State Response ' . $response->toJsonString());
|
||||
|
||||
$result = $response->StreamState;
|
||||
|
||||
} catch (TencentCloudSDKException $e) {
|
||||
|
||||
$this->logger->error('Describe Live Stream State Exception ' . kg_json_encode([
|
||||
'code' => $e->getErrorCode(),
|
||||
'message' => $e->getMessage(),
|
||||
'requestId' => $e->getRequestId(),
|
||||
]));
|
||||
|
||||
$result = false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁推直播推流
|
||||
*
|
||||
* @param string $streamName
|
||||
* @param string $appName
|
||||
* @param string $reason
|
||||
* @return array|bool
|
||||
*/
|
||||
public function forbidStream($streamName, $appName = 'live', $reason = '')
|
||||
{
|
||||
try {
|
||||
|
||||
$request = new ForbidLiveStreamRequest();
|
||||
|
||||
$params = json_encode([
|
||||
'DomainName' => $this->settings['push_domain'],
|
||||
'AppName' => $appName ?: 'live',
|
||||
'StreamName' => $streamName,
|
||||
'Reason' => $reason,
|
||||
]);
|
||||
|
||||
$request->fromJsonString($params);
|
||||
|
||||
$this->logger->debug('Forbid Live Stream Request ' . $params);
|
||||
|
||||
$response = $this->client->ForbidLiveStream($request);
|
||||
|
||||
$this->logger->debug('Forbid Live Stream Response ' . $response->toJsonString());
|
||||
|
||||
$result = json_decode($response->toJsonString(), true);
|
||||
|
||||
} catch (TencentCloudSDKException $e) {
|
||||
|
||||
$this->logger->error('Forbid Live Stream Exception ' . kg_json_encode([
|
||||
'code' => $e->getErrorCode(),
|
||||
'message' => $e->getMessage(),
|
||||
'requestId' => $e->getRequestId(),
|
||||
]));
|
||||
|
||||
$result = false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复直播推流
|
||||
*
|
||||
* @param string $streamName
|
||||
* @param string $appName
|
||||
* @return array|bool
|
||||
*/
|
||||
public function resumeStream($streamName, $appName = 'live')
|
||||
{
|
||||
try {
|
||||
|
||||
$request = new ResumeLiveStreamRequest();
|
||||
|
||||
$params = json_encode([
|
||||
'DomainName' => $this->settings['push_domain'],
|
||||
'AppName' => $appName ?: 'live',
|
||||
'StreamName' => $streamName,
|
||||
]);
|
||||
|
||||
$request->fromJsonString($params);
|
||||
|
||||
$this->logger->debug('Resume Live Stream Request ' . $params);
|
||||
|
||||
$response = $this->client->ResumeLiveStream($request);
|
||||
|
||||
$this->logger->debug('Resume Live Stream Response ' . $response->toJsonString());
|
||||
|
||||
$result = json_decode($response->toJsonString(), true);
|
||||
|
||||
} catch (TencentCloudSDKException $e) {
|
||||
|
||||
$this->logger->error('Resume Live Stream Exception ' . kg_json_encode([
|
||||
'code' => $e->getErrorCode(),
|
||||
'message' => $e->getMessage(),
|
||||
'requestId' => $e->getRequestId(),
|
||||
]));
|
||||
|
||||
$result = false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推流地址
|
||||
*
|
||||
* @param string $streamName
|
||||
* @param string $appName
|
||||
* @return string
|
||||
*/
|
||||
function getPushUrl($streamName)
|
||||
function getPushUrl($streamName, $appName = 'live')
|
||||
{
|
||||
$appName = $appName ?: 'live';
|
||||
|
||||
$authEnabled = $this->settings['push_auth_enabled'];
|
||||
$authKey = $this->settings['push_auth_key'];
|
||||
$expireTime = $this->settings['push_auth_delta'] + time();
|
||||
$domain = $this->settings['push_domain'];
|
||||
$appName = 'live';
|
||||
|
||||
$authParams = $this->getAuthParams($streamName, $authKey, $expireTime);
|
||||
|
||||
@ -46,6 +205,8 @@ class Live extends Service
|
||||
*/
|
||||
public function getPullUrls($streamName, $appName = 'live')
|
||||
{
|
||||
$appName = $appName ?: 'live';
|
||||
|
||||
$protocol = $this->settings['pull_protocol'];
|
||||
$domain = $this->settings['pull_domain'];
|
||||
$authEnabled = $this->settings['pull_auth_enabled'];
|
||||
@ -116,4 +277,25 @@ class Live extends Service
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getLiveClient()
|
||||
{
|
||||
$secret = $this->getSectionSettings('secret');
|
||||
|
||||
$secretId = $secret['secret_id'];
|
||||
$secretKey = $secret['secret_key'];
|
||||
$region = '';
|
||||
|
||||
$credential = new Credential($secretId, $secretKey);
|
||||
|
||||
$httpProfile = new HttpProfile();
|
||||
|
||||
$httpProfile->setEndpoint(self::END_POINT);
|
||||
|
||||
$clientProfile = new ClientProfile();
|
||||
|
||||
$clientProfile->setHttpProfile($httpProfile);
|
||||
|
||||
return new LiveClient($credential, $region, $clientProfile);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -123,12 +123,12 @@ $config['throttle']['lifetime'] = 60;
|
||||
$config['throttle']['rate_limit'] = 60;
|
||||
|
||||
/**
|
||||
* 客户端连接地址
|
||||
* 客户端连接地址(外部可访问的ip或域名)
|
||||
*/
|
||||
$config['websocket']['url'] = 'ws://127.0.0.1:8282';
|
||||
|
||||
/**
|
||||
* gateway和worker注册地址
|
||||
* gateway和worker注册地址(内部访问)
|
||||
*/
|
||||
$config['websocket']['register_address'] = '127.0.0.1:1238';
|
||||
|
||||
|
@ -183,6 +183,7 @@
|
||||
|
||||
.index-course-list .course-card .info {
|
||||
border: 1px solid #eee;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
.index-carousel {
|
||||
@ -803,6 +804,14 @@
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.chat-login-tips {
|
||||
border-top: 1px solid #f2f2f2;
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chat-msg-form .layui-input {
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
@ -822,18 +831,6 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.live-user-card {
|
||||
margin-bottom: 10px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.chat-login-tips {
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.chapter-bg {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
13
public/static/web/js/index.js
Normal file
13
public/static/web/js/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
layui.use(['carousel', 'flow'], function () {
|
||||
|
||||
var carousel = layui.carousel;
|
||||
var flow = layui.flow;
|
||||
|
||||
carousel.render({
|
||||
elem: '#carousel',
|
||||
width: '100%',
|
||||
height: '270px'
|
||||
});
|
||||
|
||||
flow.lazyimg();
|
||||
});
|
@ -65,7 +65,7 @@ layui.use(['jquery', 'form', 'helper'], function () {
|
||||
|
||||
function showNewMessage(res) {
|
||||
var html = '<div class="chat">';
|
||||
if (res.user.vip === 0) {
|
||||
if (res.user.vip === 1) {
|
||||
html += '<span class="vip-icon layui-icon layui-icon-diamond"></span>';
|
||||
}
|
||||
html += '<span class="user">' + res.user.name + ':</span>';
|
||||
|
Loading…
x
Reference in New Issue
Block a user