1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-26 01:41:43 +08:00

更新索引操作改用计划任务批量完成

This commit is contained in:
xiaochong0302 2019-12-23 22:43:04 +08:00
parent 971cac05d7
commit 8395074dde
15 changed files with 475 additions and 72 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/vendor /vendor
/config/config.php /config/config.php
/config/xs.course.ini /config/xs.course.ini
*KgTest*

View File

@ -5,7 +5,7 @@ namespace App\Console\Tasks;
use App\Repos\Category as CategoryRepo; use App\Repos\Category as CategoryRepo;
use Phalcon\Cli\Task; use Phalcon\Cli\Task;
class CourseCountTask extends Task class CountCourseTask extends Task
{ {
public function mainAction() public function mainAction()

View File

@ -24,20 +24,15 @@ class LearningTask extends Task
$keys = $this->cache->queryKeys('learning:'); $keys = $this->cache->queryKeys('learning:');
if (empty($keys)) return; if (empty($keys)) {
return;
}
$keys = array_slice($keys, 0, 500); $keys = array_slice($keys, 0, 500);
$prefix = $this->cache->getPrefix();
foreach ($keys as $key) { foreach ($keys as $key) {
/** $lastKey = $this->cache->getRawKeyName($key);
* 去掉前缀,避免重复加前缀导致找不到缓存 $this->handleLearning($lastKey);
*/
if ($prefix) {
$key = str_replace($prefix, '', $key);
}
$this->handleLearning($key);
} }
} }
@ -46,7 +41,7 @@ class LearningTask extends Task
$content = $this->cache->get($key); $content = $this->cache->get($key);
if (empty($content->user_id)) { if (empty($content->user_id)) {
return false; return;
} }
if (!empty($content->client_ip)) { if (!empty($content->client_ip)) {
@ -81,13 +76,17 @@ class LearningTask extends Task
$chapterUser = $chapterUserRepo->findChapterUser($chapterId, $userId); $chapterUser = $chapterUserRepo->findChapterUser($chapterId, $userId);
if (!$chapterUser) return false; if (!$chapterUser) {
return;
}
$chapterRepo = new ChapterRepo(); $chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterId); $chapter = $chapterRepo->findById($chapterId);
if (!$chapter) return false; if (!$chapter) {
return;
}
$chapter->duration = $chapter->attrs['duration'] ?: 0; $chapter->duration = $chapter->attrs['duration'] ?: 0;

View File

@ -0,0 +1,166 @@
<?php
namespace App\Console\Tasks;
use App\Models\Course as CourseModel;
use App\Searchers\Course as CourseSearcher;
use Phalcon\Cli\Task;
class ManageIndexTask extends Task
{
/**
* 搜索测试
*
* @command: php console.php index search {type} {query}
* @param array $params
* @throws \XSException
*/
public function searchAction($params)
{
$type = $params[0] ?? null;
$query = $params[1] ?? null;
if (!in_array($type, $this->getItemTypes())) {
exit("Invalid item type" . PHP_EOL);
}
if (!$query) {
exit("Please special a query word" . PHP_EOL);
}
$result = [];
if ($type == 'course') {
$result = $this->searchCourses($query);
}
var_export($result);
}
/**
* 清空索引
*
* @command: php console.php index clean {type}
* @param array $params
*/
public function cleanAction($params)
{
$type = $params[0] ?? null;
if (in_array($type, $this->getItemTypes())) {
exit("Invalid item type" . PHP_EOL);
}
if ($type == 'course') {
$this->cleanCourseIndex();
}
}
/**
* 重建索引
*
* @command: php console.php index rebuild {type}
* @param array $params
*/
public function rebuildAction($params)
{
$type = $params[0] ?? null;
if (in_array($type, $this->getItemTypes())) {
exit("Invalid item type" . PHP_EOL);
}
if ($type == 'course') {
$this->rebuildCourseIndex();
}
}
/**
* 清空课程索引
*/
protected function cleanCourseIndex()
{
$searcher = new CourseSearcher();
$index = $searcher->getXS()->getIndex();
echo "Start clean index" . PHP_EOL;
$index->clean();
echo "End clean index" . PHP_EOL;
}
/**
* 重建课程索引
*/
protected function rebuildCourseIndex()
{
$courses = $this->findCourses();
if ($courses->count() == 0) {
return;
}
$searcher = new CourseSearcher();
$index = $searcher->getXS()->getIndex();
echo "Start rebuild index" . PHP_EOL;
$index->beginRebuild();
foreach ($courses as $course) {
$document = $searcher->setDocument($course);
$index->add($document);
}
$index->endRebuild();
echo "End rebuild index" . PHP_EOL;
}
/**
* 搜索课程
*
* @param string $query
* @return \stdClass $result
* @throws \XSException
*/
protected function searchCourses($query)
{
$searcher = new CourseSearcher();
$result = $searcher->search($query);
return $result;
}
/**
* 查找课程
*
* @return \Phalcon\Mvc\Model\ResultsetInterface
*/
protected function findCourses()
{
$courses = CourseModel::query()
->where('published = 1')
->execute();
return $courses;
}
/**
* 获取条目类型
*
* @return array
*/
protected function getItemTypes()
{
$types = ['course'];
return $types;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Console\Tasks;
use App\Models\Course as CourseModel;
use App\Searchers\Course as CourseSearcher;
class SyncIndexTask extends Task
{
/**
* @var \App\Library\Cache\Backend\Redis
*/
protected $cache;
public function mainAction()
{
$this->cache = $this->getDI()->get('cache');
$this->handleCourseIndex();
}
protected function handleCourseIndex()
{
$keys = $this->cache->queryKeys('sync:index:course:');
if (empty($keys)) {
return;
}
$keys = array_slice($keys, 0, 100);
$searcher = new CourseSearcher();
$index = $searcher->getXS()->getIndex();
$index->openBuffer();
foreach ($keys as $key) {
$lastKey = $this->cache->getRawKeyName($key);
$content = $this->cache->get($lastKey);
if (empty($content->id)) {
continue;
}
$course = CourseModel::findFirstById($content->id);
$document = $searcher->setDocument($course);
if ($content->type == 'update') {
$index->update($document);
} elseif ($content->type == 'delete') {
$index->del($course->id);
} elseif ($content->type == 'restore') {
$index->update($document);
}
$this->cache->delete($lastKey);
}
$index->closeBuffer();
}
}

View File

@ -66,6 +66,8 @@ class Course extends Service
$course->create($data); $course->create($data);
$this->eventsManager->fire('course:afterCreate', $this, $course);
return $course; return $course;
} }
@ -135,6 +137,8 @@ class Course extends Service
$course->update($data); $course->update($data);
$this->eventsManager->fire('course:afterUpdate', $this, $course);
return $course; return $course;
} }
@ -150,6 +154,8 @@ class Course extends Service
$course->update(); $course->update();
$this->eventsManager->fire('course:afterDelete', $this, $course);
return $course; return $course;
} }
@ -165,6 +171,8 @@ class Course extends Service
$course->update(); $course->update();
$this->eventsManager->fire('course:afterRestore', $this, $course);
return $course; return $course;
} }

