1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-10 10:40:03 +08:00

整理功能

This commit is contained in:
xiaochong0302 2020-07-19 20:18:49 +08:00
parent 23e19cd279
commit ab27040f2f
72 changed files with 402 additions and 1804 deletions

View File

@ -1,99 +0,0 @@
<?php
namespace App\Builders;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class CommentList extends Builder
{
public function handleCourses(array $comments)
{
$courses = $this->getCourses($comments);
foreach ($comments as $key => $comment) {
$comments[$key]['course'] = $courses[$comment['course_id']] ?? new \stdClass();
}
return $comments;
}
public function handleChapters(array $comments)
{
$chapters = $this->getChapters($comments);
foreach ($comments as $key => $comment) {
$comments[$key]['chapter'] = $chapters[$comment['chapter_id']] ?? new \stdClass();
}
return $comments;
}
public function handleUsers(array $comments)
{
$users = $this->getUsers($comments);
foreach ($comments as $key => $comment) {
$comments[$key]['user'] = $users[$comment['user_id']] ?? new \stdClass();
}
return $comments;
}
public function getCourses(array $comments)
{
$ids = kg_array_column($comments, 'course_id');
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($courses->toArray() as $course) {
$result[$course['id']] = $course;
}
return $result;
}
public function getChapters(array $comments)
{
$ids = kg_array_column($comments, 'chapter_id');
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($chapters->toArray() as $chapter) {
$result[$chapter['id']] = $chapter;
}
return $result;
}
public function getUsers(array $comments)
{
$ids = kg_array_column($comments, 'user_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_ci_base_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -29,8 +29,7 @@ class ChapterCounter extends Counter
return [
'user_count' => $chapter->user_count,
'lesson_count' => $chapter->lesson_count,
'comment_count' => $chapter->comment_count,
'consult_count' => $chapter->consult_count,
'like_count' => $chapter->like_count,
];
}

View File

@ -1,36 +0,0 @@
<?php
namespace App\Caches;
use App\Repos\Comment as CommentRepo;
class CommentCounter extends Counter
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "comment_counter:{$id}";
}
public function getContent($id = null)
{
$commentRepo = new CommentRepo();
$comment = $commentRepo->findById($id);
if (!$comment) return null;
return [
'reply_count' => $comment->reply_count,
'like_count' => $comment->like_count,
];
}
}

View File

