1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-24 20:06:09 +08:00

Merge branch 'develop'

This commit is contained in:
xiaochong0302 2021-01-03 14:40:55 +08:00
commit 4b9bd42d91
56 changed files with 18372 additions and 10897 deletions

1
.gitignore vendored
View File

@ -6,7 +6,6 @@
/config/xs.user.ini
/config/alipay/*.crt
/config/wxpay/*.pem
/db/migrations/schema.php
/public/robots.txt
/public/sitemap.xml
/public/h5

View File

@ -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)
#### 增加

View File

@ -6,9 +6,9 @@
酷瓜云课堂依托腾讯云基础服务架构采用C扩展框架Phalcon开发GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。
![](https://img.shields.io/static/v1?label=release&message=1.2.2&color=blue)
![](https://img.shields.io/static/v1?label=stars&message=136&color=blue)
![](https://img.shields.io/static/v1?label=forks&message=50&color=blue)
![](https://img.shields.io/static/v1?label=release&message=1.2.3&color=blue)
![](https://img.shields.io/static/v1?label=stars&message=160&color=blue)
![](https://img.shields.io/static/v1?label=forks&message=60&color=blue)
![](https://img.shields.io/static/v1?label=license&message=GPL-2.0&color=blue)
#### 系统功能

View 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();
}
}

View 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();
}
}

View File

@ -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);
}
}

View File

@ -2,14 +2,9 @@
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\Library\Utils\Password as PasswordUtil;
use App\Services\Utils\IndexCourseCache as IndexCourseCacheUtil;
use App\Validators\Account as AccountValidator;
class MaintainTask extends Task
@ -25,39 +20,9 @@ class MaintainTask extends Task
{
$section = $params[0] ?? null;
$site = $this->getSettings('site');
$util = new IndexCourseCacheUtil();
$type = $site['index_tpl_type'] ?: 'full';
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();
}
}
$util->rebuild($section);
echo 'rebuild index course cache success' . PHP_EOL;
}

View File

@ -31,7 +31,7 @@ class SitemapTask extends Task
$this->sitemap = new Sitemap();
$filename = public_path('sitemap.xml');
$filename = tmp_path('sitemap.xml');
$this->addIndex();
$this->addCourses();

View 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();
}
}

View File

@ -42,13 +42,9 @@ class UpgradeTask extends Task
*/
public function resetAnnotationAction()
{
$config = $this->getConfig();
$redis = $this->getRedis();
$dbIndex = $config->path('annotation.db');
$statsKey = $config->path('annotation.statsKey');
$redis->select($dbIndex);
$statsKey = '_ANNOTATION_';
$keys = $redis->sMembers($statsKey);
@ -70,13 +66,9 @@ class UpgradeTask extends Task
*/
public function resetMetadataAction()
{
$config = $this->getConfig();
$redis = $this->getRedis();
$dbIndex = $config->path('metadata.db');
$statsKey = $config->path('metadata.statsKey');
$redis->select($dbIndex);
$statsKey = '_METADATA_';
$keys = $redis->sMembers($statsKey);

View 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');
}
}

View File

@ -14,6 +14,7 @@ class AuthNode extends Service
$nodes[] = $this->getFinanceNodes();
$nodes[] = $this->getUserNodes();
$nodes[] = $this->getSettingNodes();
$nodes[] = $this->getUtilNodes();
return $nodes;
}
@ -768,4 +769,27 @@ class AuthNode extends Service
];
}
protected function getUtilNodes()
{
return [
'id' => '6',
'title' => '实用工具',
'children' => [
[
'id' => '-1',
'title' => '常用工具',
'type' => 'menu',
'children' => [
[
'id' => '-1-1',
'title' => '首页缓存',
'type' => 'menu',
'route' => 'admin.util.index_cache',
],
],
],
],
];
}
}

View File

@ -172,6 +172,10 @@ class Course extends Service
}
}
if (isset($post['featured'])) {
$data['featured'] = $validator->checkFeatureStatus($post['featured']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
if ($post['published'] == 1) {

View 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');
}
}
}

View File