View File

@ -224,7 +224,7 @@ class Redis extends \Phalcon\Cache\Backend\Redis
} }
/** /**
* Get Prefix * Get prefix
* *
* @return string * @return string
*/ */
@ -234,7 +234,7 @@ class Redis extends \Phalcon\Cache\Backend\Redis
} }
/** /**
* Get Redis Connection * Get redis connection
* *
* @return \Redis * @return \Redis
*/ */
@ -251,14 +251,29 @@ class Redis extends \Phalcon\Cache\Backend\Redis
} }
/** /**
* Get Key Name * Get key name
* *
* @param $keyName * @param $keyName
* @return string * @return string
*/ */
protected function getKeyName($keyName) public function getKeyName($keyName)
{ {
return $this->_prefix . $keyName; return $this->_prefix . $keyName;
} }
/**
* Get raw key name
*
* @param $keyName
* @return string
*/
public function getRawKeyName($keyName)
{
if ($this->_prefix) {
$keyName = str_replace($this->_prefix, '', $keyName);
}
return $keyName;
}
} }

124
app/Listeners/Course.php Normal file
View File

@ -0,0 +1,124 @@
<?php
namespace App\Listeners;
use App\Models\Course as CourseModel;
use Phalcon\Events\Event;
class Course extends Listener
{
protected $logger;
public function __construct()
{
$this->logger = $this->getLogger();
}
public function afterCreate(Event $event, $source, CourseModel $course)
{
$this->logger->debug('Event: {event}, Source: {source}, Data: {data}', [
'event' => $event->getType(),
'source' => get_class($source),
'data' => kg_json_encode($course),
]);
}
public function afterUpdate(Event $event, $source, CourseModel $course)
{
if ($course->published == 1) {
$this->syncIndexAfterUpdate($course);
}
$this->logger->debug('Event: {event}, Source: {source}, Data: {data}', [
'event' => $event->getType(),
'source' => get_class($source),
'data' => kg_json_encode($course),
]);
}
public function afterDelete(Event $event, $source, CourseModel $course)
{
if ($course->published == 1) {
$this->syncIndexAfterDelete($course);
}
$this->logger->debug('Event: {event}, Source: {source}, Data: {data}', [
'event' => $event->getType(),
'source' => get_class($source),
'data' => kg_json_encode($course),
]);
}
public function afterRestore(Event $event, $source, CourseModel $course)
{
if ($course->published == 1) {
$this->syncIndexAfterRestore($course);
}
$this->logger->debug('Event: {event}, Source: {source}, Data: {data}', [
'event' => $event->getType(),
'source' => get_class($source),
'data' => kg_json_encode($course),
]);
}
protected function syncIndexAfterUpdate(CourseModel $course)
{
/**
* @var \Phalcon\Cache\Backend $cache
*/
$cache = $this->getDI()->get('cache');
$key = $this->getSyncIndexKey($course->id);
$content = [
'id' => $course->id,
'type' => 'update',
];
$cache->save($key, $content, 86400);
}
protected function syncIndexAfterDelete($course)
{
/**
* @var \Phalcon\Cache\Backend $cache
*/
$cache = $this->getDI()->get('cache');
$key = $this->getSyncIndexKey($course->id);
$content = [
'id' => $course->id,
'type' => 'delete',
];
$cache->save($key, $content, 86400);
}
protected function syncIndexAfterRestore($course)
{
/**
* @var \Phalcon\Cache\Backend $cache
*/
$cache = $this->getDI()->get('cache');
$key = $this->getSyncIndexKey($course->id);
$content = [
'id' => $course->id,
'type' => 'restore',
];
$cache->save($key, $content, 86400);
}
protected function getSyncIndexKey($courseId)
{
$key = "sync:index:course:{$courseId}";
return $key;
}
}

