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

增加 XunSearch Paginator

This commit is contained in:
xiaochong0302 2020-05-18 20:25:50 +08:00
parent 8853bffc67
commit d5dfd75c3d
19 changed files with 536 additions and 100 deletions

View File

@ -23,6 +23,8 @@ class NavTreeList extends Builder
$list[] = [
'id' => $nav->id,
'name' => $nav->name,
'target' => $nav->target,
'url' => $nav->url,
'children' => $this->handleChildren($nav),
];
}
@ -44,6 +46,8 @@ class NavTreeList extends Builder
$list[] = [
'id' => $nav->id,
'name' => $nav->name,
'target' => $nav->target,
'url' => $nav->url,
];
}
@ -70,7 +74,7 @@ class NavTreeList extends Builder
{
return NavModel::query()
->where('position = :position:', ['position' => $position])
->andWhere('deleted = 0')
->andWhere('level = 1 AND deleted = 0')
->execute();
}

View File

@ -4,7 +4,7 @@ namespace App\Console\Tasks;
use App\Models\Course as CourseModel;
use App\Services\Search\CourseDocument;
use App\Services\Search\CourseHandler;
use App\Services\Search\CourseSearcher;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
@ -57,7 +57,7 @@ class CourseIndexTask extends Task
*/
protected function cleanCourseIndex()
{
$handler = new CourseHandler();
$handler = new CourseSearcher();
$index = $handler->getXS()->getIndex();
@ -79,7 +79,7 @@ class CourseIndexTask extends Task
return;
}
$handler = new CourseHandler();
$handler = new CourseSearcher();
$documenter = new CourseDocument();
@ -108,7 +108,7 @@ class CourseIndexTask extends Task
*/
protected function searchCourses($query)
{
$handler = new CourseHandler();
$handler = new CourseSearcher();
return $handler->search($query);
}

View File

@ -5,7 +5,7 @@ namespace App\Console\Tasks;
use App\Library\Cache\Backend\Redis as RedisCache;
use App\Repos\Course as CourseRepo;
use App\Services\Search\CourseDocument;
use App\Services\Search\CourseHandler;
use App\Services\Search\CourseSearcher;
use App\Services\Syncer\CourseIndex as CourseIndexSyncer;
class SyncCourseIndexTask extends Task
@ -48,7 +48,7 @@ class SyncCourseIndexTask extends Task
$document = new CourseDocument();
$handler = new CourseHandler();
$handler = new CourseSearcher();
$index = $handler->getXS()->getIndex();

View File

