1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-25 20:17:23 +08:00

跟蹤學習記錄數據流

This commit is contained in:
xiaochong0302 2020-06-15 19:32:33 +08:00
parent 4665bdda33
commit 98e6a69e4a
17 changed files with 202 additions and 92 deletions

View File

@ -67,15 +67,21 @@ class SyncLearningTask extends Task
$dbLearning = $learningRepo->findByRequestId($cacheLearning->request_id); $dbLearning = $learningRepo->findByRequestId($cacheLearning->request_id);
if (!$dbLearning) { if (!$dbLearning) {
$cacheLearning->create(); $cacheLearning->create();
$this->updateChapterUser($cacheLearning);
} else { } else {
$dbLearning->duration = $cacheLearning->duration;
$dbLearning->duration += $cacheLearning->duration;
$dbLearning->position = $cacheLearning->position; $dbLearning->position = $cacheLearning->position;
$dbLearning->active_time = $cacheLearning->active_time; $dbLearning->active_time = $cacheLearning->active_time;
$dbLearning->update();
}
$this->updateChapterUser($dbLearning); $dbLearning->update();
$this->updateChapterUser($dbLearning);
}
$this->cache->delete($itemKey); $this->cache->delete($itemKey);
} }
@ -87,7 +93,7 @@ class SyncLearningTask extends Task
{ {
$chapterUserRepo = new ChapterUserRepo(); $chapterUserRepo = new ChapterUserRepo();
$chapterUser = $chapterUserRepo->findChapterUser($learning->chapter_id, $learning->user_id); $chapterUser = $chapterUserRepo->findPlanChapterUser($learning->chapter_id, $learning->user_id, $learning->plan_id);
if (!$chapterUser) return; if (!$chapterUser) return;
@ -130,44 +136,42 @@ class SyncLearningTask extends Task
$chapterUser->update(); $chapterUser->update();
if ($chapterUser->consumed == 1) { if ($chapterUser->consumed == 1) {
$this->updateCourseUser($chapterUser->course_id, $chapterUser->user_id); $this->updateCourseUser($learning);
} }
} }
/** /**
* @param int $courseId * @param LearningModel $learning
* @param int $userId
*/ */
protected function updateCourseUser($courseId, $userId) protected function updateCourseUser(LearningModel $learning)
{ {
$courseUserRepo = new CourseUserRepo(); $courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($courseId, $userId); $courseUser = $courseUserRepo->findPlanCourseUser($learning->course_id, $learning->user_id, $learning->plan_id);
if (!$courseUser) return; if (!$courseUser) return;
$courseRepo = new CourseRepo(); $courseRepo = new CourseRepo();
$courseLessons = $courseRepo->findLessons($courseId); $courseLessons = $courseRepo->findLessons($learning->course_id);
if ($courseLessons->count() == 0) { if ($courseLessons->count() == 0) {
return; return;
} }
$userLearnings = $courseRepo->findUserLearnings($courseId, $userId, $courseUser->plan_id); $userLearnings = $courseRepo->findUserLearnings($learning->course_id, $learning->user_id, $learning->plan_id);
if ($userLearnings->count() == 0) { if ($userLearnings->count() == 0) {
return; return;
} }
/** $consumedUserLearnings = [];
* @var array $consumedUserLearnings
*/ foreach ($userLearnings->toArray() as $userLearning) {
$consumedUserLearnings = $userLearnings->filter(function ($item) { if ($userLearning['consumed'] == 1) {
if ($item->consumed == 1) { $consumedUserLearnings[] = $userLearning;
return $item;
} }
}); }
if (count($consumedUserLearnings) == 0) { if (count($consumedUserLearnings) == 0) {
return; return;
@ -175,8 +179,8 @@ class SyncLearningTask extends Task
$duration = 0; $duration = 0;
foreach ($consumedUserLearnings as $learning) { foreach ($consumedUserLearnings as $userLearning) {
$duration += $learning['duration']; $duration += $userLearning['duration'];
} }
$courseLessonIds = kg_array_column($courseLessons->toArray(), 'id'); $courseLessonIds = kg_array_column($courseLessons->toArray(), 'id');

View File

@ -157,16 +157,16 @@
var changeVideoTemplate = function (format) { var changeVideoTemplate = function (format) {
var template = $('input[name=video_template]'); var template = $('input[name=video_template]');
if (format == 'mp4') { if (format === 'mp4') {
template.val('10,20,30'); template.val('100010,100020,100030');
} else { } else {
template.val('210,220,230'); template.val('100210,100220,100230');
} }
}; };
var changeAudioTemplate = function (format) { var changeAudioTemplate = function (format) {
var template = $('input[name=audio_template]'); var template = $('input[name=audio_template]');
if (format == 'mp3') { if (format === 'mp3') {
template.val('1010'); template.val('1010');
} else { } else {
template.val('1110'); template.val('1110');
@ -175,7 +175,7 @@
form.on('radio(storage-type)', function (data) { form.on('radio(storage-type)', function (data) {
var block = $('#storage-region-block'); var block = $('#storage-region-block');
if (data.value == 'fixed') { if (data.value === 'fixed') {
block.show(); block.show();
} else { } else {
block.hide(); block.hide();
@ -184,7 +184,7 @@
form.on('radio(watermark-enabled)', function (data) { form.on('radio(watermark-enabled)', function (data) {
var block = $('#watermark-template-block'); var block = $('#watermark-template-block');
if (data.value == 1) { if (data.value === 1) {
block.show(); block.show();
} else { } else {
block.hide(); block.hide();
@ -193,7 +193,7 @@
form.on('radio(key-anti-enabled)', function (data) { form.on('radio(key-anti-enabled)', function (data) {
var block = $('#key-anti-block'); var block = $('#key-anti-block');
if (data.value == 1) { if (data.value === 1) {
block.show(); block.show();
} else { } else {
block.hide(); block.hide();

View File

@ -1,4 +1,4 @@
<table class="kg-table layui-table layui-form"> <table class="kg-table layui-table">
<colgroup> <colgroup>
<col> <col>
<col> <col>
@ -25,7 +25,7 @@
<p>类型:{{ item.client_type }}</p> <p>类型:{{ item.client_type }}</p>
<p>地址:<a href="javascript:" class="kg-ip2region" title="查看位置" data-ip="{{ item.client_ip }}">{{ item.client_ip }}</a></p> <p>地址:<a href="javascript:" class="kg-ip2region" title="查看位置" data-ip="{{ item.client_ip }}">{{ item.client_ip }}</a></p>
</td> </td>
<td>{{ item.duration|play_duration }}</td> <td>{{ item.duration|total_duration }}</td>
<td>{{ date('Y-m-d H:i:s',item.active_time) }}</td> <td>{{ date('Y-m-d H:i:s',item.active_time) }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -3,6 +3,7 @@
namespace App\Http\Web\Controllers; namespace App\Http\Web\Controllers;
use App\Models\ContentImage as ContentImageModel; use App\Models\ContentImage as ContentImageModel;
use App\Services\Frontend\Chapter\Learning as LearningService;
use App\Services\Storage as StorageService; use App\Services\Storage as StorageService;
use App\Traits\Response as ResponseTrait; use App\Traits\Response as ResponseTrait;
use PHPQRCode\QRcode as PHPQRCode; use PHPQRCode\QRcode as PHPQRCode;
@ -51,4 +52,16 @@ class PublicController extends \Phalcon\Mvc\Controller
exit; exit;
} }
/**
* @Post("/learning", name="web.learning")
*/
public function learningAction()
{
$service = new LearningService();
$service->handle();
return $this->jsonSuccess();
}
} }

View File

@ -30,17 +30,40 @@
var interval = null; var interval = null;
var intervalTime = 5000; var intervalTime = 5000;
var requestId = getRequestId();
var chapterId = '{{ chapter.id }}';
var playUrl = 'https://1255691183.vod2.myqcloud.com/81258db0vodtransgzp1255691183/89b3d8955285890796532522693/v.f220.m3u8?t=5ee5c6ed&exper=0&us=697028&sign=2b7fd89eff92236184eadbaa14a895dd';
var position = 0; var position = 0;
var chapterId = '{{ chapter.id }}';
var courseId = '{{ chapter.course.id }}';
var planId = '{{ chapter.me.plan_id }}';
var userId = '{{ auth_user.id }}';
var requestId = getRequestId();
var playUrls = JSON.parse('{{ chapter.play_urls|json_encode }}');
var player = new TcPlayer('player', { var options = {
m3u8: playUrl, autoplay: false,
autoplay: true,
width: 760, width: 760,
height: 450, height: 450,
listener: function (msg) { clarity: 'hd',
clarityLabel: {
od: '高清',
hd: '标清',
sd: '流畅'
}
};
if ('hd' in playUrls) {
options.m3u8 = playUrls.hd.url;
}
if ('sd' in playUrls) {
options.m3u8_hd = playUrls.sd.url;
}
if ('fd' in playUrls) {
options.m3u8_sd = playUrls.fd.url;
}
if (userId !== '0') {
options.listener = function (msg) {
if (msg.type === 'play') { if (msg.type === 'play') {
start(); start();
} else if (msg.type === 'pause') { } else if (msg.type === 'pause') {
@ -49,7 +72,9 @@
stop(); stop();
} }
} }
}); }
var player = new TcPlayer('player', options);
if (position > 0) { if (position > 0) {
player.currentTime(position); player.currentTime(position);
@ -70,11 +95,14 @@
function learning() { function learning() {
$.ajax({ $.ajax({
type: 'GET', type: 'POST',
url: '/admin/vod/learning', url: '/learning',
data: { data: {
request_id: requestId, request_id: requestId,
chapter_id: chapterId, chapter_id: chapterId,
course_id: courseId,
user_id: userId,
plan_id: planId,
interval: intervalTime, interval: intervalTime,
position: player.currentTime(), position: player.currentTime(),
} }

View File

@ -49,4 +49,18 @@ class ChapterUser extends Repository
]); ]);
} }
/**
* @param int $chapterId
* @param int $userId
* @param int $planId
* @return ChapterUserModel|Model|bool
*/
public function findPlanChapterUser($chapterId, $userId, $planId)
{
return ChapterUserModel::findFirst([
'conditions' => 'chapter_id = ?1 AND user_id = ?2 AND plan_id = ?3 AND deleted = 0',
'bind' => [1 => $chapterId, 2 => $userId, 3 => $planId],
]);
}
} }

View File

@ -65,6 +65,20 @@ class CourseUser extends Repository
return CourseUserModel::findFirst($id); return CourseUserModel::findFirst($id);
} }
/**
* @param int $courseId
* @param int $userId
* @param int $planId
* @return CourseUserModel|Model|bool
*/
public function findPlanCourseUser($courseId, $userId, $planId)
{
return CourseUserModel::findFirst([
'conditions' => 'course_id = ?1 AND user_id = ?2 AND plan_id = ?3 AND deleted = 0',
'bind' => [1 => $courseId, 2 => $userId, 3 => $planId],
]);
}
/** /**
* @param int $courseId * @param int $courseId
* @param int $userId * @param int $userId

View File

@ -24,11 +24,45 @@ class ChapterVod extends Service
$vod = new Vod(); $vod = new Vod();
$result = [];
foreach ($transcode as $key => $file) { foreach ($transcode as $key => $file) {
$transcode[$key]['url'] = $vod->getPlayUrl($file['url']);
$file['url'] = $vod->getPlayUrl($file['url']);
$definition = $this->getDefinitionType($file['height'], $file['rate']);
$result[$definition] = $file;
} }
return $transcode; return $result;
}
protected function getDefinitionType($height, $rate)
{
$default = 'od';
$vodTemplates = $this->getVodTemplates();
foreach ($vodTemplates as $key => $template) {
if ($height >= $template['height'] || $rate >= $template['rate']) {
return $key;
}
}
return $default;
}
/**
* @return array
*/
protected function getVodTemplates()
{
return [
'hd' => ['height' => 720, 'rate' => 1800],
'sd' => ['height' => 540, 'rate' => 1000],
'fd' => ['height' => 360, 'rate' => 400],
];
} }
} }

View File

@ -45,9 +45,9 @@ class ChapterInfo extends FrontendService
$this->user = $user; $this->user = $user;
$this->setCourseUser($course, $user); $this->setCourseUser($course, $user);
$this->setChapterUser($chapter, $user);
$this->handleCourseUser($course, $user); $this->handleCourseUser($course, $user);
$this->setChapterUser($chapter, $user);
$this->handleChapterUser($chapter, $user); $this->handleChapterUser($chapter, $user);
return $this->handleChapter($chapter, $user); return $this->handleChapter($chapter, $user);
@ -60,11 +60,19 @@ class ChapterInfo extends FrontendService
$result['course'] = $this->handleCourse($this->course); $result['course'] = $this->handleCourse($this->course);
$me = [ $me = [
'plan_id' => 0,
'joined' => 0,
'owned' => 0,
'agreed' => 0, 'agreed' => 0,
'opposed' => 0, 'opposed' => 0,
]; ];
$me['owned'] = $this->ownedChapter; if ($this->courseUser) {
$me['plan_id'] = $this->courseUser->plan_id;
}
$me['joined'] = $this->joinedChapter ? 1 : 0;
$me['owned'] = $this->ownedChapter ? 1 : 0;
if ($user->id > 0) { if ($user->id > 0) {
@ -217,9 +225,12 @@ class ChapterInfo extends FrontendService
$courseUser->user_id = $user->id; $courseUser->user_id = $user->id;
$courseUser->source_type = CourseUserModel::SOURCE_FREE; $courseUser->source_type = CourseUserModel::SOURCE_FREE;
$courseUser->role_type = CourseUserModel::ROLE_STUDENT; $courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->expiry_time = strtotime('+3 years');
$courseUser->create(); $courseUser->create();
$this->courseUser = $courseUser;
$this->incrCourseUserCount($course); $this->incrCourseUserCount($course);
} }
@ -227,14 +238,7 @@ class ChapterInfo extends FrontendService
{ {
if ($user->id == 0) return; if ($user->id == 0) return;
/** if ($this->joinedChapter) return;
* 一个课程可能购买学习多次
*/
if ($this->chapterUser && $this->courseUser) {
if ($this->chapterUser->plan_id == $this->courseUser->plan_id) {
return;
}
}
if (!$this->ownedChapter) return; if (!$this->ownedChapter) return;
@ -247,6 +251,8 @@ class ChapterInfo extends FrontendService
$chapterUser->create(); $chapterUser->create();
$this->chapterUser = $chapterUser;
$this->incrChapterUserCount($chapter); $this->incrChapterUserCount($chapter);
} }

View File

@ -14,24 +14,24 @@ class Learning extends FrontendService
use ChapterTrait; use ChapterTrait;
public function handle($id) public function handle()
{ {
$chapter = $this->checkChapterCache($id);
$user = $this->getCurrentUser();
$post = $this->request->getPost(); $post = $this->request->getPost();
if ($user->id == 0) return; $chapter = $this->checkChapterCache($post['chapter_id']);
$user = $this->getLoginUser();
$validator = new LearningValidator(); $validator = new LearningValidator();
$data = [ $data = [
'course_id' => $chapter->course_id,
'chapter_id' => $chapter->id, 'chapter_id' => $chapter->id,
'user_id' => $user->id, 'user_id' => $user->id,
]; ];
$data['request_id'] = $validator->checkRequestId($post['request_id']); $data['request_id'] = $validator->checkRequestId($post['request_id']);
$data['plan_id'] = $validator->checkPlanId($post['plan_id']);
/** /**
* @var array $attrs * @var array $attrs

View File

@ -22,7 +22,7 @@ trait ChapterTrait
protected $joinedChapter = false; protected $joinedChapter = false;
/** /**
* @var ChapterUserModel * @var ChapterUserModel|null
*/ */
protected $chapterUser; protected $chapterUser;
@ -42,12 +42,18 @@ trait ChapterTrait
public function setChapterUser(ChapterModel $chapter, UserModel $user) public function setChapterUser(ChapterModel $chapter, UserModel $user)
{ {
$chapterUserRepo = new ChapterUserRepo(); $chapterUser = null;
$chapterUser = $chapterUserRepo->findChapterUser($chapter->id, $user->id); $courseUser = $this->courseUser;
if ($chapterUser) { if ($user->id > 0 && $courseUser) {
$this->chapterUser = $chapterUser; $chapterUserRepo = new ChapterUserRepo();
$chapterUser = $chapterUserRepo->findChapterUser($chapter->id, $user->id);
}
$this->chapterUser = $chapterUser;
if ($chapterUser && $chapterUser->plan_id == $courseUser->plan_id) {
$this->joinedChapter = true; $this->joinedChapter = true;
} }

View File

@ -53,6 +53,7 @@ class CourseInfo extends FrontendService
]; ];
$me = [ $me = [
'plan_id' => 0,
'joined' => 0, 'joined' => 0,
'owned' => 0, 'owned' => 0,
'reviewed' => 0, 'reviewed' => 0,
@ -60,6 +61,9 @@ class CourseInfo extends FrontendService
'progress' => 0, 'progress' => 0,
]; ];
$me['joined'] = $this->joinedCourse ? 1 : 0;
$me['owned'] = $this->ownedCourse ? 1 : 0;
if ($user->id > 0) { if ($user->id > 0) {
$favoriteRepo = new CourseFavoriteRepo(); $favoriteRepo = new CourseFavoriteRepo();
@ -73,10 +77,8 @@ class CourseInfo extends FrontendService
if ($this->courseUser) { if ($this->courseUser) {
$me['reviewed'] = $this->courseUser->reviewed ? 1 : 0; $me['reviewed'] = $this->courseUser->reviewed ? 1 : 0;
$me['progress'] = $this->courseUser->progress ? 1 : 0; $me['progress'] = $this->courseUser->progress ? 1 : 0;
$me['plan_id'] = $this->courseUser->plan_id;
} }
$me['joined'] = $this->joinedCourse ? 1 : 0;
$me['owned'] = $this->ownedCourse ? 1 : 0;
} }
$result['me'] = $me; $result['me'] = $me;

View File

@ -4,7 +4,6 @@ namespace App\Services\Syncer;
use App\Library\Cache\Backend\Redis as RedisCache; use App\Library\Cache\Backend\Redis as RedisCache;
use App\Models\Learning as LearningModel; use App\Models\Learning as LearningModel;
use App\Repos\Chapter as ChapterRepo;
use App\Services\Service; use App\Services\Service;
use App\Traits\Client as ClientTrait; use App\Traits\Client as ClientTrait;
@ -50,11 +49,6 @@ class Learning extends Service
if (!$cacheLearning) { if (!$cacheLearning) {
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($learning->chapter_id);
$learning->course_id = $chapter->course_id;
$learning->client_type = $this->getClientType(); $learning->client_type = $this->getClientType();
$learning->client_ip = $this->getClientIp(); $learning->client_ip = $this->getClientIp();
$learning->duration = $interval; $learning->duration = $interval;

View File

@ -380,7 +380,7 @@ class Vod extends Service
foreach ($videoTransTemplates as $key => $template) { foreach ($videoTransTemplates as $key => $template) {
$caseA = $originVideoInfo['width'] >= $template['width']; $caseA = $originVideoInfo['height'] >= $template['height'];
$caseB = $originVideoInfo['bit_rate'] >= 1000 * $template['bit_rate']; $caseB = $originVideoInfo['bit_rate'] >= 1000 * $template['bit_rate'];
if ($caseA || $caseB) { if ($caseA || $caseB) {
@ -589,15 +589,15 @@ class Vod extends Service
public function getVideoTransTemplates() public function getVideoTransTemplates()
{ {
$hls = [ $hls = [
210 => ['width' => 480, 'bit_rate' => 256, 'frame_rate' => 24], 100210 => ['height' => 360, 'bit_rate' => 400, 'frame_rate' => 25],
220 => ['width' => 640, 'bit_rate' => 512, 'frame_rate' => 24], 100220 => ['height' => 540, 'bit_rate' => 1000, 'frame_rate' => 25],
230 => ['width' => 1280, 'bit_rate' => 1024, 'frame_rate' => 25], 100230 => ['height' => 720, 'bit_rate' => 1800, 'frame_rate' => 25],
]; ];
$mp4 = [ $mp4 = [
10 => ['width' => 480, 'bit_rate' => 256, 'frame_rate' => 24], 100010 => ['height' => 360, 'bit_rate' => 400, 'frame_rate' => 25],
20 => ['width' => 640, 'bit_rate' => 512, 'frame_rate' => 24], 100020 => ['height' => 540, 'bit_rate' => 1000, 'frame_rate' => 25],
30 => ['width' => 1280, 'bit_rate' => 1024, 'frame_rate' => 25], 100030 => ['height' => 720, 'bit_rate' => 1800, 'frame_rate' => 25],
]; ];
$format = $this->settings['video_format']; $format = $this->settings['video_format'];

View File

@ -3,22 +3,18 @@
namespace App\Validators; namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException; use App\Exceptions\BadRequest as BadRequestException;
use App\Repos\Chapter as ChapterRepo; use App\Library\Validators\Common as CommonValidator;
class Learning extends Validator class Learning extends Validator
{ {
public function checkChapterId($chapterId) public function checkPlanId($planId)
{ {
$chapterRepo = new ChapterRepo(); if (!CommonValidator::date($planId, 'Ymd')) {
throw new BadRequestException('learning.invalid_plan_id');
$chapter = $chapterRepo->findById($chapterId);
if (!$chapter) {
throw new BadRequestException('learning.invalid_chapter_id');
} }
return $chapterId; return $planId;
} }
public function checkRequestId($requestId) public function checkRequestId($requestId)
@ -50,7 +46,7 @@ class Learning extends Validator
public function checkPosition($position) public function checkPosition($position)
{ {
$value = $this->filter->sanitize($position, ['trim', 'int']); $value = $this->filter->sanitize($position, ['trim', 'float']);
if ($value < 0 || $value > 3 * 3600) { if ($value < 0 || $value > 3 * 3600) {
throw new BadRequestException('learning.invalid_position'); throw new BadRequestException('learning.invalid_position');

View File

@ -320,7 +320,7 @@ $error['course_query.invalid_sort'] = '无效的排序类别';
/** /**
* 课时学习 * 课时学习
*/ */
$error['learning.invalid_chapter_id'] = '无效的章节编号'; $error['learning.invalid_plan_id'] = '无效的计划编号';
$error['learning.invalid_request_id'] = '无效的请求编号'; $error['learning.invalid_request_id'] = '无效的请求编号';
$error['learning.invalid_position'] = '无效的播放位置'; $error['learning.invalid_position'] = '无效的播放位置';
$error['learning.invalid_interval'] = '无效的间隔时间'; $error['learning.invalid_interval'] = '无效的间隔时间';

View File

@ -363,7 +363,6 @@
position: relative; position: relative;
float: left; float: left;
width: 80%; width: 80%;
font-size: 12px;
} }
.course-meta .share { .course-meta .share {