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-08-15 20:41:36 +08:00
parent 3c2cc976b6
commit fddaa3573e
30 changed files with 1154 additions and 162 deletions

View File

@ -42,13 +42,13 @@ class HelpList extends Cache
'name' => $category->name,
];
$item['list'] = [];
$item['helps'] = [];
$helps = $this->findHelps($category->id);
if ($helps->count() > 0) {
foreach ($helps as $help) {
$item['list'][] = [
$item['helps'][] = [
'id' => $help->id,
'title' => $help->title,
];

View File

@ -28,8 +28,6 @@ class IndexFreeCourseList extends Cache
public function getContent($id = null)
{
$result = [];
$categoryLimit = 5;
$courseLimit = 10;
@ -40,13 +38,19 @@ class IndexFreeCourseList extends Cache
return [];
}
$result = [];
foreach ($categories as $category) {
$categoryItem = [
$item = [];
$item['category'] = [
'id' => $category->id,
'name' => $category->name,
];
$item['courses'] = [];
$courses = $this->findCategoryCourses($category->id, $courseLimit);
if ($courses->count() == 0) {
@ -69,9 +73,9 @@ class IndexFreeCourseList extends Cache
];
}
$categoryItem['courses'] = $categoryCourses;
$item['courses'] = $categoryCourses;
$result[] = $categoryItem;
$result[] = $item;
}
return $result;

View File

@ -28,8 +28,6 @@ class IndexNewCourseList extends Cache
public function getContent($id = null)
{
$result = [];
$categoryLimit = 5;
$courseLimit = 10;
@ -40,13 +38,19 @@ class IndexNewCourseList extends Cache
return [];
}
$result = [];
foreach ($categories as $category) {
$categoryItem = [
$item = [];
$item['category'] = [
'id' => $category->id,
'name' => $category->name,
];
$item['courses'] = [];
$courses = $this->findCategoryCourses($category->id, $courseLimit);
if ($courses->count() == 0) {
@ -69,9 +73,9 @@ class IndexNewCourseList extends Cache
];
}
$categoryItem['courses'] = $categoryCourses;
$item['courses'] = $categoryCourses;
$result[] = $categoryItem;
$result[] = $item;
}
return $result;

View File

@ -28,8 +28,6 @@ class IndexVipCourseList extends Cache
public function getContent($id = null)
{
$result = [];
$categoryLimit = 5;
$courseLimit = 10;
@ -40,13 +38,19 @@ class IndexVipCourseList extends Cache
return [];
}
$result = [];
foreach ($categories as $category) {
$categoryItem = [
$item = [];
$item['category'] = [
'id' => $category->id,
'name' => $category->name,
];
$item['courses'] = [];
$courses = $this->findCategoryCourses($category->id, $courseLimit);
if ($courses->count() == 0) {
@ -69,9 +73,9 @@ class IndexVipCourseList extends Cache
];
}
$categoryItem['courses'] = $categoryCourses;
$item['courses'] = $categoryCourses;
$result[] = $categoryItem;
$result[] = $item;
}
return $result;

View File

@ -0,0 +1,128 @@
<?php
namespace App\Console\Tasks;
use App\Models\ImGroup as GroupModel;
use App\Services\Search\GroupDocument;
use App\Services\Search\GroupSearcher;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class GroupIndexTask extends Task
{
/**
* 搜索测试
*
* @command: php console.php group_index search {query}
* @param array $params
* @throws \XSException
*/
public function searchAction($params)
{
$query = $params[0] ?? null;
if (!$query) {
exit("please special a query word" . PHP_EOL);
}
$result = $this->searchGroups($query);
var_export($result);
}
/**
* 清空索引
*
* @command: php console.php group_index clean
*/
public function cleanAction()
{
$this->cleanGroupIndex();
}
/**
* 重建索引
*
* @command: php console.php group_index rebuild
*/
public function rebuildAction()
{
$this->rebuildGroupIndex();
}
/**
* 清空索引
*/
protected function cleanGroupIndex()
{
$handler = new GroupSearcher();
$index = $handler->getXS()->getIndex();
echo "start clean index" . PHP_EOL;
$index->clean();
echo "end clean index" . PHP_EOL;
}
/**
* 重建索引
*/
protected function rebuildGroupIndex()
{
$groups = $this->findGroups();
if ($groups->count() == 0) {
return;
}
$handler = new GroupSearcher();
$documenter = new GroupDocument();
$index = $handler->getXS()->getIndex();
echo "start rebuild index" . PHP_EOL;
$index->beginRebuild();
foreach ($groups as $group) {
$document = $documenter->setDocument($group);
$index->add($document);
}
$index->endRebuild();
echo "end rebuild index" . PHP_EOL;
}
/**
* 搜索课程
*
* @param string $query
* @return array
* @throws \XSException
*/
protected function searchGroups($query)
{
$handler = new GroupSearcher();
return $handler->search($query);
}
/**
* 查找课程
*
* @return ResultsetInterface|Resultset|GroupModel[]
*/
protected function findGroups()
{
return GroupModel::query()
->where('published = 1')
->execute();
}
}

View File

@ -0,0 +1,128 @@
<?php
namespace App\Console\Tasks;
use App\Models\User as UserModel;
use App\Services\Search\UserDocument;
use App\Services\Search\UserSearcher;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class UserIndexTask extends Task
{
/**
* 搜索测试
*
* @command: php console.php user_index search {query}
* @param array $params
* @throws \XSException
*/
public function searchAction($params)
{
$query = $params[0] ?? null;
if (!$query) {
exit("please special a query word" . PHP_EOL);
}
$result = $this->searchUsers($query);
var_export($result);
}
/**
* 清空索引
*
* @command: php console.php user_index clean
*/
public function cleanAction()
{
$this->cleanUserIndex();
}
/**
* 重建索引
*
* @command: php console.php user_index rebuild
*/
public function rebuildAction()
{
$this->rebuildUserIndex();
}
/**
* 清空索引
*/
protected function cleanUserIndex()
{
$handler = new UserSearcher();
$index = $handler->getXS()->getIndex();
echo "start clean index" . PHP_EOL;
$index->clean();
echo "end clean index" . PHP_EOL;
}
/**
* 重建索引
*/
protected function rebuildUserIndex()
{
$users = $this->findUsers();
if ($users->count() == 0) {
return;
}
$handler = new UserSearcher();
$documenter = new UserDocument();
$index = $handler->getXS()->getIndex();
echo "start rebuild index" . PHP_EOL;
$index->beginRebuild();
foreach ($users as $user) {
$document = $documenter->setDocument($user);
$index->add($document);
}
$index->endRebuild();
echo "end rebuild index" . PHP_EOL;
}
/**
* 搜索课程
*
* @param string $query
* @return array
* @throws \XSException
*/
protected function searchUsers($query)
{
$handler = new UserSearcher();
return $handler->search($query);
}
/**
* 查找课程
*
* @return ResultsetInterface|Resultset|UserModel[]
*/
protected function findUsers()
{
return UserModel::query()
->where('deleted = 0')
->execute();
}
}

View File

@ -18,11 +18,11 @@ class HelpController extends Controller
{
$service = new HelpListService();
$helps = $service->handle();
$items = $service->handle();
$this->seo->prependTitle('帮助');
$this->view->setVar('helps', $helps);
$this->view->setVar('items', $items);
}
/**

View File

@ -2,9 +2,9 @@
namespace App\Http\Web\Controllers;
use App\Services\Frontend\Search\CourseHotQuery as CourseHotQueryService;
use App\Services\Frontend\Search\CourseList as CourseListService;
use App\Services\Frontend\Search\CourseRelatedQuery as CourseRelatedQueryService;
use App\Services\Frontend\Search\Course as CourseSearchService;
use App\Services\Frontend\Search\Group as GroupSearchService;
use App\Services\Frontend\Search\User as UserSearchService;
use App\Traits\Response as ResponseTrait;
/**
@ -20,7 +20,8 @@ class SearchController extends Controller
*/
public function indexAction()
{
$query = $this->request->get('query', ['trim']);
$query = $this->request->get('query', ['trim', 'string']);
$type = $this->request->get('type', ['trim'], 'course');
if (empty($query)) {
return $this->response->redirect(['for' => 'web.course.list']);
@ -28,21 +29,38 @@ class SearchController extends Controller
$this->seo->prependTitle(['搜索', $query]);
$service = new CourseHotQueryService();
$service = $this->getSearchService($type);
$hotQueries = $service->handle();
$hotQueries = $service->hotQuery();
$service = new CourseRelatedQueryService();
$relatedQueries = $service->relatedQuery($query);
$relatedQueries = $service->handle($query);
$service = new CourseListService();
$pager = $service->handle();
$pager = $service->search();
$this->view->setVar('hot_queries', $hotQueries);
$this->view->setVar('related_queries', $relatedQueries);
$this->view->setVar('pager', $pager);
}
/**
* @param string $type
* @return CourseSearchService|GroupSearchService|UserSearchService
*/
protected function getSearchService($type)
{
switch ($type) {
case 'group':
$service = new GroupSearchService;
break;
case 'user':
$service = new UserSearchService();
break;
default:
$service = new CourseSearchService();
break;
}
return $service;
}
}

View File

@ -13,12 +13,12 @@
<div class="layout-content">
<div class="wrap">
<div class="layui-collapse">
{% for item in helps %}
{% for item in items %}
<div class="layui-colla-item">
<h2 class="layui-colla-title">{{ item.category.name }}</h2>
<div class="layui-colla-content layui-show">
<ul class="help-list">
{% for help in item.list %}
{% for help in item.helps %}
<li><a href="{{ url({'for':'web.help.show','id':help.id}) }}"><i class="layui-icon layui-icon-right"></i>{{ help.title }}</a></li>
{% endfor %}
</ul>

View File

@ -4,21 +4,21 @@
{{ partial('macros/course') }}
{%- macro category_courses(courses) %}
{%- macro category_courses(items) %}
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title">
{% for category in courses %}
{% for item in items %}
{% set class = loop.first ? 'layui-this' : 'none' %}
<li class="{{ class }}">{{ category.name }}</li>
<li class="{{ class }}">{{ item.category.name }}</li>
{% endfor %}
</ul>
<div class="layui-tab-content">
{% for category in courses %}
{% for item in items %}
{% set class = loop.first ? 'layui-tab-item layui-show' : 'layui-tab-item' %}
<div class="{{ class }}">
<div class="index-course-list clearfix">
<div class="layui-row layui-col-space20">
{% for course in category.courses %}
{% for course in item.courses %}
<div class="layui-col-md3">
{{ course_card(course) }}
</div>

View File

@ -17,20 +17,18 @@
</ul>
</div>
{% set query = request.get('query','striptags','') %}
<div class="search">
<form class="layui-form" action="{{ url({'for':'web.search.index'}) }}">
<input class="layui-input" type="text" name="query" maxlength="30" autocomplete="off" placeholder="请输入课程关键字..." value="{{ query }}">
</form>
</div>
{% set query = request.get('query',['trim','striptags'],'') %}
{% set type = request.get('type',['trim','string'],'course') %}
<div class="user layui-layout-right">
{% if auth_user.id > 0 %}
<ul class="layui-nav">
<li class="layui-nav-item">
<a href="{{ url({'for':'web.im'}) }}" target="im">微聊<span class="layui-badge-dot"></span></a>
</li>
<ul class="layui-nav">
<li class="layui-nav-item">
<a href="javascript:" class="kg-search" data-type="{{ type }}" data-query="{{ query }}"><i class="layui-icon layui-icon-search"></i> 搜索</a>
</li>
<li class="layui-nav-item">
<a href="{{ url({'for':'web.im'}) }}" target="im">微聊<span class="layui-badge-dot"></span></a>
</li>
{% if auth_user.id > 0 %}
<li class="layui-nav-item">
<a href="javascript:">{{ auth_user.name }}</a>
<dl class="layui-nav-child">
@ -42,11 +40,9 @@
<dd><a href="{{ url({'for':'web.account.logout'}) }}">退出登录</a></dd>
</dl>
</li>
</ul>
{% else %}
<ul class="layui-nav">
{% else %}
<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>
{% endif %}
</ul>
</div>

View File

@ -0,0 +1,23 @@
<div class="search-group-list">
{% for item in pager.items %}
{% set group_url = url({'for':'web.im_group.show','id':item.id}) %}
{% set item.about = item.about ? item.about : '这个家伙真懒,什么也没有留下!' %}
<div class="search-group-card clearfix">
<div class="avatar">
<a href="{{ group_url }}">
<img src="{{ item.avatar }}" alt="{{ item.name }}">
</a>
</div>
<div class="info">
<div class="name layui-elip">
<a href="{{ group_url }}">{{ item.name }}</a>
</div>
<div class="about layui-elip">{{ item.about }}</div>
<div class="meta">
<span>组长:{{ item.owner.name }}</span>
<span>组员:{{ item.user_count }}</span>
</div>
</div>
</div>
{% endfor %}
</div>

View File

@ -2,11 +2,12 @@
{% block content %}
{{ partial('macros/course') }}
{% set types = {'course':'课程','group':'群组','user':'用户'} %}
{% set type = request.get('type','trim','course') %}
{% set query = request.get('query','striptags','') %}
{{ partial('macros/course') }}
<div class="layui-breadcrumb breadcrumb">
<a href="/">首页</a>
<a href="#">搜索</a>
@ -15,12 +16,21 @@
<div class="layout-main clearfix">
<div class="layout-content">
<div class="search-tab wrap">
{% for key,value in types %}
{% set class = (type == key) ? 'layui-btn layui-btn-xs' : 'none' %}
{% set url = url({'for':'web.search.index'},{'type':key,'query':query}) %}
<a class="{{ class }}" href="{{ url }}">{{ value }}</a>
{% endfor %}
</div>
{% if pager.total_pages > 0 %}
<div class="wrap">
{% if type == 'course' %}
{{ partial('search/course') }}
{% elseif type == 'other' %}
{{ partial('search/other') }}
{% elseif type == 'group' %}
{{ partial('search/group') }}
{% elseif type == 'user' %}
{{ partial('search/user') }}
{% endif %}
</div>
{{ partial('partials/pager') }}

View File

@ -0,0 +1,33 @@
{%- macro gender_info(gender) %}
{% if gender == 1 %}
{% elseif gender == 2 %}
{% else %}
保密
{% endif %}
{%- endmacro %}
<div class="search-user-list">
{% for item in pager.items %}
{% set user_url = url({'for':'web.user.show','id':item.id}) %}
{% set item.about = item.about ? item.about : '这个家伙真懒,什么也没有留下!' %}
<div class="search-group-card clearfix">
<div class="avatar">
<a href="{{ user_url }}">
<img src="{{ item.avatar }}" alt="{{ item.name }}">
</a>
</div>
<div class="info">
<div class="name layui-elip">
<a href="{{ user_url }}">{{ item.name }}</a>
</div>
<div class="about layui-elip">{{ item.about }}</div>
<div class="meta">
<span>性别:{{ gender_info(item.gender) }}</span>
<span>地区:{{ item.location }}</span>
</div>
</div>
</div>
{% endfor %}
</div>

View File

@ -0,0 +1,82 @@
<?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 Course extends FrontendService
{
public function search()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$searcher = new CourseSearcherService();
$paginator = new XunSearchPaginator([
'xs' => $searcher->getXS(),
'highlight' => $searcher->getHighlightFields(),
'query' => $params['query'],
'page' => $page,
'limit' => $limit,
]);
$pager = $paginator->getPaginate();
return $this->handleCourses($pager);
}
public function hotQuery($limit = 10, $type = 'total')
{
$searcher = new CourseSearcherService();
return $searcher->getHotQuery($limit, $type);
}
public function relatedQuery($query, $limit = 10)
{
$searcher = new CourseSearcherService();
return $searcher->getRelatedQuery($query, $limit);
}
protected function handleCourses($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$items = [];
foreach ($pager->items as $item) {
$items[] = [
'id' => (int)$item['id'],
'title' => $item['title'],
'cover' => $item['cover'],
'summary' => $item['summary'],
'model' => $item['model'],
'level' => $item['level'],
'market_price' => (float)$item['market_price'],
'vip_price' => (float)$item['vip_price'],
'user_count' => (int)$item['user_count'],
'lesson_count' => (int)$item['lesson_count'],
'review_count' => (int)$item['review_count'],
'favorite_count' => (int)$item['favorite_count'],
'teacher' => json_decode($item['teacher'], true),
'category' => json_decode($item['category'], true),
];
}
$pager->items = $items;
return $pager;
}
}

View File

@ -0,0 +1,75 @@
<?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\GroupSearcher as GroupSearcherService;
class Group extends FrontendService
{
public function search()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$searcher = new GroupSearcherService();
$paginator = new XunSearchPaginator([
'xs' => $searcher->getXS(),
'highlight' => $searcher->getHighlightFields(),
'query' => $params['query'],
'page' => $page,
'limit' => $limit,
]);
$pager = $paginator->getPaginate();
return $this->handleGroups($pager);
}
public function hotQuery($limit = 10, $type = 'total')
{
$searcher = new GroupSearcherService();
return $searcher->getHotQuery($limit, $type);
}
public function relatedQuery($query, $limit = 10)
{
$searcher = new GroupSearcherService();
return $searcher->getRelatedQuery($query, $limit);
}
protected function handleGroups($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$items = [];
foreach ($pager->items as $item) {
$items[] = [
'id' => (int)$item['id'],
'type' => $item['type'],
'name' => $item['name'],
'avatar' => $item['avatar'],
'about' => $item['about'],
'user_count' => (int)$item['user_count'],
'owner' => json_decode($item['owner'], true),
];
}
$pager->items = $items;
return $pager;
}
}

View File

@ -0,0 +1,76 @@
<?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\UserSearcher as UserSearcherService;
class User extends FrontendService
{
public function search()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$searcher = new UserSearcherService();
$paginator = new XunSearchPaginator([
'xs' => $searcher->getXS(),
'highlight' => $searcher->getHighlightFields(),
'query' => $params['query'],
'page' => $page,
'limit' => $limit,
]);
$pager = $paginator->getPaginate();
return $this->handleUsers($pager);
}
public function hotQuery($limit = 10, $type = 'total')
{
$searcher = new UserSearcherService();
return $searcher->getHotQuery($limit, $type);
}
public function relatedQuery($query, $limit = 10)
{
$searcher = new UserSearcherService();
return $searcher->getRelatedQuery($query, $limit);
}
protected function handleUsers($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$items = [];
foreach ($pager->items as $item) {
$items[] = [
'id' => (int)$item['id'],
'name' => $item['name'],
'avatar' => $item['avatar'],
'title' => $item['title'],
'about' => $item['about'],
'vip' => (int)$item['vip'],
'gender' => (int)$item['gender'],
'location' => $item['location'],
];
}
$pager->items = $items;
return $pager;
}
}

