1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-24 20:06:09 +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);
if (!$dbLearning) {
$cacheLearning->create();
$this->updateChapterUser($cacheLearning);
} else {
$dbLearning->duration = $cacheLearning->duration;
$dbLearning->duration += $cacheLearning->duration;
$dbLearning->position = $cacheLearning->position;
$dbLearning->active_time = $cacheLearning->active_time;
$dbLearning->update();
}
$this->updateChapterUser($dbLearning);
$dbLearning->update();
$this->updateChapterUser($dbLearning);
}
$this->cache->delete($itemKey);
}
@ -87,7 +93,7 @@ class SyncLearningTask extends Task
{
$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;
@ -130,44 +136,42 @@ class SyncLearningTask extends Task
$chapterUser->update();
if ($chapterUser->consumed == 1) {
$this->updateCourseUser($chapterUser->course_id, $chapterUser->user_id);
$this->updateCourseUser($learning);
}
}
/**
* @param int $courseId
* @param int $userId
* @param LearningModel $learning
*/
protected function updateCourseUser($courseId, $userId)
protected function updateCourseUser(LearningModel $learning)
{
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($courseId, $userId);
$courseUser = $courseUserRepo->findPlanCourseUser($learning->course_id, $learning->user_id, $learning->plan_id);
if (!$courseUser) return;
$courseRepo = new CourseRepo();
$courseLessons = $courseRepo->findLessons($courseId);
$courseLessons = $courseRepo->findLessons($learning->course_id);
if ($courseLessons->count() == 0) {
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) {
return;
}
/**
* @var array $consumedUserLearnings
*/
$consumedUserLearnings = $userLearnings->filter(function ($item) {
if ($item->consumed == 1) {
return $item;
$consumedUserLearnings = [];
foreach ($userLearnings->toArray() as $userLearning) {
if ($userLearning['consumed'] == 1) {
$consumedUserLearnings[] = $userLearning;
}
});
}
if (count($consumedUserLearnings) == 0) {
return;
@ -175,8 +179,8 @@ class SyncLearningTask extends Task
$duration = 0;
foreach ($consumedUserLearnings as $learning) {
$duration += $learning['duration'];
foreach ($consumedUserLearnings as $userLearning) {
$duration += $userLearning['duration'];
}
$courseLessonIds = kg_array_column($courseLessons->toArray(), 'id');

View File

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

View File

@ -1,4 +1,4 @@
<table class="kg-table layui-table layui-form">
<table class="kg-table layui-table">
<colgroup>
<col>
<col>
@ -25,7 +25,7 @@
<p>类型:{{ item.client_type }}</p>
<p>地址:<a href="javascript:" class="kg-ip2region" title="查看位置" data-ip="{{ item.client_ip }}">{{ item.client_ip }}</a></p>
</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>
</tr>
{% endfor %}

View File

@ -3,6 +3,7 @@
namespace App\Http\Web\Controllers;
use App\Models\ContentImage as ContentImageModel;
use App\Services\Frontend\Chapter\Learning as LearningService;
use App\Services\Storage as StorageService;
use App\Traits\Response as ResponseTrait;
use PHPQRCode\QRcode as PHPQRCode;
@ -51,4 +52,16 @@ class PublicController extends \Phalcon\Mvc\Controller
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 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 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', {
m3u8: playUrl,
autoplay: true,
var options = {
autoplay: false,
width: 760,
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') {
start();
} else if (msg.type === 'pause') {
@ -49,7 +72,9 @@
stop();
}
}
});
}
var player = new TcPlayer('player', options);
if (position > 0) {
player.currentTime(position);
@ -70,11 +95,14 @@
function learning() {
$.ajax({
type: 'GET',
url: '/admin/vod/learning',
type: 'POST',
url: '/learning',
data: {
request_id: requestId,
chapter_id: chapterId,
course_id: courseId,
user_id: userId,
plan_id: planId,
interval: intervalTime,
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);
}
/**
* @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 $userId

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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