1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-29 22:01:38 +08:00

Merge branch 'koogua/v1.4.1'

This commit is contained in:
koogua 2021-08-08 21:05:22 +08:00
commit b2efe1f61f
26 changed files with 247 additions and 131 deletions

View File

@ -1,3 +1,17 @@
### [v1.4.1](https://gitee.com/koogua/course-tencent-cloud/releases/v1.4.1)(2021-08-08)
- AnswerInfo结构补充遗漏的comment_count字段
- AnswerList结构去除deleted字段
- ChapterList结构补充published字段
- 使用开源的播放器DPlayer替换腾讯TcPlayer
- 修正第三方登录开关的判断
- 修正课程方向过滤问题
- 修正教师主页会显示未发布课程问题
- 修正评论删除点击无效问题
- 优化课时列表可点击权限判断
- 优化来源检查域名带端口问题
- 优化微信公众号业务处理类
### [v1.4.0](https://gitee.com/koogua/course-tencent-cloud/releases/v1.4.0)(2021-08-03)
### 更新

View File

@ -34,6 +34,7 @@ class CourseChapterList extends Builder
'id' => $chapter->id,
'title' => $chapter->title,
'model' => $chapter->model,
'published' => $chapter->published,
'children' => $this->handleChildren($chapter),
];
}
@ -70,6 +71,7 @@ class CourseChapterList extends Builder
'id' => $lesson->id,
'title' => $lesson->title,
'model' => $lesson->model,
'published' => $lesson->published,
'free' => $lesson->free,
'attrs' => $attrs,
];

View File

@ -4,6 +4,10 @@
<div id="player"></div>
<div class="layui-hide">
<textarea id="play_urls">{{ pull_urls|json_encode }}</textarea>
</div>
{% endblock %}
{% block inline_css %}
@ -12,13 +16,19 @@
.kg-body {
padding: 0;
}
#player {
width: 720px;
height: 405px;
}
</style>
{% endblock %}
{% block include_js %}
{{ js_include('lib/tc-player-2.4.0.js') }}
{{ js_include('lib/dplayer/flv.min.js') }}
{{ js_include('lib/dplayer/DPlayer.min.js') }}
{% endblock %}
@ -30,27 +40,38 @@
var $ = layui.jquery;
var options = {
live: true,
autoplay: true,
width: 720,
height: 405
};
var playUrls = JSON.parse($('#play_urls').val());
var playUrls = JSON.parse('{{ pull_urls|json_encode }}');
var formats = ['m3u8'];
var rates = ['od', 'hd', 'sd'];
var formats = ['flv'];
var rates = [
{name: 'od', label: '原画'},
{name: 'hd', label: '高清'},
{name: 'sd', label: '标清'},
];
var quality = [];
$.each(formats, function (i, format) {
$.each(rates, function (k, rate) {
if (playUrls.hasOwnProperty(format) && playUrls[format].hasOwnProperty(rate)) {
var key = k === 0 ? format : format + '_' + rate;
options[key] = playUrls[format][rate];
if (playUrls.hasOwnProperty(format) && playUrls[format].hasOwnProperty(rate.name)) {
quality[k] = {
name: rate.label,
url: playUrls[format][rate.name],
type: 'flv',
};
}
});
});
new TcPlayer('player', options);
new DPlayer({
container: document.getElementById('player'),
live: true,
video: {
quality: quality,
defaultQuality: 0,
}
});
});

View File

@ -16,13 +16,19 @@
.kg-body {
padding: 0;
}
#player {
width: 720px;
height: 405px;
}
</style>
{% endblock %}
{% block include_js %}
{{ js_include('lib/tc-player-2.4.0.js') }}
{{ js_include('lib/dplayer/hls.min.js') }}
{{ js_include('lib/dplayer/DPlayer.min.js') }}
{% endblock %}
@ -36,11 +42,9 @@
var playUrl = $('input[name=play_url]').val();
new TcPlayer('player', {
m3u8: playUrl,
autoplay: false,
width: 720,
height: 405
new DPlayer({
container: document.getElementById('player'),
video: {url: playUrl},
});
});

View File

@ -47,19 +47,14 @@ class ChapterController extends Controller
return $this->notFound();
}
if ($chapter['me']['owned'] == 0) {
return $this->forbidden();
}
$service = new CourseInfoService();
$course = $service->handle($chapter['course']['id']);
$owned = $chapter['me']['owned'] ?? false;
if (!$owned) {
$this->response->redirect([
'for' => 'home.course.show',
'id' => $course['id'],
]);
}
$service = new CourseChapterListService();
$catalog = $service->handle($course['id']);

View File