@ -59,6 +59,7 @@
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
@ -67,6 +68,7 @@
<th>课时数</th>
<th>用户数</th>
<th>价格</th>
<th>推荐</th>
<th>发布</th>
<th>操作</th>
</tr>
@ -101,6 +103,7 @@
<p>市场:{{ '¥%0.2f'|format(item.market_price) }}</p>
<p>会员:{{ '¥%0.2f'|format(item.vip_price) }}</p>
</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 class="center">
<div class="layui-dropdown">
@ -129,4 +132,45 @@
{{ partial('partials/pager') }}
{% 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 %}

View File

@ -54,6 +54,13 @@
<input type="radio" name="free" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">推荐</label>
<div class="layui-input-block">
<input type="radio" name="featured" value="1" title="是">
<input type="radio" name="featured" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发布</label>
<div class="layui-input-block">

View 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 %}

View File

@ -2,6 +2,7 @@
namespace App\Http\Api\Controllers;
use App\Caches\IndexSimpleFeaturedCourseList;
use App\Caches\IndexSimpleFreeCourseList;
use App\Caches\IndexSimpleNewCourseList;
use App\Caches\IndexSimpleVipCourseList;
@ -25,6 +26,18 @@ class IndexController extends Controller
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")
*/

View File

@ -47,6 +47,7 @@ class IndexController extends Controller
$this->view->pick('index/full');
$this->view->setVar('lives', $service->getLives());
$this->view->setVar('slides', $service->getSlides());
$this->view->setVar('featured_courses', $service->getFeaturedCourses());
$this->view->setVar('new_courses', $service->getNewCourses());
$this->view->setVar('free_courses', $service->getFreeCourses());
$this->view->setVar('vip_courses', $service->getVipCourses());
@ -59,6 +60,7 @@ class IndexController extends Controller
$this->view->pick('index/simple');
$this->view->setVar('lives', $service->getLives());
$this->view->setVar('slides', $service->getSlides());
$this->view->setVar('featured_courses', $service->getSimpleFeaturedCourses());
$this->view->setVar('new_courses', $service->getSimpleNewCourses());
$this->view->setVar('free_courses', $service->getSimpleFreeCourses());
$this->view->setVar('vip_courses', $service->getSimpleVipCourses());

View File