View File

@ -25,6 +25,7 @@ class Cache extends AbstractProvider
'port' => $config->redis->port, 'port' => $config->redis->port,
'auth' => $config->redis->auth, 'auth' => $config->redis->auth,
'index' => $config->redis->index, 'index' => $config->redis->index,
'prefix' => $config->redis->prefix,
'persistent' => $config->redis->persistent, 'persistent' => $config->redis->persistent,
]); ]);

View File

@ -20,6 +20,7 @@ class Session extends AbstractProvider
'port' => $config->redis->port, 'port' => $config->redis->port,
'auth' => $config->redis->auth, 'auth' => $config->redis->auth,
'index' => $config->session->index, 'index' => $config->session->index,
'prefix' => $config->session->prefix,
'lifetime' => $config->session->lifetime, 'lifetime' => $config->session->lifetime,
'persistent' => $config->redis->persistent, 'persistent' => $config->redis->persistent,
]); ]);

View File

@ -8,7 +8,10 @@ use Phalcon\Mvc\User\Component as UserComponent;
class Course extends UserComponent class Course extends UserComponent
{ {
private $xs; /**
* @var \XS
*/
protected $xs;
public function __construct() public function __construct()
{ {
@ -18,61 +21,53 @@ class Course extends UserComponent
} }
/** /**
* 搜索 * 获取XS
* @return \XS
*/
public function getXS()
{
return $this->xs;
}
/**
* 搜索课程
* *
* @param string $query * @param string $query
* @param integer $limit * @param integer $limit
* @param integer $offset * @param integer $offset
* @return \stdClass
* @throws \XSException * @throws \XSException
* @return array
*/ */
public function search($query, $limit = 15, $offset = 0) public function search($query, $limit = 15, $offset = 0)
{ {
$search = $this->xs->search; $search = $this->xs->getSearch();
$items = $search->setQuery($query)->setLimit($limit, $offset)->search(); $docs = $search->setQuery($query)->setLimit($limit, $offset)->search();
$total = $search->getLastCount(); $total = $search->getLastCount();
return [ $fields = array_keys($this->xs->getAllFields());
'total' => $total,
'items' => $items,
];
}
/** $items = [];
* 添加索引
*
* @param CourseModel $course
*/
public function addIndex($course)
{
$doc = $this->setXSDocument($course);
$this->xs->index->add($doc); foreach ($docs as $doc) {
} $item = new \stdClass();
foreach ($fields as $field) {
if (in_array($field, ['title', 'summary'])) {
$item->{$field} = $search->highlight($doc->{$field});
} else {
$item->{$field} = $doc->{$field};
}
}
$items[] = $item;
}
/** $result = new \stdClass();
* 更新索引
*
* @param CourseModel $course
* @throws \XSException
*/
public function updateIndex($course)
{
$doc = $this->setXSDocument($course);
$this->xs->index->update($doc); $result->total = $total;
} $result->items = $items;
/** return $result;
* 删除索引
*
* @param CourseModel $course
*/
public function deleteIndex($course)
{
$this->xs->index->del($course->id);
} }
/** /**
@ -81,7 +76,24 @@ class Course extends UserComponent
* @param CourseModel $course * @param CourseModel $course
* @return \XSDocument * @return \XSDocument
*/ */
private function setXSDocument($course) public function setDocument(CourseModel $course)
{
$doc = new \XSDocument();
$data = $this->formatDocument($course);
$doc->setFields($data);
return $doc;
}
/**
* 格式化文档
*
* @param CourseModel $course
* @return array
*/
public function formatDocument(CourseModel $course)
{ {
$data = [ $data = [
'id' => $course->id, 'id' => $course->id,
@ -101,11 +113,7 @@ class Course extends UserComponent
'created_at' => $course->created_at, 'created_at' => $course->created_at,
]; ];
$doc = new \XSDocument(); return $data;
$doc->setFields($data);
return $doc;
} }
} }