@ -56,51 +56,35 @@ class WeChatOfficialAccount extends Service
switch ($message['Event']) {
case 'subscribe':
return $this->handleSubscribeEvent($message);
break;
case 'unsubscribe':
return $this->handleUnsubscribeEvent($message);
break;
case 'SCAN':
return $this->handleScanEvent($message);
break;
case 'CLICK':
return $this->handleClickEvent($message);
break;
case 'VIEW':
return $this->handleViewEvent($message);
break;
case 'LOCATION':
return $this->handleLocationEvent($message);
break;
default:
return $this->noMatchReply();
break;
}
break;
case 'text':
return $this->handleTextReply($message);
break;
case 'image':
return $this->handleImageReply($message);
break;
case 'voice':
return $this->handleVoiceReply($message);
break;
case 'video':
return $this->handleVideoReply($message);
break;
case 'shortvideo':
return $this->handleShortVideoReply($message);
break;
case 'location':
return $this->handleLocationReply($message);
break;
case 'link':
return $this->handleLinkReply($message);
break;
default:
return $this->noMatchReply();
break;
}
}
@ -135,7 +119,7 @@ class WeChatOfficialAccount extends Service
$user = $userRepo->findById($userId);
if (!$user) return;
if (!$user) return $this->emptyReply();
$subscribeRepo = new WeChatSubscribeRepo();

View File

@ -71,7 +71,8 @@
{% block include_js %}
{{ js_include('lib/tc-player-2.4.0.js') }}
{{ js_include('lib/dplayer/flv.min.js') }}
{{ js_include('lib/dplayer/DPlayer.min.js') }}
{{ js_include('home/js/chapter.live.player.js') }}
{{ js_include('home/js/chapter.live.chat.js') }}
{{ js_include('home/js/chapter.show.js') }}

View File

@ -57,7 +57,8 @@
{% block include_js %}
{{ js_include('lib/tc-player-2.4.0.js') }}
{{ js_include('lib/dplayer/hls.min.js') }}
{{ js_include('lib/dplayer/DPlayer.min.js') }}
{{ js_include('home/js/course.share.js') }}
{{ js_include('home/js/chapter.show.js') }}
{{ js_include('home/js/chapter.vod.player.js') }}

View File

@ -22,7 +22,7 @@
</div>
<div class="right">
<div class="column">
<span class="action action-delete" data-id="{{ comment.id }}" data-url="{{ delete_url }}">删除</span>
<span class="action comment-delete" data-id="{{ comment.id }}" data-url="{{ delete_url }}">删除</span>
</div>
</div>
</div>
@ -54,7 +54,7 @@
</div>
<div class="right">
<span class="column">
<span class="action action-delete" data-id="{{ comment.id }}" data-url="{{ delete_url }}">删除</span>
<span class="action comment-delete" data-id="{{ comment.id }}" data-url="{{ delete_url }}">删除</span>
</span>
</div>
</div>

View File

@ -5,7 +5,7 @@
<i class="layui-icon layui-icon-play"></i>
<span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="layui-badge free-badge">免费</span>
<span class="layui-badge free-badge">试听</span>
{% endif %}
{% if lesson.me.duration > 0 %}
<span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span>

View File

@ -92,10 +92,10 @@
{% if oauth_provider.qq.enabled == 1 %}
<a class="layui-icon layui-icon-login-qq login-qq" href="{{ url({'for':'home.oauth.qq'}) }}"></a>
{% endif %}
{% if oauth_provider.qq.enabled == 1 %}
{% if oauth_provider.weixin.enabled == 1 %}
<a class="layui-icon layui-icon-login-wechat login-wechat" href="{{ url({'for':'home.oauth.weixin'}) }}"></a>
{% endif %}
{% if oauth_provider.qq.enabled == 1 %}
{% if oauth_provider.weibo.enabled == 1 %}
<a class="layui-icon layui-icon-login-weibo login-weibo" href="{{ url({'for':'home.oauth.weibo'}) }}"></a>
{% endif %}
</div>

View File

