mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-06-25 12:09:09 +08:00
Merge branch 'develop'
This commit is contained in:
commit
4b9bd42d91
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,7 +6,6 @@
|
|||||||
/config/xs.user.ini
|
/config/xs.user.ini
|
||||||
/config/alipay/*.crt
|
/config/alipay/*.crt
|
||||||
/config/wxpay/*.pem
|
/config/wxpay/*.pem
|
||||||
/db/migrations/schema.php
|
|
||||||
/public/robots.txt
|
/public/robots.txt
|
||||||
/public/sitemap.xml
|
/public/sitemap.xml
|
||||||
/public/h5
|
/public/h5
|
||||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,3 +1,18 @@
|
|||||||
|
### [v1.2.3](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.3)(2021-01-03)
|
||||||
|
|
||||||
|
#### 增加
|
||||||
|
|
||||||
|
- 多人使用同一帐号防范机制
|
||||||
|
- 首页缓存刷新工具
|
||||||
|
- 课程综合评分
|
||||||
|
- 课程推荐
|
||||||
|
|
||||||
|
#### 修复
|
||||||
|
|
||||||
|
- phinx-migration-generator 无符号问题
|
||||||
|
- online表并发写入重复记录问题
|
||||||
|
- 计划任务生成sitemap.xml失败
|
||||||
|
|
||||||
### [v1.2.2](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.2)(2020-12-24)
|
### [v1.2.2](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.2)(2020-12-24)
|
||||||
|
|
||||||
#### 增加
|
#### 增加
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
酷瓜云课堂,依托腾讯云基础服务架构,采用C扩展框架Phalcon开发,GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。
|
酷瓜云课堂,依托腾讯云基础服务架构,采用C扩展框架Phalcon开发,GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
#### 系统功能
|
#### 系统功能
|
||||||
|
118
app/Caches/IndexFeaturedCourseList.php
Normal file
118
app/Caches/IndexFeaturedCourseList.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Caches;
|
||||||
|
|
||||||
|
use App\Models\Category as CategoryModel;
|
||||||
|
use App\Models\Course as CourseModel;
|
||||||
|
use App\Services\Category as CategoryService;
|
||||||
|
use Phalcon\Mvc\Model\Resultset;
|
||||||
|
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐课程
|
||||||
|
*/
|
||||||
|
class IndexFeaturedCourseList extends Cache
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $lifetime = 1 * 86400;
|
||||||
|
|
||||||
|
public function getLifetime()
|
||||||
|
{
|
||||||
|
return $this->lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey($id = null)
|
||||||
|
{
|
||||||
|
return 'index_featured_course_list';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent($id = null)
|
||||||
|
{
|
||||||
|
$categoryLimit = 5;
|
||||||
|
|
||||||
|
$courseLimit = 8;
|
||||||
|
|
||||||
|
$categories = $this->findCategories($categoryLimit);
|
||||||
|
|
||||||
|
if ($categories->count() == 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($categories as $category) {
|
||||||
|
|
||||||
|
$item = [];
|
||||||
|
|
||||||
|
$item['category'] = [
|
||||||
|
'id' => $category->id,
|
||||||
|
'name' => $category->name,
|
||||||
|
];
|
||||||
|
|
||||||
|
$item['courses'] = [];
|
||||||
|
|
||||||
|
$courses = $this->findCategoryCourses($category->id, $courseLimit);
|
||||||
|
|
||||||
|
if ($courses->count() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categoryCourses = [];
|
||||||
|
|
||||||
|
foreach ($courses as $course) {
|
||||||
|
$categoryCourses[] = [
|
||||||
|
'id' => $course->id,
|
||||||
|
'title' => $course->title,
|
||||||
|
'cover' => $course->cover,
|
||||||
|
'market_price' => $course->market_price,
|
||||||
|
'vip_price' => $course->vip_price,
|
||||||
|
'model' => $course->model,
|
||||||
|
'level' => $course->level,
|
||||||
|
'user_count' => $course->user_count,
|
||||||
|
'lesson_count' => $course->lesson_count,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$item['courses'] = $categoryCourses;
|
||||||
|
|
||||||
|
$result[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $limit
|
||||||
|
* @return ResultsetInterface|Resultset|CategoryModel[]
|
||||||
|
*/
|
||||||
|
protected function findCategories($limit = 5)
|
||||||
|
{
|
||||||
|
return CategoryModel::query()
|
||||||
|
->where('type = :type:', ['type' => CategoryModel::TYPE_COURSE])
|
||||||
|
->andWhere('level = 1 AND published = 1')
|
||||||
|
->orderBy('priority ASC')
|
||||||
|
->limit($limit)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $categoryId
|
||||||
|
* @param int $limit
|
||||||
|
* @return ResultsetInterface|Resultset|CourseModel[]
|
||||||
|
*/
|
||||||
|
protected function findCategoryCourses($categoryId, $limit = 8)
|
||||||
|
{
|
||||||
|
$categoryService = new CategoryService();
|
||||||
|
|
||||||
|
$categoryIds = $categoryService->getChildCategoryIds($categoryId);
|
||||||
|
|
||||||
|
return CourseModel::query()
|
||||||
|
->inWhere('category_id', $categoryIds)
|
||||||
|
->andWhere('published = 1')
|
||||||
|
->andWhere('featured = 1')
|
||||||
|
->orderBy('id DESC')
|
||||||
|
->limit($limit)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
app/Caches/IndexSimpleFeaturedCourseList.php
Normal file
70
app/Caches/IndexSimpleFeaturedCourseList.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Caches;
|
||||||
|
|
||||||
|
use App\Models\Course as CourseModel;
|
||||||
|
use Phalcon\Mvc\Model\Resultset;
|
||||||
|
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简版推荐课程
|
||||||
|
*/
|
||||||
|
class IndexSimpleFeaturedCourseList extends Cache
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $lifetime = 1 * 86400;
|
||||||
|
|
||||||
|
public function getLifetime()
|
||||||
|
{
|
||||||
|
return $this->lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey($id = null)
|
||||||
|
{
|
||||||
|
return 'index_simple_featured_course_list';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent($id = null)
|
||||||
|
{
|
||||||
|
$limit = 8;
|
||||||
|
|
||||||
|
$courses = $this->findCourses($limit);
|
||||||
|
|
||||||
|
if ($courses->count() == 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($courses as $course) {
|
||||||
|
$result[] = [
|
||||||
|
'id' => $course->id,
|
||||||
|
'title' => $course->title,
|
||||||
|
'cover' => $course->cover,
|
||||||
|
'market_price' => $course->market_price,
|
||||||
|
'vip_price' => $course->vip_price,
|
||||||
|
'model' => $course->model,
|
||||||
|
'level' => $course->level,
|
||||||
|
'user_count' => $course->user_count,
|
||||||
|
'lesson_count' => $course->lesson_count,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $limit
|
||||||
|
* @return ResultsetInterface|Resultset|CourseModel[]
|
||||||
|
*/
|
||||||
|
protected function findCourses($limit = 8)
|
||||||
|
{
|
||||||
|
return CourseModel::query()
|
||||||
|
->where('published = 1')
|
||||||
|
->andWhere('featured = 1')
|
||||||
|
->orderBy('id DESC')
|
||||||
|
->limit($limit)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Tasks;
|
|
||||||
|
|
||||||
class CleanSessionTask extends Task
|
|
||||||
{
|
|
||||||
|
|
||||||
public function mainAction()
|
|
||||||
{
|
|
||||||
$config = $this->getConfig();
|
|
||||||
$redis = $this->getRedis();
|
|
||||||
|
|
||||||
$redis->select($config->path('session.db'));
|
|
||||||
|
|
||||||
$keys = $this->querySessionKeys(10000);
|
|
||||||
|
|
||||||
if (count($keys) == 0) return;
|
|
||||||
|
|
||||||
$lifetime = $config->path('session.lifetime');
|
|
||||||
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
$ttl = $redis->ttl($key);
|
|
||||||
$content = $redis->get($key);
|
|
||||||
if (empty($content) && $ttl < $lifetime * 0.5) {
|
|
||||||
$redis->del($key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查找待清理会话
|
|
||||||
*
|
|
||||||
* @param int $limit
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function querySessionKeys($limit)
|
|
||||||
{
|
|
||||||
$cache = $this->getCache();
|
|
||||||
|
|
||||||
return $cache->queryKeys('_PHCR', $limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -2,14 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Console\Tasks;
|
namespace App\Console\Tasks;
|
||||||
|
|
||||||
use App\Caches\IndexFreeCourseList as IndexFreeCourseListCache;
|
|
||||||
use App\Caches\IndexNewCourseList as IndexNewCourseListCache;
|
|
||||||
use App\Caches\IndexSimpleFreeCourseList as IndexSimpleFreeCourseListCache;
|
|
||||||
use App\Caches\IndexSimpleNewCourseList as IndexSimpleNewCourseListCache;
|
|
||||||
use App\Caches\IndexSimpleVipCourseList as IndexSimpleVipCourseListCache;
|
|
||||||
use App\Caches\IndexVipCourseList as IndexVipCourseListCache;
|
|
||||||
use App\Http\Admin\Services\Setting as SettingService;
|
use App\Http\Admin\Services\Setting as SettingService;
|
||||||
use App\Library\Utils\Password as PasswordUtil;
|
use App\Library\Utils\Password as PasswordUtil;
|
||||||
|
use App\Services\Utils\IndexCourseCache as IndexCourseCacheUtil;
|
||||||
use App\Validators\Account as AccountValidator;
|
use App\Validators\Account as AccountValidator;
|
||||||
|
|
||||||
class MaintainTask extends Task
|
class MaintainTask extends Task
|
||||||
@ -25,39 +20,9 @@ class MaintainTask extends Task
|
|||||||
{
|
{
|
||||||
$section = $params[0] ?? null;
|
$section = $params[0] ?? null;
|
||||||
|
|
||||||
$site = $this->getSettings('site');
|
$util = new IndexCourseCacheUtil();
|
||||||
|
|
||||||
$type = $site['index_tpl_type'] ?: 'full';
|
$util->rebuild($section);
|
||||||
|
|
||||||
if (!$section || $section == 'new_course') {
|
|
||||||
if ($type == 'full') {
|
|
||||||
$cache = new IndexNewCourseListCache();
|
|
||||||
$cache->rebuild();
|
|
||||||
} else {
|
|
||||||
$cache = new IndexSimpleNewCourseListCache();
|
|
||||||
$cache->rebuild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$section || $section == 'free_course') {
|
|
||||||
if ($type == 'full') {
|
|
||||||
$cache = new IndexFreeCourseListCache();
|
|
||||||
$cache->rebuild();
|
|
||||||
} else {
|
|
||||||
$cache = new IndexSimpleFreeCourseListCache();
|
|
||||||
$cache->rebuild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$section || $section == 'vip_course') {
|
|
||||||
if ($type == 'full') {
|
|
||||||
$cache = new IndexVipCourseListCache();
|
|
||||||
$cache->rebuild();
|
|
||||||
} else {
|
|
||||||
$cache = new IndexSimpleVipCourseListCache();
|
|
||||||
$cache->rebuild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
echo 'rebuild index course cache success' . PHP_EOL;
|
echo 'rebuild index course cache success' . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ class SitemapTask extends Task
|
|||||||
|
|
||||||
$this->sitemap = new Sitemap();
|
$this->sitemap = new Sitemap();
|
||||||
|
|
||||||
$filename = public_path('sitemap.xml');
|
$filename = tmp_path('sitemap.xml');
|
||||||
|
|
||||||
$this->addIndex();
|
$this->addIndex();
|
||||||
$this->addCourses();
|
$this->addCourses();
|
||||||
|
44
app/Console/Tasks/SyncCourseScoreTask.php
Normal file
44
app/Console/Tasks/SyncCourseScoreTask.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Tasks;
|
||||||
|
|
||||||
|
use App\Repos\Course as CourseRepo;
|
||||||
|
use App\Services\CourseStat as CourseStatService;
|
||||||
|
use App\Services\Sync\CourseScore as CourseScoreSync;
|
||||||
|
|
||||||
|
class SyncCourseScoreTask extends Task
|
||||||
|
{
|
||||||
|
|
||||||
|
public function mainAction()
|
||||||
|
{
|
||||||
|
$redis = $this->getRedis();
|
||||||
|
|
||||||
|
$key = $this->getSyncKey();
|
||||||
|
|
||||||
|
$courseIds = $redis->sRandMember($key, 1000);
|
||||||
|
|
||||||
|
if (!$courseIds) return;
|
||||||
|
|
||||||
|
$courseRepo = new CourseRepo();
|
||||||
|
|
||||||
|
$courses = $courseRepo->findByIds($courseIds);
|
||||||
|
|
||||||
|
if ($courses->count() == 0) return;
|
||||||
|
|
||||||
|
$statService = new CourseStatService();
|
||||||
|
|
||||||
|
foreach ($courses as $course) {
|
||||||
|
$statService->updateScore($course->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$redis->sRem($key, ...$courseIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSyncKey()
|
||||||
|
{
|
||||||
|
$sync = new CourseScoreSync();
|
||||||
|
|
||||||
|
return $sync->getSyncKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -42,13 +42,9 @@ class UpgradeTask extends Task
|
|||||||
*/
|
*/
|
||||||
public function resetAnnotationAction()
|
public function resetAnnotationAction()
|
||||||
{
|
{
|
||||||
$config = $this->getConfig();
|
|
||||||
$redis = $this->getRedis();
|
$redis = $this->getRedis();
|
||||||
|
|
||||||
$dbIndex = $config->path('annotation.db');
|
$statsKey = '_ANNOTATION_';
|
||||||
$statsKey = $config->path('annotation.statsKey');
|
|
||||||
|
|
||||||
$redis->select($dbIndex);
|
|
||||||
|
|
||||||
$keys = $redis->sMembers($statsKey);
|
$keys = $redis->sMembers($statsKey);
|
||||||
|
|
||||||
@ -70,13 +66,9 @@ class UpgradeTask extends Task
|
|||||||
*/
|
*/
|
||||||
public function resetMetadataAction()
|
public function resetMetadataAction()
|
||||||
{
|
{
|
||||||
$config = $this->getConfig();
|
|
||||||
$redis = $this->getRedis();
|
$redis = $this->getRedis();
|
||||||
|
|
||||||
$dbIndex = $config->path('metadata.db');
|
$statsKey = '_METADATA_';
|
||||||
$statsKey = $config->path('metadata.statsKey');
|
|
||||||
|
|
||||||
$redis->select($dbIndex);
|
|
||||||
|
|
||||||
$keys = $redis->sMembers($statsKey);
|
$keys = $redis->sMembers($statsKey);
|
||||||
|
|
||||||
|
30
app/Http/Admin/Controllers/UtilController.php
Normal file
30
app/Http/Admin/Controllers/UtilController.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Admin\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Admin\Services\Util as UtilService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @RoutePrefix("/admin/util")
|
||||||
|
*/
|
||||||
|
class UtilController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/index/cache", name="admin.util.index_cache")
|
||||||
|
*/
|
||||||
|
public function indexCacheAction()
|
||||||
|
{
|
||||||
|
$service = new UtilService();
|
||||||
|
|
||||||
|
if ($this->request->isPost()) {
|
||||||
|
|
||||||
|
$service->handleIndexCache();
|
||||||
|
|
||||||
|
return $this->jsonSuccess(['msg' => '更新缓存成功']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->pick('util/index_cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -14,6 +14,7 @@ class AuthNode extends Service
|
|||||||
$nodes[] = $this->getFinanceNodes();
|
$nodes[] = $this->getFinanceNodes();
|
||||||
$nodes[] = $this->getUserNodes();
|
$nodes[] = $this->getUserNodes();
|
||||||
$nodes[] = $this->getSettingNodes();
|
$nodes[] = $this->getSettingNodes();
|
||||||
|
$nodes[] = $this->getUtilNodes();
|
||||||
|
|
||||||
return $nodes;
|
return $nodes;
|
||||||
}
|
}
|
||||||
@ -768,4 +769,27 @@ class AuthNode extends Service
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getUtilNodes()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => '6',
|
||||||
|
'title' => '实用工具',
|
||||||
|
'children' => [
|
||||||
|
[
|
||||||
|
'id' => '6-1',
|
||||||
|
'title' => '常用工具',
|
||||||
|
'type' => 'menu',
|
||||||
|
'children' => [
|
||||||
|
[
|
||||||
|
'id' => '6-1-1',
|
||||||
|
'title' => '首页缓存',
|
||||||
|
'type' => 'menu',
|
||||||
|
'route' => 'admin.util.index_cache',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,10 @@ class Course extends Service
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($post['featured'])) {
|
||||||
|
$data['featured'] = $validator->checkFeatureStatus($post['featured']);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($post['published'])) {
|
if (isset($post['published'])) {
|
||||||
$data['published'] = $validator->checkPublishStatus($post['published']);
|
$data['published'] = $validator->checkPublishStatus($post['published']);
|
||||||
if ($post['published'] == 1) {
|
if ($post['published'] == 1) {
|
||||||
|
40
app/Http/Admin/Services/Util.php
Normal file
40
app/Http/Admin/Services/Util.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Admin\Services;
|
||||||
|
|
||||||
|
use App\Caches\IndexSlideList as IndexSlideListCache;
|
||||||
|
use App\Services\Utils\IndexCourseCache as IndexCourseCacheUtil;
|
||||||
|
|
||||||
|
class Util extends Service
|
||||||
|
{
|
||||||
|
|
||||||
|
public function handleIndexCache()
|
||||||
|
{
|
||||||
|
$items = $this->request->getPost('items');
|
||||||
|
|
||||||
|
if ($items['slide'] == 1) {
|
||||||
|
$cache = new IndexSlideListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
$util = new IndexCourseCacheUtil();
|
||||||
|
|
||||||
|
if ($items['featured_course'] == 1) {
|
||||||
|
$util->rebuild('featured_course');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($items['new_course'] == 1) {
|
||||||
|
$util->rebuild('new_course');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($items['free_course'] == 1) {
|
||||||
|
$util->rebuild('free_course');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($items['vip_course'] == 1) {
|
||||||
|
$util->rebuild('vip_course');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -59,6 +59,7 @@
|
|||||||
<col>
|
<col>
|
||||||
<col>
|
<col>
|
||||||
<col>
|
<col>
|
||||||
|
<col>
|
||||||
<col width="10%">
|
<col width="10%">
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
@ -67,6 +68,7 @@
|
|||||||
<th>课时数</th>
|
<th>课时数</th>
|
||||||
<th>用户数</th>
|
<th>用户数</th>
|
||||||
<th>价格</th>
|
<th>价格</th>
|
||||||
|
<th>推荐</th>
|
||||||
<th>发布</th>
|
<th>发布</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -101,6 +103,7 @@
|
|||||||
<p>市场:{{ '¥%0.2f'|format(item.market_price) }}</p>
|
<p>市场:{{ '¥%0.2f'|format(item.market_price) }}</p>
|
||||||
<p>会员:{{ '¥%0.2f'|format(item.vip_price) }}</p>
|
<p>会员:{{ '¥%0.2f'|format(item.vip_price) }}</p>
|
||||||
</td>
|
</td>
|
||||||
|
<td><input type="checkbox" name="featured" value="1" lay-skin="switch" lay-text="是|否" lay-filter="featured" data-url="{{ update_url }}" {% if item.featured == 1 %}checked="checked"{% endif %}></td>
|
||||||
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="published" data-url="{{ update_url }}" {% if item.published == 1 %}checked="checked"{% endif %}></td>
|
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="published" data-url="{{ update_url }}" {% if item.published == 1 %}checked="checked"{% endif %}></td>
|
||||||
<td class="center">
|
<td class="center">
|
||||||
<div class="layui-dropdown">
|
<div class="layui-dropdown">
|
||||||
@ -130,3 +133,44 @@
|
|||||||
{{ partial('partials/pager') }}
|
{{ partial('partials/pager') }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block inline_js %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
layui.define(['jquery', 'form', 'layer'], function () {
|
||||||
|
|
||||||
|
var $ = layui.jquery;
|
||||||
|
var form = layui.form;
|
||||||
|
var layer = layui.layer;
|
||||||
|
|
||||||
|
form.on('switch(featured)', function (data) {
|
||||||
|
var checked = $(this).is(':checked');
|
||||||
|
var featured = checked ? 1 : 0;
|
||||||
|
var url = $(this).data('url');
|
||||||
|
var tips = featured === 1 ? '确定要推荐?' : '确定要取消推荐?';
|
||||||
|
layer.confirm(tips, function () {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: url,
|
||||||
|
data: {featured: featured},
|
||||||
|
success: function (res) {
|
||||||
|
layer.msg(res.msg, {icon: 1});
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
var json = JSON.parse(xhr.responseText);
|
||||||
|
layer.msg(json.msg, {icon: 2});
|
||||||
|
data.elem.checked = !checked;
|
||||||
|
form.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
data.elem.checked = !checked;
|
||||||
|
form.render();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -54,6 +54,13 @@
|
|||||||
<input type="radio" name="free" value="0" title="否">
|
<input type="radio" name="free" value="0" title="否">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">推荐</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="featured" value="1" title="是">
|
||||||
|
<input type="radio" name="featured" value="0" title="否">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<label class="layui-form-label">发布</label>
|
<label class="layui-form-label">发布</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
|
89
app/Http/Admin/Views/util/index_cache.volt
Normal file
89
app/Http/Admin/Views/util/index_cache.volt
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{% extends 'templates/main.volt' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.util.index_cache'}) }}">
|
||||||
|
<fieldset class="layui-elem-field layui-field-title">
|
||||||
|
<legend>首页缓存</legend>
|
||||||
|
</fieldset>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">轮播图</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="items[slide]" value="1" title="是">
|
||||||
|
<input type="radio" name="items[slide]" value="0" title="否" checked="checked">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">推荐课程</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="items[featured_course]" value="1" title="是">
|
||||||
|
<input type="radio" name="items[featured_course]" value="0" title="否" checked="checked">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">新上课程</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="items[new_course]" value="1" title="是">
|
||||||
|
<input type="radio" name="items[new_course]" value="0" title="否" checked="checked">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">免费课程</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="items[free_course]" value="1" title="是">
|
||||||
|
<input type="radio" name="items[free_course]" value="0" title="否" checked="checked">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<label class="layui-form-label">会员课程</label>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<input type="radio" name="items[vip_course]" value="1" title="是">
|
||||||
|
<input type="radio" name="items[vip_course]" 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="button" class="kg-back layui-btn layui-btn-primary">返回</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block inline_js %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
layui.use(['jquery', 'form', 'layer'], function () {
|
||||||
|
|
||||||
|
var $ = layui.jquery;
|
||||||
|
var form = layui.form;
|
||||||
|
var layer = layui.layer;
|
||||||
|
|
||||||
|
form.on('submit(back_verify)', function (data) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: data.form.action,
|
||||||
|
data: data.field,
|
||||||
|
success: function (res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
$('#back-verify-btn').remove();
|
||||||
|
$('#back-verify-tips').removeClass('layui-hide');
|
||||||
|
}
|
||||||
|
layer.msg(res.msg, {icon: 1});
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
var json = JSON.parse(xhr.responseText);
|
||||||
|
layer.msg(json.msg, {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Api\Controllers;
|
namespace App\Http\Api\Controllers;
|
||||||
|
|
||||||
|
use App\Caches\IndexSimpleFeaturedCourseList;
|
||||||
use App\Caches\IndexSimpleFreeCourseList;
|
use App\Caches\IndexSimpleFreeCourseList;
|
||||||
use App\Caches\IndexSimpleNewCourseList;
|
use App\Caches\IndexSimpleNewCourseList;
|
||||||
use App\Caches\IndexSimpleVipCourseList;
|
use App\Caches\IndexSimpleVipCourseList;
|
||||||
@ -25,6 +26,18 @@ class IndexController extends Controller
|
|||||||
return $this->jsonSuccess(['slides' => $slides]);
|
return $this->jsonSuccess(['slides' => $slides]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Get("/courses/featured", name="api.index.featured_courses")
|
||||||
|
*/
|
||||||
|
public function featuredCoursesAction()
|
||||||
|
{
|
||||||
|
$cache = new IndexSimpleFeaturedCourseList();
|
||||||
|
|
||||||
|
$courses = $cache->get();
|
||||||
|
|
||||||
|
return $this->jsonSuccess(['courses' => $courses]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Get("/courses/new", name="api.index.new_courses")
|
* @Get("/courses/new", name="api.index.new_courses")
|
||||||
*/
|
*/
|
||||||
|
@ -47,6 +47,7 @@ class IndexController extends Controller
|
|||||||
$this->view->pick('index/full');
|
$this->view->pick('index/full');
|
||||||
$this->view->setVar('lives', $service->getLives());
|
$this->view->setVar('lives', $service->getLives());
|
||||||
$this->view->setVar('slides', $service->getSlides());
|
$this->view->setVar('slides', $service->getSlides());
|
||||||
|
$this->view->setVar('featured_courses', $service->getFeaturedCourses());
|
||||||
$this->view->setVar('new_courses', $service->getNewCourses());
|
$this->view->setVar('new_courses', $service->getNewCourses());
|
||||||
$this->view->setVar('free_courses', $service->getFreeCourses());
|
$this->view->setVar('free_courses', $service->getFreeCourses());
|
||||||
$this->view->setVar('vip_courses', $service->getVipCourses());
|
$this->view->setVar('vip_courses', $service->getVipCourses());
|
||||||
@ -59,6 +60,7 @@ class IndexController extends Controller
|
|||||||
$this->view->pick('index/simple');
|
$this->view->pick('index/simple');
|
||||||
$this->view->setVar('lives', $service->getLives());
|
$this->view->setVar('lives', $service->getLives());
|
||||||
$this->view->setVar('slides', $service->getSlides());
|
$this->view->setVar('slides', $service->getSlides());
|
||||||
|
$this->view->setVar('featured_courses', $service->getSimpleFeaturedCourses());
|
||||||
$this->view->setVar('new_courses', $service->getSimpleNewCourses());
|
$this->view->setVar('new_courses', $service->getSimpleNewCourses());
|
||||||
$this->view->setVar('free_courses', $service->getSimpleFreeCourses());
|
$this->view->setVar('free_courses', $service->getSimpleFreeCourses());
|
||||||
$this->view->setVar('vip_courses', $service->getSimpleVipCourses());
|
$this->view->setVar('vip_courses', $service->getSimpleVipCourses());
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Home\Services;
|
namespace App\Http\Home\Services;
|
||||||
|
|
||||||
|
use App\Caches\IndexFeaturedCourseList;
|
||||||
use App\Caches\IndexFreeCourseList;
|
use App\Caches\IndexFreeCourseList;
|
||||||
use App\Caches\IndexLiveList;
|
use App\Caches\IndexLiveList;
|
||||||
use App\Caches\IndexNewCourseList;
|
use App\Caches\IndexNewCourseList;
|
||||||
|
use App\Caches\IndexSimpleFeaturedCourseList;
|
||||||
use App\Caches\IndexSimpleFreeCourseList;
|
use App\Caches\IndexSimpleFreeCourseList;
|
||||||
use App\Caches\IndexSimpleNewCourseList;
|
use App\Caches\IndexSimpleNewCourseList;
|
||||||
use App\Caches\IndexSimpleVipCourseList;
|
use App\Caches\IndexSimpleVipCourseList;
|
||||||
@ -61,6 +63,15 @@ class Index extends Service
|
|||||||
return $cache->get();
|
return $cache->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFeaturedCourses()
|
||||||
|
{
|
||||||
|
$cache = new IndexFeaturedCourseList();
|
||||||
|
|
||||||
|
$courses = $cache->get();
|
||||||
|
|
||||||
|
return $this->handleCategoryCourses($courses);
|
||||||
|
}
|
||||||
|
|
||||||
public function getNewCourses()
|
public function getNewCourses()
|
||||||
{
|
{
|
||||||
$cache = new IndexNewCourseList();
|
$cache = new IndexNewCourseList();
|
||||||
@ -95,6 +106,13 @@ class Index extends Service
|
|||||||
return $cache->get();
|
return $cache->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSimpleFeaturedCourses()
|
||||||
|
{
|
||||||
|
$cache = new IndexSimpleFeaturedCourseList();
|
||||||
|
|
||||||
|
return $cache->get();
|
||||||
|
}
|
||||||
|
|
||||||
public function getSimpleFreeCourses()
|
public function getSimpleFreeCourses()
|
||||||
{
|
{
|
||||||
$cache = new IndexSimpleFreeCourseList();
|
$cache = new IndexSimpleFreeCourseList();
|
||||||
|
@ -45,6 +45,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="index-wrap wrap">
|
||||||
|
<div class="header">推荐课程</div>
|
||||||
|
<div class="content">
|
||||||
|
{{ category_courses(featured_courses) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="index-wrap wrap">
|
<div class="index-wrap wrap">
|
||||||
<div class="header">新上课程</div>
|
<div class="header">新上课程</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -30,6 +30,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="index-wrap wrap">
|
||||||
|
<div class="header">推荐课程</div>
|
||||||
|
<div class="content">
|
||||||
|
{{ show_courses(featured_courses) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="index-wrap wrap">
|
<div class="index-wrap wrap">
|
||||||
<div class="header">新上课程</div>
|
<div class="header">新上课程</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -11,7 +11,7 @@ class AppInfo
|
|||||||
|
|
||||||
protected $link = 'https://gitee.com/koogua';
|
protected $link = 'https://gitee.com/koogua';
|
||||||
|
|
||||||
protected $version = '1.2.2';
|
protected $version = '1.2.3';
|
||||||
|
|
||||||
public function __get($name)
|
public function __get($name)
|
||||||
{
|
{
|
||||||
|
79
app/Library/Utils/Lock.php
Normal file
79
app/Library/Utils/Lock.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Library\Utils;
|
||||||
|
|
||||||
|
use App\Library\Cache\Backend\Redis as RedisCache;
|
||||||
|
use Phalcon\Di;
|
||||||
|
use Phalcon\Text;
|
||||||
|
|
||||||
|
class Lock
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $itemId
|
||||||
|
* @param int $expire
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public static function addLock($itemId, $expire = 600)
|
||||||
|
{
|
||||||
|
if (!$itemId || $expire <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RedisCache $cache
|
||||||
|
*/
|
||||||
|
$cache = Di::getDefault()->getShared('cache');
|
||||||
|
|
||||||
|
$redis = $cache->getRedis();
|
||||||
|
|
||||||
|
$lockId = Text::random(Text::RANDOM_ALNUM, 16);
|
||||||
|
|
||||||
|
$keyName = self::getLockKey($itemId);
|
||||||
|
|
||||||
|
$result = $redis->set($keyName, $lockId, ['nx', 'ex' => $expire]);
|
||||||
|
|
||||||
|
return $result ? $lockId : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $itemId
|
||||||
|
* @param string $lockId
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function releaseLock($itemId, $lockId)
|
||||||
|
{
|
||||||
|
if (!$itemId || !$lockId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RedisCache $cache
|
||||||
|
*/
|
||||||
|
$cache = Di::getDefault()->getShared('cache');
|
||||||
|
|
||||||
|
$redis = $cache->getRedis();
|
||||||
|
|
||||||
|
$keyName = self::getLockKey($itemId);
|
||||||
|
|
||||||
|
$redis->watch($keyName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听key防止被修改或删除,提交事务后会自动取消监控,其他情况需手动解除监控
|
||||||
|
*/
|
||||||
|
if ($lockId == $redis->get($keyName)) {
|
||||||
|
$redis->multi()->del($keyName)->exec();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$redis->unwatch();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLockKey($itemId)
|
||||||
|
{
|
||||||
|
return sprintf('kg_lock:%s', $itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Listeners;
|
namespace App\Listeners;
|
||||||
|
|
||||||
|
use App\Library\Utils\Lock as LockUtil;
|
||||||
use App\Models\Online as OnlineModel;
|
use App\Models\Online as OnlineModel;
|
||||||
use App\Models\User as UserModel;
|
use App\Models\User as UserModel;
|
||||||
use App\Repos\Online as OnlineRepo;
|
use App\Repos\Online as OnlineRepo;
|
||||||
@ -15,7 +16,13 @@ class User extends Listener
|
|||||||
|
|
||||||
public function online(Event $event, $source, UserModel $user)
|
public function online(Event $event, $source, UserModel $user)
|
||||||
{
|
{
|
||||||
|
$itemId = "user:{$user->id}";
|
||||||
|
|
||||||
|
$lockId = LockUtil::addLock($itemId);
|
||||||
|
|
||||||
$now = time();
|
$now = time();
|
||||||
|
$clientType = $this->getClientType();
|
||||||
|
$clientIp = $this->getClientIp();
|
||||||
|
|
||||||
if ($now - $user->active_time > 600) {
|
if ($now - $user->active_time > 600) {
|
||||||
|
|
||||||
@ -25,28 +32,46 @@ class User extends Listener
|
|||||||
|
|
||||||
$onlineRepo = new OnlineRepo();
|
$onlineRepo = new OnlineRepo();
|
||||||
|
|
||||||
$online = $onlineRepo->findByUserDate($user->id, date('Y-m-d'));
|
$records = $onlineRepo->findByUserDate($user->id, date('Y-m-d'));
|
||||||
|
|
||||||
|
if ($records->count() > 0) {
|
||||||
|
|
||||||
|
$online = null;
|
||||||
|
|
||||||
|
foreach ($records as $record) {
|
||||||
|
if ($record->client_type == $clientType && $record->client_ip == $clientIp) {
|
||||||
|
$online = $record;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($online) {
|
if ($online) {
|
||||||
|
|
||||||
$online->active_time = $now;
|
$online->active_time = $now;
|
||||||
$online->client_type = $this->getClientType();
|
|
||||||
$online->client_ip = $this->getClientIp();
|
|
||||||
|
|
||||||
$online->update();
|
$online->update();
|
||||||
|
} else {
|
||||||
|
$this->createOnline($user->id, $clientType, $clientIp);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
$this->createOnline($user->id, $clientType, $clientIp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LockUtil::releaseLock($itemId, $lockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createOnline($userId, $clientType, $clientIp)
|
||||||
|
{
|
||||||
$online = new OnlineModel();
|
$online = new OnlineModel();
|
||||||
|
|
||||||
$online->user_id = $user->id;
|
$online->user_id = $userId;
|
||||||
$online->active_time = $now;
|
$online->client_type = $clientType;
|
||||||
$online->client_type = $this->getClientType();
|
$online->client_ip = $clientIp;
|
||||||
$online->client_ip = $this->getClientIp();
|
$online->active_time = time();
|
||||||
|
|
||||||
$online->create();
|
$online->create();
|
||||||
}
|
|
||||||
}
|
return $online;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Caches\MaxCourseId as MaxCourseIdCache;
|
use App\Caches\MaxCourseId as MaxCourseIdCache;
|
||||||
use App\Services\Sync\CourseIndex as CourseIndexSync;
|
use App\Services\Sync\CourseIndex as CourseIndexSync;
|
||||||
|
use App\Services\Sync\CourseScore as CourseScoreSync;
|
||||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||||
use Phalcon\Text;
|
use Phalcon\Text;
|
||||||
|
|
||||||
@ -165,6 +166,13 @@ class Course extends Model
|
|||||||
*/
|
*/
|
||||||
public $attrs;
|
public $attrs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推荐标识
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $featured;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布标识
|
* 发布标识
|
||||||
*
|
*
|
||||||
@ -302,6 +310,9 @@ class Course extends Model
|
|||||||
if (time() - $this->update_time > 3 * 3600) {
|
if (time() - $this->update_time > 3 * 3600) {
|
||||||
$sync = new CourseIndexSync();
|
$sync = new CourseIndexSync();
|
||||||
$sync->addItem($this->id);
|
$sync->addItem($this->id);
|
||||||
|
|
||||||
|
$sync = new CourseScoreSync();
|
||||||
|
$sync->addItem($this->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Text::startsWith($this->cover, 'http')) {
|
if (Text::startsWith($this->cover, 'http')) {
|
||||||
@ -377,6 +388,7 @@ class Course extends Model
|
|||||||
'rating' => '好评',
|
'rating' => '好评',
|
||||||
'latest' => '最新',
|
'latest' => '最新',
|
||||||
'popular' => '最热',
|
'popular' => '最热',
|
||||||
|
'featured' => '推荐',
|
||||||
'free' => '免费',
|
'free' => '免费',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,6 @@ class Online extends Model
|
|||||||
*/
|
*/
|
||||||
public $user_id;
|
public $user_id;
|
||||||
|
|
||||||
/**
|
|
||||||
* 计划编号
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public $date;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端类型
|
* 客户端类型
|
||||||
*
|
*
|
||||||
|
93
app/Models/UserSession.php
Normal file
93
app/Models/UserSession.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||||
|
|
||||||
|
class UserSession extends Model
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键编号
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $user_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话编号
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $session_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端类型
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $client_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端IP
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $client_ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除标识
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $create_time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $update_time;
|
||||||
|
|
||||||
|
public function getSource(): string
|
||||||
|
{
|
||||||
|
return 'kg_user_session';
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
93
app/Models/UserToken.php
Normal file
93
app/Models/UserToken.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||||
|
|
||||||
|
class UserToken extends Model
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键编号
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $user_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端类型
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $client_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端IP
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $client_ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除标识
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $create_time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $update_time;
|
||||||
|
|
||||||
|
public function getSource(): string
|
||||||
|
{
|
||||||
|
return 'kg_user_token';
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,13 +23,14 @@ class Annotation extends Provider
|
|||||||
if ($config->get('env') == ENV_DEV) {
|
if ($config->get('env') == ENV_DEV) {
|
||||||
$annotations = new MemoryAnnotations();
|
$annotations = new MemoryAnnotations();
|
||||||
} else {
|
} else {
|
||||||
|
$statsKey = '_ANNOTATION_';
|
||||||
$annotations = new RedisAnnotations([
|
$annotations = new RedisAnnotations([
|
||||||
'host' => $config->path('redis.host'),
|
'host' => $config->path('redis.host'),
|
||||||
'port' => $config->path('redis.port'),
|
'port' => $config->path('redis.port'),
|
||||||
'auth' => $config->path('redis.auth'),
|
'auth' => $config->path('redis.auth'),
|
||||||
'index' => $config->path('annotation.db'),
|
'lifetime' => $config->path('annotation.lifetime') ?: 30 * 86400,
|
||||||
'lifetime' => $config->path('annotation.lifetime'),
|
'prefix' => $statsKey . ':',
|
||||||
'statsKey' => $config->path('annotation.statsKey'),
|
'statsKey' => $statsKey,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,13 +23,14 @@ class MetaData extends Provider
|
|||||||
if ($config->get('env') == ENV_DEV) {
|
if ($config->get('env') == ENV_DEV) {
|
||||||
$metaData = new MemoryMetaData();
|
$metaData = new MemoryMetaData();
|
||||||
} else {
|
} else {
|
||||||
|
$statsKey = '_METADATA_';
|
||||||
$metaData = new RedisMetaData([
|
$metaData = new RedisMetaData([
|
||||||
'host' => $config->path('redis.host'),
|
'host' => $config->path('redis.host'),
|
||||||
'port' => $config->path('redis.port'),
|
'port' => $config->path('redis.port'),
|
||||||
'auth' => $config->path('redis.auth'),
|
'auth' => $config->path('redis.auth'),
|
||||||
'index' => $config->path('metadata.db'),
|
'lifetime' => $config->path('metadata.lifetime') ?: 30 * 86400,
|
||||||
'statsKey' => $config->path('metadata.statsKey'),
|
'prefix' => $statsKey . ':',
|
||||||
'lifetime' => $config->path('metadata.lifetime'),
|
'statsKey' => $statsKey,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ class Session extends Provider
|
|||||||
'host' => $config->path('redis.host'),
|
'host' => $config->path('redis.host'),
|
||||||
'port' => $config->path('redis.port'),
|
'port' => $config->path('redis.port'),
|
||||||
'auth' => $config->path('redis.auth'),
|
'auth' => $config->path('redis.auth'),
|
||||||
'index' => $config->path('session.db'),
|
'lifetime' => $config->path('session.lifetime') ?: 24 * 3600,
|
||||||
'lifetime' => $config->path('session.lifetime'),
|
'prefix' => '_SESSION_:',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$session->start();
|
$session->start();
|
||||||
|
@ -67,6 +67,10 @@ class Course extends Repository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($where['featured'])) {
|
||||||
|
$builder->andWhere('featured = :featured:', ['featured' => $where['featured']]);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($where['published'])) {
|
if (isset($where['published'])) {
|
||||||
$builder->andWhere('published = :published:', ['published' => $where['published']]);
|
$builder->andWhere('published = :published:', ['published' => $where['published']]);
|
||||||
}
|
}
|
||||||
@ -77,6 +81,8 @@ class Course extends Repository
|
|||||||
|
|
||||||
if ($sort == 'free') {
|
if ($sort == 'free') {
|
||||||
$builder->andWhere('market_price = 0');
|
$builder->andWhere('market_price = 0');
|
||||||
|
} elseif ($sort == 'featured') {
|
||||||
|
$builder->andWhere('featured = 1');
|
||||||
} elseif ($sort == 'vip_discount') {
|
} elseif ($sort == 'vip_discount') {
|
||||||
$builder->andWhere('vip_price < market_price');
|
$builder->andWhere('vip_price < market_price');
|
||||||
$builder->andWhere('vip_price > 0');
|
$builder->andWhere('vip_price > 0');
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
namespace App\Repos;
|
namespace App\Repos;
|
||||||
|
|
||||||
use App\Models\Online as OnlineModel;
|
use App\Models\Online as OnlineModel;
|
||||||
use Phalcon\Mvc\Model;
|
use Phalcon\Mvc\Model\Resultset;
|
||||||
|
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||||
|
|
||||||
class Online extends Repository
|
class Online extends Repository
|
||||||
{
|
{
|
||||||
@ -11,20 +12,18 @@ class Online extends Repository
|
|||||||
/**
|
/**
|
||||||
* @param int $userId
|
* @param int $userId
|
||||||
* @param string $activeDate
|
* @param string $activeDate
|
||||||
* @return OnlineModel|Model|bool
|
* @return ResultsetInterface|Resultset|OnlineModel[]
|
||||||
*/
|
*/
|
||||||
public function findByUserDate($userId, $activeDate)
|
public function findByUserDate($userId, $activeDate)
|
||||||
{
|
{
|
||||||
$activeTime = strtotime($activeDate);
|
$startTime = strtotime($activeDate);
|
||||||
|
|
||||||
return OnlineModel::findFirst([
|
$endTime = $startTime + 86400;
|
||||||
'conditions' => 'user_id = ?1 AND active_time BETWEEN ?2 AND ?3',
|
|
||||||
'bind' => [
|
return OnlineModel::query()
|
||||||
1 => $userId,
|
->where('user_id = :user_id:', ['user_id' => $userId])
|
||||||
2 => $activeTime,
|
->betweenWhere('active_time', $startTime, $endTime)
|
||||||
3 => $activeTime + 86400,
|
->execute();
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
24
app/Repos/UserSession.php
Normal file
24
app/Repos/UserSession.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repos;
|
||||||
|
|
||||||
|
use App\Models\UserSession as UserSessionModel;
|
||||||
|
use Phalcon\Mvc\Model\Resultset;
|
||||||
|
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||||
|
|
||||||
|
class UserSession extends Repository
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $userId
|
||||||
|
* @return ResultsetInterface|Resultset|UserSessionModel[]
|
||||||
|
*/
|
||||||
|
public function findByUserId($userId)
|
||||||
|
{
|
||||||
|
return UserSessionModel::query()
|
||||||
|
->where('user_id = :user_id:', ['user_id' => $userId])
|
||||||
|
->andWhere('deleted = 0')
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
app/Repos/UserToken.php
Normal file
24
app/Repos/UserToken.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repos;
|
||||||
|
|
||||||
|
use App\Models\UserToken as UserTokenModel;
|
||||||
|
use Phalcon\Mvc\Model\Resultset;
|
||||||
|
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||||
|
|
||||||
|
class UserToken extends Repository
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $userId
|
||||||
|
* @return ResultsetInterface|Resultset|UserTokenModel[]
|
||||||
|
*/
|
||||||
|
public function findByUserId($userId)
|
||||||
|
{
|
||||||
|
return UserTokenModel::query()
|
||||||
|
->where('user_id = :user_id:', ['user_id' => $userId])
|
||||||
|
->andWhere('deleted = 0')
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,70 +3,114 @@
|
|||||||
namespace App\Services\Auth;
|
namespace App\Services\Auth;
|
||||||
|
|
||||||
use App\Models\User as UserModel;
|
use App\Models\User as UserModel;
|
||||||
|
use App\Models\UserToken as UserTokenModel;
|
||||||
|
use App\Repos\UserToken as UserTokenRepo;
|
||||||
use App\Services\Auth as AuthService;
|
use App\Services\Auth as AuthService;
|
||||||
use Lcobucci\JWT\Builder as JwtBuilder;
|
use App\Traits\Client as ClientTrait;
|
||||||
use Lcobucci\JWT\Parser as JwtParser;
|
|
||||||
use Lcobucci\JWT\Signer\Hmac\Sha256 as JwtSingerSha256;
|
|
||||||
use Lcobucci\JWT\Signer\Key as JwtSingerKey;
|
|
||||||
use Lcobucci\JWT\ValidationData as JwtValidationData;
|
|
||||||
|
|
||||||
class Api extends AuthService
|
class Api extends AuthService
|
||||||
{
|
{
|
||||||
|
|
||||||
|
use ClientTrait;
|
||||||
|
|
||||||
public function saveAuthInfo(UserModel $user)
|
public function saveAuthInfo(UserModel $user)
|
||||||
{
|
{
|
||||||
$builder = new JwtBuilder();
|
$token = $this->generateToken($user->id);
|
||||||
|
|
||||||
|
$this->logoutOtherClients($user->id);
|
||||||
|
|
||||||
|
$this->createUserToken($user->id, $token);
|
||||||
|
|
||||||
|
$cache = $this->getCache();
|
||||||
|
|
||||||
|
$key = $this->getTokenCacheKey($token);
|
||||||
|
|
||||||
|
$authInfo = [
|
||||||
|
'id' => $user->id,
|
||||||
|
'name' => $user->name,
|
||||||
|
];
|
||||||
|
|
||||||
$config = $this->getConfig();
|
$config = $this->getConfig();
|
||||||
|
|
||||||
$expireTime = time() + $config->path('jwt.lifetime');
|
$lifetime = $config->path('token.lifetime') ?: 7 * 86400;
|
||||||
|
|
||||||
$builder->expiresAt($expireTime);
|
$cache->save($key, $authInfo, $lifetime);
|
||||||
$builder->withClaim('user_id', $user->id);
|
|
||||||
$builder->withClaim('user_name', $user->name);
|
|
||||||
|
|
||||||
$singer = new JwtSingerSha256();
|
return $token;
|
||||||
|
|
||||||
$key = new JwtSingerKey($config->path('jwt.key'));
|
|
||||||
|
|
||||||
$token = $builder->getToken($singer, $key);
|
|
||||||
|
|
||||||
return $token->__toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clearAuthInfo()
|
public function clearAuthInfo()
|
||||||
{
|
{
|
||||||
|
$token = $this->request->getHeader('X-Token');
|
||||||
|
|
||||||
|
if (empty($token)) return null;
|
||||||
|
|
||||||
|
$cache = $this->getCache();
|
||||||
|
|
||||||
|
$key = $this->getTokenCacheKey($token);
|
||||||
|
|
||||||
|
$cache->delete($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAuthInfo()
|
public function getAuthInfo()
|
||||||
{
|
{
|
||||||
$authToken = $this->request->getHeader('X-Token');
|
$token = $this->request->getHeader('X-Token');
|
||||||
|
|
||||||
if (!$authToken) return null;
|
if (empty($token)) return null;
|
||||||
|
|
||||||
$config = $this->getConfig();
|
$cache = $this->getCache();
|
||||||
|
|
||||||
$parser = new JWTParser();
|
$key = $this->getTokenCacheKey($token);
|
||||||
|
|
||||||
$token = $parser->parse($authToken);
|
$authInfo = $cache->get($key);
|
||||||
|
|
||||||
$data = new JWTValidationData(time(), $config->path('jwt.leeway'));
|
return $authInfo ?: null;
|
||||||
|
|
||||||
if (!$token->validate($data)) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$singer = new JwtSingerSha256();
|
protected function createUserToken($userId, $token)
|
||||||
|
{
|
||||||
|
$userToken = new UserTokenModel();
|
||||||
|
|
||||||
if (!$token->verify($singer, $config->path('jwt.key'))) {
|
$userToken->user_id = $userId;
|
||||||
return null;
|
$userToken->token = $token;
|
||||||
|
$userToken->client_type = $this->getClientType();
|
||||||
|
$userToken->client_ip = $this->getClientIp();
|
||||||
|
|
||||||
|
$userToken->create();
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
protected function logoutOtherClients($userId)
|
||||||
'id' => $token->getClaim('user_id'),
|
{
|
||||||
'name' => $token->getClaim('user_name'),
|
$repo = new UserTokenRepo();
|
||||||
];
|
|
||||||
|
$records = $repo->findByUserId($userId);
|
||||||
|
|
||||||
|
$cache = $this->getCache();
|
||||||
|
|
||||||
|
$clientType = $this->getClientType();
|
||||||
|
|
||||||
|
if ($records->count() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($records as $record) {
|
||||||
|
if ($record->client_type == $clientType) {
|
||||||
|
$record->deleted = 1;
|
||||||
|
$record->update();
|
||||||
|
$key = $this->getTokenCacheKey($record->token);
|
||||||
|
$cache->delete($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateToken($userId)
|
||||||
|
{
|
||||||
|
return md5(uniqid() . time() . $userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTokenCacheKey($token)
|
||||||
|
{
|
||||||
|
return "_PHCR_TOKEN_:{$token}";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,24 @@
|
|||||||
namespace App\Services\Auth;
|
namespace App\Services\Auth;
|
||||||
|
|
||||||
use App\Models\User as UserModel;
|
use App\Models\User as UserModel;
|
||||||
|
use App\Models\UserSession as UserSessionModel;
|
||||||
|
use App\Repos\UserSession as UserSessionRepo;
|
||||||
use App\Services\Auth as AuthService;
|
use App\Services\Auth as AuthService;
|
||||||
|
use App\Traits\Client as ClientTrait;
|
||||||
|
|
||||||
class Home extends AuthService
|
class Home extends AuthService
|
||||||
{
|
{
|
||||||
|
|
||||||
|
use ClientTrait;
|
||||||
|
|
||||||
public function saveAuthInfo(UserModel $user)
|
public function saveAuthInfo(UserModel $user)
|
||||||
{
|
{
|
||||||
|
$sessionId = $this->session->getId();
|
||||||
|
|
||||||
|
$this->logoutOtherClients($user->id);
|
||||||
|
|
||||||
|
$this->createUserSession($user->id, $sessionId);
|
||||||
|
|
||||||
$authKey = $this->getAuthKey();
|
$authKey = $this->getAuthKey();
|
||||||
|
|
||||||
$authInfo = [
|
$authInfo = [
|
||||||
@ -41,4 +52,41 @@ class Home extends AuthService
|
|||||||
return 'home_auth_info';
|
return 'home_auth_info';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function createUserSession($userId, $sessionId)
|
||||||
|
{
|
||||||
|
$userSession = new UserSessionModel();
|
||||||
|
|
||||||
|
$userSession->user_id = $userId;
|
||||||
|
$userSession->session_id = $sessionId;
|
||||||
|
$userSession->client_type = $this->getClientType();
|
||||||
|
$userSession->client_ip = $this->getClientIp();
|
||||||
|
|
||||||
|
$userSession->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function logoutOtherClients($userId)
|
||||||
|
{
|
||||||
|
$cache = $this->getCache();
|
||||||
|
|
||||||
|
$repo = new UserSessionRepo();
|
||||||
|
|
||||||
|
$records = $repo->findByUserId($userId);
|
||||||
|
|
||||||
|
if ($records->count() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($records as $record) {
|
||||||
|
$record->deleted = 1;
|
||||||
|
$record->update();
|
||||||
|
$key = $this->getSessionCacheKey($record->session_id);
|
||||||
|
$cache->delete($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSessionCacheKey($sessionId)
|
||||||
|
{
|
||||||
|
return "_PHCR_SESSION_:{$sessionId}";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Course as CourseModel;
|
||||||
use App\Repos\Course as CourseRepo;
|
use App\Repos\Course as CourseRepo;
|
||||||
use App\Repos\CourseRating as CourseRatingRepo;
|
use App\Repos\CourseRating as CourseRatingRepo;
|
||||||
|
|
||||||
@ -58,9 +59,19 @@ class CourseStat extends Service
|
|||||||
|
|
||||||
public function updateScore($courseId)
|
public function updateScore($courseId)
|
||||||
{
|
{
|
||||||
/**
|
$courseRepo = new CourseRepo();
|
||||||
* @todo 计算综合评分
|
|
||||||
*/
|
$course = $courseRepo->findById($courseId);
|
||||||
|
|
||||||
|
if ($course->market_price == 0) {
|
||||||
|
$score = $this->calculateFreeCourseScore($course);
|
||||||
|
} else {
|
||||||
|
$score = $this->calculateChargeCourseScore($course);
|
||||||
|
}
|
||||||
|
|
||||||
|
$course->score = $score;
|
||||||
|
|
||||||
|
$course->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateReadAttrs($courseId)
|
public function updateReadAttrs($courseId)
|
||||||
@ -181,4 +192,72 @@ class CourseStat extends Service
|
|||||||
$course->update(['attrs' => $attrs]);
|
$course->update(['attrs' => $attrs]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function calculateFreeCourseScore(CourseModel $course)
|
||||||
|
{
|
||||||
|
$weight = [
|
||||||
|
'factor1' => 0.1,
|
||||||
|
'factor2' => 0.25,
|
||||||
|
'factor3' => 0.2,
|
||||||
|
'factor4' => 0.1,
|
||||||
|
'factor5' => 0.25,
|
||||||
|
'factor6' => 0.1,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->calculateCourseScore($course, $weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function calculateChargeCourseScore(CourseModel $course)
|
||||||
|
{
|
||||||
|
$weight = [
|
||||||
|
'factor1' => 0.1,
|
||||||
|
'factor2' => 0.3,
|
||||||
|
'factor3' => 0.15,
|
||||||
|
'factor4' => 0.15,
|
||||||
|
'factor5' => 0.2,
|
||||||
|
'factor6' => 0.1,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->calculateCourseScore($course, $weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function calculateCourseScore(CourseModel $course, $weight)
|
||||||
|
{
|
||||||
|
$items = [
|
||||||
|
'factor1' => 0.0,
|
||||||
|
'factor2' => 0.0,
|
||||||
|
'factor3' => 0.0,
|
||||||
|
'factor4' => 0.0,
|
||||||
|
'factor5' => 0.0,
|
||||||
|
'factor6' => 0.0,
|
||||||
|
];
|
||||||
|
|
||||||
|
$items['factor1'] = ($course->featured == 1 ? 1 : 0) * 10 * $weight['factor1'];
|
||||||
|
|
||||||
|
if ($course->user_count > 0) {
|
||||||
|
$items['factor2'] = log($course->user_count) * $weight['factor2'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($course->favorite_count > 0) {
|
||||||
|
$items['factor3'] = log($course->favorite_count) * $weight['factor3'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($course->consult_count > 0) {
|
||||||
|
$items['factor4'] = log($course->consult_count) * $weight['factor4'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($course->review_count > 0 && $course->rating > 0) {
|
||||||
|
$items['factor5'] = log($course->review_count * $course->rating) * $weight['factor5'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sumCount = $course->lesson_count + $course->package_count + $course->resource_count;
|
||||||
|
|
||||||
|
if ($sumCount > 0) {
|
||||||
|
$items['factor6'] = log($sumCount) * $weight['factor6'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$score = array_sum($items) / log(time() - $course->create_time);
|
||||||
|
|
||||||
|
return round($score, 4);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
33
app/Services/Sync/CourseScore.php
Normal file
33
app/Services/Sync/CourseScore.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Sync;
|
||||||
|
|
||||||
|
use App\Services\Service;
|
||||||
|
|
||||||
|
class CourseScore extends Service
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $lifetime = 86400;
|
||||||
|
|
||||||
|
public function addItem($courseId)
|
||||||
|
{
|
||||||
|
$redis = $this->getRedis();
|
||||||
|
|
||||||
|
$key = $this->getSyncKey();
|
||||||
|
|
||||||
|
$redis->sAdd($key, $courseId);
|
||||||
|
|
||||||
|
if ($redis->sCard($key) == 1) {
|
||||||
|
$redis->expire($key, $this->lifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSyncKey()
|
||||||
|
{
|
||||||
|
return 'sync_course_score';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
app/Services/Utils/IndexCourseCache.php
Normal file
65
app/Services/Utils/IndexCourseCache.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Utils;
|
||||||
|
|
||||||
|
use App\Caches\IndexFeaturedCourseList as IndexFeaturedCourseListCache;
|
||||||
|
use App\Caches\IndexFreeCourseList as IndexFreeCourseListCache;
|
||||||
|
use App\Caches\IndexNewCourseList as IndexNewCourseListCache;
|
||||||
|
use App\Caches\IndexSimpleFeaturedCourseList as IndexSimpleFeaturedCourseListCache;
|
||||||
|
use App\Caches\IndexSimpleFreeCourseList as IndexSimpleFreeCourseListCache;
|
||||||
|
use App\Caches\IndexSimpleNewCourseList as IndexSimpleNewCourseListCache;
|
||||||
|
use App\Caches\IndexSimpleVipCourseList as IndexSimpleVipCourseListCache;
|
||||||
|
use App\Caches\IndexVipCourseList as IndexVipCourseListCache;
|
||||||
|
use App\Services\Service;
|
||||||
|
|
||||||
|
class IndexCourseCache extends Service
|
||||||
|
{
|
||||||
|
|
||||||
|
public function rebuild($section = null)
|
||||||
|
{
|
||||||
|
$site = $this->getSettings('site');
|
||||||
|
|
||||||
|
$type = $site['index_tpl_type'] ?: 'full';
|
||||||
|
|
||||||
|
if (!$section || $section == 'featured_course') {
|
||||||
|
if ($type == 'full') {
|
||||||
|
$cache = new IndexFeaturedCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
} else {
|
||||||
|
$cache = new IndexSimpleFeaturedCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$section || $section == 'new_course') {
|
||||||
|
if ($type == 'full') {
|
||||||
|
$cache = new IndexNewCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
} else {
|
||||||
|
$cache = new IndexSimpleNewCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$section || $section == 'free_course') {
|
||||||
|
if ($type == 'full') {
|
||||||
|
$cache = new IndexFreeCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
} else {
|
||||||
|
$cache = new IndexSimpleFreeCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$section || $section == 'vip_course') {
|
||||||
|
if ($type == 'full') {
|
||||||
|
$cache = new IndexVipCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
} else {
|
||||||
|
$cache = new IndexSimpleVipCourseListCache();
|
||||||
|
$cache->rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -27,6 +27,14 @@ trait Client
|
|||||||
*/
|
*/
|
||||||
$request = Di::getDefault()->get('request');
|
$request = Di::getDefault()->get('request');
|
||||||
|
|
||||||
|
$platform = $request->getHeader('X-Platform');
|
||||||
|
|
||||||
|
$types = array_flip(ClientModel::types());
|
||||||
|
|
||||||
|
if (!empty($platform) && isset($types[$platform])) {
|
||||||
|
return $types[$platform];
|
||||||
|
}
|
||||||
|
|
||||||
$userAgent = $request->getServer('HTTP_USER_AGENT');
|
$userAgent = $request->getServer('HTTP_USER_AGENT');
|
||||||
|
|
||||||
$result = new BrowserParser($userAgent);
|
$result = new BrowserParser($userAgent);
|
||||||
|
@ -212,6 +212,15 @@ class Course extends Validator
|
|||||||
return $expiry;
|
return $expiry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkFeatureStatus($status)
|
||||||
|
{
|
||||||
|
if (!in_array($status, [0, 1])) {
|
||||||
|
throw new BadRequestException('course.invalid_feature_status');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
public function checkPublishStatus($status)
|
public function checkPublishStatus($status)
|
||||||
{
|
{
|
||||||
if (!in_array($status, [0, 1])) {
|
if (!in_array($status, [0, 1])) {
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"overtrue/wechat": "^4.2"
|
"overtrue/wechat": "^4.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"odan/phinx-migrations-generator": "^5.1",
|
"odan/phinx-migrations-generator": "^5.3",
|
||||||
"phalcon/ide-stubs": "^3.4"
|
"phalcon/ide-stubs": "^3.4"
|
||||||
},
|
},
|
||||||
"repositories": {
|
"repositories": {
|
||||||
|
21
composer.lock
generated
21
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "60ff0e1868be7414a1b31d397ced7fbd",
|
"content-hash": "09a618cffed2c4cfb593c0a791c19b3f",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aferrandini/phpqrcode",
|
"name": "aferrandini/phpqrcode",
|
||||||
@ -4194,16 +4194,16 @@
|
|||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "odan/phinx-migrations-generator",
|
"name": "odan/phinx-migrations-generator",
|
||||||
"version": "5.1.2",
|
"version": "5.3.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/odan/phinx-migrations-generator.git",
|
"url": "https://github.com/odan/phinx-migrations-generator.git",
|
||||||
"reference": "f3cb7cc6bc7eb22e85f34f229b6d476e96d99c73"
|
"reference": "2d3620f8251838b53717f7a43a348de31e9d451c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/odan/phinx-migrations-generator/zipball/f3cb7cc6bc7eb22e85f34f229b6d476e96d99c73",
|
"url": "https://api.github.com/repos/odan/phinx-migrations-generator/zipball/2d3620f8251838b53717f7a43a348de31e9d451c",
|
||||||
"reference": "f3cb7cc6bc7eb22e85f34f229b6d476e96d99c73",
|
"reference": "2d3620f8251838b53717f7a43a348de31e9d451c",
|
||||||
"shasum": "",
|
"shasum": "",
|
||||||
"mirrors": [
|
"mirrors": [
|
||||||
{
|
{
|
||||||
@ -4218,11 +4218,12 @@
|
|||||||
"php": "^7.2",
|
"php": "^7.2",
|
||||||
"riimu/kit-phpencoder": "^2.4",
|
"riimu/kit-phpencoder": "^2.4",
|
||||||
"robmorgan/phinx": "^0.12",
|
"robmorgan/phinx": "^0.12",
|
||||||
"symfony/console": "^2.8 || ^3.0 || ^4.0 || ^5.0"
|
"symfony/console": "^2.8 || ^3.0 || ^4.0 || ^5.0",
|
||||||
|
"symfony/polyfill-php73": "^1.18"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"friendsofphp/php-cs-fixer": "^2.16",
|
"friendsofphp/php-cs-fixer": "^2.16",
|
||||||
"overtrue/phplint": "^1.1",
|
"overtrue/phplint": "^1.1 || ^2.0",
|
||||||
"phpstan/phpstan": "^0.12",
|
"phpstan/phpstan": "^0.12",
|
||||||
"phpunit/phpunit": "^8 || ^9",
|
"phpunit/phpunit": "^8 || ^9",
|
||||||
"squizlabs/php_codesniffer": "^3.4"
|
"squizlabs/php_codesniffer": "^3.4"
|
||||||
@ -4250,7 +4251,11 @@
|
|||||||
"mysql",
|
"mysql",
|
||||||
"phinx"
|
"phinx"
|
||||||
],
|
],
|
||||||
"time": "2020-06-15T19:36:35+00:00"
|
"support": {
|
||||||
|
"issues": "https://github.com/odan/phinx-migrations-generator/issues",
|
||||||
|
"source": "https://github.com/odan/phinx-migrations-generator/tree/5.3.2"
|
||||||
|
},
|
||||||
|
"time": "2020-12-30T23:59:57+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phalcon/ide-stubs",
|
"name": "phalcon/ide-stubs",
|
||||||
|
@ -83,70 +83,30 @@ $config['redis']['port'] = 6379;
|
|||||||
$config['redis']['auth'] = '1qaz2wsx3edc';
|
$config['redis']['auth'] = '1qaz2wsx3edc';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* redis库编号
|
* 缓存有效期(秒)
|
||||||
*/
|
|
||||||
$config['cache']['db'] = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有效期(秒)
|
|
||||||
*/
|
*/
|
||||||
$config['cache']['lifetime'] = 24 * 3600;
|
$config['cache']['lifetime'] = 24 * 3600;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* redis库编号
|
* 会话有效期(秒)
|
||||||
*/
|
|
||||||
$config['session']['db'] = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有效期(秒)
|
|
||||||
*/
|
*/
|
||||||
$config['session']['lifetime'] = 24 * 3600;
|
$config['session']['lifetime'] = 24 * 3600;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* redis库编号
|
* 令牌有效期(秒)
|
||||||
*/
|
*/
|
||||||
$config['metadata']['db'] = 2;
|
$config['token']['lifetime'] = 7 * 86400;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 有效期(秒)
|
* 元数据有效期(秒)
|
||||||
*/
|
*/
|
||||||
$config['metadata']['lifetime'] = 7 * 86400;
|
$config['metadata']['lifetime'] = 7 * 86400;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* statsKey
|
* 注解有效期(秒)
|
||||||
*/
|
|
||||||
$config['metadata']['statsKey'] = '_METADATA_';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* redis库编号
|
|
||||||
*/
|
|
||||||
$config['annotation']['db'] = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有效期(秒)
|
|
||||||
*/
|
*/
|
||||||
$config['annotation']['lifetime'] = 7 * 86400;
|
$config['annotation']['lifetime'] = 7 * 86400;
|
||||||
|
|
||||||
/**
|
|
||||||
* statsKey
|
|
||||||
*/
|
|
||||||
$config['annotation']['statsKey'] = '_ANNOTATION_';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密钥
|
|
||||||
*/
|
|
||||||
$config['jwt']['key'] = 'fu6ckEc8pv8k5K7m';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有效期(秒)
|
|
||||||
*/
|
|
||||||
$config['jwt']['lifetime'] = 7 * 86400;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回旋时间(秒)
|
|
||||||
*/
|
|
||||||
$config['jwt']['leeway'] = 30;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 允许跨域
|
* 允许跨域
|
||||||
*/
|
*/
|
||||||
|
@ -114,6 +114,7 @@ $error['course.invalid_vip_price'] = '无效的会员价格(范围:0-10000
|
|||||||
$error['course.invalid_compare_price'] = '无效的比较定价(会员价格高于市场价格)';
|
$error['course.invalid_compare_price'] = '无效的比较定价(会员价格高于市场价格)';
|
||||||
$error['course.invalid_study_expiry'] = '无效的学习期限';
|
$error['course.invalid_study_expiry'] = '无效的学习期限';
|
||||||
$error['course.invalid_refund_expiry'] = '无效的退款期限';
|
$error['course.invalid_refund_expiry'] = '无效的退款期限';
|
||||||
|
$error['course.invalid_feature_status'] = '无效的推荐状态';
|
||||||
$error['course.invalid_publish_status'] = '无效的发布状态';
|
$error['course.invalid_publish_status'] = '无效的发布状态';
|
||||||
$error['course.pub_chapter_not_found'] = '尚未发现已发布的课时';
|
$error['course.pub_chapter_not_found'] = '尚未发现已发布的课时';
|
||||||
$error['course.pub_chapter_not_enough'] = '已发布的课时太少(小于30%)';
|
$error['course.pub_chapter_not_enough'] = '已发布的课时太少(小于30%)';
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ use Phinx\Db\Adapter\MysqlAdapter;
|
|||||||
|
|
||||||
class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
||||||
{
|
{
|
||||||
|
|
||||||
public function change()
|
public function change()
|
||||||
{
|
{
|
||||||
$this->table('kg_online', [
|
$this->table('kg_online', [
|
||||||
@ -18,6 +19,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
->addColumn('id', 'integer', [
|
->addColumn('id', 'integer', [
|
||||||
'null' => false,
|
'null' => false,
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'identity' => 'enable',
|
'identity' => 'enable',
|
||||||
'comment' => '主键编号',
|
'comment' => '主键编号',
|
||||||
])
|
])
|
||||||
@ -25,6 +27,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '用户编号',
|
'comment' => '用户编号',
|
||||||
'after' => 'id',
|
'after' => 'id',
|
||||||
])
|
])
|
||||||
@ -32,6 +35,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '1',
|
'default' => '1',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '终端类型',
|
'comment' => '终端类型',
|
||||||
'after' => 'user_id',
|
'after' => 'user_id',
|
||||||
])
|
])
|
||||||
@ -48,6 +52,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '活跃时间',
|
'comment' => '活跃时间',
|
||||||
'after' => 'client_ip',
|
'after' => 'client_ip',
|
||||||
])
|
])
|
||||||
@ -55,6 +60,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '创建时间',
|
'comment' => '创建时间',
|
||||||
'after' => 'active_time',
|
'after' => 'active_time',
|
||||||
])
|
])
|
||||||
@ -62,6 +68,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '更新时间',
|
'comment' => '更新时间',
|
||||||
'after' => 'create_time',
|
'after' => 'create_time',
|
||||||
])
|
])
|
||||||
@ -90,7 +97,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
'engine' => 'InnoDB',
|
'engine' => 'InnoDB',
|
||||||
'encoding' => 'utf8mb4',
|
'encoding' => 'utf8mb4',
|
||||||
'collation' => 'utf8mb4_general_ci',
|
'collation' => 'utf8mb4_general_ci',
|
||||||
'comment' => '',
|
'comment' => '主键编号',
|
||||||
'row_format' => 'DYNAMIC',
|
'row_format' => 'DYNAMIC',
|
||||||
])
|
])
|
||||||
->changeColumn('channel_sn', 'string', [
|
->changeColumn('channel_sn', 'string', [
|
||||||
@ -104,4 +111,5 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
|
|||||||
])
|
])
|
||||||
->save();
|
->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use Phinx\Db\Adapter\MysqlAdapter;
|
|||||||
|
|
||||||
class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
||||||
{
|
{
|
||||||
|
|
||||||
public function change()
|
public function change()
|
||||||
{
|
{
|
||||||
$this->table('kg_connect', [
|
$this->table('kg_connect', [
|
||||||
@ -18,6 +19,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
|||||||
->addColumn('id', 'integer', [
|
->addColumn('id', 'integer', [
|
||||||
'null' => false,
|
'null' => false,
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'identity' => 'enable',
|
'identity' => 'enable',
|
||||||
'comment' => '主键编号',
|
'comment' => '主键编号',
|
||||||
])
|
])
|
||||||
@ -25,13 +27,14 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '用户编号',
|
'comment' => '用户编号',
|
||||||
'after' => 'id',
|
'after' => 'id',
|
||||||
])
|
])
|
||||||
->addColumn('open_id', 'string', [
|
->addColumn('open_id', 'string', [
|
||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'limit' => 50,
|
'limit' => 64,
|
||||||
'collation' => 'utf8mb4_general_ci',
|
'collation' => 'utf8mb4_general_ci',
|
||||||
'encoding' => 'utf8mb4',
|
'encoding' => 'utf8mb4',
|
||||||
'comment' => '开放ID',
|
'comment' => '开放ID',
|
||||||
@ -59,6 +62,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '提供方',
|
'comment' => '提供方',
|
||||||
'after' => 'open_avatar',
|
'after' => 'open_avatar',
|
||||||
])
|
])
|
||||||
@ -66,6 +70,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '删除标识',
|
'comment' => '删除标识',
|
||||||
'after' => 'provider',
|
'after' => 'provider',
|
||||||
])
|
])
|
||||||
@ -73,6 +78,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '创建时间',
|
'comment' => '创建时间',
|
||||||
'after' => 'deleted',
|
'after' => 'deleted',
|
||||||
])
|
])
|
||||||
@ -80,6 +86,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '更新时间',
|
'comment' => '更新时间',
|
||||||
'after' => 'create_time',
|
'after' => 'create_time',
|
||||||
])
|
])
|
||||||
@ -89,4 +96,5 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
|
|||||||
])
|
])
|
||||||
->create();
|
->create();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use Phinx\Db\Adapter\MysqlAdapter;
|
|||||||
|
|
||||||
class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
||||||
{
|
{
|
||||||
|
|
||||||
public function change()
|
public function change()
|
||||||
{
|
{
|
||||||
$this->table('kg_consult')
|
$this->table('kg_consult')
|
||||||
@ -11,6 +12,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '回复者编号',
|
'comment' => '回复者编号',
|
||||||
'after' => 'owner_id',
|
'after' => 'owner_id',
|
||||||
])
|
])
|
||||||
@ -19,7 +21,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
->addColumn('union_id', 'string', [
|
->addColumn('union_id', 'string', [
|
||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'limit' => 50,
|
'limit' => 64,
|
||||||
'collation' => 'utf8mb4_general_ci',
|
'collation' => 'utf8mb4_general_ci',
|
||||||
'encoding' => 'utf8mb4',
|
'encoding' => 'utf8mb4',
|
||||||
'comment' => 'union_id',
|
'comment' => 'union_id',
|
||||||
@ -46,6 +48,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
->addColumn('id', 'integer', [
|
->addColumn('id', 'integer', [
|
||||||
'null' => false,
|
'null' => false,
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'identity' => 'enable',
|
'identity' => 'enable',
|
||||||
'comment' => '主键编号',
|
'comment' => '主键编号',
|
||||||
])
|
])
|
||||||
@ -53,13 +56,14 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '用户编号',
|
'comment' => '用户编号',
|
||||||
'after' => 'id',
|
'after' => 'id',
|
||||||
])
|
])
|
||||||
->addColumn('open_id', 'string', [
|
->addColumn('open_id', 'string', [
|
||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'limit' => 50,
|
'limit' => 64,
|
||||||
'collation' => 'utf8mb4_general_ci',
|
'collation' => 'utf8mb4_general_ci',
|
||||||
'encoding' => 'utf8mb4',
|
'encoding' => 'utf8mb4',
|
||||||
'comment' => '开放ID',
|
'comment' => '开放ID',
|
||||||
@ -69,6 +73,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '删除标识',
|
'comment' => '删除标识',
|
||||||
'after' => 'open_id',
|
'after' => 'open_id',
|
||||||
])
|
])
|
||||||
@ -76,6 +81,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '创建时间',
|
'comment' => '创建时间',
|
||||||
'after' => 'deleted',
|
'after' => 'deleted',
|
||||||
])
|
])
|
||||||
@ -83,6 +89,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
'null' => false,
|
'null' => false,
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'limit' => MysqlAdapter::INT_REGULAR,
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
'comment' => '更新时间',
|
'comment' => '更新时间',
|
||||||
'after' => 'create_time',
|
'after' => 'create_time',
|
||||||
])
|
])
|
||||||
@ -96,4 +103,5 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
|
|||||||
])
|
])
|
||||||
->create();
|
->create();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
22
db/migrations/20201227081614_schema_202012271615.php
Normal file
22
db/migrations/20201227081614_schema_202012271615.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Phinx\Db\Adapter\MysqlAdapter;
|
||||||
|
|
||||||
|
class Schema202012271615 extends Phinx\Migration\AbstractMigration
|
||||||
|
{
|
||||||
|
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$this->table('kg_course')
|
||||||
|
->addColumn('featured', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '推荐标识',
|
||||||
|
'after' => 'attrs',
|
||||||
|
])
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
170
db/migrations/20210102041941_schema_202101021220.php
Normal file
170
db/migrations/20210102041941_schema_202101021220.php
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Phinx\Db\Adapter\MysqlAdapter;
|
||||||
|
|
||||||
|
class Schema202101021220 extends Phinx\Migration\AbstractMigration
|
||||||
|
{
|
||||||
|
|
||||||
|
public function change()
|
||||||
|
{
|
||||||
|
$this->table('kg_user_session', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'engine' => 'InnoDB',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'comment' => '',
|
||||||
|
'row_format' => 'DYNAMIC',
|
||||||
|
])
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'identity' => 'enable',
|
||||||
|
'comment' => '主键编号',
|
||||||
|
])
|
||||||
|
->addColumn('user_id', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '用户编号',
|
||||||
|
'after' => 'id',
|
||||||
|
])
|
||||||
|
->addColumn('session_id', 'string', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '',
|
||||||
|
'limit' => 64,
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
'comment' => '会话编号',
|
||||||
|
'after' => 'user_id',
|
||||||
|
])
|
||||||
|
->addColumn('client_type', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '终端类型',
|
||||||
|
'after' => 'session_id',
|
||||||
|
])
|
||||||
|
->addColumn('client_ip', 'string', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '',
|
||||||
|
'limit' => 64,
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
'comment' => '终端IP',
|
||||||
|
'after' => 'client_type',
|
||||||
|
])
|
||||||
|
->addColumn('deleted', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '删除标识',
|
||||||
|
'after' => 'client_ip',
|
||||||
|
])
|
||||||
|
->addColumn('create_time', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '创建时间',
|
||||||
|
'after' => 'deleted',
|
||||||
|
])
|
||||||
|
->addColumn('update_time', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '更新时间',
|
||||||
|
'after' => 'create_time',
|
||||||
|
])
|
||||||
|
->addIndex(['user_id'], [
|
||||||
|
'name' => 'user_id',
|
||||||
|
'unique' => false,
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
$this->table('kg_user_token', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'engine' => 'InnoDB',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'comment' => '',
|
||||||
|
'row_format' => 'DYNAMIC',
|
||||||
|
])
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'identity' => 'enable',
|
||||||
|
'comment' => '主键编号',
|
||||||
|
])
|
||||||
|
->addColumn('user_id', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '用户编号',
|
||||||
|
'after' => 'id',
|
||||||
|
])
|
||||||
|
->addColumn('token', 'string', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '',
|
||||||
|
'limit' => 64,
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
'comment' => '身份令牌',
|
||||||
|
'after' => 'user_id',
|
||||||
|
])
|
||||||
|
->addColumn('client_type', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '终端类型',
|
||||||
|
'after' => 'token',
|
||||||
|
])
|
||||||
|
->addColumn('client_ip', 'string', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '',
|
||||||
|
'limit' => 64,
|
||||||
|
'collation' => 'utf8mb4_general_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
'comment' => '终端IP',
|
||||||
|
'after' => 'client_type',
|
||||||
|
])
|
||||||
|
->addColumn('deleted', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '删除标识',
|
||||||
|
'after' => 'client_ip',
|
||||||
|
])
|
||||||
|
->addColumn('create_time', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '创建时间',
|
||||||
|
'after' => 'deleted',
|
||||||
|
])
|
||||||
|
->addColumn('update_time', 'integer', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => '0',
|
||||||
|
'limit' => MysqlAdapter::INT_REGULAR,
|
||||||
|
'signed' => false,
|
||||||
|
'comment' => '更新时间',
|
||||||
|
'after' => 'create_time',
|
||||||
|
])
|
||||||
|
->addIndex(['user_id'], [
|
||||||
|
'name' => 'user_id',
|
||||||
|
'unique' => false,
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16169
db/migrations/schema.php
Normal file
16169
db/migrations/schema.php
Normal file
File diff suppressed because it is too large
Load Diff
10331
public/sitemap.xml
10331
public/sitemap.xml
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,9 @@ $scheduler->php($script, $bin, ['--task' => 'sync_group_index', '--action' => 'm
|
|||||||
$scheduler->php($script, $bin, ['--task' => 'sync_user_index', '--action' => 'main'])
|
$scheduler->php($script, $bin, ['--task' => 'sync_user_index', '--action' => 'main'])
|
||||||
->hourly(23);
|
->hourly(23);
|
||||||
|
|
||||||
|
$scheduler->php($script, $bin, ['--task' => 'sync_course_score', '--action' => 'main'])
|
||||||
|
->hourly(29);
|
||||||
|
|
||||||
$scheduler->php($script, $bin, ['--task' => 'clean_log', '--action' => 'main'])
|
$scheduler->php($script, $bin, ['--task' => 'clean_log', '--action' => 'main'])
|
||||||
->daily(3, 3);
|
->daily(3, 3);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user