@ -2,9 +2,11 @@
namespace App\Http\Home\Services;
use App\Caches\IndexFeaturedCourseList;
use App\Caches\IndexFreeCourseList;
use App\Caches\IndexLiveList;
use App\Caches\IndexNewCourseList;
use App\Caches\IndexSimpleFeaturedCourseList;
use App\Caches\IndexSimpleFreeCourseList;
use App\Caches\IndexSimpleNewCourseList;
use App\Caches\IndexSimpleVipCourseList;
@ -61,6 +63,15 @@ class Index extends Service
return $cache->get();
}
public function getFeaturedCourses()
{
$cache = new IndexFeaturedCourseList();
$courses = $cache->get();
return $this->handleCategoryCourses($courses);
}
public function getNewCourses()
{
$cache = new IndexNewCourseList();
@ -95,6 +106,13 @@ class Index extends Service
return $cache->get();
}
public function getSimpleFeaturedCourses()
{
$cache = new IndexSimpleFeaturedCourseList();
return $cache->get();
}
public function getSimpleFreeCourses()
{
$cache = new IndexSimpleFreeCourseList();

View File

@ -45,6 +45,13 @@
</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="header">新上课程</div>
<div class="content">

View File

@ -30,6 +30,13 @@
</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="header">新上课程</div>
<div class="content">

View File

@ -11,7 +11,7 @@ class AppInfo
protected $link = 'https://gitee.com/koogua';
protected $version = '1.2.2';
protected $version = '1.2.3';
public function __get($name)
{

View 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);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Listeners;
use App\Library\Utils\Lock as LockUtil;
use App\Models\Online as OnlineModel;
use App\Models\User as UserModel;
use App\Repos\Online as OnlineRepo;
@ -15,7 +16,13 @@ class User extends Listener
public function online(Event $event, $source, UserModel $user)
{
$itemId = "user:{$user->id}";
$lockId = LockUtil::addLock($itemId);
$now = time();
$clientType = $this->getClientType();
$clientIp = $this->getClientIp();
if ($now - $user->active_time > 600) {
@ -25,28 +32,46 @@ class User extends Listener
$onlineRepo = new OnlineRepo();
$online = $onlineRepo->findByUserDate($user->id, date('Y-m-d'));
$records = $onlineRepo->findByUserDate($user->id, date('Y-m-d'));
if ($online) {
if ($records->count() > 0) {
$online->active_time = $now;
$online->client_type = $this->getClientType();
$online->client_ip = $this->getClientIp();
$online = null;
$online->update();
foreach ($records as $record) {
if ($record->client_type == $clientType && $record->client_ip == $clientIp) {
$online = $record;
break;
}
}
if ($online) {
$online->active_time = $now;
$online->update();
} else {
$this->createOnline($user->id, $clientType, $clientIp);
}
} else {
$online = new OnlineModel();
$online->user_id = $user->id;
$online->active_time = $now;
$online->client_type = $this->getClientType();
$online->client_ip = $this->getClientIp();
$online->create();
$this->createOnline($user->id, $clientType, $clientIp);
}
}
LockUtil::releaseLock($itemId, $lockId);
}
protected function createOnline($userId, $clientType, $clientIp)
{
$online = new OnlineModel();
$online->user_id = $userId;
$online->client_type = $clientType;
$online->client_ip = $clientIp;
$online->active_time = time();
$online->create();
return $online;
}
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
use App\Caches\MaxCourseId as MaxCourseIdCache;
use App\Services\Sync\CourseIndex as CourseIndexSync;
use App\Services\Sync\CourseScore as CourseScoreSync;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
use Phalcon\Text;
@ -165,6 +166,13 @@ class Course extends Model
*/
public $attrs;
/**
* 推荐标识
*
* @var int
*/
public $featured;
/**
* 发布标识
*
@ -302,6 +310,9 @@ class Course extends Model
if (time() - $this->update_time > 3 * 3600) {
$sync = new CourseIndexSync();
$sync->addItem($this->id);
$sync = new CourseScoreSync();
$sync->addItem($this->id);
}
if (Text::startsWith($this->cover, 'http')) {
@ -377,6 +388,7 @@ class Course extends Model
'rating' => '好评',
'latest' => '最新',
'popular' => '最热',
'featured' => '推荐',
'free' => '免费',
];
}

View File

@ -19,13 +19,6 @@ class Online extends Model
*/
public $user_id;
/**
* 计划编号
*
* @var string
*/
public $date;
/**
* 客户端类型
*

View 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
View 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();
}
}

View File

@ -23,13 +23,14 @@ class Annotation extends Provider
if ($config->get('env') == ENV_DEV) {
$annotations = new MemoryAnnotations();
} else {
$statsKey = '_ANNOTATION_';
$annotations = new RedisAnnotations([
'host' => $config->path('redis.host'),
'port' => $config->path('redis.port'),
'auth' => $config->path('redis.auth'),
'index' => $config->path('annotation.db'),
'lifetime' => $config->path('annotation.lifetime'),
'statsKey' => $config->path('annotation.statsKey'),
'lifetime' => $config->path('annotation.lifetime') ?: 30 * 86400,
'prefix' => $statsKey . ':',
'statsKey' => $statsKey,
]);
}

View File

@ -23,13 +23,14 @@ class MetaData extends Provider
if ($config->get('env') == ENV_DEV) {
$metaData = new MemoryMetaData();
} else {
$statsKey = '_METADATA_';
$metaData = new RedisMetaData([
'host' => $config->path('redis.host'),
'port' => $config->path('redis.port'),
'auth' => $config->path('redis.auth'),
'index' => $config->path('metadata.db'),
'statsKey' => $config->path('metadata.statsKey'),
'lifetime' => $config->path('metadata.lifetime'),
'lifetime' => $config->path('metadata.lifetime') ?: 30 * 86400,
'prefix' => $statsKey . ':',
'statsKey' => $statsKey,
]);
}

View File

@ -23,8 +23,8 @@ class Session extends Provider
'host' => $config->path('redis.host'),
'port' => $config->path('redis.port'),
'auth' => $config->path('redis.auth'),
'index' => $config->path('session.db'),
'lifetime' => $config->path('session.lifetime'),
'lifetime' => $config->path('session.lifetime') ?: 24 * 3600,
'prefix' => '_SESSION_:',
]);
$session->start();

View File

@ -67,6 +67,10 @@ class Course extends Repository
}
}
if (isset($where['featured'])) {
$builder->andWhere('featured = :featured:', ['featured' => $where['featured']]);
}
if (isset($where['published'])) {
$builder->andWhere('published = :published:', ['published' => $where['published']]);
}
@ -77,6 +81,8 @@ class Course extends Repository
if ($sort == 'free') {
$builder->andWhere('market_price = 0');
} elseif ($sort == 'featured') {
$builder->andWhere('featured = 1');
} elseif ($sort == 'vip_discount') {
$builder->andWhere('vip_price < market_price');
$builder->andWhere('vip_price > 0');

View File

@ -3,7 +3,8 @@
namespace App\Repos;
use App\Models\Online as OnlineModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class Online extends Repository
{
@ -11,20 +12,18 @@ class Online extends Repository
/**
* @param int $userId
* @param string $activeDate
* @return OnlineModel|Model|bool
* @return ResultsetInterface|Resultset|OnlineModel[]
*/
public function findByUserDate($userId, $activeDate)
{
$activeTime = strtotime($activeDate);
$startTime = strtotime($activeDate);
return OnlineModel::findFirst([
'conditions' => 'user_id = ?1 AND active_time BETWEEN ?2 AND ?3',
'bind' => [
1 => $userId,
2 => $activeTime,
3 => $activeTime + 86400,
],
]);
$endTime = $startTime + 86400;
return OnlineModel::query()
->where('user_id = :user_id:', ['user_id' => $userId])
->betweenWhere('active_time', $startTime, $endTime)
->execute();
}
}

24
app/Repos/UserSession.php Normal file
View 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
View 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();
}
}