@ -16,7 +16,7 @@ class AppInfo
protected $link = 'https://koogua.com';
protected $version = '1.4.0';
protected $version = '1.4.1';
public function __get($name)
{

View File

@ -46,7 +46,9 @@ class Course extends Repository
if (!empty($where['category_id'])) {
$where['id'] = $this->getCategoryCourseIds($where['category_id']);
$fakeId = empty($where['id']);
} elseif (!empty($where['teacher_id'])) {
}
if (!empty($where['teacher_id'])) {
$where['id'] = $this->getTeacherCourseIds($where['teacher_id']);
$fakeId = empty($where['id']);
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Models\Course as CourseModel;
use App\Models\CourseUser as CourseUserModel;
class TeacherCourse extends Repository
{
public function paginate($userId, $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder()
->columns('course.*')
->addFrom(CourseModel::class, 'course')
->join(CourseUserModel::class, 'course.id = cu.course_id', 'cu')
->where('cu.user_id = :user_id:', ['user_id' => $userId])
->andWhere('cu.role_type = :role_type:', ['role_type' => CourseUserModel::ROLE_TEACHER])
->andWhere('course.published = 1')
->andWhere('course.deleted = 0')
->orderBy('cu.id DESC');
$pager = new PagerQueryBuilder([
'builder' => $builder,
'page' => $page,
'limit' => $limit,
]);
return $pager->paginate();
}
}

View File

@ -40,6 +40,7 @@ class AnswerInfo extends LogicService
'accepted' => $answer->accepted,
'published' => $answer->published,
'deleted' => $answer->deleted,
'comment_count' => $answer->comment_count,
'like_count' => $answer->like_count,
'create_time' => $answer->create_time,
'update_time' => $answer->update_time,

View File

@ -42,8 +42,13 @@ class ChapterList extends LogicService
$mapping = $this->getLearningMapping($course->id, $user->id, $this->courseUser->plan_id);
foreach ($chapters as &$chapter) {
foreach ($chapter['children'] as &$lesson) {
/**
* @todo v1.4.1之前缓存中无published字段临时给默认值
*/
$lesson['published'] = $lesson['published'] ?? 1;
$owned = ($this->ownedCourse || $lesson['free'] == 1) && $lesson['published'] == 1;
$lesson['me'] = [
'owned' => $this->ownedCourse || $lesson['free'] ? 1 : 0,
'owned' => $owned ? 1 : 0,
'progress' => $mapping[$lesson['id']]['progress'] ?? 0,
'duration' => $mapping[$lesson['id']]['duration'] ?? 0,
];
@ -52,8 +57,13 @@ class ChapterList extends LogicService
} else {
foreach ($chapters as &$chapter) {
foreach ($chapter['children'] as &$lesson) {
/**
* @todo v1.4.1之前缓存中无published字段临时给默认值
*/
$lesson['published'] = $lesson['published'] ?? 1;
$owned = ($this->ownedCourse || $lesson['free'] == 1) && $lesson['published'] == 1;
$lesson['me'] = [
'owned' => $this->ownedCourse || $lesson['free'] ? 1 : 0,
'owned' => $owned ? 1 : 0,
'progress' => 0,
'duration' => 0,
];

View File

@ -38,7 +38,10 @@ class CourseList extends LogicService
$childCategoryIds = $categoryService->getChildCategoryIds($params['tc']);
$params['category_id'] = $childCategoryIds;
/**
* 构造空记录条件
*/
$params['category_id'] = $childCategoryIds ?: -999;
}
$params['published'] = 1;

View File

@ -71,7 +71,6 @@ class AnswerList extends LogicService
'content' => $answer['content'],
'anonymous' => $answer['anonymous'],
'accepted' => $answer['accepted'],
'published' => $answer['published'],
'like_count' => $answer['like_count'],
'comment_count' => $answer['comment_count'],
'create_time' => $answer['create_time'],

View File

@ -7,10 +7,8 @@
namespace App\Services\Logic\Teacher;
use App\Builders\CourseUserList as CourseUserListBuilder;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\TeacherCourse as TeacherCourseRepo;
use App\Services\Logic\Service as LogicService;
use App\Services\Logic\UserTrait;
@ -25,19 +23,12 @@ class CourseList extends LogicService
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['role_type'] = CourseUserModel::ROLE_TEACHER;
$params['user_id'] = $user->id;
$params['deleted'] = 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseUserRepo = new CourseUserRepo();
$repo = new TeacherCourseRepo();
$pager = $courseUserRepo->paginate($params, $sort, $page, $limit);
$pager = $repo->paginate($user->id, $page, $limit);
return $this->handleCourses($pager);
}
@ -48,13 +39,33 @@ class CourseList extends LogicService
return $pager;
}
$builder = new CourseUserListBuilder();
$courses = $pager->items->toArray();
$relations = $pager->items->toArray();
$baseUrl = kg_cos_url();
$courses = $builder->getCourses($relations);
$items = [];
$pager->items = array_values($courses);
foreach ($courses as $course) {
$course['cover'] = $baseUrl . $course['cover'];
$items[] = [
'id' => $course['id'],
'title' => $course['title'],
'cover' => $course['cover'],
'market_price' => (float)$course['market_price'],
'vip_price' => (float)$course['vip_price'],
'rating' => (float)$course['rating'],
'model' => $course['model'],
'level' => $course['level'],
'user_count' => $course['user_count'],
'lesson_count' => $course['lesson_count'],
'review_count' => $course['review_count'],
'favorite_count' => $course['favorite_count'],
];
}
$pager->items = $items;
return $pager;
}

View File

@ -30,11 +30,11 @@ class Security extends Validator
public function checkHttpReferer()
{
$httpHost = parse_url($this->request->getHttpReferer(), PHP_URL_HOST);
$refererHost = parse_url($this->request->getHttpReferer(), PHP_URL_HOST);
$result = $httpHost == $this->request->getHttpHost();
$httpHost = preg_replace('/:\d+/', '', $this->request->getHttpHost());
if (!$result) {
if ($refererHost != $httpHost) {
throw new BadRequestException('security.invalid_http_referer');
}
}

View File

@ -6,41 +6,53 @@ layui.use(['jquery', 'helper'], function () {
var interval = null;
var intervalTime = 15000;
var userId = window.user.id;
var requestId = helper.getRequestId();
var planId = $('input[name="chapter.plan_id"]').val();
var learningUrl = $('input[name="chapter.learning_url"]').val();
var playUrls = JSON.parse($('input[name="chapter.play_urls"]').val());
var requestId = helper.getRequestId();
var options = {
live: true,
autoplay: true,
width: 760,
height: 428,
};
var formats = ['flv'];
var formats = ['m3u8'];
var rates = ['od', 'hd', 'sd'];
var rates = [
{name: 'od', label: '原画'},
{name: 'hd', label: '高清'},
{name: 'sd', label: '标清'},
];
var quality = [];
$.each(formats, function (i, format) {
$.each(rates, function (k, rate) {
if (playUrls.hasOwnProperty(format) && playUrls[format].hasOwnProperty(rate)) {
var key = k === 0 ? format : format + '_' + rate;
options[key] = playUrls[format][rate];
if (playUrls.hasOwnProperty(format) && playUrls[format].hasOwnProperty(rate.name)) {
quality[k] = {
name: rate.label,
url: playUrls[format][rate.name],
type: 'flv',
};
}
});
});
options.listener = function (msg) {
if (msg.type === 'play') {
start();
} else if (msg.type === 'pause') {
stop();
} else if (msg.type === 'ended') {
stop();
var player = new DPlayer({
container: document.getElementById('player'),
live: true,
video: {
quality: quality,
defaultQuality: 0,
}
};
});
var player = new TcPlayer('player', options);
player.on('play', function () {
start();
});
player.on('pause', function () {
stop();
});
player.on('ended', function () {
stop();
});
function start() {
if (interval != null) {

View File

@ -12,43 +12,50 @@ layui.use(['jquery', 'helper'], function () {
var learningUrl = $('input[name="chapter.learning_url"]').val();
var playUrls = JSON.parse($('input[name="chapter.play_urls"]').val());
var options = {
autoplay: false,
width: 760,
height: 428,
};
var rates = [
{name: 'od', label: '原画'},
{name: 'hd', label: '高清'},
{name: 'sd', label: '标清'},
];
if (playUrls.hasOwnProperty('od')) {
options.m3u8 = playUrls.od.url;
}
var quality = [];
if (playUrls.hasOwnProperty('hd')) {
options.m3u8_hd = playUrls.hd.url;
}
if (playUrls.hasOwnProperty('sd')) {
options.m3u8_sd = playUrls.sd.url;
}
options.listener = function (msg) {
if (msg.type === 'play') {
play();
} else if (msg.type === 'pause') {
pause();
} else if (msg.type === 'ended') {
ended();
$.each(rates, function (k, rate) {
if (playUrls.hasOwnProperty(rate.name)) {
quality[k] = {
name: rate.label,
url: playUrls[rate.name]['url'],
};
}
};
});
var player = new TcPlayer('player', options);
var player = new DPlayer({
container: document.getElementById('player'),
video: {
quality: quality,
defaultQuality: 0,
}
});
player.on('play', function () {
play();
});
player.on('pause', function () {
pause();
});
player.on('ended', function () {
ended();
});
var position = parseInt(lastPosition);
/**
* 过于接近结束位置当作已结束处理
*/
if (position > 0 && player.duration() - position > 10) {
player.currentTime(position);
if (position > 0 && player.video.duration - position > 10) {
player.seek(position);
}
function clearLearningInterval() {
@ -85,7 +92,7 @@ layui.use(['jquery', 'helper'], function () {
plan_id: planId,
request_id: requestId,
interval_time: intervalTime,
position: player.currentTime(),
position: player.video.currentTime,
}
});
}

File diff suppressed because one or more lines are too long

10
public/static/lib/dplayer/flv.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/static/lib/dplayer/hls.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long