@ -87,6 +87,10 @@ class Slide extends Service
$data['cover'] = $validator->checkCover($post['cover']);
}
if (isset($post['bg_color'])) {
$data['bg_color'] = $validator->checkBgColor($post['bg_color']);
}
if (isset($post['content'])) {
if ($slide->target == SlideModel::TARGET_COURSE) {
$course = $validator->checkCourse($post['content']);

View File

@ -29,6 +29,16 @@
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">背景色</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="bg_color" value="{{ slide.bg_color }}" lay-verify="required">
</div>
<div class="layui-inline">
<div id="bg-color"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
@ -75,4 +85,19 @@
</form>
{{ partial('partials/cover_uploader') }}
{{ partial('partials/cover_uploader') }}
<script>
layui.use(['jquery', 'colorpicker'], function () {
var $ = layui.jquery;
var colorPicker = layui.colorpicker;
colorPicker.render({
elem: '#bg-color',
color: '{{ slide.bg_color }}',
predefine: true,
change: function (color) {
$('input[name=bg_color]').val(color);
}
});
});
</script>

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Web\Controllers;
use App\Services\Frontend\Page\PageInfo as PageInfoService;
/**
* @RoutePrefix("/page")
*/
class PageController extends Controller
{
/**
* @Get("/{id:[0-9]+}", name="web.page.show")
*/
public function showAction($id)
{
$service = new PageInfoService();
$page = $service->handle($id);
$this->view->setVar('page', $page);
}
}

View File

@ -2,6 +2,8 @@
namespace App\Http\Web\Controllers;
use App\Services\Frontend\Search\CourseSearch;
/**
* @RoutePrefix("/search")
*/
@ -9,80 +11,12 @@ class SearchController extends Controller
{
/**
* @Get("/courses", name="web.search.courses")
* @Get("/", name="web.search.show")
*/
public function coursesAction()
public function showAction()
{
$query = $this->request->getQuery('q');
$indexer = new \App\Library\Indexer\Course();
$courses = $indexer->search($query);
echo "total: {$courses['total']}<br>";
echo "<hr>";
foreach ($courses['items'] as $course) {
echo "title:{$course->title}<br>";
echo "summary:{$course->summary}<br>";
echo "tags:{$course->tags}<br>";
echo "<hr>";
}
exit;
}
/**
* @Get("/course/update", name="web.search.update_course")
*/
public function updateCourseAction()
{
$indexer = new \App\Library\Indexer\Course();
$courseRepo = new \App\Repos\Course();
$course = $courseRepo->findById(1);
$indexer->updateIndex($course);
echo "update ok";
exit;
}
/**
* @Get("/course/create", name="web.search.create_course")
*/
public function createCourseAction()
{
$indexer = new \App\Library\Indexer\Course();
$courseRepo = new \App\Repos\Course();
$course = $courseRepo->findById(1);
$indexer->addIndex($course);
echo "create ok";
exit;
}
/**
* @Get("/course/delete", name="web.search.delete_course")
*/
public function deleteCourseAction()
{
$indexer = new \App\Library\Indexer\Course();
$courseRepo = new \App\Repos\Course();
$course = $courseRepo->findById(1);
$indexer->deleteIndex($course);
echo "delete ok";
$service = new CourseSearch();
dd($service->handle());
exit;
}

View File

@ -7,6 +7,7 @@ use App\Caches\IndexLiveList as IndexLiveListCache;
use App\Caches\IndexNewCourseList as IndexNewCourseListCache;
use App\Caches\IndexSlideList as IndexSlideListCache;
use App\Caches\IndexVipCourseList as IndexVipCourseListCache;
use App\Models\Slide as SlideModel;
class Index extends Service
{
@ -15,7 +16,36 @@ class Index extends Service
{
$cache = new IndexSlideListCache();
return $cache->get();
/**
* @var array $slides
*/
$slides = $cache->get();
if (!$slides) return [];
foreach ($slides as $key => $slide) {
switch ($slide['target']) {
case SlideModel::TARGET_COURSE:
$slides[$key]['url'] = $this->url->get([
'for' => 'web.course.show',
'id' => $slide['content'],
]);
break;
case SlideModel::TARGET_PAGE:
$slides[$key]['url'] = $this->url->get([
'for' => 'web.page.show',
'id' => $slide['content'],
]);
break;
case SlideModel::TARGET_LINK:
$slides[$key]['url'] = $slide['content'];
break;
default:
break;
}
}
return $slides;
}
public function getLives()

View File

@ -2,18 +2,77 @@
{% block content %}
{%- macro model_info(value) %}
{% if value == 'vod' %}
<span class="layui-badge layui-bg-green">点播{{ request.get('id') }}</span>
{% elseif value == 'live' %}
<span class="layui-badge layui-bg-blue">直播</span>
{% elseif value == 'read' %}
<span class="layui-badge layui-bg-black">图文</span>
{% endif %}
{%- macro category_courses(courses) %}
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title">
{% for category in courses %}
<li {% if loop.first %}class="layui-this"{% endif %}>{{ category.name }}</li>
{% endfor %}
</ul>
<div class="layui-tab-content">
{% for category in courses %}
<div class="layui-tab-item {% if loop.first %}layui-show{% endif %}">
{% for course in category.courses %}
<div class="course-card">
<div class="cover"></div>
<div class="title">{{ course.title }}</div>
<div class="info"></div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
{%- endmacro %}
<div class="model">{{ model_info('vod') }}</div>
<h1>I am body</h1>
<h2>ID:{{ request.get('id') }}</h2>
<div class="index-module">
<div class="layui-carousel" id="carousel">
<div class="carousel" carousel-item>
{% for slide in slides %}
<div class="item" style="background-color:{{ slide.bg_color }}">
<a href="{{ slide.url }}">
<img src="{{ slide.cover }}" alt="{{ slide.title }}">
</a>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="index-module">
<div class="header">新上课程</div>
<div class="content">
{{ category_courses(new_courses) }}
</div>
</div>
<div class="index-module">
<div class="header">免费课程</div>
<div class="content">
{{ category_courses(free_courses) }}
</div>
</div>
<div class="index-module">
<div class="header">会员课程</div>
<div class="content">
{{ category_courses(vip_courses) }}
</div>
</div>
{% endblock %}
{% block inline_js %}
<script>
layui.use(['carousel'], function () {
var carousel = layui.carousel;
carousel.render({
elem: '#carousel',
width: '600px',
height: '338px'
});
});
</script>
{% endblock %}

View File

@ -1 +1,14 @@
<h1>I am footer</h1>
<div class="bottom-nav">
{% for nav in site_navs.bottom %}
<a href="{{ nav.url }}" target="{{ nav.target }}">{{ nav.name }}</a>
{% endfor %}
</div>
<div class="copyright">
{{ site_settings.copyright }}
{% if site_settings.icp_sn %}
<a href="{{ site_settings.icp_link }}">{{ site_settings.icp_sn }}</a>
{% endif %}
{% if site_settings.police_sn %}
<a href="{{ site_settings.police_link }}">{{ site_settings.police_sn }}</a>
{% endif %}
</div>

View File

@ -1 +1,54 @@
<h1>I am header</h1>
<div class="logo"></div>
<div class="top-nav">
<ul class="layui-nav">
{% for nav in site_navs.top %}
{% if nav.children %}
<li class="layui-nav-item">
<a href="javascript:">{{ nav.name }}</a>
<dl class="layui-nav-child">
{% for child in nav.children %}
<dd><a href="{{ child.url }}" target="{{ child.target }}">{{ child.name }}</a></dd>
{% endfor %}
</dl>
</li>
{% else %}
<li class="layui-nav-item">
<a href="{{ nav.url }}">{{ nav.name }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="search">
<form class="layui-form" action="{{ url({'for':'web.search.show'}) }}">
<div class="layui-inline">
<input type="text" name="q" placeholder="请输入课程关键字...">
</div>
</form>
</div>
<div class="user layui-layout-right">
{% if auth_user %}
<ul class="layui-nav">
<li class="layui-nav-item"><a href="{{ url({'for':'web.my.courses'}) }}">消息</a></li>
<li class="layui-nav-item">
<a href="javascript:">{{ auth_user.name }}</a>
<dl class="layui-nav-child">
<dd><a href="{{ url({'for':'web.my.courses'}) }} }}">我的课程</a></dd>
<dd><a href="{{ url({'for':'web.my.courses'}) }} }}">我的收藏</a></dd>
<dd><a href="{{ url({'for':'web.my.courses'}) }} }}">我的咨询</a></dd>
<dd><a href="{{ url({'for':'web.my.courses'}) }} }}">我的订单</a></dd>
<dd><a href="{{ url({'for':'web.my.courses'}) }} }}">个人设置</a></dd>
<dd><a href="{{ url({'for':'web.my.courses'}) }} }}">退出登录</a></dd>
</dl>
</li>
</ul>
{% else %}
<ul class="layui-nav">
<li class="layui-nav-item"><a href="{{ url({'for':'web.account.login'}) }}">登录</a></li>
<li class="layui-nav-item"><a href="{{ url({'for':'web.account.register'}) }}">注册</a></li>
</ul>
{% endif %}
</div>

View File

@ -1,29 +1,42 @@
<!DOCTYPE html>
<html>
<html lang="zh-CN-Hans">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="keywords" content="{{ site_seo.getKeywords() }}">
<meta name="description" content="{{ site_seo.getDescription() }}">
<title>{{ site_seo.getTitle() }}</title>
{{ icon_link("favicon.ico") }}
{{ css_link("lib/layui/css/layui.css") }}
{{ css_link("web/css/common.css") }}
{{ icon_link('favicon.ico') }}
{{ css_link('lib/layui/css/layui.css') }}
{{ css_link('web/css/common.css') }}
{% block link_css %}{% endblock %}
{% block inline_css %}{% endblock %}
</head>
<body>
<div id="header">
{{ partial('partials/header') }}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
<div id="footer">
{{ partial('partials/footer') }}
</div>
{{ js_include("lib/layui/layui.js") }}
{{ js_include('lib/layui/layui.js') }}
<script>
layui.use(['element'], function () {
var element = layui.element;
})
</script>
{% block include_js %}{% endblock %}
{% block inline_js %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,155 @@
<?php
namespace App\Library\Paginator\Adapter;
use Phalcon\Http\Request as HttpRequest;
use Phalcon\Paginator\Adapter as PaginatorAdapter;
use Phalcon\Paginator\Exception as PaginatorException;
/**
*
* Pagination using xunsearch as source of data
*
* <code>
* use App\Library\Paginator\Adapter\XunSearch;
*
* $paginator = new XunSearch(
* [
* "xs" => $xs,
* "query" => $query,
* "highlight" => $highlight,
* "page" => $page,
* "limit" => $limit,
* ]
* );
*</code>
*/
class XunSearch extends PaginatorAdapter
{
protected $config;
protected $url;
protected $params = [];
public function __construct(array $config)
{
if (!isset($config['xs']) || ($config['xs'] instanceof \XS) == false) {
throw new PaginatorException('Invalid xs parameter');
}
if (empty($config['query'])) {
throw new PaginatorException('Invalid query parameter');
}
if (empty($config['page']) || $config['page'] != intval($config['page'])) {
throw new PaginatorException('Invalid page parameter');
}
if (empty($config['limit']) || $config['limit'] != intval($config['limit'])) {
throw new PaginatorException('Invalid limit parameter');
}
if (isset($config['highlight']) && !is_array($config['highlight'])) {
throw new PaginatorException('Invalid highlight parameter');
}
$this->config = $config;
$this->_page = $config['page'] ?? 1;
$this->_limitRows = $config['limit'] ?? 15;
}
public function paginate()
{
/**
* @var \XS $xs
*/
$xs = $this->config['xs'];
$page = $this->_page;
$limit = $this->_limitRows;
$offset = ($page - 1) * $limit;
$search = $xs->getSearch();
$docs = $search->setQuery($this->config['query'])
->setLimit($limit, $offset)
->search();
$totalCount = $search->getLastCount();
$fields = array_keys($xs->getAllFields());
$items = [];
foreach ($docs as $doc) {
$item = [];
foreach ($fields as $field) {
if (in_array($field, $this->config['highlight'])) {
$item[$field] = $search->highlight($doc->{$field});
} else {
$item[$field] = $doc->{$field};
}
}
$items[] = $item;
}
$totalPages = ceil($totalCount / $limit);
$pager = new \stdClass();
$pager->first = 1;
$pager->previous = $page > 1 ? $page - 1 : 1;
$pager->next = $page < $totalPages ? $page + 1 : $page;
$pager->last = $totalPages;
$pager->total_items = $totalCount;
$pager->items = $items;
$this->initParams();
$pager->first = $this->buildPageUrl($pager->first);
$pager->previous = $this->buildPageUrl($pager->previous);
$pager->next = $this->buildPageUrl($pager->next);
$pager->last = $this->buildPageUrl($pager->last);
return $pager;
}
public function getPaginate()
{
return $this->paginate();
}
protected function initParams()
{
$request = new HttpRequest();
$params = $request->get();
if ($params) {
foreach ($params as $key => $value) {
if (strlen($value) == 0) {
unset($params[$key]);
}
}
}
$this->params = $params;
if (!empty($this->params['_url'])) {
$this->url = $this->params['_url'];
unset($this->params['_url']);
} else {
$this->url = $request->get('_url');
}
}
protected function buildPageUrl($page)
{
$this->params['page'] = $page;
return $this->url . '?' . http_build_query($this->params);
}
}

View File

@ -36,6 +36,13 @@ class Slide extends Model
*/
public $cover;
/**
* 背景色
*
* @var string
*/
public $bg_color;
/**
* 摘要
*

View File

@ -0,0 +1,32 @@
<?php
namespace App\Services\Frontend\Page;
use App\Models\Page as PageModel;
use App\Services\Frontend\PageTrait;
use App\Services\Frontend\Service as FrontendService;
class PageInfo extends FrontendService
{
use PageTrait;
public function handle($id)
{
$page = $this->checkPageCache($id);
return $this->handlePage($page);
}
protected function handlePage(PageModel $page)
{
return [
'id' => $page->id,
'title' => $page->title,
'content' => $page->content,
'create_time' => $page->create_time,
'update_time' => $page->update_time,
];
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Services\Frontend;
use App\Validators\Page as PageValidator;
trait PageTrait
{
public function checkPage($id)
{
$validator = new PageValidator();
return $validator->checkPage($id);
}
public function checkPageCache($id)
{
$validator = new PageValidator();
return $validator->checkPageCache($id);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Services\Frontend\Search;
use App\Library\Paginator\Adapter\XunSearch as XunSearchPaginator;
use App\Library\Paginator\Query as PagerQuery;
use App\Services\Frontend\Service as FrontendService;
use App\Services\Search\CourseSearcher as CourseSearcherService;
class CourseSearch extends FrontendService
{
public function handle()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseSearcher = new CourseSearcherService();
$paginator = new XunSearchPaginator([
'xs' => $courseSearcher->getXS(),
'highlight' => $courseSearcher->getHighlightFields(),
'query' => $params['query'],
'page' => $page,
'limit' => $limit,
]);
$pager = $paginator->getPaginate();
dd($pager);
}
}

View File

@ -4,7 +4,7 @@ namespace App\Services\Search;
use Phalcon\Mvc\User\Component;
class CourseHandler extends Component
class CourseSearcher extends Component
{
/**
@ -29,6 +29,16 @@ class CourseHandler extends Component
return $this->xs;
}
/**
* 获取高亮字段
*
* @return array
*/
public function getHighlightFields()
{
return ['title', 'summary'];
}
/**
* 搜索课程
*

View File

@ -66,6 +66,17 @@ class Slide extends Validator
return $value;
}
public function checkBgColor($bgColor)
{
$value = $this->filter->sanitize($bgColor, ['trim', 'string']);
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $bgColor)) {
throw new BadRequestException('slide.invalid_bg_color');
}
return $value;
}
public function checkTarget($target)
{
$list = SlideModel::targetTypes();