View File

@ -3,70 +3,114 @@
namespace App\Services\Auth;
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 Lcobucci\JWT\Builder as JwtBuilder;
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;
use App\Traits\Client as ClientTrait;
class Api extends AuthService
{
use ClientTrait;
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();
$expireTime = time() + $config->path('jwt.lifetime');
$lifetime = $config->path('token.lifetime') ?: 7 * 86400;
$builder->expiresAt($expireTime);
$builder->withClaim('user_id', $user->id);
$builder->withClaim('user_name', $user->name);
$cache->save($key, $authInfo, $lifetime);
$singer = new JwtSingerSha256();
$key = new JwtSingerKey($config->path('jwt.key'));
$token = $builder->getToken($singer, $key);
return $token->__toString();
return $token;
}
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()
{
$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;
protected function createUserToken($userId, $token)
{
$userToken = new UserTokenModel();
$userToken->user_id = $userId;
$userToken->token = $token;
$userToken->client_type = $this->getClientType();
$userToken->client_ip = $this->getClientIp();
$userToken->create();
}
protected function logoutOtherClients($userId)
{
$repo = new UserTokenRepo();
$records = $repo->findByUserId($userId);
$cache = $this->getCache();
$clientType = $this->getClientType();
if ($records->count() == 0) {
return;
}
$singer = new JwtSingerSha256();
if (!$token->verify($singer, $config->path('jwt.key'))) {
return null;
foreach ($records as $record) {
if ($record->client_type == $clientType) {
$record->deleted = 1;
$record->update();
$key = $this->getTokenCacheKey($record->token);
$cache->delete($key);
}
}
}
return [
'id' => $token->getClaim('user_id'),
'name' => $token->getClaim('user_name'),
];
protected function generateToken($userId)
{
return md5(uniqid() . time() . $userId);
}
protected function getTokenCacheKey($token)
{
return "_PHCR_TOKEN_:{$token}";
}
}

View File

@ -3,13 +3,24 @@
namespace App\Services\Auth;
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\Traits\Client as ClientTrait;
class Home extends AuthService
{
use ClientTrait;
public function saveAuthInfo(UserModel $user)
{
$sessionId = $this->session->getId();
$this->logoutOtherClients($user->id);
$this->createUserSession($user->id, $sessionId);
$authKey = $this->getAuthKey();
$authInfo = [
@ -41,4 +52,41 @@ class Home extends AuthService
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}";
}
}

View File

@ -2,6 +2,7 @@
namespace App\Services;
use App\Models\Course as CourseModel;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseRating as CourseRatingRepo;
@ -58,9 +59,19 @@ class CourseStat extends Service
public function updateScore($courseId)
{
/**
* @todo 计算综合评分
*/
$courseRepo = new CourseRepo();
$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)
@ -181,4 +192,72 @@ class CourseStat extends Service
$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);
}
}