@ -65,7 +65,7 @@ class CourseRecommendedList extends Cache
public function findCourses($limit = 5)
{
return CourseModel::query()
->where('published = 1 AND deleted = 0 AND market_price > 0')
->where('published = 1 AND market_price > 0')
->orderBy('RAND()')
->limit($limit)
->execute();

View File

@ -58,7 +58,7 @@ class CourseTopicList extends Cache
public function findTopics($limit = 5)
{
return TopicModel::query()
->where('published = 1 AND deleted = 0')
->where('published = 1')
->orderBy('RAND()')
->limit($limit)
->execute();

View File

@ -63,7 +63,7 @@ class IndexSlideList extends Cache
public function findSlides($limit = 5)
{
return SlideModel::query()
->where('published = 1 AND deleted = 0')
->where('published = 1')
->orderBy('priority ASC')
->limit($limit)
->execute();

View File

@ -16,7 +16,7 @@ class MaxImGroupId extends Cache
public function getKey($id = null)
{
return 'max_im_chat_group_id';
return 'max_im_group_id';
}
public function getContent($id = null)

View File

@ -0,0 +1,55 @@
<?php
namespace App\Console\Tasks;
use App\Library\Cache\Backend\Redis as RedisCache;
use Phalcon\Cli\Task;
class CleanSessionTask extends Task
{
/**
* @var RedisCache
*/
protected $cache;
/**
* @var \Redis
*/
protected $redis;
public function mainAction()
{
$this->cache = $this->getDI()->get('cache');
$this->redis = $this->cache->getRedis();
$keys = $this->querySessionKeys(10000);
if (count($keys) == 0) return;
$config = $this->getDI()->get('config');
$lifetime = $config->session->lifetime;
foreach ($keys as $key) {
$ttl = $this->redis->ttl($key);
$content = $this->redis->get($key);
if (empty($content) && $ttl < $lifetime * 0.5) {
$this->redis->del($key);
}
}
}
/**
* 查找待清理会话
*
* @param int $limit
* @return array
*/
protected function querySessionKeys($limit)
{
return $this->cache->queryKeys('_PHCR', $limit);
}
}

View File

@ -1,89 +0,0 @@
<?php
namespace App\Console\Tasks;
use App\Caches\CommentCounter as CommentCounterCache;
use App\Library\Cache\Backend\Redis as RedisCache;
use App\Repos\Comment as CommentRepo;
use App\Services\Syncer\CommentCounter as CommentCounterSyncer;
class SyncCommentCounterTask extends Task
{
/**
* @var RedisCache
*/
protected $cache;
/**
* @var \Redis
*/
protected $redis;
public function mainAction()
{
$this->cache = $this->getDI()->get('cache');
$this->redis = $this->cache->getRedis();
$this->rebuild();
}
protected function rebuild()
{
$key = $this->getCacheKey();
$commentIds = $this->redis->sRandMember($key, 500);
if (!$commentIds) return;
$commentRepo = new CommentRepo();
$comments = $commentRepo->findByIds($commentIds);
if ($comments->count() == 0) {
return;
}
$counterCache = new CommentCounterCache();
$allowRecount = $this->allowRecount();
foreach ($comments as $comment) {
if ($allowRecount) {
$comment->reply_count = $commentRepo->countReplies($comment->id);
$comment->like_count = $commentRepo->countLikes($comment->id);
$comment->update();
$counterCache->rebuild($comment->id);
} else {
$counter = $counterCache->get($comment->id);
if ($counter) {
$comment->reply_count = $counter['reply_count'];
$comment->like_count = $counter['like_count'];
$comment->update();
}
}
}
$this->redis->sRem($key, ...$commentIds);
}
protected function getCacheKey()
{
$syncer = new CommentCounterSyncer();
return $syncer->getSyncKey();
}
protected function allowRecount()
{
return date('H') % 4 == 0;
}
}

View File

@ -1,121 +0,0 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Comment as CommentService;
/**
* @RoutePrefix("/admin/comment")
*/
class CommentController extends Controller
{
/**
* @Get("/search", name="admin.comment.search")
*/
public function searchAction()
{
}
/**
* @Get("/list", name="admin.comment.list")
*/
public function listAction()
{
$courseId = $this->request->getQuery('course_id', 'int', 0);
$chapterId = $this->request->getQuery('chapter_id', 'int', 0);
$commentService = new CommentService();
$pager = $commentService->getComments();
$chapter = null;
if ($chapterId > 0) {
$chapter = $commentService->getChapter($chapterId);
$courseId = $chapter->course_id;
}
$course = null;
if ($courseId > 0) {
$course = $commentService->getCourse($courseId);
}
$this->view->setVar('pager', $pager);
$this->view->setVar('course', $course);
$this->view->setVar('chapter', $chapter);
}
/**
* @Get("/{id:[0-9]+}/edit", name="admin.comment.edit")
*/
public function editAction($id)
{
$commentService = new CommentService();
$comment = $commentService->getComment($id);
$this->view->setVar('comment', $comment);
}
/**
* @Post("/{id:[0-9]+}/update", name="admin.comment.update")
*/
public function updateAction($id)
{
$commentService = new CommentService();
$commentService->update($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '更新评论成功',
];
return $this->jsonSuccess($content);
}
/**
* @Post("/{id:[0-9]+}/delete", name="admin.comment.delete")
*/
public function deleteAction($id)
{
$commentService = new CommentService();
$commentService->deleteComment($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除评论成功',
];
return $this->jsonSuccess($content);
}
/**
* @Post("/{id:[0-9]+}/restore", name="admin.comment.restore")
*/
public function restoreAction($id)
{
$commentService = new CommentService();
$commentService->restoreComment($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原评论成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -325,37 +325,6 @@ class AuthNode extends Service
],
],
],
[
'id' => '2-4',
'title' => '评论管理',
'type' => 'menu',
'children' => [
[
'id' => '2-4-1',
'title' => '评论列表',
'type' => 'menu',
'route' => 'admin.comment.list',
],
[
'id' => '2-4-2',
'title' => '搜索评论',
'type' => 'menu',
'route' => 'admin.comment.search',
],
[
'id' => '2-4-3',
'title' => '编辑评论',
'type' => 'button',
'route' => 'admin.comment.edit',
],
[
'id' => '2-4-4',
'title' => '删除评论',
'type' => 'button',
'route' => 'admin.comment.delete',
],
],
],
[
'id' => '2-5',
'title' => '轮播管理',

View File

@ -36,7 +36,7 @@ class Category extends Service
return $categoryRepo->findAll([
'parent_id' => 0,
'deleted' => 0,
'published' => 1,
]);
}

View File

@ -1,151 +0,0 @@
<?php
namespace App\Http\Admin\Services;
use App\Builders\CommentList as CommentListBuilder;
use App\Library\Paginator\Query as PagerQuery;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Comment as CommentRepo;
use App\Repos\Course as CourseRepo;
use App\Validators\Comment as CommentValidator;
class Comment extends Service
{
public function getComments()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$commentRepo = new CommentRepo();
$pager = $commentRepo->paginate($params, $sort, $page, $limit);
return $this->handleComments($pager);
}
public function getCourse($courseId)
{
$courseRepo = new CourseRepo();
return $courseRepo->findById($courseId);
}
public function getChapter($chapterId)
{
$chapterRepo = new ChapterRepo();
return $chapterRepo->findById($chapterId);
}
public function getComment($id)
{
return $this->findOrFail($id);
}
public function updateComment($id)
{
$comment = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new CommentValidator();
$data = [];
if (isset($post['content'])) {
$data['content'] = $validator->checkContent($post['content']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
$comment->update($data);
return $comment;
}
public function deleteComment($id)
{
$comment = $this->findOrFail($id);
$comment->deleted = 1;
$comment->update();
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($comment->chapter_id);
$chapter->comment_count -= 1;
$chapter->update();
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($comment->course_id);
$course->comment_count -= 1;
$course->update();
}
public function restoreComment($id)
{
$comment = $this->findOrFail($id);
$comment->deleted = 0;
$comment->update();
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($comment->chapter_id);
$chapter->comment_count += 1;
$chapter->update();
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($comment->course_id);
$course->comment_count += 1;
$course->update();
}
private function findOrFail($id)
{
$validator = new CommentValidator();
return $validator->checkComment($id);
}
private function handleComments($pager)
{
if ($pager->total_items > 0) {
$builder = new CommentListBuilder();
$pipeA = $pager->items->toArray();
$pipeB = $builder->handleCourses($pipeA);
$pipeC = $builder->handleChapters($pipeB);
$pipeD = $builder->handleUsers($pipeC);
$pipeE = $builder->objects($pipeD);
$pager->items = $pipeE;
}
return $pager;
}
}

View File

@ -35,19 +35,17 @@ class Nav extends Service
return $navRepo->findAll([
'parent_id' => 0,
'position' => 'top',
'deleted' => 0,
'published' => 1,
]);
}
public function getChildNavs($parentId)
{
$deleted = $this->request->getQuery('deleted', 'int', 0);
$navRepo = new NavRepo();
return $navRepo->findAll([
'parent_id' => $parentId,
'deleted' => $deleted,
'published' => 1,
]);
}

View File

@ -1,30 +0,0 @@
<fieldset class="layui-elem-field layui-field-title">
<legend>编辑评论</legend>
</fieldset>
<form class="layui-form" method="POST" action="{{ url({'for':'admin.comment.update','id':comment.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">内容</label>
<div class="layui-input-block">
<textarea name="content" lay-verify="required" class="layui-textarea">{{ comment.content }}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开启</label>
<div class="layui-input-block">
<input type="radio" name="published" value="1" title="是" {% if comment.published == 1 %}checked="true"{% endif %}>
<input type="radio" name="published" value="0" title="否" {% if comment.published == 0 %}checked="true"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="go">提交</button>
<button type="reset" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>

View File

@ -1,73 +0,0 @@
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a class="kg-back"><i class="layui-icon layui-icon-return"></i> 返回</a>
{% if course %}
<a><cite>{{ course.title }}</cite></a>
{% endif %}
{% if chapter %}
<a><cite>{{ chapter.title }}</cite></a>
{% endif %}
<a><cite>评论管理</cite></a>
</span>
</div>
<div class="kg-nav-right">
<a class="layui-btn layui-btn-sm" href="{{ url({'for':'admin.comment.search'}) }}">
<i class="layui-icon layui-icon-search"></i>搜索评论
</a>
</div>
</div>
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col width="10%">
<col width="10%">
</colgroup>
<thead>
<tr>
<th>评论</th>
<th>用户</th>
<th>时间</th>
<th>发布</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
<tr>
<td>
<p>课程:<a href="{{ url({'for':'admin.comment.list'},{'course_id':item.course.id}) }}">{{ item.course.title }}</a></p>
{% if item.chapter %}
<p>章节:<a href="{{ url({'for':'admin.comment.list'},{'chapter_id':item.chapter.id}) }}">{{ item.chapter.title }}</a></p>
{% endif %}
<p>评论:<a href="javascript:" title="{{ item.content }}">{{ substr(item.content,0,30) }}</a></p>
</td>
<td>
<p>昵称:{{ item.user.name }}</p>
<p>编号:{{ item.user.id }}</p>
</td>
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="published" data-url="{{ url({'for':'admin.comment.update','id':item.id}) }}" {% if item.published == 1 %}checked{% endif %}></td>
<td align="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <span class="layui-icon layui-icon-triangle-d"></span>
</button>
<ul>
<li><a href="{{ url({'for':'admin.comment.edit','id':item.id}) }}">编辑</a></li>
{% if item.deleted == 0 %}
<li><a href="javascript:" class="kg-delete" data-url="{{ url({'for':'admin.comment.delete','id':item.id}) }}">删除</a></li>
{% else %}
<li><a href="javascript:" class="kg-restore" data-url="{{ url({'for':'admin.comment.restore','id':item.id}) }}">还原</a></li>
{% endif %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}

View File

@ -1,67 +0,0 @@
<fieldset class="layui-elem-field layui-field-title">
<legend>搜索评论</legend>
</fieldset>
<form class="layui-form" method="GET" action="{{ url({'for':'admin.comment.list'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">评论编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="id" placeholder="评论编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">课程编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="course_id" placeholder="课程编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">章节编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="chapter_id" placeholder="章节编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="author_id" placeholder="用户编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发布</label>
<div class="layui-input-block">
<input type="radio" name="published" value="1" title="是">
<input type="radio" name="published" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">删除</label>
<div class="layui-input-block">
<input type="radio" name="deleted" value="1" title="是">
<input type="radio" name="deleted" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit>提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
<script>
layui.use('form', function () {
var form = layui.form;
});
</script>

View File

@ -1,81 +0,0 @@
<?php
namespace App\Http\Web\Controllers;
use App\Services\Frontend\Comment\CommentCreate as CommentCreateService;
use App\Services\Frontend\Comment\CommentDelete as CommentDeleteService;
use App\Services\Frontend\Comment\CommentInfo as CommentInfoService;
use App\Services\Frontend\Comment\CommentLike as CommentLikeService;
use App\Services\Frontend\Comment\CommentUpdate as CommentUpdateService;
/**
* @RoutePrefix("/comment")
*/
class CommentController extends Controller
{
/**
* @Get("/{id:[0-9]+}/info", name="web.comment.info")
*/
public function infoAction($id)
{
$service = new CommentInfoService();
$comment = $service->handle($id);
return $this->jsonSuccess(['comment' => $comment]);
}
/**
* @Post("/create", name="web.comment.create")
*/
public function createAction()
{
$service = new CommentCreateService();
$comment = $service->handle();
$service = new CommentInfoService();
$comment = $service->handle($comment->id);
return $this->jsonSuccess(['comment' => $comment]);
}
/**
* @Post("/{id:[0-9]+}/update", name="web.comment.update")
*/
public function updateAction($id)
{
$service = new CommentUpdateService();
$comment = $service->handle($id);
return $this->jsonSuccess(['comment' => $comment]);
}
/**
* @Post("/{id:[0-9]+}/delete", name="web.comment.delete")
*/
public function deleteAction($id)
{
$service = new CommentDeleteService();
$service->handle($id);
return $this->jsonSuccess();
}
/**
* @Post("/{id:[0-9]+}/like", name="web.comment.like")
*/
public function likeAction($id)
{
$service = new CommentLikeService();
$service->handle($id);
return $this->jsonSuccess();
}
}

View File

@ -14,6 +14,16 @@ use App\Services\Frontend\Consult\ConsultUpdate as ConsultUpdateService;
class ConsultController extends Controller
{
/**
* @Get("/add", name="web.consult.add")
*/
public function addAction()
{
$chapterId = $this->request->getQuery('chapter_id');
$this->view->setVar('chapter_id', $chapterId);
}
/**
* @Get("/{id:[0-9]+}/info", name="web.consult.info")
*/
@ -39,7 +49,12 @@ class ConsultController extends Controller
$consult = $service->handle($consult->id);
return $this->jsonSuccess(['consult' => $consult]);
$content = [
'consult' => $consult,
'msg' => '提交课程咨询成功',
];
return $this->jsonSuccess($content);
}
/**
@ -51,7 +66,12 @@ class ConsultController extends Controller
$consult = $service->handle($id);
return $this->jsonSuccess(['consult' => $consult]);
$content = [
'consult' => $consult,
'msg' => '更新课程咨询成功',
];
return $this->jsonSuccess($content);
}
/**
@ -63,7 +83,9 @@ class ConsultController extends Controller
$service->handle($id);
return $this->jsonSuccess();
$content = ['msg' => '删除课程咨询成功'];
return $this->jsonSuccess($content);
}
/**

View File

@ -5,7 +5,6 @@ namespace App\Http\Web\Controllers;
use App\Http\Web\Services\CourseQuery as CourseQueryService;
use App\Services\Frontend\Course\ChapterList as CourseChapterListService;
use App\Services\Frontend\Course\ConsultList as CourseConsultListService;
use App\Services\Frontend\Course\CourseBasic as CourseBasicService;
use App\Services\Frontend\Course\CourseInfo as CourseInfoService;
use App\Services\Frontend\Course\CourseList as CourseListService;
use App\Services\Frontend\Course\Favorite as CourseFavoriteService;
@ -59,7 +58,7 @@ class CourseController extends Controller
$pager->target = 'course-list';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_pager');
$this->view->pick('course/pager');
$this->view->setVar('pager', $pager);
}
@ -98,7 +97,7 @@ class CourseController extends Controller
$teachers = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_teachers');
$this->view->pick('course/teachers');
$this->view->setVar('teachers', $teachers);
}
@ -112,7 +111,7 @@ class CourseController extends Controller
$chapters = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_chapters');
$this->view->pick('course/chapters');
$this->view->setVar('chapters', $chapters);
}
@ -126,7 +125,7 @@ class CourseController extends Controller
$packages = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_packages');
$this->view->pick('course/packages');
$this->view->setVar('packages', $packages);
}
@ -142,7 +141,7 @@ class CourseController extends Controller
$pager->target = 'tab-consults';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_consults');
$this->view->pick('course/consults');
$this->view->setVar('pager', $pager);
}
@ -158,7 +157,7 @@ class CourseController extends Controller
$pager->target = 'tab-reviews';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_reviews');
$this->view->pick('course/reviews');
$this->view->setVar('pager', $pager);
}
@ -172,7 +171,7 @@ class CourseController extends Controller
$courses = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_recommended');
$this->view->pick('course/recommended');
$this->view->setVar('courses', $courses);
}
@ -186,7 +185,7 @@ class CourseController extends Controller
$courses = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_related');
$this->view->pick('course/related');
$this->view->setVar('courses', $courses);
}
@ -200,23 +199,10 @@ class CourseController extends Controller
$topics = $service->handle($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('course/ajax_topics');
$this->view->pick('course/topics');
$this->view->setVar('topics', $topics);
}
/**
* @Get("/{id:[0-9]+}/rating", name="web.course.rating")
*/
public function ratingAction($id)
{
$service = new CourseBasicService();
$course = $service->handle($id);
$this->view->pick('course/rating');
$this->view->setVar('course', $course);
}
/**
* @Post("/{id:[0-9]+}/favorite", name="web.course.favorite")
*/

View File

@ -14,6 +14,16 @@ use App\Services\Frontend\Review\ReviewUpdate as ReviewUpdateService;
class ReviewController extends Controller
{
/**
* @Get("/add", name="web.review.add")
*/
public function addAction()
{
$courseId = $this->request->getQuery('course_id');
$this->view->setVar('course_id', $courseId);
}
/**
* @Get("/{id:[0-9]+}/info", name="web.review.info")
*/
@ -73,7 +83,9 @@ class ReviewController extends Controller
$service->handle($id);
return $this->jsonSuccess();
$content = ['msg' => '删除课程评价成功'];
return $this->jsonSuccess($content);
}
/**

View File

@ -31,7 +31,7 @@ class TeacherController extends Controller
$pager->target = 'teacher-list';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('teacher/ajax_pager');
$this->view->pick('teacher/pager');
$this->view->setVar('pager', $pager);
}

View File

@ -38,7 +38,7 @@ class UserController extends Controller
$pager->target = 'tab-courses';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('user/ajax_courses');
$this->view->pick('user/courses');
$this->view->setVar('pager', $pager);
}
@ -54,7 +54,7 @@ class UserController extends Controller
$pager->target = 'tab-favorites';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('user/ajax_favorites');
$this->view->pick('user/favorites');
$this->view->setVar('pager', $pager);
}
@ -70,7 +70,7 @@ class UserController extends Controller
$pager->target = 'tab-friends';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('user/ajax_friends');
$this->view->pick('user/friends');
$this->view->setVar('pager', $pager);
}

View File

@ -7,6 +7,7 @@
{% set danmu_url = url({'for':'web.chapter.danmu','id':chapter.id}) %}
{% set like_url = url({'for':'web.chapter.like','id':chapter.id}) %}
{% set qrcode_url = url({'for':'web.qrcode_img'},{'text':chapter_full_url}) %}
{% set consult_url = url({'for':'web.consult.add'},{'chapter_id':chapter.id}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">
@ -14,9 +15,10 @@
<a><cite>{{ chapter.title }}</cite></a>
</span>
<span class="share">
<a href="javascript:" title="点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="学习人次"><i class="layui-icon layui-icon-user"></i><em>{{ chapter.user_count }}</em></a>
<a href="javascript:" title="分享到微信" data-url=""><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="我要点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="我要提问" data-url="{{ consult_url }}"><i class="layui-icon layui-icon-help icon-help"></i></a>
<a href="javascript:" title="分享到微信"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>
</span>
@ -115,6 +117,7 @@
{{ js_include('lib/jquery.danmu.min.js') }}
{{ js_include('web/js/course.share.js') }}
{{ js_include('web/js/chapter.like.js') }}
{{ js_include('web/js/chapter.vod.js') }}
{{ js_include('web/js/chapter.vod.player.js') }}
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<form class="layui-form" method="post" action="{{ url({'for':'web.consult.create'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">咨询内容</label>
<div class="layui-input-block">
<textarea name="question" class="layui-textarea" placeholder="请详细描述问题,我们会尽快回复您"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">私密问题</label>
<div class="layui-input-block">
<input type="radio" name="private" value="1" title="是">
<input type="radio" name="private" value="0" title="否" checked="checked">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
<input type="hidden" name="chapter_id" value="{{ chapter_id }}">
</div>
</div>
</form>
{% endblock %}

View File

@ -1,7 +1,7 @@
{%- macro vod_lesson_info(lesson) %}
{% set url = lesson.me.owned ? url({'for':'web.chapter.show','id':lesson.id}) : 'javascript:' %}
{% set url = lesson.me.owned ? url({'for':'web.chapter.show','id':lesson.id}) : '' %}
{% set priv = lesson.me.owned ? 'allow' : 'deny' %}
<a class="{{ priv }}" href="{{ url }}">
<a class="{{ priv }} view-lesson" href="javascript:" data-url="{{ url }}">
<i class="layui-icon layui-icon-play"></i>
<span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
@ -15,10 +15,10 @@
{%- endmacro %}
{%- macro live_lesson_info(lesson) %}
{% set url = lesson.me.owned ? url({'for':'web.chapter.show','id':lesson.id}) : 'javascript:' %}
{% set url = lesson.me.owned ? url({'for':'web.chapter.show','id':lesson.id}) : '' %}
{% set priv = lesson.me.owned ? 'allow' : 'deny' %}
{% set over_flag = lesson.attrs.end_time < time() ? '已结束' : '' %}
<a class="{{ priv }}" href="{{ url }}">
<a class="{{ priv }} view-lesson" href="javascript:" data-url="{{ url }}">
<i class="layui-icon layui-icon-video"></i>
<span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
@ -32,9 +32,9 @@
{%- endmacro %}
{%- macro read_lesson_info(lesson) %}
{% set url = lesson.me.owned ? url({'for':'web.chapter.show','id':lesson.id}) : 'javascript:' %}
{% set url = lesson.me.owned ? url({'for':'web.chapter.show','id':lesson.id}) : '' %}
{% set priv = lesson.me.owned ? 'allow' : 'deny' %}
<a class="{{ priv }}" href="{{ url }}">
<a class="{{ priv }} view-lesson" href="javascript:" data-url="{{ url }}">
<i class="layui-icon layui-icon-read"></i>
<span class="title">{{ lesson.title|e }}</span>
{% if lesson.free == 1 %}

View File

@ -13,7 +13,7 @@
<div class="title">{{ item.question }}</div>
<div class="content">{{ item.answer }}</div>
<div class="footer">
<span class="time">{{ time_ago('Y-m-d',item.create_time) }}</span>
<span class="time">{{ item.create_time|time_ago }}</span>
<a href="javascript:" class="like" title="点赞" data-url="{{ like_url }}">
<i class="layui-icon layui-icon-praise icon-praise"></i>
<em class="like-count">{{ item.like_count }}</em>

View File

@ -15,7 +15,7 @@
<span>会员价 <i>{{ '¥%0.2f'|format(package.vip_price) }}</i></span>
</div>
<div class="order">
<a class="layui-btn layui-btn-sm layui-bg-red" href="{{ order_url }}">立即购买</a>
<a class="layui-btn layui-btn-sm layui-bg-red btn-buy" href="javascript:" data-url="{{ order_url }}">立即购买</a>
</div>
</div>
<div class="package-course-list">

View File

@ -4,6 +4,11 @@
{{ partial('partials/macro_course') }}
{% set favorite_star_class = course.me.favorited ? 'layui-icon-star-fill' : 'layui-icon-star' %}
{% set full_course_url = full_url({'for':'web.course.show','id':course.id}) %}
{% set favorite_url = url({'for':'web.course.favorite','id':course.id}) %}
{% set qrcode_url = url({'for':'web.qrcode_img'},{'text':full_course_url}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="{{ url({'for':'web.course.list'}) }}">全部课程</a>
@ -12,11 +17,15 @@
{% endfor %}
<a><cite>{{ course.title }}</cite></a>
</span>
<div class="share">
<a href="javascript:" title="收藏" data-url="{{ favorite_url }}"><i class="layui-icon {{ favorite_star_class }} icon-star"></i></a>
<a href="javascript:" title="分享到微信" data-url="{{ qrcode_url }}"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>
</div>
</div>
<div class="course-meta wrap clearfix">
{{ partial('course/show_meta') }}
</div>
{{ partial('course/show_meta') }}
<div class="layout-main clearfix">
@ -95,6 +104,13 @@
</div>
<div class="layui-hide">
<input type="hidden" name="share.title" value="{{ course.title }}">
<input type="hidden" name="share.pic" value="{{ course.cover }}">
<input type="hidden" name="share.url" value="{{ full_course_url }}">
<input type="hidden" name="share.qrcode" value="{{ qrcode_url }}">
</div>
{% endblock %}
{% block include_js %}

View File

@ -1,53 +1,52 @@
<div class="cover">
<img src="{{ course.cover }}" alt="{{ course.title|e }}">
</div>
<div class="info">
{% if course.model == 'vod' %}
<p>课程时长 <span>{{ course.attrs.duration|total_duration }}</span></p>
{% elseif course.model == 'live' %}
<p>直播时间 <span>{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span></p>
{% endif %}
{% if course.market_price > 0 %}
<p>
学习期限 <span class="expiry">{{ course.study_expiry }}个月</span>
退款期限 <span class="expiry">{{ course.refund_expiry }}天</span>
</p>
{% endif %}
<p>
{% if course.market_price > 0 %}
市场价格 <span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>
{% else %}
市场价格 <span class="free">免费</span>
{% endif %}
{% if course.vip_price > 0 %}
会员价格 <span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
{% else %}
会员价格 <span class="free">免费</span>
{% endif %}
</p>
<p>
难度 <span>{{ level_info(course.level) }}</span>
课时 <span>{{ course.lesson_count }}</span>
学员 <span>{{ course.user_count }}</span>
评分 <span>{{ course.rating }}</span>
</p>
{% set favorite_url = url({'for':'web.course.favorite','id':course.id}) %}
{% set full_course_url = full_url({'for':'web.course.show','id':course.id}) %}
{% set qrcode_url = url({'for':'web.qrcode_img'},{'text':full_course_url}) %}
<div class="layui-hide">
<input type="hidden" name="share.title" value="{{ course.title }}">
<input type="hidden" name="share.pic" value="{{ course.cover }}">
<input type="hidden" name="share.url" value="{{ full_course_url }}">
<input type="hidden" name="share.qrcode" value="{{ qrcode_url }}">
<div class="course-meta wrap clearfix">
<div class="cover">
<img src="{{ course.cover }}" alt="{{ course.title|e }}">
</div>
<div class="share">
<a href="javascript:" title="收藏" data-url="{{ favorite_url }}"><i class="layui-icon layui-icon-star icon-star"></i></a>
<a href="javascript:" title="分享到微信" data-url="{{ qrcode_url }}"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>
<div class="info">
{% if course.model == 'vod' %}
<p>课程时长 <span>{{ course.attrs.duration|total_duration }}</span></p>
{% elseif course.model == 'live' %}
<p>直播时间 <span>{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</span></p>
{% endif %}
{% if course.market_price > 0 %}
<p>
学习期限 <span class="expiry">{{ course.study_expiry }}个月</span>
退款期限 <span class="expiry">{{ course.refund_expiry }}天</span>
</p>
{% endif %}
<p>
{% if course.market_price > 0 %}
市场价格 <span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>
{% else %}
市场价格 <span class="free">免费</span>
{% endif %}
{% if course.vip_price > 0 %}
会员价格 <span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
{% else %}
会员价格 <span class="free">免费</span>
{% endif %}
</p>
<p>
难度级别 <span>{{ level_info(course.level) }}</span>
学习人次 <span>{{ course.user_count }}</span>
综合评分 <span>{{ course.ratings.rating }}</span>
</p>
</div>
<div class="rating">
<p class="item">
<span class="name">内容实用</span>
<span class="star">{{ star_info(course.ratings.rating1) }}</span>
<span class="score">{{ course.ratings.rating1 }}分</span>
</p>
<p class="item">
<span class="name">简洁易懂</span>
<span class="star">{{ star_info(course.ratings.rating2) }}</span>
<span class="score">{{ course.ratings.rating2 }}分</span>
</p>
<p class="item">
<span class="name">逻辑清晰</span>
<span class="star">{{ star_info(course.ratings.rating3) }}</span>
<span class="score">{{ course.ratings.rating3 }}分</span>
</p>
</div>
</div>

View File

@ -20,6 +20,13 @@
{% endif %}
{%- endmacro %}
{%- macro star_info(rating) %}
{% set stars = [1,2,3,4,5] %}
{% for val in stars if val <= rating %}
<i class="layui-icon layui-icon-star-fill"></i>
{% endfor %}
{%- endmacro %}
{%- macro course_card(course) %}
{% set course_url = url({'for':'web.course.show','id':course.id}) %}
<div class="course-card">

View File

@ -30,7 +30,7 @@
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="layui-btn layui-btn-primary cancel-rating">取消</button>
<button type="button" class="layui-btn layui-btn-primary btn-cancel">取消</button>
<input type="hidden" name="course_id" value="{{ course.id }}">
<input type="hidden" name="rating1" value="5">
<input type="hidden" name="rating2" value="5">

View File

@ -143,9 +143,10 @@ class Redis extends \Phalcon\Cache\Backend\Redis
* {@inheritdoc}
*
* @param string $prefix
* @param int $limit
* @return array
*/
public function queryKeys($prefix = null): array
public function queryKeys($prefix = null, $limit = 1000): array
{
$result = [];
@ -158,6 +159,7 @@ class Redis extends \Phalcon\Cache\Backend\Redis
$it = null;
while ($keys = $redis->scan($it, $pattern)) {
if (count($result) > $limit) break;
$result = array_merge($result, $keys);
}

View File

@ -31,16 +31,16 @@ class ChapterCounter extends Listener
$this->syncChapterCounter($chapter);
}
public function incrCommentCount(Event $event, $source, ChapterModel $chapter)
public function incrConsultCount(Event $event, $source, ChapterModel $chapter)
{
$this->counter->hIncrBy($chapter->id, 'comment_count');
$this->counter->hIncrBy($chapter->id, 'consult_count');
$this->syncChapterCounter($chapter);
}
public function decrCommentCount(Event $event, $source, ChapterModel $chapter)
public function decrConsultCount(Event $event, $source, ChapterModel $chapter)
{
$this->counter->hDecrBy($chapter->id, 'comment_count');
$this->counter->hDecrBy($chapter->id, 'consult_count');
$this->syncChapterCounter($chapter);
}

View File

@ -1,55 +0,0 @@
<?php
namespace App\Listeners;
use App\Caches\CommentCounter as CacheCommentCounter;
use App\Models\Comment as CommentModel;
use App\Services\Syncer\CommentCounter as CommentCounterSyncer;
use Phalcon\Events\Event;
class CommentCounter extends Listener
{
protected $counter;
public function __construct()
{
$this->counter = new CacheCommentCounter();
}
public function incrReplyCount(Event $event, $source, CommentModel $comment)
{
$this->counter->hIncrBy($comment->id, 'reply_count');
$this->syncCommentCounter($comment);
}
public function decrReplyCount(Event $event, $source, CommentModel $comment)
{
$this->counter->hDecrBy($comment->id, 'reply_count');
$this->syncCommentCounter($comment);
}
public function incrLikeCount(Event $event, $source, CommentModel $comment)
{
$this->counter->hIncrBy($comment->id, 'like_count');
$this->syncCommentCounter($comment);
}
public function decrLikeCount(Event $event, $source, CommentModel $comment)
{
$this->counter->hDecrBy($comment->id, 'like_count');
$this->syncCommentCounter($comment);
}
protected function syncCommentCounter(CommentModel $comment)
{
$syncer = new CommentCounterSyncer();
$syncer->addItem($comment->id);
}
}

View File

@ -68,20 +68,6 @@ class CourseCounter extends Listener
$this->syncCourseIndex($course);
}
public function incrCommentCount(Event $event, $source, CourseModel $course)
{
$this->counter->hIncrBy($course->id, 'comment_count');
$this->syncCourseCounter($course);
}
public function decrCommentCount(Event $event, $source, CourseModel $course)
{
$this->counter->hDecrBy($course->id, 'comment_count');
$this->syncCourseCounter($course);
}
public function incrFavoriteCount(Event $event, $source, CourseModel $course)
{
$this->counter->hIncrBy($course->id, 'favorite_count');

View File

@ -21,11 +21,6 @@ class UserDailyCounter extends Listener
$this->counter->hIncrBy($user->id, 'favorite_count');
}
public function incrCommentCount(Event $event, $source, UserModel $user)
{
$this->counter->hIncrBy($user->id, 'comment_count');
}
public function incrDanmuCount(Event $event, $source, UserModel $user)
{
$this->counter->hIncrBy($user->id, 'danmu_count');
@ -46,11 +41,6 @@ class UserDailyCounter extends Listener
$this->counter->hIncrBy($user->id, 'order_count');
}
public function incrCommentLikeCount(Event $event, $source, UserModel $user)
{
$this->counter->hIncrBy($user->id, 'comment_like_count');
}
public function incrConsultLikeCount(Event $event, $source, UserModel $user)
{
$this->counter->hIncrBy($user->id, 'consult_like_count');

View File

@ -1,132 +0,0 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class Comment extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 父级编号
*
* @var int
*/
public $parent_id;
/**
* 课程编号
*
* @var int
*/
public $course_id;
/**
* 章节编号
*
* @var int
*/
public $chapter_id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 回复用户
*
* @var int
*/
public $to_user_id;
/**
* 内容
*
* @var string
*/
public $content;
/**
* 回复数
*
* @var int
*/
public $reply_count;
/**
* 点赞数
*
* @var int
*/
public $like_count;
/**
* 发布标识
*
* @var int
*/
public $published;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_comment';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
if ($this->deleted == 1) {
$this->published = 0;
}
}
}

View File

@ -1,79 +0,0 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class CommentLike extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 评论编号
*
* @var int
*/
public $comment_id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_comment_like';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -7,6 +7,13 @@ use Phalcon\Mvc\Model\Behavior\SoftDelete;
class Consult extends Model
{
/**
* 优先级
*/
const PRIORITY_HIGH = 10; // 高
const PRIORITY_MIDDLE = 20; // 中
const PRIORITY_LOW = 30; // 低
/**
* 主键编号
*
@ -21,6 +28,13 @@ class Consult extends Model
*/
public $course_id;
/**
* 章节编号
*
* @var int
*/
public $chapter_id;
/**
* 用户编号
*

View File

@ -51,7 +51,14 @@ class ImGroup extends Model
public $about;
/**
* 状态
* 发布状态
*
* @var integer
*/
public $published;
/**
* 删除状态
*
* @var integer
*/
@ -109,6 +116,10 @@ class ImGroup extends Model
public function beforeUpdate()
{
$this->update_time = time();
if ($this->deleted == 1) {
$this->published = 0;
}
}
public function afterFetch()

View File

@ -1,111 +0,0 @@
<?php
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Models\Comment as CommentModel;
use App\Models\CommentLike as CommentLikeModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class Comment extends Repository
{
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder();
$builder->from(CommentModel::class);
$builder->where('1 = 1');
if (!empty($where['id'])) {
$builder->andWhere('id = :id:', ['id' => $where['id']]);
}
if (!empty($where['parent_id'])) {
$builder->andWhere('parent_id = :parent_id:', ['parent_id' => $where['parent_id']]);
}
if (!empty($where['course_id'])) {
$builder->andWhere('course_id = :course_id:', ['course_id' => $where['course_id']]);
}
if (!empty($where['chapter_id'])) {
$builder->andWhere('chapter_id = :chapter_id:', ['chapter_id' => $where['chapter_id']]);
}
if (!empty($where['user_id'])) {
$builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
}
if (isset($where['published'])) {
$builder->andWhere('published = :published:', ['published' => $where['published']]);
}
if (isset($where['deleted'])) {
$builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
}
switch ($sort) {
default:
$orderBy = 'id DESC';
break;
}
$builder->orderBy($orderBy);
$pager = new PagerQueryBuilder([
'builder' => $builder,
'page' => $page,
'limit' => $limit,
]);
return $pager->paginate();
}
/**
* @param int $id
* @return CommentModel|Model|bool
*/
public function findById($id)
{
return CommentModel::findFirst($id);
}
/**
* @param array $ids
* @param string|array $columns
* @return ResultsetInterface|Resultset|CommentModel[]
*/
public function findByIds($ids, $columns = '*')
{
return CommentModel::query()
->columns($columns)
->inWhere('id', $ids)
->execute();
}
public function countComments()
{
return (int)CommentModel::count(['conditions' => 'deleted = 0']);
}
public function countReplies($commentId)
{
return (int)CommentModel::count([
'conditions' => 'parent_id = :parent_id: AND deleted = 0',
'bind' => ['parent_id' => $commentId],
]);
}
public function countLikes($commentId)
{
return (int)CommentLikeModel::count([
'conditions' => 'comment_id = :comment_id: AND deleted = 0',
'bind' => ['comment_id' => $commentId],
]);
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Repos;
use App\Models\CommentLike as CommentLikeModel;
use Phalcon\Mvc\Model;
class CommentLike extends Repository
{
/**
* @param int $commentId
* @param int $userId
* @return CommentLikeModel|Model|bool
*/
public function findCommentLike($commentId, $userId)
{
return CommentLikeModel::findFirst([
'conditions' => 'comment_id = :comment_id: AND user_id = :user_id:',
'bind' => ['comment_id' => $commentId, 'user_id' => $userId],
]);
}
}

View File

@ -1,77 +0,0 @@
<?php
namespace App\Services\Frontend\Comment;
use App\Models\Chapter as ChapterModel;
use App\Models\Comment as CommentModel;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use App\Services\Frontend\ChapterTrait;
use App\Services\Frontend\CourseTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Validators\Comment as CommentValidator;
use App\Validators\UserDailyLimit as UserDailyLimitValidator;
class CommentCreate extends FrontendService
{
use ChapterTrait, CourseTrait;
public function handle()
{
$post = $this->request->getPost();
$user = $this->getLoginUser();
$chapter = $this->checkChapter($post['chapter_id']);
$course = $this->checkCourse($chapter->course_id);
$validator = new UserDailyLimitValidator();
$validator->checkCommentLimit($user);
$validator = new CommentValidator();
$data = [];
$data['content'] = $validator->checkContent($post['content']);
if (isset($post['parent_id'])) {
$parent = $validator->checkParent($post['parent_id']);
$data['parent_id'] = $parent->id;
}
$comment = new CommentModel();
$data['course_id'] = $course->id;
$data['chapter_id'] = $chapter->id;
$data['user_id'] = $user->id;
$comment->create($data);
$this->incrChapterCommentCount($chapter);
$this->incrCourseCommentCount($course);
$this->incrUserDailyCommentCount($user);
return $comment;
}
protected function incrChapterCommentCount(ChapterModel $chapter)
{
$this->eventsManager->fire('chapterCounter:incrCommentCount', $this, $chapter);
}
protected function incrCourseCommentCount(CourseModel $course)
{
$this->eventsManager->fire('courseCounter:incrCommentCount', $this, $course);
}
protected function incrUserDailyCommentCount(UserModel $user)
{
$this->eventsManager->fire('userDailyCounter:incrCommentCount', $this, $user);
}
}

View File

@ -1,49 +0,0 @@
<?php
namespace App\Services\Frontend\Comment;
use App\Models\Chapter as ChapterModel;
use App\Models\Course as CourseModel;
use App\Services\Frontend\ChapterTrait;
use App\Services\Frontend\CommentTrait;
use App\Services\Frontend\CourseTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Validators\Comment as CommentValidator;
class CommentDelete extends FrontendService
{
use CommentTrait, ChapterTrait, CourseTrait;
public function handle($id)
{
$comment = $this->checkComment($id);
$chapter = $this->checkChapter($comment->chapter_id);
$course = $this->checkCourse($comment->course_id);
$user = $this->getLoginUser();
$validator = new CommentValidator();
$validator->checkOwner($user->id, $comment->user_id);
$comment->delete();
$this->decrChapterCommentCount($chapter);
$this->decrCourseCommentCount($course);
}
protected function decrChapterCommentCount(ChapterModel $chapter)
{
$this->eventsManager->fire('chapterCounter:decrCommentCount', $this, $chapter);
}
protected function decrCourseCommentCount(CourseModel $course)
{
$this->eventsManager->fire('courseCounter:decrCommentCount', $this, $course);
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Services\Frontend\Comment;
use App\Models\Comment as CommentModel;
use App\Repos\User as UserRepo;
use App\Services\Frontend\CommentTrait;
use App\Services\Frontend\Service as FrontendService;
class CommentInfo extends FrontendService
{
use CommentTrait;
public function handle($id)
{
$comment = $this->checkComment($id);
return $this->handleComment($comment);
}
protected function handleComment(CommentModel $comment)
{
$result = [
'id' => $comment->id,
'content' => $comment->content,
'like_count' => $comment->like_count,
'create_time' => $comment->create_time,
'update_time' => $comment->update_time,
];
$userRepo = new UserRepo();
$owner = $userRepo->findById($comment->user_id);
$result['user'] = [
'id' => $owner->id,
'name' => $owner->name,
'avatar' => $owner->avatar,
];
return $result;
}
}

View File

@ -1,88 +0,0 @@
<?php
namespace App\Services\Frontend\Comment;
use App\Models\Comment as CommentModel;
use App\Models\CommentLike as CommentLikeModel;
use App\Models\User as UserModel;
use App\Services\Frontend\CommentTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Validators\Comment as CommentValidator;
use App\Validators\UserDailyLimit as UserDailyLimitValidator;
use Phalcon\Events\Manager as EventsManager;
class CommentLike extends FrontendService
{
use CommentTrait;
public function handle($id)
{
$comment = $this->checkComment($id);
$user = $this->getLoginUser();
$validator = new UserDailyLimitValidator();
$validator->checkCommentLikeLimit($user);
$validator = new CommentValidator();
$commentLike = $validator->checkIfLiked($comment->id, $user->id);
if (!$commentLike) {
$commentLike = new CommentLikeModel();
$commentLike->create([
'comment_id' => $comment->id,
'user_id' => $user->id,
]);
$this->incrLikeCount($comment);
} else {
if ($commentLike->deleted == 0) {
$commentLike->update(['deleted' => 1]);
$this->decrLikeCount($comment);
} else {
$commentLike->update(['deleted' => 0]);
$this->incrLikeCount($comment);
}
}
$this->incrUserDailyCommentLikeCount($user);
return $comment;
}
protected function incrLikeCount(CommentModel $comment)
{
$this->getPhEventsManager()->fire('commentCounter:incrLikeCount', $this, $comment);
}
protected function decrLikeCount(CommentModel $comment)
{
$this->getPhEventsManager()->fire('commentCounter:decrLikeCount', $this, $comment);
}
protected function incrUserDailyCommentLikeCount(UserModel $user)
{
$this->getPhEventsManager()->fire('userDailyCounter:incrCommentLikeCount', $this, $user);
}
/**
* @return EventsManager
*/
protected function getPhEventsManager()
{
return $this->getDI()->get('eventsManager');
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Services\Frontend\Comment;
use App\Services\Frontend\CommentTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Validators\Comment as CommentValidator;
class CommentUpdate extends FrontendService
{
use CommentTrait;
public function handle($id)
{
$post = $this->request->getPost();
$user = $this->getLoginUser();
$comment = $this->checkComment($id);
$validator = new CommentValidator();
$validator->checkOwner($user->id, $comment->user_id);
$data = [];
$data['content'] = $validator->checkContent($post['content']);
if (isset($post['mentions'])) {
$data['mentions'] = $validator->checkMentions($post['mentions']);
}
$comment->update($data);
}
protected function handleMentions($mentions)
{
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace App\Services\Frontend;
use App\Validators\Comment as CommentValidator;
trait CommentTrait
{
public function checkComment($id)
{
$validator = new CommentValidator();
return $validator->checkComment($id);
}
}

View File

@ -2,9 +2,11 @@
namespace App\Services\Frontend\Consult;
use App\Models\Chapter as ChapterModel;
use App\Models\Consult as ConsultModel;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use App\Services\Frontend\ChapterTrait;
use App\Services\Frontend\CourseTrait;
use App\Services\Frontend\Service as FrontendService;
use App\Validators\Consult as ConsultValidator;
@ -13,7 +15,7 @@ use App\Validators\UserDailyLimit as UserDailyLimitValidator;
class ConsultCreate extends FrontendService
{
use CourseTrait;
use CourseTrait, ChapterTrait;
public function handle()
{
@ -21,7 +23,9 @@ class ConsultCreate extends FrontendService
$user = $this->getLoginUser();
$course = $this->checkCourseCache($post['course_id']);
$chapter = $this->checkChapter($post['chapter_id']);
$course = $this->checkCourse($chapter->course_id);
$validator = new UserDailyLimitValidator();
@ -31,26 +35,51 @@ class ConsultCreate extends FrontendService
$question = $validator->checkQuestion($post['question']);
$priority = $this->getPriority($course, $user);
$consult = new ConsultModel();
$consult->course_id = $course->id;
$consult->user_id = $user->id;
$consult->question = $question;
$consult->priority = $priority;
$consult->course_id = $course->id;
$consult->chapter_id = $chapter->id;
$consult->user_id = $user->id;
$consult->create();
$this->incrCourseConsultCount($course);
$this->incrChapterConsultCount($chapter);
$this->incrUserDailyConsultCount($user);
return $consult;
}
protected function getPriority(CourseModel $course, UserModel $user)
{
$charge = $course->market_price > 0;
$vip = $user->vip == 1;
if ($vip && $charge) {
$priority = ConsultModel::PRIORITY_HIGH;
} elseif ($charge) {
$priority = ConsultModel::PRIORITY_MIDDLE;
} else {
$priority = ConsultModel::PRIORITY_LOW;
}
return $priority;
}
protected function incrCourseConsultCount(CourseModel $course)
{
$this->eventsManager->fire('courseCounter:incrConsultCount', $this, $course);
}
protected function incrChapterConsultCount(ChapterModel $chapter)
{
$this->eventsManager->fire('chapterCounter:incrConsultCount', $this, $chapter);
}
protected function incrUserDailyConsultCount(UserModel $user)
{
$this->eventsManager->fire('userDailyCounter:incrConsultCount', $this, $user);

View File

@ -27,7 +27,6 @@ class ConsultList extends FrontendService
'course_id' => $course->id,
'private' => 0,
'published' => 1,
'deleted' => 0,
];
$consultRepo = new ConsultRepo();

View File

@ -4,6 +4,7 @@ namespace App\Services\Frontend\Course;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseFavorite as CourseFavoriteRepo;
use App\Services\Frontend\CourseTrait;
use App\Services\Frontend\Service as FrontendService;
@ -26,6 +27,17 @@ class CourseInfo extends FrontendService
protected function handleCourse(CourseModel $course, UserModel $user)
{
$repo = new CourseRepo();
$rating = $repo->findCourseRating($course->id);
$ratings = [
'rating' => $rating->rating,
'rating1' => $rating->rating1,
'rating2' => $rating->rating2,
'rating3' => $rating->rating3,
];
$result = [
'id' => $course->id,
'title' => $course->title,
@ -39,7 +51,7 @@ class CourseInfo extends FrontendService
'vip_price' => $course->vip_price,
'study_expiry' => $course->study_expiry,
'refund_expiry' => $course->refund_expiry,
'rating' => $course->rating,
'ratings' => $ratings,
'model' => $course->model,
'level' => $course->level,
'attrs' => $course->attrs,
@ -47,7 +59,6 @@ class CourseInfo extends FrontendService
'lesson_count' => $course->lesson_count,
'package_count' => $course->package_count,
'review_count' => $course->review_count,
'comment_count' => $course->comment_count,
'consult_count' => $course->consult_count,
'favorite_count' => $course->favorite_count,
];

View File

@ -26,7 +26,6 @@ class ReviewList extends FrontendService
$params = [
'course_id' => $course->id,
'published' => 1,
'deleted' => 0,
];
$reviewRepo = new ReviewRepo();

View File

@ -1,94 +0,0 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Comment as CommentRepo;
use App\Repos\CommentLike as CommentLikeRepo;
use App\Repos\Course as CourseRepo;
class Comment extends Validator
{
public function checkComment($id)
{
$commentRepo = new CommentRepo();
$comment = $commentRepo->findById($id);
if (!$comment) {
throw new BadRequestException('comment.not_found');
}
return $comment;
}
public function checkChapter($chapterId)
{
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterId);
if (!$chapter) {
throw new BadRequestException('comment.invalid_chapter_id');
}
return $chapter;
}
public function checkParent($parentId)
{
$commentRepo = new CourseRepo();
$parent = $commentRepo->findById($parentId);
if (!$parent) {
throw new BadRequestException('comment.invalid_parent_id');
}
return $parent;
}
public function checkContent($content)
{
$value = $this->filter->sanitize($content, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 1) {
throw new BadRequestException('comment.content_too_short');
}
if ($length > 1000) {
throw new BadRequestException('comment.content_too_long');
}
return $value;
}
public function checkPublishStatus($status)
{
if (!in_array($status, [0, 1])) {
throw new BadRequestException('consult.invalid_publish_status');
}
return $status;
}
public function checkIfLiked($chapterId, $userId)
{
$repo = new CommentLikeRepo();
$like = $repo->findCommentLike($chapterId, $userId);
if ($like) {
if ($like->deleted == 0 && time() - $like->create_time > 5 * 60) {
throw new BadRequestException('comment.has_liked');
}
}
return $like;
}
}

View File

@ -29,6 +29,13 @@ class Consult extends Validator
return $validator->checkCourse($id);
}
public function checkChapter($id)
{
$validator = new Chapter();
return $validator->checkChapter($id);
}
public function checkQuestion($question)
{
$value = $this->filter->sanitize($question, ['trim', 'string']);

View File

@ -27,17 +27,6 @@ class UserDailyLimit extends Validator
}
}
public function checkCommentLimit(UserModel $user)
{
$count = $this->counter->hGet($user->id, 'comment_count');
$limit = $user->vip ? 100 : 50;
if ($count > $limit) {
throw new BadRequestException('user_daily_limit.reach_comment_limit');
}
}
public function checkDanmuLimit(UserModel $user)
{
$count = $this->counter->hGet($user->id, 'danmu_count');
@ -89,17 +78,6 @@ class UserDailyLimit extends Validator
}
}
public function checkCommentLikeLimit(UserModel $user)
{
$count = $this->counter->hGet($user->id, 'comment_like_count');
$limit = $user->vip ? 200 : 100;
if ($count > $limit) {
throw new BadRequestException('user_daily_limit.reach_like_limit');
}
}
public function checkConsultLikeLimit(UserModel $user)
{
$count = $this->counter->hGet($user->id, 'consult_like_count');

View File

@ -201,21 +201,12 @@ $error['review.has_liked'] = '你已经点过赞啦';
$error['consult.not_found'] = '咨询不存在';
$error['consult.invalid_private_status'] = '无效的私密状态';
$error['consult.invalid_publish_status'] = '无效的发布状态';
$error['consult.question_too_short'] = '问太短少于5个字符';
$error['consult.question_too_long'] = '问太长多于1000个字符';
$error['consult.answer_too_short'] = '回复太短少于5个字符';
$error['consult.answer_too_long'] = '回复太长多于1000个字符';
$error['consult.question_too_short'] = '题内容太短少于5个字符';
$error['consult.question_too_long'] = '题内容太长多于1000个字符';
$error['consult.answer_too_short'] = '回复内容太短少于5个字符';
$error['consult.answer_too_long'] = '回复内容太长多于1000个字符';
$error['consult.has_liked'] = '你已经点过赞啦';
/**
* 评论相关
*/
$error['comment.not_found'] = '评价不存在';
$error['comment.invalid_publish_status'] = '无效的发布状态';
$error['comment.content_too_short'] = '评价太短少于1个字符';
$error['comment.content_too_long'] = '评价太长多于1000个字符';
$error['comment.has_liked'] = '你已经点过赞啦';
/**
* 单页相关
*/

View File

@ -1,7 +1,6 @@
<?php
use App\Listeners\ChapterCounter;
use App\Listeners\CommentCounter;
use App\Listeners\ConsultCounter;
use App\Listeners\CourseCounter;
use App\Listeners\Pay;
@ -14,7 +13,6 @@ return [
'pay' => Pay::class,
'courseCounter' => CourseCounter::class,
'chapterCounter' => ChapterCounter::class,
'commentCounter' => CommentCounter::class,
'consultCounter' => ConsultCounter::class,
'reviewCounter' => ReviewCounter::class,
'userDailyCounter' => UserDailyCounter::class,

View File

@ -432,38 +432,17 @@ body {
}
.course-meta .info {
position: relative;
float: left;
width: 80%;
width: 400px;
}
.course-meta .share {
position: absolute;
top: 0;
right: 0;
.course-meta .rating {
float: right;
padding: 10px 50px 0 0;
}
.course-meta .share a {
margin-right: 5px;
color: #999;
}
.course-meta .share a:hover {
color: #000;
}
.course-meta .price {
color: red;
font-size: 14px;
}
.course-meta .free {
color: green;
}
.course-meta span {
color: #666;
margin: 0 5px;
.course-meta p {
line-height: 30px;
}
.course-meta .cover img {
@ -471,8 +450,30 @@ body {
height: 118px;
}
.course-meta p {
line-height: 30px;
.course-meta .info .price {
color: red;
font-size: 14px;
}
.course-meta .info .free {
color: green;
}
.course-meta .info span {
color: #666;
margin: 0 5px;
}
.course-meta .rating span {
margin: 0 3px;
}
.course-meta .rating .layui-icon {
color: orange;
}
.course-meta .rating .score {
color: #666;
}
.layui-tab-title li {

View File

@ -0,0 +1,21 @@
layui.use(['jquery', 'helper'], function () {
var $ = layui.jquery;
var helper = layui.helper;
/**
* 咨询
*/
$('.icon-help').on('click', function () {
var url = $(this).parent().data('url');
helper.checkLogin(function () {
layer.open({
type: 2,
title: '课程咨询',
content: [url, 'no'],
area: ['640px', '300px']
});
});
});
});

View File

@ -3,12 +3,13 @@ layui.use(['jquery', 'rate'], function () {
var $ = layui.jquery;
var rate = layui.rate;
$('.cancel-rating').on('click', function () {
$('.btn-cancel').on('click', function () {
parent.layer.closeAll();
});
rate.render({
elem: '#rating1',
value: 5,
choose: function (value) {
$('input[name=rating1]').val(value);
}
@ -16,6 +17,7 @@ layui.use(['jquery', 'rate'], function () {
rate.render({
elem: '#rating2',
value: 5,
choose: function (value) {
$('input[name=rating2]').val(value);
}
@ -23,6 +25,7 @@ layui.use(['jquery', 'rate'], function () {
rate.render({
elem: '#rating3',
value: 5,
choose: function (value) {
$('input[name=rating3]').val(value);
}

View File

@ -4,6 +4,9 @@ layui.use(['jquery', 'layer', 'helper'], function () {
var layer = layui.layer;
var helper = layui.helper;
/**
* 收藏
*/
$('.icon-star').on('click', function () {
var $this = $(this);
helper.checkLogin(function () {
@ -23,6 +26,9 @@ layui.use(['jquery', 'layer', 'helper'], function () {
});
});
/**
* 打赏
*/
$('.btn-reward').on('click', function () {
var url = $(this).data('url');
helper.checkLogin(function () {
@ -30,7 +36,10 @@ layui.use(['jquery', 'layer', 'helper'], function () {
});
});
$('.btn-buy').on('click', function () {
/**
* 购买课程|套餐)
*/
$('body').on('click', '.btn-buy', function () {
var url = $(this).data('url');
helper.checkLogin(function () {
window.location.href = url;
@ -47,6 +56,22 @@ layui.use(['jquery', 'layer', 'helper'], function () {
});
});
/**
* 浏览章节
*/
$('body').on('click', '.view-lesson', function () {
if ($(this).hasClass('deny')) {
return false;
}
var url = $(this).data('url');
helper.checkLogin(function () {
window.location.href = url;
});
});
/**
* 点赞咨询|评价
*/
$('body').on('click', '.icon-praise', function () {
var $this = $(this);
var $likeCount = $this.next();