View File

@ -2,112 +2,24 @@
namespace App\Services\Search;
use Phalcon\Mvc\User\Component;
class CourseSearcher extends Component
class CourseSearcher extends Searcher
{
/**
* @var \XS
*/
protected $xs;
public function __construct()
{
$filename = config_path('xs.course.ini');
$this->xs = new \XS($filename);
$this->xs = $this->getXS();
}
/**
* 获取XS
*
* @return \XS
*/
public function getXS()
{
return $this->xs;
$filename = config_path('xs.course.ini');
return new \XS($filename);
}
/**
* 获取高亮字段
*
* @return array
*/
public function getHighlightFields()
{
return ['title', 'summary'];
}
/**
* 搜索课程
*
* @param string $query
* @param int $limit
* @param int $offset
* @return array
* @throws \XSException
*/
public function search($query, $limit = 15, $offset = 0)
{
$search = $this->xs->getSearch();
$docs = $search->setQuery($query)->setLimit($limit, $offset)->search();
$total = $search->getLastCount();
$fields = array_keys($this->xs->getAllFields());
$items = [];
foreach ($docs as $doc) {
$item = [];
foreach ($fields as $field) {
if (in_array($field, $this->getHighlightFields())) {
$item[$field] = $search->highlight($doc->{$field});
} else {
$item[$field] = $doc->{$field};
}
}
$items[] = $item;
}
return [
'total' => $total,
'items' => $items,
];
}
/**
* 获取相关搜索
*
* @param string $query
* @param int $limit
* @return array
* @throws \XSException
*/
public function getRelatedQuery($query, $limit = 10)
{
$search = $this->xs->getSearch();
$search->setQuery($query);
return $search->getRelatedQuery($query, $limit);
}
/**
* @param int $limit
* @param string $type [total => 总量, lastnum => 上周, currnum => 本周]
* @return array
* @throws \XSException
*/
public function getHotQuery($limit = 10, $type = 'total')
{
$search = $this->xs->getSearch();
$hotQuery = $search->getHotQuery($limit, $type);
return array_keys($hotQuery);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Services\Search;
use App\Models\ImGroup as GroupModel;
use App\Models\User as UserModel;
use Phalcon\Mvc\User\Component;
class GroupDocument extends Component
{
/**
* 设置文档
*
* @param GroupModel $group
* @return \XSDocument
*/
public function setDocument(GroupModel $group)
{
$doc = new \XSDocument();
$data = $this->formatDocument($group);
$doc->setFields($data);
return $doc;
}
/**
* 格式化文档
*
* @param GroupModel $group
* @return array
*/
public function formatDocument(GroupModel $group)
{
$owner = '';
if ($group->owner_id > 0) {
$record = UserModel::findFirst($group->owner_id);
$owner = kg_json_encode([
'id' => $record->id,
'name' => $record->name,
]);
}
return [
'id' => $group->id,
'type' => $group->type,
'name' => $group->name,
'avatar' => $group->avatar,
'about' => $group->about,
'user_count' => $group->user_count,
'owner' => $owner,
];
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Services\Search;
class GroupSearcher extends Searcher
{
public function __construct()
{
$this->xs = $this->getXS();
}
public function getXS()
{
$filename = config_path('xs.group.ini');
return new \XS($filename);
}
public function getHighlightFields()
{
return ['name', 'about'];
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Services\Search;
use Phalcon\Mvc\User\Component;
abstract class Searcher extends Component
{
/**
* @var \XS
*/
protected $xs;
/**
* 获取XS
*
* @return \XS
*/
abstract public function getXS();
/**
* 获取高亮字段
*
* @return array
*/
abstract public function getHighlightFields();
/**
* 搜索课程
*
* @param string $query
* @param int $limit
* @param int $offset
* @return array
* @throws \XSException
*/
public function search($query, $limit = 15, $offset = 0)
{
$search = $this->xs->getSearch();
$docs = $search->setQuery($query)->setLimit($limit, $offset)->search();
$total = $search->getLastCount();
$fields = array_keys($this->xs->getAllFields());
$items = [];
foreach ($docs as $doc) {
$item = [];
foreach ($fields as $field) {
if (in_array($field, $this->getHighlightFields())) {
$item[$field] = $search->highlight($doc->{$field});
} else {
$item[$field] = $doc->{$field};
}
}
$items[] = $item;
}
return [
'total' => $total,
'items' => $items,
];
}
/**
* 获取相关搜索
*
* @param string $query
* @param int $limit
* @return array
* @throws \XSException
*/
public function getRelatedQuery($query, $limit = 10)
{
$search = $this->xs->getSearch();
$search->setQuery($query);
return $search->getRelatedQuery($query, $limit);
}
/**
* @param int $limit
* @param string $type [total => 总量, lastnum => 上周, currnum => 本周]
* @return array
* @throws \XSException
*/
public function getHotQuery($limit = 10, $type = 'total')
{
$search = $this->xs->getSearch();
$hotQuery = $search->getHotQuery($limit, $type);
return array_keys($hotQuery);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Services\Search;
use App\Models\User as UserModel;
use Phalcon\Mvc\User\Component;
class UserDocument extends Component
{
/**
* 设置文档
*
* @param UserModel $user
* @return \XSDocument
*/
public function setDocument(UserModel $user)
{
$doc = new \XSDocument();
$data = $this->formatDocument($user);
$doc->setFields($data);
return $doc;
}
/**
* 格式化文档
*
* @param UserModel $user
* @return array
*/
public function formatDocument(UserModel $user)
{
return [
'id' => $user->id,
'name' => $user->name,
'title' => $user->title,
'avatar' => $user->avatar,
'about' => $user->about,
'gender' => $user->gender,
'location' => $user->location,
'vip' => $user->vip,
];
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Services\Search;
class UserSearcher extends Searcher
{
public function __construct()
{
$this->xs = $this->getXS();
}
public function getXS()
{
$filename = config_path('xs.user.ini');
return new \XS($filename);
}
public function getHighlightFields()
{
return ['name', 'about'];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Services\Syncer;
use App\Library\Cache\Backend\Redis as RedisCache;
use App\Services\Service;
class GroupIndex extends Service
{
/**
* @var RedisCache
*/
protected $cache;
/**
* @var \Redis
*/
protected $redis;
/**
* @var int
*/
protected $lifetime = 86400;
public function __construct()
{
$this->cache = $this->getDI()->get('cache');
$this->redis = $this->cache->getRedis();
}
public function addItem($groupId)
{
$key = $this->getSyncKey();
$this->redis->sAdd($key, $groupId);
$this->redis->expire($key, $this->lifetime);
}
public function getSyncKey()
{
return 'group_index_sync';
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Services\Syncer;
use App\Library\Cache\Backend\Redis as RedisCache;
use App\Services\Service;
class UserIndex extends Service
{
/**
* @var RedisCache
*/
protected $cache;
/**
* @var \Redis
*/
protected $redis;
/**
* @var int
*/
protected $lifetime = 86400;
public function __construct()
{
$this->cache = $this->getDI()->get('cache');
$this->redis = $this->cache->getRedis();
}
public function addItem($userId)
{
$key = $this->getSyncKey();
$this->redis->sAdd($key, $userId);
$this->redis->expire($key, $this->lifetime);
}
public function getSyncKey()
{
return 'user_index_sync';
}
}

View File

@ -0,0 +1,28 @@
project.name = group
project.default_charset = UTF-8
server.index = xunsearch:8383
server.search = xunsearch:8384
[id]
type = id
[type]
type = string
index = self
tokenizer = full
[name]
type = title
[avatar]
type = string
[about]
type = body
[owner]
type = string
[user_count]
type = string

28
config/xs.group.ini Normal file
View File

@ -0,0 +1,28 @@
project.name = group
project.default_charset = UTF-8
server.index = 8383
server.search = 8384
[id]
type = id
[type]
type = string
index = self
tokenizer = full
[name]
type = title
[avatar]
type = string
[about]
type = body
[owner]
type = string
[user_count]
type = string

View File

@ -0,0 +1,33 @@
project.name = user
project.default_charset = UTF-8
server.index = xunsearch:8383
server.search = xunsearch:8384
[id]
type = id
[name]
type = title
[avatar]
type = string
[title]
type = string
[about]
type = body
[location]
type = string
index = self
tokenizer = full
[gender]
type = string
index = self
tokenizer = full
[vip]
type = string

33
config/xs.user.ini Normal file
View File

@ -0,0 +1,33 @@
project.name = user
project.default_charset = UTF-8
server.index = 8383
server.search = 8384
[id]
type = id
[name]
type = title
[avatar]
type = string
[title]
type = string
[about]
type = body
[location]
type = string
index = self
tokenizer = full
[gender]
type = string
index = self
tokenizer = full
[vip]
type = string

View File

@ -245,6 +245,15 @@ body {
max-height: 270px;
}
.search-tab {
padding: 10px 20px;
}
.search-tab a {
min-width: 50px;
margin-right: 30px;
}
.search-empty {
padding: 50px 0;
text-align: center;
@ -269,13 +278,13 @@ body {
padding: 0 5px;
}
.search-course-card {
.search-course-card, .search-group-card {
padding-bottom: 15px;
margin-bottom: 15px;
border-bottom: 1px dashed #ccc;
}
.search-course-card:last-child {
.search-course-card:last-child, .search-group-card:last-child {
padding-bottom: 0;
margin-bottom: 0;
border: none;
@ -288,17 +297,17 @@ body {
margin-right: 15px;
}
.search-course-card .cover img {
.search-course-card .cover img, .search-group-card .avatar img {
width: 100%;
height: 100%;
}
.search-course-card .info {
.search-course-card .info, .search-group-card .info {
float: left;
width: 500px;
}
.search-course-card .title {
.search-course-card .title, .search-group-card .name {
margin-bottom: 10px;
white-space: nowrap;
}
@ -313,19 +322,36 @@ body {
overflow-wrap: break-word;
}
.search-course-card .meta {
.search-course-card .meta, .search-group-card .meta {
color: #666;
}
.search-course-card .meta span {
.search-course-card .meta span, .search-group-card .meta span {
margin-right: 10px;
}
.search-course-card em {
.search-course-card em, .search-group-card em {
color: red;
font-style: normal;
}
.search-group-card .avatar {
float: left;
width: 90px;
height: 90px;
margin-right: 15px;
}
.search-group-card .avatar img {
border-radius: 100%;
}
.search-group-card .about {
margin-bottom: 10px;
line-height: 1.5em;
color: #666;
}
.query-badge {
padding: 2px 5px;
margin-right: 5px;
@ -421,7 +447,6 @@ body {
.course-sort {
padding: 10px 20px;
margin-bottom: 20px;
}
.course-sort a {