View 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';
}
}

View 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();
}
}
}
}

View File

@ -27,6 +27,14 @@ trait Client
*/
$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');
$result = new BrowserParser($userAgent);

View File

@ -212,6 +212,15 @@ class Course extends Validator
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)
{
if (!in_array($status, [0, 1])) {

View File

@ -24,7 +24,7 @@
"overtrue/wechat": "^4.2"
},
"require-dev": {
"odan/phinx-migrations-generator": "^5.1",
"odan/phinx-migrations-generator": "^5.3",
"phalcon/ide-stubs": "^3.4"
},
"repositories": {

677
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "60ff0e1868be7414a1b31d397ced7fbd",
"content-hash": "09a618cffed2c4cfb593c0a791c19b3f",
"packages": [
{
"name": "aferrandini/phpqrcode",
@ -15,16 +15,16 @@
"reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aferrandini/PHPQRCode/zipball/3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46",
"reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
"type": "zip",
"url": "https://api.github.com/repos/aferrandini/PHPQRCode/zipball/3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46",
"reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.0"
@ -46,16 +46,16 @@
"homepage": "http://www.ferrandini.com/",
"role": "Developer"
}
],
"description": "PHPQRCode porting and changed for PHP 5.3 compatibility",
"homepage": "https://github.com/aferrandini/PHPQRCode",
"keywords": [
"barcode",
"php",
"qrcode"
],
"abandoned": "endroid/qr-code",
"time": "2013-07-08T09:39:08+00:00"
],
"description": "PHPQRCode porting and changed for PHP 5.3 compatibility",
"homepage": "https://github.com/aferrandini/PHPQRCode",
"keywords": [
"barcode",
"php",
"qrcode"
],
"abandoned": "endroid/qr-code",
"time": "2013-07-08T09:39:08+00:00"
},
{
"name": "cakephp/core",
@ -334,16 +334,16 @@
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"keywords": [
"annotations",
"docblock",
@ -418,16 +418,16 @@
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23",
"reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"doctrine/lexer": "^1.0.1",
"php": ">= 5.5"
},
@ -1066,16 +1066,16 @@
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
"keywords": [
"cron",
@ -1242,16 +1242,16 @@
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7"
},
"require-dev": {
@ -1399,15 +1399,15 @@
"BSD-3-Clause"
],
"authors": [
{
"name": "Phalcon Team",
"email": "team@phalconphp.com",
"homepage": "https://phalconphp.com/en/team"
},
{
"name": "Contributors",
"homepage": "https://github.com/phalcon/incubator/graphs/contributors"
}
{
"name": "Phalcon Team",
"email": "team@phalconphp.com",
"homepage": "https://phalconphp.com/en/team"
},
{
"name": "Contributors",
"homepage": "https://github.com/phalcon/incubator/graphs/contributors"
}
],
"description": "Adapters, prototypes or functionality that can be potentially incorporated to the C-framework.",
"homepage": "https://phalconphp.com",
@ -1488,16 +1488,16 @@
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
"reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
@ -1707,16 +1707,16 @@
"reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
@ -1738,14 +1738,14 @@
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"time": "2017-10-23T01:57:42+00:00"
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"time": "2017-10-23T01:57:42+00:00"
},
{
"name": "qcloud/cos-sdk-v5",
@ -2026,15 +2026,15 @@
"license": [
"MIT"
],
"authors": [
{
"name": "Chris Corbyn"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"authors": [
{
"name": "Chris Corbyn"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Swiftmailer, free feature-rich PHP mailer",
"homepage": "https://swiftmailer.symfony.com",
"keywords": [
@ -2241,16 +2241,16 @@
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/cf63f0613a6c6918e96db39c07a43b01e19a0773",
"reference": "cf63f0613a6c6918e96db39c07a43b01e19a0773",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"reference": "cf63f0613a6c6918e96db39c07a43b01e19a0773",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/filesystem": "^4.4|^5.0",
@ -2403,15 +2403,15 @@
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-06T13:18:39+00:00"
"time": "2020-07-06T13:18:39+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -2433,16 +2433,16 @@
}
]
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
@ -2473,16 +2473,16 @@
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-09-07T11:33:47+00:00"
},
},
{
"name": "symfony/event-dispatcher",
"version": "v4.4.17",
@ -2503,16 +2503,16 @@
}
]
},
"require": {
"php": ">=7.1.3",
"symfony/event-dispatcher-contracts": "^1.1"
},
"conflict": {
"symfony/dependency-injection": "<3.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "1.1"
"require": {
"php": ">=7.1.3",
"symfony/event-dispatcher-contracts": "^1.1"
},
"conflict": {
"symfony/dependency-injection": "<3.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "1.1"
},
"require-dev": {
"psr/log": "~1.0",
@ -2541,16 +2541,16 @@
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"funding": [
@ -2589,23 +2589,23 @@
}
]
},
"require": {
"php": ">=7.1.3"
},
"suggest": {
"psr/event-dispatcher": "",
"symfony/event-dispatcher-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"require": {
"php": ">=7.1.3"
},
"suggest": {
"psr/event-dispatcher": "",
"symfony/event-dispatcher-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\EventDispatcher\\": ""
@ -2615,16 +2615,16 @@
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to dispatching event",
"homepage": "https://symfony.com",
"keywords": [
@ -2651,16 +2651,16 @@
],
"time": "2020-07-06T13:19:58+00:00"
},
{
"name": "symfony/filesystem",
"version": "v5.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "6e4320f06d5f2cce0d96530162491f4465179157"
},
"dist": {
"type": "zip",
{
"name": "symfony/filesystem",
"version": "v5.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "6e4320f06d5f2cce0d96530162491f4465179157"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157",
"reference": "6e4320f06d5f2cce0d96530162491f4465179157",
"shasum": "",
@ -2711,16 +2711,16 @@
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-30T20:35:19+00:00"
},
"time": "2020-05-30T20:35:19+00:00"
},
{
"name": "symfony/http-foundation",
"version": "v5.2.0",
@ -2741,16 +2741,16 @@
}
]
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-mbstring": "~1.1",
"symfony/polyfill-php80": "^1.15"
},
"require-dev": {
"predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-mbstring": "~1.1",
"symfony/polyfill-php80": "^1.15"
},
"require-dev": {
"predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/mime": "^4.4|^5.0"
},
"suggest": {
@ -3112,15 +3112,15 @@
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@ -3142,16 +3142,16 @@
}
]
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -3439,15 +3439,15 @@
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-php80",
@ -3469,16 +3469,16 @@
}
]
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.20-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
@ -3520,10 +3520,10 @@
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
@ -3643,17 +3643,17 @@
}
]
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0"
},
"suggest": {
"symfony/service-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
"require": {
"php": ">=7.2.5",
"psr/container": "^1.0"
},
"suggest": {
"symfony/service-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -3690,10 +3690,10 @@
"standards"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
@ -3793,16 +3793,16 @@
"reference": "a3b3054262e48776e8014d5e385a8932b0102f29"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TencentCloud/tencentcloud-sdk-php/zipball/a3b3054262e48776e8014d5e385a8932b0102f29",
"reference": "a3b3054262e48776e8014d5e385a8932b0102f29",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
"type": "zip",
"url": "https://api.github.com/repos/TencentCloud/tencentcloud-sdk-php/zipball/a3b3054262e48776e8014d5e385a8932b0102f29",
"reference": "a3b3054262e48776e8014d5e385a8932b0102f29",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"guzzlehttp/guzzle": "^6.3",
@ -4192,40 +4192,41 @@
}
],
"packages-dev": [
{
"name": "odan/phinx-migrations-generator",
"version": "5.1.2",
"source": {
"type": "git",
"url": "https://github.com/odan/phinx-migrations-generator.git",
"reference": "f3cb7cc6bc7eb22e85f34f229b6d476e96d99c73"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/odan/phinx-migrations-generator/zipball/f3cb7cc6bc7eb22e85f34f229b6d476e96d99c73",
"reference": "f3cb7cc6bc7eb22e85f34f229b6d476e96d99c73",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
{
"name": "odan/phinx-migrations-generator",
"version": "5.3.2",
"source": {
"type": "git",
"url": "https://github.com/odan/phinx-migrations-generator.git",
"reference": "2d3620f8251838b53717f7a43a348de31e9d451c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/odan/phinx-migrations-generator/zipball/2d3620f8251838b53717f7a43a348de31e9d451c",
"reference": "2d3620f8251838b53717f7a43a348de31e9d451c",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*",
"ext-pdo": "*",
"php": "^7.2",
"riimu/kit-phpencoder": "^2.4",
"robmorgan/phinx": "^0.12",
"symfony/console": "^2.8 || ^3.0 || ^4.0 || ^5.0"
"ext-json": "*",
"ext-pdo": "*",
"php": "^7.2",
"riimu/kit-phpencoder": "^2.4",
"robmorgan/phinx": "^0.12",
"symfony/console": "^2.8 || ^3.0 || ^4.0 || ^5.0",
"symfony/polyfill-php73": "^1.18"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
"overtrue/phplint": "^1.1",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^8 || ^9",
"squizlabs/php_codesniffer": "^3.4"
"friendsofphp/php-cs-fixer": "^2.16",
"overtrue/phplint": "^1.1 || ^2.0",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^8 || ^9",
"squizlabs/php_codesniffer": "^3.4"
},
"bin": [
"./bin/phinx-migrations"
@ -4240,18 +4241,22 @@
"license": [
"MIT"
],
"description": "Migration generator for Phinx",
"homepage": "https://github.com/odan/phinx-migrations-generator",
"keywords": [
"database",
"generator",
"migration",
"migrations",
"mysql",
"phinx"
],
"time": "2020-06-15T19:36:35+00:00"
"description": "Migration generator for Phinx",
"homepage": "https://github.com/odan/phinx-migrations-generator",
"keywords": [
"database",
"generator",
"migration",
"migrations",
"mysql",
"phinx"
],
"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",
"version": "v3.4.3",
@ -4262,15 +4267,15 @@
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/65144f2b0fad32b182ccb062b1efc1b4edea5d44",
"reference": "65144f2b0fad32b182ccb062b1efc1b4edea5d44",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
"url": "https://api.github.com/repos/phalcon/ide-stubs/zipball/65144f2b0fad32b182ccb062b1efc1b4edea5d44",
"reference": "65144f2b0fad32b182ccb062b1efc1b4edea5d44",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.0"
@ -4287,16 +4292,16 @@
"homepage": "https://phalconphp.com/en/team"
},
{
"name": "Contributors",
"homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors"
}
],
"description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.",
"homepage": "https://phalconphp.com",
"keywords": [
"Devtools",
"Eclipse",
"autocomplete",
"name": "Contributors",
"homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors"
}
],
"description": "The most complete Phalcon Framework IDE stubs library which enables autocompletion in modern IDEs.",
"homepage": "https://phalconphp.com",
"keywords": [
"Devtools",
"Eclipse",
"autocomplete",
"ide",
"netbeans",
"phalcon",

View File

@ -83,70 +83,30 @@ $config['redis']['port'] = 6379;
$config['redis']['auth'] = '1qaz2wsx3edc';
/**
* redis库编号
*/
$config['cache']['db'] = 0;
/**
* 有效期(秒)
* 缓存有效期(秒)
*/
$config['cache']['lifetime'] = 24 * 3600;
/**
* redis库编号
*/
$config['session']['db'] = 1;
/**
* 有效期(秒)
* 会话有效期(秒)
*/
$config['session']['lifetime'] = 24 * 3600;
/**
* redis库编号
* 令牌有效期(秒)
*/
$config['metadata']['db'] = 2;
$config['token']['lifetime'] = 7 * 86400;
/**
* 有效期(秒)
* 元数据有效期(秒)
*/
$config['metadata']['lifetime'] = 7 * 86400;
/**
* statsKey
*/
$config['metadata']['statsKey'] = '_METADATA_';
/**
* redis库编号
*/
$config['annotation']['db'] = 2;
/**
* 有效期(秒)
* 注解有效期(秒)
*/
$config['annotation']['lifetime'] = 7 * 86400;
/**
* statsKey
*/
$config['annotation']['statsKey'] = '_ANNOTATION_';
/**
* 密钥
*/
$config['jwt']['key'] = 'fu6ckEc8pv8k5K7m';
/**
* 有效期(秒)
*/
$config['jwt']['lifetime'] = 7 * 86400;
/**
* 回旋时间(秒)
*/
$config['jwt']['leeway'] = 30;
/**
* 允许跨域
*/

View File

@ -114,6 +114,7 @@ $error['course.invalid_vip_price'] = '无效的会员价格范围0-10000
$error['course.invalid_compare_price'] = '无效的比较定价(会员价格高于市场价格)';
$error['course.invalid_study_expiry'] = '无效的学习期限';
$error['course.invalid_refund_expiry'] = '无效的退款期限';
$error['course.invalid_feature_status'] = '无效的推荐状态';
$error['course.invalid_publish_status'] = '无效的发布状态';
$error['course.pub_chapter_not_found'] = '尚未发现已发布的课时';
$error['course.pub_chapter_not_enough'] = '已发布的课时太少小于30%';

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ use Phinx\Db\Adapter\MysqlAdapter;
class CreateOnlineTable extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('kg_online', [
@ -18,6 +19,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
->addColumn('id', 'integer', [
'null' => false,
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'identity' => 'enable',
'comment' => '主键编号',
])
@ -25,6 +27,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '用户编号',
'after' => 'id',
])
@ -32,6 +35,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '1',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '终端类型',
'after' => 'user_id',
])
@ -48,6 +52,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '活跃时间',
'after' => 'client_ip',
])
@ -55,6 +60,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '创建时间',
'after' => 'active_time',
])
@ -62,6 +68,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '更新时间',
'after' => 'create_time',
])
@ -90,7 +97,7 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'comment' => '主键编号',
'row_format' => 'DYNAMIC',
])
->changeColumn('channel_sn', 'string', [
@ -104,4 +111,5 @@ class CreateOnlineTable extends Phinx\Migration\AbstractMigration
])
->save();
}
}