View File

@ -73,7 +73,7 @@ $config['redis']['port'] = 6379;
$config['redis']['auth'] = '1qaz2wsx3edc'; $config['redis']['auth'] = '1qaz2wsx3edc';
/** /**
* 数据库编号 * redis库编号
*/ */
$config['redis']['index'] = 0; $config['redis']['index'] = 0;
@ -83,17 +83,27 @@ $config['redis']['index'] = 0;
$config['redis']['persistent'] = false; $config['redis']['persistent'] = false;
/** /**
* 默认有限期(秒) * redis键前缀
*/ */
$config['redis']['lifetime'] = 86400; $config['redis']['prefix'] = '';
/**
* redis有限期
*/
$config['redis']['lifetime'] = 7 * 86400;
/**
* 会话键前缀
*/
$config['session']['prefix'] = '';
/** /**
* 会话有效期(秒) * 会话有效期(秒)
*/ */
$config['session']['lifetime'] = 7200; $config['session']['lifetime'] = 2 * 3600;
/** /**
* 数据库编号 * redis库编号
*/ */
$config['session']['index'] = 1; $config['session']['index'] = 1;

View File

@ -3,6 +3,7 @@
$events = [ $events = [
'db' => \App\Listeners\Profiler::class, 'db' => \App\Listeners\Profiler::class,
'course' => \App\Listeners\Course::class,
'payment' => \App\Listeners\Payment::class, 'payment' => \App\Listeners\Payment::class,
]; ];

View File

@ -27,8 +27,8 @@ layui.use(['jquery', 'form', 'element', 'layer', 'dropdown'], function () {
url: data.form.action, url: data.form.action,
data: data.field, data: data.field,
success: function (res) { success: function (res) {
var icon = res.code == 0 ? 1 : 2;
if (res.msg != '') { if (res.msg != '') {
var icon = (res.code == 0) ? 1 : 2;
layer.msg(res.msg, {icon: icon}); layer.msg(res.msg, {icon: icon});
} }
if (res.location) { if (res.location) {

View File

@ -16,18 +16,21 @@ $scheduler->php($script, $bin, ['--task' => 'close_trade', '--action' => 'main']
->at('*/15 * * * *'); ->at('*/15 * * * *');
$scheduler->php($script, $bin, ['--task' => 'close_order', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'close_order', '--action' => 'main'])
->at('* */6 * * *'); ->at('* */3 * * *');
$scheduler->php($script, $bin, ['--task' => 'learning', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'learning', '--action' => 'main'])
->at('*/10 * * * *'); ->at('*/10 * * * *');
$scheduler->php($script, $bin, ['--task' => 'refund', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'refund', '--action' => 'main'])
->hourly(15); ->hourly(3);
$scheduler->php($script, $bin, ['--task' => 'sync_index', '--action' => 'main'])
->hourly(13);
$scheduler->php($script, $bin, ['--task' => 'clean_log', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'clean_log', '--action' => 'main'])
->daily(3, 10); ->daily(3, 10);
$scheduler->php($script, $bin, ['--task' => 'course_count', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'count_course', '--action' => 'main'])
->daily(3, 20); ->daily(3, 20);
$scheduler->php($script, $bin, ['--task' => 'unlock_user', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'unlock_user', '--action' => 'main'])