View File

@ -4,6 +4,7 @@ use Phinx\Db\Adapter\MysqlAdapter;
class CreateConnectTable extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('kg_connect', [
@ -18,6 +19,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
->addColumn('id', 'integer', [
'null' => false,
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'identity' => 'enable',
'comment' => '主键编号',
])
@ -25,13 +27,14 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '用户编号',
'after' => 'id',
])
->addColumn('open_id', 'string', [
'null' => false,
'default' => '',
'limit' => 50,
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放ID',
@ -59,6 +62,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '提供方',
'after' => 'open_avatar',
])
@ -66,6 +70,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '删除标识',
'after' => 'provider',
])
@ -73,6 +78,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '创建时间',
'after' => 'deleted',
])
@ -80,6 +86,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '更新时间',
'after' => 'create_time',
])
@ -89,4 +96,5 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration
])
->create();
}
}

View File

@ -4,6 +4,7 @@ use Phinx\Db\Adapter\MysqlAdapter;
class Schema202012121830 extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('kg_consult')
@ -11,6 +12,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '回复者编号',
'after' => 'owner_id',
])
@ -19,7 +21,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
->addColumn('union_id', 'string', [
'null' => false,
'default' => '',
'limit' => 50,
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => 'union_id',
@ -46,6 +48,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
->addColumn('id', 'integer', [
'null' => false,
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'identity' => 'enable',
'comment' => '主键编号',
])
@ -53,13 +56,14 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '用户编号',
'after' => 'id',
])
->addColumn('open_id', 'string', [
'null' => false,
'default' => '',
'limit' => 50,
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '开放ID',
@ -69,6 +73,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '删除标识',
'after' => 'open_id',
])
@ -76,6 +81,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '创建时间',
'after' => 'deleted',
])
@ -83,6 +89,7 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'signed' => false,
'comment' => '更新时间',
'after' => 'create_time',
])
@ -96,4 +103,5 @@ class Schema202012121830 extends Phinx\Migration\AbstractMigration
])
->create();
}
}

View 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();
}
}

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,9 @@ $scheduler->php($script, $bin, ['--task' => 'sync_group_index', '--action' => 'm
$scheduler->php($script, $bin, ['--task' => 'sync_user_index', '--action' => 'main'])
->hourly(23);
$scheduler->php($script, $bin, ['--task' => 'sync_course_score', '--action' => 'main'])
->hourly(29);
$scheduler->php($script, $bin, ['--task' => 'clean_log', '--action' => 'main'])
->daily(3, 3);