1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-22 19:44:02 +08:00

Merge branch 'develop'

# Conflicts:
#	LICENSE
This commit is contained in:
xiaochong0302 2020-09-27 17:53:23 +08:00
commit bfe66b00d0
1030 changed files with 105444 additions and 19328 deletions

10
.gitignore vendored
View File

@ -1,3 +1,11 @@
/.idea
/vendor
/config/config.php
/vendor
/config/xs.course.ini
/config/xs.group.ini
/config/xs.user.ini
/config/alipay/*.crt
/config/wxpay/*.pem
/public/robots.txt
/public/sitemap.xml
*KgTest*

View File

@ -1,39 +1,82 @@
# course-tencent-cloud
## 酷瓜云课堂
#### 介绍
{**以下是码云平台说明,您可以替换此简介**
码云是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN。专为开发者提供稳定、高效、安全的云端软件开发协作平台
无论是个人、团队、或是企业,都能够用码云实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
#### 项目介绍
#### 软件架构
软件架构说明
酷瓜云课堂,依托腾讯云基础服务架构,采用 C 扩展框架 Phalcon 开发,致力网络教育软件。
#### 系统功能
#### 安装教程
实现了点播、直播、专栏、会员、微聊等,是一个完整的产品,具体功能我也不想写一大堆,自己体验吧!
1. xxxx
2. xxxx
3. xxxx
- [前台演示](https://ctc.koogua.com)
- [后台演示](https://ctc.koogua.com/admin)
#### 使用说明
帐号100015@163.com / 123456 (前后台通用)
1. xxxx
2. xxxx
3. xxxx
友情提示:
#### 参与贡献
- 系统配置低1核 1G 1M 跑多个容器),切莫压测
- 课程数据来源于网络(无实质内容),切莫购买
- 管理后台已禁止数据提交,私密配置已过滤
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 项目组件
- 后台框架:[phalcon 3.4.5](https://phalcon.io)
- 前端框架:[layui 2.5.6](https://layui.com) [layim 3.9.5](https://www.layui.com/layim)(已授权)
- 全文检索:[xunsearch 1.4.9](http://www.xunsearch.com)
- 即时通讯:[workerman 3.5.22](https://workerman.net)
- 基础依赖:[php7.3](https://php.net) [mysql5.7](https://mysql.com) [redis5.0](https://redis.io)
#### 码云特技
#### 使用协议
虽然尝试了解过开源协议,但是理解的模棱两可,干脆用自己的协议吧。
1. 本系统属于强业务类型,非通用类库框架,不适合再次衍生发布。
2. 在保留我们版权标识的前提下,用户可以修改以满足自己的需求,可以用于商业用途。
3. 有限社区支持,用户对自己的行为负责。
#### 安装指南
- [运行环境搭建](https://gitee.com/koogua/course-tencent-cloud-docker)
- [系统服务配置](https://gitee.com/koogua/course-tencent-cloud/wikis)
#### 开发计划
- 桌面端:进行中
- 移动端:待启动
- 小程序:待启动
#### 意见反馈
- [在线反馈](https://gitee.com/koogua/course-tencent-cloud/issues)(推荐)
- QQ邮箱: 76632555@qq.com
- QQ群组: 787363898
#### 加入我们
这是一个创业项目,个人能力和精力有限,要兼顾产品规划以及开发,还要处理很多琐碎事情。目前在南山科技园某个众创空间,希望有 **深圳前端同学** 加入我们。
联系邮箱76632555@qq.com
#### 通过这个项目能学到什么?
- 项目规划phalcon缓存JWT即时通讯全文检索
- dockersupervisordevops
- gitlinuxphpmysqlredisnginx
#### 有阿里云版吗?
阿里云版规划中,之前阿里云服务过期未续费,所以腾讯云版本先出。
#### 代码有加密吗?
所有代码都公开授权代码除外例如layim没有所谓的商业版和付费插件。
#### 有商业服务吗?
生存才能发展,我们目前提供的服务包括:
- 系统安装
- 系统定制
- 企业授权
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

15
app/Builders/Builder.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace App\Builders;
use Phalcon\Mvc\User\Component;
class Builder extends Component
{
public function objects(array $items)
{
return kg_array_object($items);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Builders;
use App\Models\Category as CategoryModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CategoryTreeList extends Builder
{
public function handle($type)
{
$topCategories = $this->findChildCategories($type, 0);
if ($topCategories->count() == 0) {
return [];
}
$list = [];
foreach ($topCategories as $category) {
$list[] = [
'id' => $category->id,
'name' => $category->name,
'children' => $this->handleChildren($category),
];
}
return $list;
}
protected function handleChildren(CategoryModel $category)
{
$subCategories = $this->findChildCategories($category->type, $category->id);
if ($subCategories->count() == 0) {
return [];
}
$list = [];
foreach ($subCategories as $category) {
$list[] = [
'id' => $category->id,
'name' => $category->name,
];
}
return $list;
}
/**
* @param string $type
* @param int $parentId
* @return ResultsetInterface|Resultset|CategoryModel[]
*/
protected function findChildCategories($type = 'course', $parentId = 0)
{
$query = CategoryModel::query();
$query->where('published = 1');
if ($type) {
$query->andWhere('type = :type:', ['type' => $type]);
}
if ($parentId) {
$query->andWhere('parent_id = :parent_id:', ['parent_id' => $parentId]);
}
$query->orderBy('priority ASC');
return $query->execute();
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace App\Builders;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class ConsultList extends Builder
{
public function handleCourses(array $consults)
{
$courses = $this->getCourses($consults);
foreach ($consults as $key => $consult) {
$consults[$key]['course'] = $courses[$consult['course_id']] ?? new \stdClass();
}
return $consults;
}
public function handleUsers(array $consults)
{
$users = $this->getUsers($consults);
foreach ($consults as $key => $consult) {
$consults[$key]['owner'] = $users[$consult['owner_id']] ?? new \stdClass();
}
return $consults;
}
public function getCourses(array $consults)
{
$ids = kg_array_column($consults, 'course_id');
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($courses->toArray() as $course) {
$result[$course['id']] = $course;
}
return $result;
}
public function getChapters(array $consults)
{
$ids = kg_array_column($consults, 'chapter_id');
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($chapters->toArray() as $chapter) {
$result[$chapter['id']] = $chapter;
}
return $result;
}
public function getUsers(array $consults)
{
$ids = kg_array_column($consults, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace App\Builders;
use App\Models\Chapter as ChapterModel;
use App\Models\Course as CourseModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CourseChapterList extends Builder
{
/**
* @param int $courseId
* @return array
*/
public function handle($courseId)
{
$list = [];
$chapters = $this->findChapters($courseId);
if ($chapters->count() == 0) {
return [];
}
foreach ($chapters as $chapter) {
$list[] = [
'id' => $chapter->id,
'title' => $chapter->title,
'model' => $chapter->model,
'children' => $this->handleChildren($chapter),
];
}
return $list;
}
/**
* @param ChapterModel $chapter
* @return array
*/
protected function handleChildren(ChapterModel $chapter)
{
$lessons = $this->findLessons($chapter->id);
if ($lessons->count() == 0) {
return [];
}
$list = [];
foreach ($lessons as $lesson) {
/**
* @var $attrs array
*/
$attrs = $lesson->attrs;
if ($chapter->model == CourseModel::MODEL_VOD) {
unset($attrs['file_id'], $attrs['file_status']);
}
$list[] = [
'id' => $lesson->id,
'title' => $lesson->title,
'model' => $lesson->model,
'free' => $lesson->free,
'attrs' => $attrs,
];
}
return $list;
}
/**
* @param int $courseId
* @return ResultsetInterface|Resultset|ChapterModel[]
*/
protected function findChapters($courseId)
{
return ChapterModel::query()
->where('course_id = :course_id:', ['course_id' => $courseId])
->andWhere('parent_id = 0 AND deleted = 0')
->orderBy('priority ASC, id ASC')
->execute();
}
/**
* @param int $chapterId
* @return ResultsetInterface|Resultset|ChapterModel[]
*/
protected function findLessons($chapterId)
{
return ChapterModel::query()
->where('parent_id = :parent_id:', ['parent_id' => $chapterId])
->andWhere('deleted = 0')
->orderBy('priority ASC, id ASC')
->execute();
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace App\Builders;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class CourseFavoriteList extends Builder
{
public function handleCourses(array $relations)
{
$courses = $this->getCourses($relations);
foreach ($relations as $key => $value) {
$relations[$key]['course'] = $courses[$value['course_id']] ?? new \stdClass();
}
return $relations;
}
public function handleUsers(array $relations)
{
$users = $this->getUsers($relations);
foreach ($relations as $key => $value) {
$relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass();
}
return $relations;
}
public function getCourses(array $relations)
{
$ids = kg_array_column($relations, 'course_id');
$courseRepo = new CourseRepo();
$columns = [
'id', 'title', 'cover',
'market_price', 'vip_price',
'rating', 'model', 'level', 'attrs',
'user_count', 'lesson_count', 'review_count', 'favorite_count',
];
$courses = $courseRepo->findByIds($ids, $columns);
$baseUrl = kg_cos_url();
$result = [];
foreach ($courses->toArray() as $course) {
$course['cover'] = $baseUrl . $course['cover'];
$course['market_price'] = (float)$course['market_price'];
$course['vip_price'] = (float)$course['vip_price'];
$course['rating'] = (float)$course['rating'];
$course['attrs'] = json_decode($course['attrs'], true);
$result[$course['id']] = $course;
}
return $result;
}
public function getUsers(array $relations)
{
$ids = kg_array_column($relations, 'user_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Builders;
use App\Caches\CategoryList as CategoryListCache;
use App\Models\Category as CategoryModel;
use App\Repos\User as UserRepo;
class CourseList extends Builder
{
public function handleCategories(array $courses)
{
$categories = $this->getCategories();
foreach ($courses as $key => $course) {
$courses[$key]['category'] = $categories[$course['category_id']] ?? new \stdClass();
}
return $courses;
}
public function handleTeachers(array $courses)
{
$teachers = $this->getTeachers($courses);
foreach ($courses as $key => $course) {
$courses[$key]['teacher'] = $teachers[$course['teacher_id']] ?? new \stdClass();
}
return $courses;
}
public function getCategories()
{
$cache = new CategoryListCache();
$items = $cache->get(CategoryModel::TYPE_COURSE);
if (empty($items)) {
return [];
}
$result = [];
foreach ($items as $item) {
$result[$item['id']] = [
'id' => $item['id'],
'name' => $item['name'],
];
}
return $result;
}
public function getTeachers($courses)
{
$ids = kg_array_column($courses, 'teacher_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Builders;
use App\Repos\Course as CourseRepo;
use App\Repos\Topic as TopicRepo;
class CourseTopicList extends Builder
{
public function handleCourses($relations)
{
$courses = $this->getCourses($relations);
foreach ($relations as $key => $value) {
$relations[$key]['course'] = $courses[$value['course_id']] ?? new \stdClass();
}
return $relations;
}
public function handleTopics($relations)
{
$topics = $this->getTopics($relations);
foreach ($relations as $key => $value) {
$relations[$key]['topic'] = $topics[$value['topic_id']] ?? new \stdClass();
}
return $relations;
}
public function getCourses($relations)
{
$ids = kg_array_column($relations, 'course_id');
$courseRepo = new CourseRepo();
$columns = [
'id', 'title', 'cover',
'market_price', 'vip_price',
'rating', 'model', 'level', 'attrs',
'user_count', 'lesson_count',
];
$courses = $courseRepo->findByIds($ids, $columns);
$baseUrl = kg_cos_url();
$result = [];
foreach ($courses->toArray() as $course) {
$course['cover'] = $baseUrl . $course['cover'];
$course['attrs'] = json_decode($course['attrs'], true);
$result[$course['id']] = $course;
}
return $result;
}
public function getTopics($relations)
{
$ids = kg_array_column($relations, 'topic_id');
$topicRepo = new TopicRepo();
$topics = $topicRepo->findByIds($ids, ['id', 'title', 'summary']);
$result = [];
foreach ($topics->toArray() as $topic) {
$result[$topic['id']] = $topic;
}
return $result;
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace App\Transformers;
namespace App\Builders;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class CourseUserList extends Transformer
class CourseUserList extends Builder
{
public function handleCourses($relations)
@ -13,7 +13,7 @@ class CourseUserList extends Transformer
$courses = $this->getCourses($relations);
foreach ($relations as $key => $value) {
$relations[$key]['course'] = $courses[$value['course_id']];
$relations[$key]['course'] = $courses[$value['course_id']] ?? new \stdClass();
}
return $relations;
@ -24,40 +24,54 @@ class CourseUserList extends Transformer
$users = $this->getUsers($relations);
foreach ($relations as $key => $value) {
$relations[$key]['user'] = $users[$value['user_id']];
$relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass();
}
return $relations;
}
protected function getCourses($relations)
public function getCourses($relations)
{
$ids = kg_array_column($relations, 'course_id');
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($ids, ['id', 'title', 'cover'])->toArray();
$columns = [
'id', 'title', 'cover',
'market_price', 'vip_price',
'rating', 'model', 'level', 'attrs',
'user_count', 'lesson_count', 'review_count', 'favorite_count',
];
$courses = $courseRepo->findByIds($ids, $columns);
$baseUrl = kg_cos_url();
$result = [];
foreach ($courses as $course) {
foreach ($courses->toArray() as $course) {
$course['cover'] = $baseUrl . $course['cover'];
$course['attrs'] = json_decode($course['attrs'], true);
$result[$course['id']] = $course;
}
return $result;
}
protected function getUsers($relations)
public function getUsers($relations)
{
$ids = kg_array_column($relations, 'user_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar'])->toArray();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users as $user) {
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Builders;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class DanmuList extends Builder
{
public function handleCourses(array $danmus)
{
$courses = $this->getCourses($danmus);
foreach ($danmus as $key => $danmu) {
$danmus[$key]['course'] = $courses[$danmu['course_id']] ?? new \stdClass();
}
return $danmus;
}
public function handleChapters(array $danmus)
{
$chapters = $this->getChapters($danmus);
foreach ($danmus as $key => $danmu) {
$danmus[$key]['chapter'] = $chapters[$danmu['chapter_id']] ?? new \stdClass();
}
return $danmus;
}
public function handleUsers(array $danmus)
{
$users = $this->getUsers($danmus);
foreach ($danmus as $key => $danmu) {
$danmus[$key]['owner'] = $users[$danmu['owner_id']] ?? new \stdClass();
}
return $danmus;
}
public function getCourses(array $danmus)
{
$ids = kg_array_column($danmus, 'course_id');
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($courses->toArray() as $course) {
$result[$course['id']] = $course;
}
return $result;
}
public function getChapters(array $danmus)
{
$ids = kg_array_column($danmus, 'chapter_id');
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($chapters->toArray() as $chapter) {
$result[$chapter['id']] = $chapter;
}
return $result;
}
public function getUsers(array $danmus)
{
$ids = kg_array_column($danmus, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

44
app/Builders/HelpList.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace App\Builders;
use App\Caches\CategoryList as CategoryListCache;
use App\Models\Category as CategoryModel;
class HelpList extends Builder
{
public function handleCategories(array $helps)
{
$categories = $this->getCategories();
foreach ($helps as $key => $help) {
$helps[$key]['category'] = $categories[$help['category_id']] ?? new \stdClass();
}
return $helps;
}
public function getCategories()
{
$cache = new CategoryListCache();
$items = $cache->get(CategoryModel::TYPE_HELP);
if (empty($items)) {
return [];
}
$result = [];
foreach ($items as $item) {
$result[$item['id']] = [
'id' => $item['id'],
'name' => $item['name'],
];
}
return $result;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Builders;
use App\Repos\User as UserRepo;
class ImFriendUserList extends Builder
{
public function handleFriends(array $relations)
{
$users = $this->getFriends($relations);
foreach ($relations as $key => $value) {
$relations[$key]['friend'] = $users[$value['friend_id']] ?? new \stdClass();
}
return $relations;
}
public function getFriends(array $relations)
{
$ids = kg_array_column($relations, 'friend_id');
$userRepo = new UserRepo();
$columns = [
'id', 'name', 'avatar', 'title', 'about', 'vip',
'gender', 'area', 'active_time',
];
$users = $userRepo->findByIds($ids, $columns);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Builders;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class ImGroupList extends Builder
{
public function handleCourses(array $groups)
{
$courses = $this->getCourses($groups);
foreach ($groups as $key => $group) {
$groups[$key]['course'] = $courses[$group['course_id']] ?? new \stdClass();
}
return $groups;
}
public function handleUsers(array $groups)
{
$users = $this->getUsers($groups);
foreach ($groups as $key => $group) {
$groups[$key]['owner'] = $users[$group['owner_id']] ?? new \stdClass();
}
return $groups;
}
public function getCourses(array $groups)
{
$ids = kg_array_column($groups, 'course_id');
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($courses->toArray() as $course) {
$result[$course['id']] = $course;
}
return $result;
}
public function getUsers(array $groups)
{
$ids = kg_array_column($groups, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Builders;
use App\Repos\ImGroup as ImGroupRepo;
use App\Repos\User as UserRepo;
class ImGroupUserList extends Builder
{
public function handleGroups(array $relations)
{
$groups = $this->getGroups($relations);
foreach ($relations as $key => $value) {
$relations[$key]['group'] = $groups[$value['group_id']] ?? new \stdClass();
}
return $relations;
}
public function handleUsers(array $relations)
{
$users = $this->getUsers($relations);
foreach ($relations as $key => $value) {
$relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass();
}
return $relations;
}
public function getUsers(array $relations)
{
$ids = kg_array_column($relations, 'user_id');
$userRepo = new UserRepo();
$columns = ['id', 'name', 'avatar', 'title', 'about', 'vip'];
$users = $userRepo->findByIds($ids, $columns);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
public function getGroups(array $relations)
{
$ids = kg_array_column($relations, 'group_id');
$groupRepo = new ImGroupRepo();
$columns = ['id', 'type', 'name', 'avatar', 'about', 'owner_id', 'user_count', 'msg_count'];
$groups = $groupRepo->findByIds($ids, $columns);
$users = $this->getGroupOwners($groups->toArray());
$baseUrl = kg_cos_url();
$result = [];
foreach ($groups->toArray() as $group) {
$group['avatar'] = $baseUrl . $group['avatar'];
$group['owner'] = $users[$group['owner_id']] ?? new \stdClass();
unset($group['owner_id']);
$result[$group['id']] = $group;
}
return $result;
}
protected function getGroupOwners(array $groups)
{
$ids = kg_array_column($groups, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name']);
$result = [];
if ($users->count() > 0) {
foreach ($users->toArray() as $user) {
$result[$user['id']] = $user;
}
}
return $result;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Builders;
use App\Repos\User as UserRepo;
class ImMessageList extends Builder
{
public function handleSenders(array $messages)
{
$users = $this->getSenders($messages);
foreach ($messages as $key => $message) {
$messages[$key]['sender'] = $users[$message['sender_id']] ?? new \stdClass();
}
return $messages;
}
public function getSenders(array $messages)
{
$ids = kg_array_column($messages, 'sender_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -1,12 +1,12 @@
<?php
namespace App\Transformers;
namespace App\Builders;
use App\Repos\Course as CourseRepo;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class LearningList extends Transformer
class LearningList extends Builder
{
public function handleCourses($relations)
@ -14,7 +14,7 @@ class LearningList extends Transformer
$courses = $this->getCourses($relations);
foreach ($relations as $key => $value) {
$relations[$key]['course'] = $courses[$value['course_id']];
$relations[$key]['course'] = $courses[$value['course_id']] ?? new \stdClass();
}
return $relations;
@ -25,7 +25,7 @@ class LearningList extends Transformer
$chapters = $this->getChapters($relations);
foreach ($relations as $key => $value) {
$relations[$key]['chapter'] = $chapters[$value['chapter_id']];
$relations[$key]['chapter'] = $chapters[$value['chapter_id']] ?? new \stdClass();
}
return $relations;
@ -36,7 +36,7 @@ class LearningList extends Transformer
$users = $this->getUsers($relations);
foreach ($relations as $key => $value) {
$relations[$key]['user'] = $users[$value['user_id']];
$relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass();
}
return $relations;
@ -48,11 +48,11 @@ class LearningList extends Transformer
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($ids, ['id', 'title', 'cover'])->toArray();
$courses = $courseRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($courses as $course) {
foreach ($courses->toArray() as $course) {
$result[$course['id']] = $course;
}
@ -65,11 +65,11 @@ class LearningList extends Transformer
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findByIds($ids, ['id', 'title'])->toArray();
$chapters = $chapterRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($chapters as $chapter) {
foreach ($chapters->toArray() as $chapter) {
$result[$chapter['id']] = $chapter;
}
@ -82,11 +82,11 @@ class LearningList extends Transformer
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar'])->toArray();
$users = $userRepo->findByIds($ids, ['id', 'name']);
$result = [];
foreach ($users as $user) {
foreach ($users->toArray() as $user) {
$result[$user['id']] = $user;
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Builders;
use App\Models\Nav as NavModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class NavTreeList extends Builder
{
public function handle($position = 'top')
{
$topNavs = $this->findTopNavs($position);
if ($topNavs->count() == 0) {
return [];
}
$list = [];
foreach ($topNavs as $nav) {
$list[] = [
'id' => $nav->id,
'name' => $nav->name,
'target' => $nav->target,
'url' => $nav->url,
'children' => $this->handleChildren($nav),
];
}
return $list;
}
protected function handleChildren(NavModel $nav)
{
$subNavs = $this->findSubNavs($nav->id);
if ($subNavs->count() == 0) {
return [];
}
$list = [];
foreach ($subNavs as $nav) {
$list[] = [
'id' => $nav->id,
'name' => $nav->name,
'target' => $nav->target,
'url' => $nav->url,
];
}
return $list;
}
/**
* @param int $navId
* @return ResultsetInterface|Resultset|NavModel[]
*/
protected function findSubNavs($navId)
{
return NavModel::query()
->where('parent_id = :parent_id:', ['parent_id' => $navId])
->andWhere('published = 1')
->orderBy('priority ASC')
->execute();
}
/**
* @param string $position
* @return ResultsetInterface|Resultset|NavModel[]
*/
protected function findTopNavs($position)
{
return NavModel::query()
->where('position = :position:', ['position' => $position])
->andWhere('level = 1 AND published = 1')
->orderBy('priority ASC')
->execute();
}
}

148
app/Builders/OrderList.php Normal file
View File

@ -0,0 +1,148 @@
<?php
namespace App\Builders;
use App\Models\Order as OrderModel;
use App\Repos\User as UserRepo;
class OrderList extends Builder
{
protected $imgBaseUrl;
public function __construct()
{
$this->imgBaseUrl = kg_cos_url();
}
/**
* @param array $orders
* @return array
*/
public function handleUsers(array $orders)
{
$users = $this->getUsers($orders);
foreach ($orders as $key => $order) {
$orders[$key]['owner'] = $users[$order['owner_id']] ?? new \stdClass();
}
return $orders;
}
/**
* @param array $orders
* @return array
*/
public function handleItems(array $orders)
{
foreach ($orders as $key => $order) {
$itemInfo = $this->handleItem($order);
$orders[$key]['item_info'] = $itemInfo;
}
return $orders;
}
/**
* @param array $order
* @return array|mixed
*/
public function handleItem(array $order)
{
$itemInfo = [];
switch ($order['item_type']) {
case OrderModel::ITEM_COURSE:
$itemInfo = $this->handleCourseInfo($order['item_info']);
break;
case OrderModel::ITEM_PACKAGE:
$itemInfo = $this->handlePackageInfo($order['item_info']);
break;
case OrderModel::ITEM_VIP:
$itemInfo = $this->handleVipInfo($order['item_info']);
break;
}
return $itemInfo;
}
/**
* @param string $itemInfo
* @return mixed
*/
protected function handleCourseInfo($itemInfo)
{
if (!empty($itemInfo) && is_string($itemInfo)) {
$itemInfo = json_decode($itemInfo, true);
$itemInfo['course']['cover'] = $this->imgBaseUrl . $itemInfo['course']['cover'];
}
return $itemInfo;
}
/**
* @param string $itemInfo
* @return mixed
*/
protected function handlePackageInfo($itemInfo)
{
if (!empty($itemInfo) && is_string($itemInfo)) {
$itemInfo = json_decode($itemInfo, true);
foreach ($itemInfo['courses'] as $key => $course) {
$itemInfo['courses'][$key]['cover'] = $this->imgBaseUrl . $course['cover'];
}
}
return $itemInfo;
}
/**
* @param string $itemInfo
* @return mixed
*/
protected function handleRewardInfo($itemInfo)
{
if (!empty($itemInfo) && is_string($itemInfo)) {
$itemInfo = json_decode($itemInfo, true);
$itemInfo['course']['cover'] = $this->imgBaseUrl . $itemInfo['course']['cover'];
}
return $itemInfo;
}
/**
* @param string $itemInfo
* @return mixed
*/
protected function handleVipInfo($itemInfo)
{
if (!empty($itemInfo) && is_string($itemInfo)) {
$itemInfo = json_decode($itemInfo, true);
}
return $itemInfo;
}
/**
* @param array $orders
* @return array
*/
protected function getUsers(array $orders)
{
$ids = kg_array_column($orders, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name']);
$result = [];
foreach ($users->toArray() as $user) {
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Builders;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
class RefundList extends Builder
{
public function handleOrders(array $trades)
{
$orders = $this->getOrders($trades);
foreach ($trades as $key => $trade) {
$trades[$key]['order'] = $orders[$trade['order_id']] ?? new \stdClass();
}
return $trades;
}
public function handleUsers(array $refunds)
{
$users = $this->getUsers($refunds);
foreach ($refunds as $key => $refund) {
$refunds[$key]['owner'] = $users[$refund['owner_id']] ?? new \stdClass();
}
return $refunds;
}
public function getOrders(array $trades)
{
$ids = kg_array_column($trades, 'order_id');
$orderRepo = new OrderRepo();
$orders = $orderRepo->findByIds($ids, ['id', 'sn', 'subject', 'amount']);
$result = [];
foreach ($orders->toArray() as $order) {
$result[$order['id']] = $order;
}
return $result;
}
public function getUsers(array $refunds)
{
$ids = kg_array_column($refunds, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name']);
$result = [];
foreach ($users->toArray() as $user) {
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -1,63 +1,66 @@
<?php
namespace App\Transformers;
namespace App\Builders;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
class ReviewList extends Transformer
class ReviewList extends Builder
{
public function handleCourses($reviews)
public function handleCourses(array $reviews)
{
$courses = $this->getCourses($reviews);
foreach ($reviews as $key => $review) {
$reviews[$key]['course'] = $courses[$review['course_id']];
$reviews[$key]['course'] = $courses[$review['course_id']] ?? new \stdClass();
}
return $reviews;
}
public function handleUsers($reviews)
public function handleUsers(array $reviews)
{
$users = $this->getUsers($reviews);
foreach ($reviews as $key => $review) {
$reviews[$key]['user'] = $users[$review['user_id']];
$reviews[$key]['owner'] = $users[$review['owner_id']] ?? new \stdClass();
}
return $reviews;
}
protected function getCourses($reviews)
public function getCourses(array $reviews)
{
$ids = kg_array_column($reviews, 'course_id');
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($ids, ['id', 'title'])->toArray();
$courses = $courseRepo->findByIds($ids, ['id', 'title']);
$result = [];
foreach ($courses as $course) {
foreach ($courses->toArray() as $course) {
$result[$course['id']] = $course;
}
return $result;
}
protected function getUsers($reviews)
public function getUsers(array $reviews)
{
$ids = kg_array_column($reviews, 'user_id');
$ids = kg_array_column($reviews, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar'])->toArray();
$users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']);
$baseUrl = kg_cos_url();
$result = [];
foreach ($users as $user) {
foreach ($users->toArray() as $user) {
$user['avatar'] = $baseUrl . $user['avatar'];
$result[$user['id']] = $user;
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Builders;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
class TradeList extends Builder
{
public function handleOrders(array $trades)
{
$orders = $this->getOrders($trades);
foreach ($trades as $key => $trade) {
$trades[$key]['order'] = $orders[$trade['order_id']] ?? new \stdClass();
}
return $trades;
}
public function handleUsers($trades)
{
$users = $this->getUsers($trades);
foreach ($trades as $key => $trade) {
$trades[$key]['owner'] = $users[$trade['owner_id']] ?? new \stdClass();
}
return $trades;
}
public function getOrders($trades)
{
$ids = kg_array_column($trades, 'order_id');
$orderRepo = new OrderRepo();
$orders = $orderRepo->findByIds($ids, ['id', 'sn', 'subject', 'amount']);
$result = [];
foreach ($orders->toArray() as $order) {
$result[$order['id']] = $order;
}
return $result;
}
public function getUsers($trades)
{
$ids = kg_array_column($trades, 'owner_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($ids, ['id', 'name']);
$result = [];
foreach ($users->toArray() as $user) {
$result[$user['id']] = $user;
}
return $result;
}
}

View File

@ -1,14 +1,25 @@
<?php
namespace App\Transformers;
namespace App\Builders;
use App\Models\User as UserModel;
use App\Repos\Role as RoleRepo;
class UserList extends Transformer
class UserList extends Builder
{
public function handleAdminRoles($users)
public function handleUsers(array $users)
{
$baseUrl = kg_cos_url();
foreach ($users as $key => $user) {
$users[$key]['avatar'] = $baseUrl . $user['avatar'];
}
return $users;
}
public function handleAdminRoles(array $users)
{
$roles = $this->getAdminRoles($users);
@ -19,9 +30,9 @@ class UserList extends Transformer
return $users;
}
public function handleEduRoles($users)
public function handleEduRoles(array $users)
{
$roles = $this->getEduRoles($users);
$roles = $this->getEduRoles();
foreach ($users as $key => $user) {
$users[$key]['edu_role'] = $roles[$user['edu_role']] ?? ['id' => 0, 'name' => 'N/A'];
@ -30,26 +41,26 @@ class UserList extends Transformer
return $users;
}
private function getAdminRoles($users)
protected function getAdminRoles(array $users)
{
$ids = kg_array_column($users, 'admin_role');
$roleRepo = new RoleRepo();
$roles = $roleRepo->findByIds($ids, ['id', 'name'])->toArray();
$roles = $roleRepo->findByIds($ids, ['id', 'name']);
$result = [];
foreach ($roles as $role) {
foreach ($roles->toArray() as $role) {
$result[$role['id']] = $role;
}
return $result;
}
private function getEduRoles()
protected function getEduRoles()
{
$result = [
return [
UserModel::EDU_ROLE_STUDENT => [
'id' => UserModel::EDU_ROLE_STUDENT,
'name' => '学员',
@ -59,8 +70,6 @@ class UserList extends Transformer
'name' => '讲师',
],
];
return $result;
}
}

View File

@ -2,36 +2,41 @@
namespace App\Caches;
use Phalcon\Mvc\User\Component as UserComponent;
use Phalcon\Cache\Backend\Redis as RedisCache;
use Phalcon\Mvc\User\Component;
abstract class Cache extends UserComponent
abstract class Cache extends Component
{
/**
* @var \Phalcon\Cache\Backend
* @var RedisCache
*/
protected $cache;
public function __construct()
{
$this->cache = $this->getDI()->get('cache');
$this->cache = $this->getDI()->getShared('cache');
}
/**
* 获取缓存内容
*
* @param mixed $params
* @param mixed $id
* @return mixed
*/
public function get($params = null)
public function get($id = null)
{
$key = $this->getKey($params);
$content = $this->cache->get($key);
$lifetime = $this->getLifetime();
$key = $this->getKey($id);
if (!$this->cache->exists($key)) {
$content = $this->getContent($id);
$lifetime = $this->getLifetime();
if (!$content) {
$content = $this->getContent($params);
$this->cache->save($key, $content, $lifetime);
} else {
$content = $this->cache->get($key);
}
@ -41,35 +46,48 @@ abstract class Cache extends UserComponent
/**
* 删除缓存内容
*
* @param mixed $params
* @param mixed $id
*/
public function delete($params = null)
public function delete($id = null)
{
$key = $this->getKey($params);
$key = $this->getKey($id);
$this->cache->delete($key);
}
/**
* 重建缓存内容
*
* @param mixed $id
*/
public function rebuild($id = null)
{
$this->delete($id);
$this->get($id);
}
/**
* 获取缓存有效期
*
* @return integer
* @return int
*/
abstract protected function getLifetime();
abstract public function getLifetime();
/**
* 获取键值
*
* @param mixed $params
* @param mixed $id
* @return string
*/
abstract protected function getKey($params = null);
abstract public function getKey($id = null);
/**
* 获取原始内容
*
* @param mixed $params
* @param mixed $id
* @return mixed
*/
abstract protected function getContent($params = null);
abstract public function getContent($id = null);
}

View File

@ -1,57 +1,31 @@
<?php
namespace App\Library\Cache;
namespace App\Caches;
use App\Models\Category as CategoryModel;
use App\Exceptions\NotFound as ModelNotFoundException;
use App\Repos\Category as CategoryRepo;
class Category extends \Phalcon\Di\Injectable
class Category extends Cache
{
private $lifetime = 86400 * 30;
public function getOrFail($id)
{
$result = $this->getById($id);
if (!$result) {
throw new ModelNotFoundException('category.not_found');
}
return $result;
}
public function get($id)
{
$cacheOptions = [
'key' => $this->getKey($id),
'lifetime' => $this->getLifetime(),
];
$result = CategoryModel::query()
->where('id = :id:', ['id' => $id])
->cache($cacheOptions)
->execute()
->getFirst();
return $result;
}
public function delete($id)
{
$key = $this->getKey($id);
$this->modelsCache->delete($key);
}
public function getKey($id)
{
return "category:{$id}";
}
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "category:{$id}";
}
public function getContent($id = null)
{
$categoryRepo = new CategoryRepo();
$category = $categoryRepo->findById($id);
return $category ?: null;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Caches;
use App\Models\Category as CategoryModel;
use Phalcon\Mvc\Model\Resultset;
class CategoryList extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($type = null)
{
return "category_list:{$type}";
}
/**
* @param null $type
* @return array
*/
public function getContent($type = null)
{
/**
* @var Resultset $categories
*/
$categories = CategoryModel::query()
->columns(['id', 'parent_id', 'name', 'priority', 'level', 'path'])
->where('type = :type:', ['type' => $type])
->andWhere('published = 1')
->execute();
if ($categories->count() == 0) {
return [];
}
return $categories->toArray();
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Builders\CategoryTreeList as CategoryTreeListBuilder;
class CategoryTreeList extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($type = null)
{
return "category_tree_list:{$type}";
}
public function getContent($type = null)
{
$builder = new CategoryTreeListBuilder();
$list = $builder->handle($type);
return $list ?: [];
}
}

31
app/Caches/Chapter.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Repos\Chapter as ChapterRepo;
class Chapter extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "chapter:{$id}";
}
public function getContent($id = null)
{
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($id);
return $chapter ?: null;
}
}

View File

@ -1,70 +0,0 @@
<?php
namespace App\Caches;
use App\Repos\Config as ConfigRepo;
class Config extends Cache
{
protected $lifetime = 365 * 86400;
/**
* 获取某组配置项
*
* @param string $section
* @return \stdClass|null
*/
public function getSectionConfig($section)
{
$items = $this->get();
if (!$items) return;
$result = new \stdClass();
foreach ($items as $item) {
if ($item->section == $section) {
$result->{$item->item_key} = $item->item_value;
}
}
return $result;
}
/**
* 获取某个配置项的值
*
* @param string $section
* @param string $key
* @return string|null
*/
public function getItemValue($section, $key)
{
$config = $this->getSectionConfig($section);
$result = $config->{$key} ?? null;
return $result;
}
protected function getLifetime()
{
return $this->lifetime;
}
protected function getKey($params = null)
{
return 'site_config';
}
protected function getContent($params = null)
{
$configRepo = new ConfigRepo();
$items = $configRepo->findAll();
return $items;
}
}

139
app/Caches/Counter.php Normal file
View File

@ -0,0 +1,139 @@
<?php
namespace App\Caches;
use App\Library\Cache\Backend\Redis as RedisCache;
use Phalcon\Mvc\User\Component;
abstract class Counter extends Component
{
/**
* @var RedisCache
*/
protected $cache;
/**
* @var \Redis
*/
protected $redis;
public function __construct()
{
$this->cache = $this->getDI()->getShared('cache');
$this->redis = $this->cache->getRedis();
}
/**
* 获取缓存内容
*
* @param mixed $id
* @return array
*/
public function get($id = null)
{
$key = $this->getKey($id);
$content = $this->redis->hGetAll($key);
if (!$this->cache->exists($key)) {
$content = $this->getContent($id);
$lifetime = $this->getLifetime();
$this->redis->hMSet($key, $content);
$this->redis->expire($key, $lifetime);
}
return $content;
}
/**
* 删除缓存内容
*
* @param mixed $id
*/
public function delete($id = null)
{
$key = $this->getKey($id);
$this->cache->delete($key);
}
/**
* 重建缓存内容
*
* @param mixed $id
*/
public function rebuild($id = null)
{
$this->delete($id);
$this->get($id);
}
public function hGet($id, $hashKey)
{
$key = $this->getKey($id);
if (!$this->redis->exists($key)) {
$this->get($id);
}
return $this->redis->hGet($key, $hashKey);
}
public function hDel($id, $hashKey)
{
$key = $this->getKey($id);
return $this->redis->hDel($key, $hashKey);
}
public function hIncrBy($id, $hashKey, $value = 1)
{
$key = $this->getKey($id);
if (!$this->redis->exists($key)) {
$this->get($id);
}
$this->redis->hIncrBy($key, $hashKey, $value);
}
public function hDecrBy($id, $hashKey, $value = 1)
{
$key = $this->getKey($id);
if (!$this->redis->exists($key)) {
$this->get($id);
}
$this->redis->hIncrBy($key, $hashKey, 0 - $value);
}
/**
* 获取缓存有效期
*
* @return int
*/
abstract public function getLifetime();
/**
* 获取键值
*
* @param mixed $id
* @return string
*/
abstract public function getKey($id = null);
/**
* 获取原始内容
*
* @param mixed $id
* @return mixed
*/
abstract public function getContent($id = null);
}

View File

@ -1,57 +1,31 @@
<?php
namespace App\Library\Cache;
namespace App\Caches;
use App\Models\Course as CourseModel;
use App\Exceptions\NotFound as ModelNotFoundException;
use App\Repos\Course as CourseRepo;
class Course extends \Phalcon\Di\Injectable
class Course extends Cache
{
private $lifetime = 86400;
public function getOrFail($id)
{
$result = $this->getById($id);
if (!$result) {
throw new ModelNotFoundException('course.not_found');
}
return $result;
}
public function get($id)
{
$cacheOptions = [
'key' => $this->getKey($id),
'lifetime' => $this->getLifetime(),
];
$result = CourseModel::query()
->where('id = :id:', ['id' => $id])
->cache($cacheOptions)
->execute()
->getFirst();
return $result;
}
public function delete($id)
{
$key = $this->getKey($id);
$this->modelsCache->delete($key);
}
public function getKey($id)
{
return "course:{$id}";
}
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course:{$id}";
}
public function getContent($id = null)
{
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($id);
return $course ?: null;
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Caches;
use App\Models\Category as CategoryModel;
use App\Repos\Course as CourseRepo;
class CourseCategoryList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_category_list:{$id}";
}
public function getContent($id = null)
{
$courseRepo = new CourseRepo();
$categories = $courseRepo->findCategories($id);
if ($categories->count() == 0) {
return [];
}
return $this->handleContent($categories);
}
/**
* @param CategoryModel[] $categories
* @return array
*/
public function handleContent($categories)
{
$result = [];
foreach ($categories as $category) {
$result[] = [
'id' => $category->id,
'name' => $category->name,
];
}
return $result;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Builders\CourseChapterList as CourseChapterListBuilder;
class CourseChapterList extends Cache
{
protected $lifetime = 7 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_chapter_list:{$id}";
}
public function getContent($id = null)
{
$builder = new CourseChapterListBuilder();
$list = $builder->handle($id);
return $list ?: [];
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Caches;
use App\Models\Package as PackageModel;
use App\Repos\Course as CourseRepo;
class CoursePackageList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_package_list:{$id}";
}
public function getContent($id = null)
{
$courseRepo = new CourseRepo();
$packages = $courseRepo->findPackages($id);
if ($packages->count() == 0) {
return [];
}
return $this->handleContent($packages);
}
/**
* @param PackageModel[] $packages
* @return array
*/
protected function handleContent($packages)
{
$result = [];
foreach ($packages as $package) {
$result[] = [
'id' => $package->id,
'title' => $package->title,
'course_count' => $package->course_count,
'market_price' => $package->market_price,
'vip_price' => $package->vip_price,
];
}
return $result;
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Caches;
use App\Models\Course as CourseModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CourseRecommendedList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_recommended_list:{$id}";
}
public function getContent($id = null)
{
$courses = $this->findCourses(5);
if ($courses->count() == 0) {
return [];
}
return $this->handleContent($courses);
}
/**
* @param CourseModel[] $courses
* @return array
*/
public function handleContent($courses)
{
$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[]
*/
public function findCourses($limit = 5)
{
return CourseModel::query()
->where('published = 1 AND market_price > 0')
->orderBy('RAND()')
->limit($limit)
->execute();
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Caches;
use App\Models\Course as CourseModel;
use App\Repos\Course as CourseRepo;
class CourseRelatedList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_related_list:{$id}";
}
public function getContent($id = null)
{
$courseRepo = new CourseRepo();
$courses = $courseRepo->findRelatedCourses($id);
if ($courses->count() == 0) {
return [];
}
return $this->handleContent($courses);
}
/**
* @param CourseModel[] $courses
* @return array
*/
public function handleContent($courses)
{
$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;
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Caches;
use App\Models\User as UserModel;
use App\Repos\Course as CourseRepo;
class CourseTeacherList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_teacher_list:{$id}";
}
public function getContent($id = null)
{
$courseRepo = new CourseRepo();
$users = $courseRepo->findTeachers($id);
if ($users->count() == 0) {
return [];
}
return $this->handleContent($users);
}
/**
* @param UserModel[] $users
* @return array
*/
public function handleContent($users)
{
$result = [];
foreach ($users as $user) {
$result[] = [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
'title' => $user->title,
'about' => $user->about,
];
}
return $result;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Caches;
use App\Models\Topic as TopicModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CourseTopicList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_topic_list:{$id}";
}
public function getContent($id = null)
{
$topics = $this->findTopics(5);
if ($topics->count() == 0) {
return [];
}
return $this->handleContent($topics);
}
/**
* @param TopicModel[] $topics
* @return array
*/
public function handleContent($topics)
{
$result = [];
foreach ($topics as $topic) {
$result[] = [
'id' => $topic->id,
'title' => $topic->title,
];
}
return $result;
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|TopicModel[]
*/
public function findTopics($limit = 5)
{
return TopicModel::query()
->where('published = 1')
->orderBy('RAND()')
->limit($limit)
->execute();
}
}

31
app/Caches/Help.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Repos\Help as HelpRepo;
class Help extends Cache
{
protected $lifetime = 7 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "help:{$id}";
}
public function getContent($id = null)
{
$helpRepo = new HelpRepo();
$help = $helpRepo->findById($id);
return $help ?: null;
}
}

89
app/Caches/HelpList.php Normal file
View File

@ -0,0 +1,89 @@
<?php
namespace App\Caches;
use App\Models\Category as CategoryModel;
use App\Models\Help as HelpModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class HelpList extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'help_list';
}
public function getContent($id = null)
{
$categories = $this->findCategories();
if ($categories->count() == 0) {
return [];
}
$result = [];
foreach ($categories as $category) {
$item = [];
$item['category'] = [
'id' => $category->id,
'name' => $category->name,
];
$item['helps'] = [];
$helps = $this->findHelps($category->id);
if ($helps->count() > 0) {
foreach ($helps as $help) {
$item['helps'][] = [
'id' => $help->id,
'title' => $help->title,
];
}
}
$result[] = $item;
}
return $result;
}
/**
* @return ResultsetInterface|Resultset|CategoryModel[]
*/
protected function findCategories()
{
return CategoryModel::query()
->where('type = :type:', ['type' => CategoryModel::TYPE_HELP])
->andWhere('level = 1 AND published = 1')
->orderBy('priority ASC')
->execute();
}
/**
* @param int $categoryId
* @return ResultsetInterface|Resultset|CategoryModel[]
*/
protected function findHelps($categoryId)
{
return HelpModel::query()
->where('category_id = :category_id:', ['category_id' => $categoryId])
->andWhere('published = 1')
->orderBy('priority ASC')
->execute();
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Caches;
use App\Models\ImMessage;
use App\Models\ImMessage as ImMessageModel;
use App\Models\User as UserModel;
use App\Repos\ImGroup as ImGroupRepo;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class ImActiveGroupList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'im_active_group_list';
}
public function getContent($id = null)
{
$groups = $this->findGroups();
if (empty($groups)) {
return [];
}
$result = [];
foreach ($groups as $group) {
$result[] = [
'id' => $group->id,
'type' => $group->type,
'name' => $group->name,
'avatar' => $group->avatar,
'about' => $group->about,
'user_count' => $group->user_count,
'msg_count' => $group->msg_count,
];
}
return $result;
}
/**
* @param int $days
* @param int $limit
* @return ResultsetInterface|Resultset|UserModel[]
*/
protected function findGroups($days = 7, $limit = 12)
{
$result = [];
$startTime = strtotime("-{$days} days");
$endTime = time();
$rows = ImMessageModel::query()
->columns(['receiver_id', 'total_count' => 'count(receiver_id)'])
->groupBy('receiver_id')
->orderBy('total_count DESC')
->where('receiver_type = :type:', ['type' => ImMessageModel::TYPE_GROUP])
->betweenWhere('create_time', $startTime, $endTime)
->limit($limit)
->execute();
if ($rows->count() > 0) {
$ids = kg_array_column($rows->toArray(), 'receiver_id');
$groupRepo = new ImGroupRepo();
$result = $groupRepo->findByIds($ids);
}
return $result;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace App\Caches;
use App\Models\ImMessage as ImMessageModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class ImActiveUserList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'im_active_user_list';
}
public function getContent($id = null)
{
$users = $this->findUsers($id);
if (empty($users)) {
return [];
}
$result = [];
foreach ($users as $user) {
$result[] = [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
'title' => $user->title,
'about' => $user->about,
'vip' => $user->vip,
];
}
return $result;
}
/**
* @param int $days
* @param int $limit
* @return ResultsetInterface|Resultset|UserModel[]
*/
protected function findUsers($days = 7, $limit = 12)
{
$result = [];
$startTime = strtotime("-{$days} days");
$endTime = time();
$rows = ImMessageModel::query()
->columns(['sender_id', 'total_count' => 'count(sender_id)'])
->groupBy('sender_id')
->orderBy('total_count DESC')
->betweenWhere('create_time', $startTime, $endTime)
->limit($limit)
->execute();
if ($rows->count() > 0) {
$ids = kg_array_column($rows->toArray(), 'sender_id');
$userRepo = new UserRepo();
$result = $userRepo->findByIds($ids);
}
return $result;
}
}

31
app/Caches/ImGroup.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Repos\ImGroup as ImGroupRepo;
class ImGroup extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "im_group:{$id}";
}
public function getContent($id = null)
{
$groupRepo = new ImGroupRepo();
$group = $groupRepo->findById($id);
return $group ?: null;
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Caches;
use App\Models\ImMessage as ImMessageModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class ImGroupActiveUserList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "im_group_active_user_list:{$id}";
}
public function getContent($id = null)
{
$users = $this->findUsers($id);
if (empty($users)) {
return [];
}
$result = [];
foreach ($users as $user) {
$result[] = [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
'title' => $user->title,
'about' => $user->about,
'vip' => $user->vip,
];
}
return $result;
}
/**
* @param int $groupId
* @param int $days
* @param int $limit
* @return ResultsetInterface|Resultset|UserModel[]
*/
protected function findUsers($groupId, $days = 7, $limit = 5)
{
$result = [];
$startTime = strtotime("-{$days} days");
$endTime = time();
$rows = ImMessageModel::query()
->columns(['sender_id', 'total_count' => 'count(sender_id)'])
->groupBy('sender_id')
->orderBy('total_count DESC')
->where('receiver_id = :group_id:', ['group_id' => $groupId])
->andWhere('receiver_type = :type:', ['type' => ImMessageModel::TYPE_GROUP])
->betweenWhere('create_time', $startTime, $endTime)
->limit($limit)
->execute();
if ($rows->count() > 0) {
$ids = kg_array_column($rows->toArray(), 'sender_id');
$userRepo = new UserRepo();
$result = $userRepo->findByIds($ids);
}
return $result;
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Caches;
use App\Models\ImGroup as ImGroupModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class ImNewGroupList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'im_new_group_list';
}
public function getContent($id = null)
{
$limit = 12;
$groups = $this->findGroups($limit);
if ($groups->count() == 0) {
return [];
}
return $this->handleContent($groups);
}
/**
* @param ImGroupModel[] $groups
* @return array
*/
protected function handleContent($groups)
{
$result = [];
foreach ($groups as $group) {
$result[] = [
'id' => $group->id,
'type' => $group->type,
'name' => $group->name,
'avatar' => $group->avatar,
'about' => $group->about,
'user_count' => $group->user_count,
'msg_count' => $group->msg_count,
];
}
return $result;
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|ImGroupModel[]
*/
public function findGroups($limit = 12)
{
return ImGroupModel::query()
->where('published = 1')
->orderBy('id DESC')
->limit($limit)
->execute();
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Caches;
use App\Models\User as UserModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class ImNewUserList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'im_new_user_list';
}
public function getContent($id = null)
{
$limit = 12;
$users = $this->findUsers($limit);
if ($users->count() == 0) {
return [];
}
return $this->handleContent($users);
}
/**
* @param UserModel[] $users
* @return array
*/
protected function handleContent($users)
{
$result = [];
foreach ($users as $user) {
$result[] = [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
'title' => $user->title,
'about' => $user->about,
'vip' => $user->vip,
];
}
return $result;
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|UserModel[]
*/
public function findUsers($limit = 12)
{
return UserModel::query()
->where('deleted = 0')
->orderBy('id DESC')
->limit($limit)
->execute();
}
}

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 IndexFreeCourseList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_free_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('market_price = 0')
->orderBy('score DESC')
->limit($limit)
->execute();
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace App\Caches;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use Phalcon\Mvc\Model\Resultset;
/**
* 直播课程
*/
class IndexLiveList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_live_list';
}
public function getContent($id = null)
{
/**
* 限制输出多少天数(一维限额)
*/
$dayLimit = 3;
/**
* 限制每天维度下的输出数(二维限额)
*/
$perDayLimit = 10;
$beginTime = strtotime('today');
$endTime = strtotime("+30 days");
/**
* @var Resultset|ChapterLiveModel[] $lives
*/
$lives = ChapterLiveModel::query()
->betweenWhere('start_time', $beginTime, $endTime)
->orderBy('start_time ASC')
->execute();
if ($lives->count() == 0) {
return [];
}
$result = [];
$chapterIds = kg_array_column($lives->toArray(), 'chapter_id');
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findByIds($chapterIds);
$chapterMapping = [];
foreach ($chapters as $chapter) {
$chapterMapping[$chapter->id] = $chapter;
}
$courseIds = kg_array_column($lives->toArray(), 'course_id');
$courseRepo = new CourseRepo();
$courses = $courseRepo->findByIds($courseIds);
$courseMapping = [];
foreach ($courses as $course) {
$courseMapping[$course->id] = $course;
}
foreach ($lives as $live) {
if (count($result) >= $dayLimit) {
break;
}
$day = date('y-m-d', $live->start_time);
if (isset($result[$day]) && count($result[$day]) >= $perDayLimit) {
continue;
}
$chapter = $chapterMapping[$live->chapter_id];
$course = $courseMapping[$chapter->course_id];
$chapterInfo = [
'id' => $chapter->id,
'title' => $chapter->title,
'start_time' => $live->start_time,
'end_time' => $live->end_time,
];
$courseInfo = [
'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,
];
$result[$day][] = [
'course' => $courseInfo,
'chapter' => $chapterInfo,
];
}
return $result;
}
}

View File

@ -0,0 +1,117 @@
<?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 IndexNewCourseList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_new_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')
->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 IndexSimpleFreeCourseList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_simple_free_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('market_price = 0')
->orderBy('score DESC')
->limit($limit)
->execute();
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\Caches;
use App\Models\Course as CourseModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
/**
* 简版新上课程
*/
class IndexSimpleNewCourseList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_simple_new_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')
->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 IndexSimpleVipCourseList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_simple_vip_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('vip_price >= 0')
->orderBy('score DESC')
->limit($limit)
->execute();
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Caches;
use App\Models\Slide as SlideModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class IndexSlideList extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_slide_list';
}
public function getContent($id = null)
{
$limit = 5;
$slides = $this->findSlides($limit);
if ($slides->count() == 0) {
return [];
}
return $this->handleContent($slides);
}
/**
* @param SlideModel[] $slides
* @return array
*/
protected function handleContent($slides)
{
$result = [];
foreach ($slides as $slide) {
$result[] = [
'id' => $slide->id,
'title' => $slide->title,
'cover' => $slide->cover,
'target' => $slide->target,
'content' => $slide->content,
];
}
return $result;
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|SlideModel[]
*/
public function findSlides($limit = 5)
{
return SlideModel::query()
->where('published = 1')
->orderBy('priority ASC')
->limit($limit)
->execute();
}
}

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 IndexVipCourseList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_vip_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('vip_price >= 0')
->orderBy('score DESC')
->limit($limit)
->execute();
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\Category as CategoryModel;
class MaxCategoryId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_category_id';
}
public function getContent($id = null)
{
$category = CategoryModel::findFirst(['order' => 'id DESC']);
return $category->id ?? 0;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\Chapter as ChapterModel;
class MaxChapterId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_chapter_id';
}
public function getContent($id = null)
{
$chapter = ChapterModel::findFirst(['order' => 'id DESC']);
return $chapter->id ?? 0;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\Course as CourseModel;
class MaxCourseId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_course_id';
}
public function getContent($id = null)
{
$course = CourseModel::findFirst(['order' => 'id DESC']);
return $course->id ?? 0;
}
}

29
app/Caches/MaxHelpId.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\Help as HelpModel;
class MaxHelpId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_help_id';
}
public function getContent($id = null)
{
$help = HelpModel::findFirst(['order' => 'id DESC']);
return $help->id ?? 0;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\ImGroup as ImGroupModel;
class MaxImGroupId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_im_group_id';
}
public function getContent($id = null)
{
$group = ImGroupModel::findFirst(['order' => 'id DESC']);
return $group->id ?? 0;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\Package as PackageModel;
class MaxPackageId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_package_id';
}
public function getContent($id = null)
{
$package = PackageModel::findFirst(['order' => 'id DESC']);
return $package->id ?? 0;
}
}

29
app/Caches/MaxPageId.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\Page as PageModel;
class MaxPageId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_page_id';
}
public function getContent($id = null)
{
$page = PageModel::findFirst(['order' => 'id DESC']);
return $page->id ?? 0;
}
}

29
app/Caches/MaxTopicId.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\Topic as TopicModel;
class MaxTopicId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_topic_id';
}
public function getContent($id = null)
{
$topic = TopicModel::findFirst(['order' => 'id DESC']);
return $topic->id ?? 0;
}
}

29
app/Caches/MaxUserId.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\User as UserModel;
class MaxUserId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_user_id';
}
public function getContent($id = null)
{
$user = UserModel::findFirst(['order' => 'id DESC']);
return $user->id ?? 0;
}
}

View File

@ -1,53 +0,0 @@
<?php
namespace App\Caches;
use App\Repos\Nav as NavRepo;
class Nav extends Cache
{
protected $lifetime = 365 * 86400;
public function getTopNav()
{
$items = $this->get();
if (!$items) return;
$result = new \stdClass();
foreach ($items as $item) {
if ($item->position == 'top') {
$result->{$item->item_key} = $item->item_value;
}
}
return $result;
}
public function getBottomNav()
{
}
protected function getLifetime()
{
return $this->lifetime;
}
protected function getKey($params = null)
{
return 'nav';
}
protected function getContent($params = null)
{
$navRepo = new NavRepo();
$items = $navRepo->findAll();
return $items;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Caches;
use App\Builders\NavTreeList as NavTreeListBuilder;
use App\Models\Nav as NavModel;
class NavTreeList extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'nav_tree_list';
}
public function getContent($id = null)
{
$builder = new NavTreeListBuilder();
return [
'top' => $builder->handle(NavModel::POS_TOP),
'bottom' => $builder->handle(NavModel::POS_BOTTOM),
];
}
}

31
app/Caches/Package.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Repos\Package as PackageRepo;
class Package extends Cache
{
protected $lifetime = 7 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "package:{$id}";
}
public function getContent($id = null)
{
$packageRepo = new PackageRepo();
$package = $packageRepo->findById($id);
return $package ?: null;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Caches;
use App\Models\Course as CourseModel;
use App\Repos\Package as PackageRepo;
class PackageCourseList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "package_course_list:{$id}";
}
public function getContent($id = null)
{
$packageRepo = new PackageRepo();
$courses = $packageRepo->findCourses($id);
if ($courses->count() == 0) {
return [];
}
return $this->handleContent($courses);
}
/**
* @param CourseModel[] $courses
* @return array
*/
public function handleContent($courses)
{
$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;
}
}

31
app/Caches/Page.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Repos\Page as PageRepo;
class Page extends Cache
{
protected $lifetime = 7 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "page:{$id}";
}
public function getContent($id = null)
{
$pageRepo = new PageRepo();
$page = $pageRepo->findById($id);
return $page ?: null;
}
}

65
app/Caches/SaleTrend.php Normal file
View File

@ -0,0 +1,65 @@
<?php
namespace App\Caches;
use App\Models\Order as OrderModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class SaleTrend extends Cache
{
protected $lifetime = 2 * 3600;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'sale_trend';
}
public function getContent($id = null)
{
}
/**
* @param OrderModel[] $sales
* @param int $days
* @return array
*/
protected function handleSales($sales, $days = 7)
{
$result = [];
foreach (array_reverse(range(1, $days)) as $num) {
$date = date('Y-m-d', strtotime("-{$num} days"));
$result[$date] = 0;
}
foreach ($sales as $sale) {
$date = date('Y-m-d', $sale->create_time);
$result[$date] += $sale->amount;
}
return $result;
}
/**
* @param int $days
* @return ResultsetInterface|Resultset|OrderModel[]
*/
protected function findSales($days = 7)
{
$time = strtotime("-{$days} days");
return OrderModel::query()
->where('status = :status:', ['status' => OrderModel::STATUS_FINISHED])
->andWhere('create_time > :time:', ['time' => $time])
->execute();
}
}

41
app/Caches/Setting.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace App\Caches;
use App\Repos\Setting as SettingRepo;
class Setting extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "setting:{$id}";
}
public function getContent($id = null)
{
$settingRepo = new SettingRepo();
$items = $settingRepo->findAll(['section' => $id]);
if ($items->count() == 0) {
return [];
}
$result = [];
foreach ($items as $item) {
$result[$item->item_key] = $item->item_value;
}
return $result;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Caches;
use App\Repos\Consult as ConsultRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\ImGroup as GroupRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\Package as PackageRepo;
use App\Repos\Review as ReviewRepo;
use App\Repos\Topic as TopicRepo;
use App\Repos\User as UserRepo;
class SiteGlobalStat extends Cache
{
protected $lifetime = 2 * 3600;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'site_global_stat';
}
public function getContent($id = null)
{
$courseRepo = new CourseRepo();
$consultRepo = new ConsultRepo();
$groupRepo = new GroupRepo();
$orderRepo = new OrderRepo();
$packageRepo = new PackageRepo();
$reviewRepo = new ReviewRepo();
$topicRepo = new TopicRepo();
$userRepo = new UserRepo();
return [
'course_count' => $courseRepo->countCourses(),
'consult_count' => $consultRepo->countConsults(),
'group_count' => $groupRepo->countGroups(),
'order_count' => $orderRepo->countOrders(),
'package_count' => $packageRepo->countPackages(),
'review_count' => $reviewRepo->countReviews(),
'topic_count' => $topicRepo->countTopics(),
'user_count' => $userRepo->countUsers(),
];
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Caches;
use App\Models\Order as OrderModel;
use App\Models\User as UserModel;
class SiteTodayStat extends Cache
{
protected $lifetime = 1 * 3600;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'site_today_stat';
}
public function getContent($id = null)
{
return [
'user_count' => $this->countUsers(),
'order_count' => $this->countOrders(),
'sale_amount' => $this->sumSales(),
];
}
protected function countUsers()
{
return (int)UserModel::count([
'conditions' => 'create_time > :time:',
'bind' => ['time' => strtotime('today')],
]);
}
protected function countOrders()
{
return (int)OrderModel::count([
'conditions' => 'create_time > :time: AND status = :status:',
'bind' => [
'time' => strtotime('today'),
'status' => OrderModel::STATUS_FINISHED,
],
]);
}
protected function sumSales()
{
return (float)OrderModel::sum([
'column' => 'amount',
'conditions' => 'create_time > :time: AND status = :status:',
'bind' => [
'time' => strtotime('today'),
'status' => OrderModel::STATUS_FINISHED,
],
]);
}
}

31
app/Caches/Topic.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Repos\Topic as TopicRepo;
class Topic extends Cache
{
protected $lifetime = 7 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "topic:{$id}";
}
public function getContent($id = null)
{
$topicRepo = new TopicRepo();
$topic = $topicRepo->findById($id);
return $topic ?: null;
}
}

View File

@ -1,57 +1,31 @@
<?php
namespace App\Library\Cache;
namespace App\Caches;
use App\Models\User as UserModel;
use App\Exceptions\NotFound as ModelNotFoundException;
use App\Repos\User as UserRepo;
class User extends \Phalcon\Di\Injectable
class User extends Cache
{
private $lifetime = 86400 * 30;
public function getOrFail($id)
{
$result = $this->getById($id);
if (!$result) {
throw new ModelNotFoundException('user.not_found');
}
return $result;
}
public function get($id)
{
$cacheOptions = [
'key' => $this->getKey($id),
'lifetime' => $this->getLifetime(),
];
$result = UserModel::query()
->where('id = :id:', ['id' => $id])
->cache($cacheOptions)
->execute()
->getFirst();
return $result;
}
public function delete($id)
{
$key = $this->getKey($id);
$this->modelsCache->delete($key);
}
public function getKey($id)
{
return "user:{$id}";
}
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "user:{$id}";
}
public function getContent($id = null)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($id);
return $user ?: null;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Caches;
class UserDailyCounter extends Counter
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
$tomorrow = strtotime('tomorrow');
return $tomorrow - time();
}
public function getKey($id = null)
{
return "user_daily_counter:{$id}";
}
public function getContent($id = null)
{
return [
'danmu_count' => 0,
'consult_count' => 0,
'order_count' => 0,
'chapter_like_count' => 0,
'consult_like_count' => 0,
'review_like_count' => 0,
];
}
}

View File

@ -2,8 +2,6 @@
namespace App\Console\Tasks;
use Phalcon\Cli\Task;
class CleanLogTask extends Task
{
@ -11,15 +9,18 @@ class CleanLogTask extends Task
{
$this->cleanCommonLog();
$this->cleanConsoleLog();
$this->cleanHttpLog();
$this->cleanSqlLog();
$this->cleanListenerLog();
$this->cleanListenLog();
$this->cleanCaptchaLog();
$this->cleanMailerLog();
$this->cleanSmserLog();
$this->cleanMailLog();
$this->cleanSmsLog();
$this->cleanVodLog();
$this->cleanLiveLog();
$this->cleanStorageLog();
$this->cleanAlipayLog();
$this->cleanWxpayLog();
$this->cleanOrderLog();
$this->cleanRefundLog();
}
@ -31,6 +32,14 @@ class CleanLogTask extends Task
$this->cleanLog('common', 7);
}
/**
* 清理Http日志
*/
protected function cleanHttpLog()
{
$this->cleanLog('http', 7);
}
/**
* 清理Console日志
*/
@ -48,11 +57,11 @@ class CleanLogTask extends Task
}
/**
* 清理监听日志
* 清理监听日志
*/
protected function cleanListenerLog()
protected function cleanListenLog()
{
$this->cleanLog('listener', 7);
$this->cleanLog('listen', 7);
}
/**
@ -71,6 +80,14 @@ class CleanLogTask extends Task
$this->cleanLog('vod', 7);
}
/**
* 清理直播服务日志
*/
protected function cleanLiveLog()
{
$this->cleanLog('live', 7);
}
/**
* 清理存储服务日志
*/
@ -82,17 +99,17 @@ class CleanLogTask extends Task
/**
* 清理短信服务日志
*/
protected function cleanSmserLog()
protected function cleanSmsLog()
{
$this->cleanLog('smser', 7);
$this->cleanLog('sms', 7);
}
/**
* 清理邮件服务日志
*/
protected function cleanMailerLog()
protected function cleanMailLog()
{
$this->cleanLog('mailer', 7);
$this->cleanLog('mail', 7);
}
/**
@ -111,6 +128,14 @@ class CleanLogTask extends Task
$this->cleanLog('wxpay', 30);
}
/**
* 清理订单日志
*/
protected function cleanOrderLog()
{
$this->cleanLog('order', 30);
}
/**
* 清理退款日志
*/
@ -123,7 +148,7 @@ class CleanLogTask extends Task
* 清理日志文件
*
* @param string $prefix
* @param integer $keepDays 保留天数
* @param int $keepDays 保留天数
* @return mixed
*/
protected function cleanLog($prefix, $keepDays)
@ -138,9 +163,9 @@ class CleanLogTask extends Task
if (strtotime($today) - strtotime($date) >= $keepDays * 86400) {
$deleted = unlink($file);
if ($deleted) {
echo "Delete {$file} success" . PHP_EOL;
echo "delete {$file} success" . PHP_EOL;
} else {
echo "Delete {$file} failed" . PHP_EOL;
echo "delete {$file} failed" . PHP_EOL;
}
}
}

View File

@ -0,0 +1,43 @@
<?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

@ -3,7 +3,8 @@
namespace App\Console\Tasks;
use App\Models\Order as OrderModel;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CloseOrderTask extends Task
{
@ -25,22 +26,20 @@ class CloseOrderTask extends Task
/**
* 查找待关闭订单
*
* @param integer $limit
* @return \Phalcon\Mvc\Model\ResultsetInterface
* @param int $limit
* @return ResultsetInterface|Resultset|OrderModel[]
*/
protected function findOrders($limit = 1000)
{
$status = OrderModel::STATUS_PENDING;
$createdAt = time() - 12 * 3600;
$createTime = time() - 12 * 3600;
$orders = OrderModel::query()
return OrderModel::query()
->where('status = :status:', ['status' => $status])
->andWhere('created_at < :created_at:', ['created_at' => $createdAt])
->andWhere('create_time < :create_time:', ['create_time' => $createTime])
->limit($limit)
->execute();
return $orders;
}
}

View File

@ -3,8 +3,10 @@
namespace App\Console\Tasks;
use App\Models\Trade as TradeModel;
use App\Services\Alipay as AlipayService;
use Phalcon\Cli\Task;
use App\Services\Pay\Alipay as AlipayService;
use App\Services\Pay\Wxpay as WxpayService;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CloseTradeTask extends Task
{
@ -19,52 +21,82 @@ class CloseTradeTask extends Task
foreach ($trades as $trade) {
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$this->closeAlipayTrade($trade);
$this->handleAlipayTrade($trade);
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$this->closeWxpayTrade($trade);
$this->handleWxpayTrade($trade);
}
}
}
/**
* 关闭支付宝交易
* 处理支付宝交易
*
* @param TradeModel $trade
*/
protected function closeAlipayTrade($trade)
protected function handleAlipayTrade(TradeModel $trade)
{
$service = new AlipayService();
$allowClosed = true;
$alyOrder = $service->findOrder($trade->sn);
$alipay = new AlipayService();
if ($alyOrder) {
if ($alyOrder->trade_status == 'WAIT_BUYER_PAY') {
$service->closeOrder($trade->sn);
$alipayTrade = $alipay->find($trade->sn);
if ($alipayTrade) {
/**
* 异步通知接收异常,补救漏网
*/
if ($alipayTrade->trade_status == 'TRADE_SUCCESS') {
$this->eventsManager->fire('pay:afterPay', $this, $trade);
$allowClosed = false;
} elseif ($alipayTrade->trade_status == 'WAIT_BUYER_PAY') {
$allowClosed = $alipay->close($trade->sn);
}
}
if (!$allowClosed) return;
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
}
/**
* 关闭微信交易
* 处理微信交易
*
* @param TradeModel $trade
*/
protected function closeWxpayTrade($trade)
protected function handleWxpayTrade(TradeModel $trade)
{
$service = new WxpayService();
$allowClosed = true;
$wxOrder = $service->findOrder($trade->sn);
$wxpay = new WxpayService();
if ($wxOrder) {
if ($wxOrder->trade_state == 'NOTPAY') {
$service->closeOrder($trade->sn);
$wxpayTrade = $wxpay->find($trade->sn);
if ($wxpayTrade) {
/**
* 异步通知接收异常,补救漏网
*/
if ($wxpayTrade->trade_state == 'SUCCESS') {
$this->eventsManager->fire('pay:afterPay', $this, $trade);
$allowClosed = false;
} elseif ($wxpayTrade->trade_state == 'NOTPAY') {
$allowClosed = $wxpay->close($trade->sn);
}
}
if (!$allowClosed) return;
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
@ -73,22 +105,20 @@ class CloseTradeTask extends Task
/**
* 查找待关闭交易
*
* @param integer $limit
* @return \Phalcon\Mvc\Model\ResultsetInterface
* @param int $limit
* @return ResultsetInterface|Resultset|TradeModel[]
*/
protected function findTrades($limit = 5)
protected function findTrades($limit = 50)
{
$status = TradeModel::STATUS_PENDING;
$createdAt = time() - 15 * 60;
$createTime = time() - 15 * 60;
$trades = TradeModel::query()
return TradeModel::query()
->where('status = :status:', ['status' => $status])
->andWhere('created_at < :created_at:', ['created_at' => $createdAt])
->andWhere('create_time < :create_time:', ['create_time' => $createTime])
->limit($limit)
->execute();
return $trades;
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Console\Tasks;
use App\Repos\Category as CategoryRepo;
use Phalcon\Cli\Task;
class CourseCountTask extends Task
{
public function mainAction()
{
$repo = new CategoryRepo();
$mapping = [];
$subCategories = $repo->findAll(['level' => 2, 'deleted' => 0]);
foreach ($subCategories as $category) {
$courseCount = $repo->countCourses($category->id);
$category->course_count = $courseCount;
$category->update();
$parentId = $category->parent_id;
if (isset($mapping[$parentId])) {
$mapping[$parentId] += $courseCount;
} else {
$mapping[$parentId] = $courseCount;
}
}
$topCategories = $repo->findAll(['level' => 1, 'deleted' => 0]);
foreach ($topCategories as $category) {
if (isset($mapping[$category->id])) {
$category->course_count = $mapping[$category->id];
$category->update();
}
}
}
}

View File

@ -0,0 +1,125 @@
<?php
namespace App\Console\Tasks;
use App\Models\Course as CourseModel;
use App\Services\Search\CourseDocument;
use App\Services\Search\CourseSearcher;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CourseIndexTask extends Task
{
/**
* 搜索测试
*
* @command: php console.php course_index search {query}
* @param array $params
* @throws \XSException
*/
public function searchAction($params)
{
$query = $params[0] ?? null;
if (!$query) {
exit('please special a query word' . PHP_EOL);
}
$result = $this->searchCourses($query);
var_export($result);
}
/**
* 清空索引
*
* @command: php console.php course_index clean
*/
public function cleanAction()
{
$this->cleanCourseIndex();
}
/**
* 重建索引
*
* @command: php console.php course_index rebuild
*/
public function rebuildAction()
{
$this->rebuildCourseIndex();
}
/**
* 清空索引
*/
protected function cleanCourseIndex()
{
$handler = new CourseSearcher();
$index = $handler->getXS()->getIndex();
echo 'start clean index' . PHP_EOL;
$index->clean();
echo 'end clean index' . PHP_EOL;
}
/**
* 重建索引
*/
protected function rebuildCourseIndex()
{
$courses = $this->findCourses();
if ($courses->count() == 0) return;
$handler = new CourseSearcher();
$documenter = new CourseDocument();
$index = $handler->getXS()->getIndex();
echo 'start rebuild index' . PHP_EOL;
$index->beginRebuild();
foreach ($courses as $course) {
$document = $documenter->setDocument($course);
$index->add($document);
}
$index->endRebuild();
echo 'end rebuild index' . PHP_EOL;
}
/**
* 搜索课程
*
* @param string $query
* @return array
* @throws \XSException
*/
protected function searchCourses($query)
{
$handler = new CourseSearcher();
return $handler->search($query);
}
/**
* 查找课程
*
* @return ResultsetInterface|Resultset|CourseModel[]
*/
protected function findCourses()
{
return CourseModel::query()
->where('published = 1')
->execute();
}
}

View File

@ -0,0 +1,262 @@
<?php
namespace App\Console\Tasks;
use App\Models\CourseUser as CourseUserModel;
use App\Models\ImGroupUser as ImGroupUserModel;
use App\Models\Order as OrderModel;
use App\Models\Refund as RefundModel;
use App\Models\Task as TaskModel;
use App\Models\Trade as TradeModel;
use App\Repos\ImGroup as ImGroupRepo;
use App\Repos\ImGroupUser as ImGroupUserRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
use App\Services\Sms\Order as OrderSms;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class DeliverTask extends Task
{
const TRY_COUNT = 3;
public function mainAction()
{
$logger = $this->getLogger('order');
$tasks = $this->findTasks();
if ($tasks->count() == 0) {
return;
}
$orderRepo = new OrderRepo();
foreach ($tasks as $task) {
/**
* @var array $itemInfo
*/
$itemInfo = $task->item_info;
$order = $orderRepo->findById($itemInfo['order']['id']);
if (!$order) continue;
try {
switch ($order->item_type) {
case OrderModel::ITEM_COURSE:
$this->handleCourseOrder($order);
break;
case OrderModel::ITEM_PACKAGE:
$this->handlePackageOrder($order);
break;
case OrderModel::ITEM_VIP:
$this->handleVipOrder($order);
break;
}
$this->finishOrder($order);
$task->status = TaskModel::STATUS_FINISHED;
$task->update();
} catch (\Exception $e) {
$task->try_count += 1;
$task->priority += 1;
if ($task->try_count > self::TRY_COUNT) {
$task->status = TaskModel::STATUS_FAILED;
}
$task->update();
$logger->info('Order Process Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
'task' => $task->toArray(),
]));
}
if ($task->status == TaskModel::STATUS_FINISHED) {
$this->handleOrderNotice($order);
} elseif ($task->status == TaskModel::STATUS_FAILED) {
$this->handleOrderRefund($order);
}
}
}
protected function finishOrder(OrderModel $order)
{
$order->status = OrderModel::STATUS_FINISHED;
if ($order->update() === false) {
throw new \RuntimeException('Finish Order Failed');
}
}
protected function handleCourseOrder(OrderModel $order)
{
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
$courseUser = new CourseUserModel();
$courseUser->user_id = $order->owner_id;
$courseUser->course_id = $order->item_id;
$courseUser->expiry_time = $itemInfo['course']['study_expiry_time'];
$courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->source_type = CourseUserModel::SOURCE_CHARGE;
if ($courseUser->create() === false) {
throw new \RuntimeException('Create Course User Failed');
}
$groupRepo = new ImGroupRepo();
$group = $groupRepo->findByCourseId($order->item_id);
$groupUserRepo = new ImGroupUserRepo();
$groupUser = $groupUserRepo->findGroupUser($group->id, $order->owner_id);
if ($groupUser) return;
$groupUser = new ImGroupUserModel();
$groupUser->group_id = $group->id;
$groupUser->user_id = $order->owner_id;
if ($groupUser->create() === false) {
throw new \RuntimeException('Create Group User Failed');
}
}
protected function handlePackageOrder(OrderModel $order)
{
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
foreach ($itemInfo['courses'] as $course) {
$courseUser = new CourseUserModel();
$courseUser->user_id = $order->owner_id;
$courseUser->course_id = $course['id'];
$courseUser->expiry_time = $course['study_expiry_time'];
$courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->source_type = CourseUserModel::SOURCE_CHARGE;
if ($courseUser->create() === false) {
throw new \RuntimeException('Create Course User Failed');
}
$groupRepo = new ImGroupRepo();
$group = $groupRepo->findByCourseId($course['id']);
$groupUserRepo = new ImGroupUserRepo();
$groupUser = $groupUserRepo->findGroupUser($group->id, $order->owner_id);
if ($groupUser) continue;
$groupUser = new ImGroupUserModel();
$groupUser->group_id = $group->id;
$groupUser->user_id = $order->owner_id;
if ($groupUser->create() === false) {
throw new \RuntimeException('Create Group User Failed');
}
}
}
protected function handleVipOrder(OrderModel $order)
{
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
$userRepo = new UserRepo();
$user = $userRepo->findById($order->owner_id);
$user->vip_expiry_time = $itemInfo['vip']['expiry_time'];
if ($user->update() === false) {
throw new \RuntimeException('Update Vip Expiry Failed');
}
}
protected function handleOrderNotice(OrderModel $order)
{
$sms = new OrderSms();
$sms->handle($order);
}
protected function handleOrderRefund(OrderModel $order)
{
$trade = $this->findFinishedTrade($order->id);
if (!$trade) return;
$refund = new RefundModel();
$refund->owner_id = $order->owner_id;
$refund->order_id = $order->id;
$refund->trade_id = $trade->id;
$refund->subject = $order->subject;
$refund->amount = $order->amount;
$refund->apply_note = '开通服务失败,自动退款';
$refund->review_note = '自动操作';
$refund->create();
}
/**
* @param int $orderId
* @return Model|TradeModel
*/
protected function findFinishedTrade($orderId)
{
$status = TradeModel::STATUS_FINISHED;
return TradeModel::findFirst([
'conditions' => ['order_id = :order_id: AND status = :status:'],
'bind' => ['order_id' => $orderId, 'status' => $status],
'order' => 'id DESC',
]);
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|TaskModel[]
*/
protected function findTasks($limit = 100)
{
$itemType = TaskModel::TYPE_DELIVER;
$status = TaskModel::STATUS_PENDING;
$tryCount = self::TRY_COUNT;
return TaskModel::query()
->where('item_type = :item_type:', ['item_type' => $itemType])
->andWhere('status = :status:', ['status' => $status])
->andWhere('try_count < :try_count:', ['try_count' => $tryCount + 1])
->orderBy('priority ASC')
->limit($limit)
->execute();
}
}

View File

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

View File

@ -1,50 +0,0 @@
<?php
namespace App\Console\Tasks;
use App\Models\Course;
use App\Services\Storage;
use Phalcon\Cli\Task;
use Phalcon\Text;
class ImageSyncTask extends Task
{
public function mainAction()
{
$courses = Course::query()
->where('id = 42')
->execute();
$storage = new Storage();
foreach ($courses as $course) {
$cover = $course->cover;
if (Text::startsWith($cover, '//')) {
$cover = 'http:' . $cover;
}
$url = str_replace('-240-135', '', $cover);
$fileName = parse_url($url, PHP_URL_PATH);
$filePath = tmp_path() . $fileName;
$content = file_get_contents($url);
file_put_contents($filePath, $content);
$keyName = $this->getKeyName($filePath);
$remoteUrl = $storage->putFile($keyName, $filePath);
if ($remoteUrl) {
$course->cover = $keyName;
$course->update();
echo "upload cover of course {$course->id} success" . PHP_EOL;
} else {
echo "upload cover of course {$course->id} failed" . PHP_EOL;
}
}
}
protected function getKeyName($filePath)
{
$ext = pathinfo($filePath, PATHINFO_EXTENSION);
return '/img/cover/' . date('YmdHis') . rand(1000, 9999) . '.' . $ext;
}
}

View File

@ -1,139 +0,0 @@
<?php
namespace App\Console\Tasks;
use App\Models\Learning as LearningModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\ChapterUser as ChapterUserRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Learning as LearningRepo;
use Phalcon\Cli\Task;
class LearningTask extends Task
{
/**
* @var \App\Library\Cache\Backend\Redis
*/
protected $cache;
public function mainAction()
{
$this->cache = $this->getDI()->get('cache');
$keys = $this->cache->queryKeys('learning:');
if (empty($keys)) return;
$keys = array_slice($keys, 0, 500);
$prefix = $this->cache->getPrefix();
foreach ($keys as $key) {
/**
* 去掉前缀,避免重复加前缀导致找不到缓存
*/
if ($prefix) {
$key = str_replace($prefix, '', $key);
}
$this->handleLearning($key);
}
}
protected function handleLearning($key)
{
$content = $this->cache->get($key);
if (empty($content->user_id)) {
return false;
}
if (!empty($content->client_ip)) {
$region = kg_ip2region($content->client_ip);
$content->country = $region->country;
$content->province = $region->province;
$content->city = $region->city;
}
$learningRepo = new LearningRepo();
$learning = $learningRepo->findByRequestId($content->request_id);
if (!$learning) {
$learning = new LearningModel();
$data = kg_object_array($content);
$learning->create($data);
} else {
$learning->duration += $content->duration;
$learning->update();
}
$this->updateChapterUser($content->chapter_id, $content->user_id, $content->duration, $content->position);
$this->updateCourseUser($content->course_id, $content->user_id, $content->duration);
$this->cache->delete($key);
}
protected function updateChapterUser($chapterId, $userId, $duration = 0, $position = 0)
{
$chapterUserRepo = new ChapterUserRepo();
$chapterUser = $chapterUserRepo->findChapterUser($chapterId, $userId);
if (!$chapterUser) return false;
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterId);
if (!$chapter) return false;
$chapter->duration = $chapter->attrs['duration'] ?: 0;
$chapterUser->duration += $duration;
$chapterUser->position = floor($position);
/**
* 观看时长超过视频时长80%标记完成学习
*/
if ($chapterUser->duration > $chapter->duration * 0.8) {
if ($chapterUser->finished == 0) {
$chapterUser->finished = 1;
$this->updateCourseProgress($chapterUser->course_id, $chapterUser->user_id);
}
}
$chapterUser->update();
}
protected function updateCourseUser($courseId, $userId, $duration)
{
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($courseId, $userId);
if ($courseUser) {
$courseUser->duration += $duration;
$courseUser->update();
}
}
protected function updateCourseProgress($courseId, $userId)
{
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($courseId, $userId);
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($courseId);
if ($courseUser) {
$count = $courseUserRepo->countFinishedChapters($courseId, $userId);
$courseUser->progress = intval(100 * $count / $course->lesson_count);
$courseUser->update();
}
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Console\Tasks;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\Chapter as ChapterRepo;
use App\Services\LiveNotify as LiveNotifyService;
use App\Services\Sms\Live as LiveSms;
class LiveNotifyTask extends Task
{
public function mainAction()
{
$redis = $this->getRedis();
$service = new LiveNotifyService();
$key = $service->getNotifyKey();
$chapterIds = $redis->sMembers($key);
if (!$chapterIds) return;
$sentKey = $service->getSentNotifyKey();
$sentChapterIds = $redis->sMembers($sentKey);
foreach ($chapterIds as $chapterId) {
if (!in_array($chapterId, $sentChapterIds)) {
$this->sendNotification($chapterId);
} else {
$redis->sAdd($sentKey, $chapterId);
}
}
if ($redis->sCard($sentKey) == 1) {
$redis->expire($sentKey, 86400);
}
}
protected function sendNotification($chapterId)
{
$chapterRepo = new ChapterRepo();
$chapterLive = $chapterRepo->findChapterLive($chapterId);
if (!$chapterLive) return;
$targetUserIds = $this->findTargetUserIds($chapterLive->course_id);
if (!$targetUserIds) return;
$sms = new LiveSms();
foreach ($targetUserIds as $userId) {
$sms->handle($chapterId, $userId, $chapterLive->start_time);
}
}
protected function findTargetUserIds($courseId)
{
$sourceTypes = [
CourseUserModel::SOURCE_CHARGE,
CourseUserModel::SOURCE_VIP,
];
$rows = CourseUserModel::query()
->where('course_id = :course_id:', ['course_id' => $courseId])
->andWhere('role_type = :role_type:', ['role_type' => CourseUserModel::ROLE_STUDENT])
->inWhere('source_type', $sourceTypes)
->execute();
if ($rows->count() == 0) {
return [];
}
return kg_array_column($rows->toArray(), 'user_id');
}
}

View File

@ -2,8 +2,6 @@
namespace App\Console\Tasks;
use App\Models\Chapter;
class MainTask extends Task
{
@ -12,13 +10,4 @@ class MainTask extends Task
echo "You are now flying with Phalcon CLI!";
}
public function okAction()
{
$chapter = Chapter::findFirstById(15224);
$chapter->duration = 123;
echo $chapter->duration;
}
}

View File

@ -2,36 +2,99 @@
namespace App\Console\Tasks;
use Phalcon\Cli\Task;
use App\Caches\IndexFreeCourseList as IndexFreeCourseListCache;
use App\Caches\IndexNewCourseList as IndexNewCourseListCache;
use App\Caches\IndexVipCourseList as IndexVipCourseListCache;
use App\Http\Admin\Services\Setting as SettingService;
use App\Library\Utils\Password as PasswordUtil;
use App\Validators\Account as AccountValidator;
class MaintainTask extends Task
{
public function mainAction()
/**
* 重建首页课程缓存
*
* @param array $params
* @command: php console.php maintain reset_index_course_cache
*/
public function rebuildIndexCourseCacheAction($params)
{
$section = $params[0] ?? null;
}
if (!$section || $section == 'new_course') {
$cache = new IndexNewCourseListCache();
$cache->rebuild();
}
public function resetAnnotationsAction()
{
$dir = cache_path('annotations');
if (!$section || $section == 'free_course') {
$cache = new IndexFreeCourseListCache();
$cache->rebuild();
}
foreach (scandir($dir) as $file) {
if (strpos($file, '.php')) {
unlink($dir . '/' . $file);
}
if (!$section || $section == 'vip_course') {
$cache = new IndexVipCourseListCache();
$cache->rebuild();
}
}
public function resetModelsMetaDataAction()
/**
* 修改密码
*
* @param array $params
* @command: php console.php maintain reset_password 13507083515 123456
*/
public function resetPasswordAction($params)
{
$dir = cache_path('metadata');
foreach (scandir($dir) as $file) {
if (strpos($file, '.php')) {
unlink($dir . '/' . $file);
}
if (empty($params[0])) {
echo 'account is required' . PHP_EOL;
}
if (empty($params[1])) {
echo 'password is required' . PHP_EOL;
}
$validator = new AccountValidator();
$account = $validator->checkAccount($params[0]);
$salt = PasswordUtil::salt();
$hash = PasswordUtil::hash($params[1], $salt);
$account->salt = $salt;
$account->password = $hash;
$account->update();
echo 'reset password success' . PHP_EOL;
}
/**
* 关闭验证码
*
* @command: php console.php maintain disable_captcha
*/
public function disableCaptchaAction()
{
$service = new SettingService();
$service->updateSettings('captcha', ['enabled' => 0]);
echo 'disable captcha success' . PHP_EOL;
}
/**
* 启用验证码
*
* @command: php console.php maintain enable_captcha
*/
public function enableCaptchaAction()
{
$service = new SettingService();
$service->updateSettings('captcha', ['enabled' => 1]);
echo 'enable captcha success' . PHP_EOL;
}
}

View File

@ -10,13 +10,20 @@ use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\Refund as RefundRepo;
use App\Repos\Trade as TradeRepo;
use App\Services\Alipay as AlipayService;
use App\Services\Wxpay as WxpayService;
use App\Repos\User as UserRepo;
use App\Services\Pay\Alipay as AlipayService;
use App\Services\Pay\Wxpay as WxpayService;
use App\Services\Sms\Refund as RefundSms;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class RefundTask extends Task
{
const TRY_COUNT = 5;
/**
* 重试次数
*/
const TRY_COUNT = 3;
public function mainAction()
{
@ -34,15 +41,32 @@ class RefundTask extends Task
foreach ($tasks as $task) {
$refund = $refundRepo->findBySn($task->item_info['refund']['sn']);
$trade = $tradeRepo->findBySn($task->item_info['refund']['trade_sn']);
$order = $orderRepo->findBySn($task->item_info['refund']['order_sn']);
/**
* @var array $itemInfo
*/
$itemInfo = $task->item_info;
$refund = $refundRepo->findById($itemInfo['refund']['id']);
$trade = $tradeRepo->findById($itemInfo['refund']['trade_id']);
$order = $orderRepo->findById($itemInfo['refund']['order_id']);
if (!$refund || !$trade || !$order) {
continue;
}
/**
* 退款存在延迟,给取消退款调解机会
*/
if (isset($itemInfo['deadline']) && $itemInfo['deadline'] > time()) {
continue;
}
try {
$this->db->begin();
$this->handleTradeRefund($trade, $refund);
$this->handleOrderRefund($order);
$refund->status = RefundModel::STATUS_FINISHED;
@ -71,25 +95,32 @@ class RefundTask extends Task
$this->db->commit();
$this->handleRefundNotice($refund);
} catch (\Exception $e) {
$this->db->rollback();
$task->try_count += 1;
$task->priority += 1;
if ($task->try_count > self::TRY_COUNT) {
$task->status = TaskModel::STATUS_FAILED;
$refund->status = RefundModel::STATUS_FAILED;
$refund->update();
}
$task->update();
$logger->info('Refund Task Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
'task' => $task->toArray(),
]));
}
if ($task->status == TaskModel::STATUS_FAILED) {
$refund->status = RefundModel::STATUS_FAILED;
$refund->update();
}
}
}
@ -104,24 +135,20 @@ class RefundTask extends Task
$response = false;
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$alipay = new AlipayService();
$response = $alipay->refundOrder([
'out_trade_no' => $trade->sn,
'out_request_no' => $refund->sn,
'refund_amount' => $refund->amount,
]);
$response = $alipay->refund($refund);
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$wxpay = new WxpayService();
$response = $wxpay->refundOrder([
'out_trade_no' => $trade->sn,
'out_refund_no' => $refund->sn,
'total_fee' => 100 * $trade->order_amount,
'refund_fee' => 100 * $refund->amount,
]);
$response = $wxpay->refund($refund);
}
if (!$response) {
throw new \RuntimeException('Payment Refund Failed');
throw new \RuntimeException('Pay Refund Failed');
}
}
@ -133,19 +160,19 @@ class RefundTask extends Task
protected function handleOrderRefund(OrderModel $order)
{
switch ($order->item_type) {
case OrderModel::TYPE_COURSE:
case OrderModel::ITEM_COURSE:
$this->handleCourseOrderRefund($order);
break;
case OrderModel::TYPE_PACKAGE:
case OrderModel::ITEM_PACKAGE:
$this->handlePackageOrderRefund($order);
break;
case OrderModel::TYPE_REWARD:
$this->handleRewardOrderRefund($order);
break;
case OrderModel::TYPE_VIP:
case OrderModel::ITEM_VIP:
$this->handleVipOrderRefund($order);
break;
case OrderModel::TYPE_TEST:
case OrderModel::ITEM_REWARD:
$this->handleRewardOrderRefund($order);
break;
case OrderModel::ITEM_TEST:
$this->handleTestOrderRefund($order);
break;
}
@ -160,10 +187,12 @@ class RefundTask extends Task
{
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseStudent($order->item_id, $order->user_id);
$courseUser = $courseUserRepo->findCourseStudent($order->item_id, $order->owner_id);
if ($courseUser) {
$courseUser->deleted = 1;
if ($courseUser->update() === false) {
throw new \RuntimeException('Delete Course User Failed');
}
@ -179,10 +208,19 @@ class RefundTask extends Task
{
$courseUserRepo = new CourseUserRepo();
foreach ($order->item_info['courses'] as $course) {
$courseUser = $courseUserRepo->findCourseStudent($course['id'], $order->user_id);
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
foreach ($itemInfo['courses'] as $course) {
$courseUser = $courseUserRepo->findCourseStudent($course['id'], $order->owner_id);
if ($courseUser) {
$courseUser->deleted = 1;
if ($courseUser->update() === false) {
throw new \RuntimeException('Delete Course User Failed');
}
@ -199,26 +237,19 @@ class RefundTask extends Task
{
$userRepo = new UserRepo();
$user = $userRepo->findById($order->user_id);
$user = $userRepo->findById($order->owner_id);
$baseTime = $user->vip_expiry;
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
switch ($order->item_info['vip']['duration']) {
case 'one_month':
$user->vip_expiry = strtotime('-1 months', $baseTime);
break;
case 'three_month':
$user->vip_expiry = strtotime('-3 months', $baseTime);
break;
case 'six_month':
$user->vip_expiry = strtotime('-6 months', $baseTime);
break;
case 'twelve_month':
$user->vip_expiry = strtotime('-12 months', $baseTime);
break;
}
$diffTime = "-{$itemInfo['vip']['expiry']} months";
$baseTime = $itemInfo['vip']['expiry_time'];
if ($user->vip_expiry < time()) {
$user->vip_expiry_time = strtotime($diffTime, $baseTime);
if ($user->vip_expiry_time < time()) {
$user->vip = 0;
}
@ -228,7 +259,7 @@ class RefundTask extends Task
}
/**
* 处理打赏订单退款
* 处理测试订单退款
*
* @param OrderModel $order
*/
@ -248,26 +279,32 @@ class RefundTask extends Task
}
/**
* 查找退款任务
*
* @param integer $limit
* @return \Phalcon\Mvc\Model\ResultsetInterface
* @param RefundModel $refund
*/
protected function findTasks($limit = 5)
protected function handleRefundNotice(RefundModel $refund)
{
$sms = new RefundSms();
$sms->handle($refund);
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|TaskModel[]
*/
protected function findTasks($limit = 30)
{
$itemType = TaskModel::TYPE_REFUND;
$status = TaskModel::STATUS_PENDING;
$tryCount = self::TRY_COUNT;
$tasks = TaskModel::query()
return TaskModel::query()
->where('item_type = :item_type:', ['item_type' => $itemType])
->andWhere('status = :status:', ['status' => $status])
->andWhere('try_count < :try_count:', ['try_count' => $tryCount])
->orderBy('priority ASC,try_count DESC')
->andWhere('try_count < :try_count:', ['try_count' => $tryCount + 1])
->orderBy('priority ASC')
->limit($limit)
->execute();
return $tasks;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Console\Tasks;
use App\Models\User as UserModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class RevokeVipTask extends Task
{
public function mainAction()
{
$users = $this->findUsers();
if ($users->count() == 0) {
return;
}
foreach ($users as $user) {
$user->vip = 0;
$user->update();
}
}
/**
* 查找待解锁用户
*
* @param int $limit
* @return ResultsetInterface|Resultset|UserModel[]
*/
protected function findUsers($limit = 1000)
{
$time = time();
return UserModel::query()
->where('vip = 1')
->andWhere('vip_expiry_time < :time:', ['time' => $time])
->limit($limit)
->execute();
}
}

View File

@ -0,0 +1,162 @@
<?php
namespace App\Console\Tasks;
use App\Library\Sitemap;
use App\Models\Course as CourseModel;
use App\Models\Help as HelpModel;
use App\Models\ImGroup as ImGroupModel;
use App\Models\Page as PageModel;
use App\Models\Topic as TopicModel;
use App\Models\User as UserModel;
use App\Services\Service as AppService;
use Phalcon\Mvc\Model\Resultset;
class SitemapTask extends Task
{
/**
* @var string
*/
protected $siteUrl;
/**
* @var Sitemap
*/
protected $sitemap;
public function mainAction()
{
$this->siteUrl = $this->getSiteUrl();
$this->sitemap = new Sitemap();
$filename = public_path('sitemap.xml');
$this->addIndex();
$this->addCourses();
$this->addTeachers();
$this->addTopics();
$this->addImGroups();
$this->addHelps();
$this->addPages();
$this->addOthers();
$this->sitemap->build($filename);
}
protected function getSiteUrl()
{
$service = new AppService();
$settings = $service->getSettings('site');
return $settings['url'] ?? '';
}
protected function addIndex()
{
$this->sitemap->addItem($this->siteUrl, 1);
}
protected function addCourses()
{
/**
* @var Resultset|CourseModel[] $courses
*/
$courses = CourseModel::query()->where('published = 1')->execute();
if ($courses->count() == 0) return;
foreach ($courses as $course) {
$loc = sprintf('%s/course/%s', $this->siteUrl, $course->id);
$this->sitemap->addItem($loc, 0.8);
}
}
protected function addTeachers()
{
/**
* @var Resultset|UserModel[] $teachers
*/
$teachers = UserModel::query()->where('edu_role = 2')->execute();
if ($teachers->count() == 0) return;
foreach ($teachers as $teacher) {
$loc = sprintf('%s/teacher/%s', $this->siteUrl, $teacher->id);
$this->sitemap->addItem($loc, 0.6);
}
}
protected function addTopics()
{
/**
* @var Resultset|TopicModel[] $topics
*/
$topics = TopicModel::query()->where('published = 1')->execute();
if ($topics->count() == 0) return;
foreach ($topics as $topic) {
$loc = sprintf('%s/topic/%s', $this->siteUrl, $topic->id);
$this->sitemap->addItem($loc, 0.6);
}
}
protected function addImGroups()
{
/**
* @var Resultset|ImGroupModel[] $groups
*/
$groups = ImGroupModel::query()->where('published = 1')->execute();
if ($groups->count() == 0) return;
foreach ($groups as $group) {
$loc = sprintf('%s/im/group/%s', $this->siteUrl, $group->id);
$this->sitemap->addItem($loc, 0.6);
}
}
protected function addPages()
{
/**
* @var Resultset|PageModel[] $pages
*/
$pages = PageModel::query()->where('published = 1')->execute();
if ($pages->count() == 0) return;
foreach ($pages as $page) {
$loc = sprintf('%s/page/%s', $this->siteUrl, $page->id);
$this->sitemap->addItem($loc, 0.7);
}
}
protected function addHelps()
{
/**
* @var Resultset|HelpModel[] $helps
*/
$helps = HelpModel::query()->where('published = 1')->execute();
if ($helps->count() == 0) return;
foreach ($helps as $help) {
$loc = sprintf('%s/help/%s', $this->siteUrl, $help->id);
$this->sitemap->addItem($loc, 0.7);
}
}
protected function addOthers()
{
$this->sitemap->addItem('/course/list', 0.6);
$this->sitemap->addItem('/im/group/list', 0.6);
$this->sitemap->addItem('/teacher/list', 0.6);
$this->sitemap->addItem('/vip', 0.6);
$this->sitemap->addItem('/help', 0.6);
$this->sitemap->addItem('/search', 0.6);
}
}

View File

@ -1,282 +0,0 @@
<?php
namespace App\Console\Tasks;
use App\Models\Chapter as ChapterModel;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use Phalcon\Cli\Task;
use QL\QueryList;
class SpiderTask extends Task
{
public function testAction()
{
$subject = '1-1 课程简介(01:40)开始学习';
preg_match('/(\d{1,}-\d{1,})\s{1,}(.*?)\((.*?)\)/', $subject, $matches);
dd($matches);
}
public function courseListAction($params)
{
$category = $params[0] ?? 'html';
$page = $params[1] ?? 1;
$categoryId = $this->getCategoryId($category);
if (empty($categoryId)) {
throw new \Exception('invalid category');
}
$url = "http://www.imooc.com/course/list?c={$category}&page={$page}";
$data = QueryList::get($url)->rules([
'link' => ['a.course-card', 'href'],
'title' => ['h3.course-card-name', 'text'],
'cover' => ['img.course-banner', 'data-original'],
'summary' => ['p.course-card-desc', 'text'],
'level' => ['.course-card-info>span:even', 'text'],
'user_count' => ['.course-card-info>span:odd', 'text'],
])->query()->getData();
if ($data->count() == 0) {
return false;
}
foreach ($data->all() as $item) {
$course = [
'id' => substr($item['link'], 7),
'category_id' => $categoryId,
'title' => $item['title'],
'cover' => $item['cover'],
'summary' => $item['summary'],
'user_count' => $item['user_count'],
'level' => $this->getLevel($item['level']),
];
$model = new CourseModel();
$model->save($course);
}
echo sprintf("saved: %d course", $data->count());
}
public function courseAction()
{
$courses = CourseModel::query()
->where('1 = 1')
->andWhere('id > :id:', ['id' => 1128])
->orderBy('id asc')
->execute();
$baseUrl = 'http://www.imooc.com/learn';
$instance = QueryList::getInstance();
foreach ($courses as $course) {
$url = $baseUrl . '/' . $course->id;
$ql = $instance->get($url);
$result = $this->handleCourseInfo($course, $ql);
if (!$result) {
continue;
}
$this->handleCourseChapters($course, $ql);
echo "finished course " . $course->id . PHP_EOL;
sleep(1);
}
}
public function teacherAction()
{
$courses = CourseModel::query()
->where('1 = 1')
->groupBy('user_id')
->execute();
foreach ($courses as $course) {
$this->handleTeacherInfo($course->user_id);
echo "finished teacher: {$course->user_id}" . PHP_EOL;
sleep(1);
}
}
public function userAction()
{
$users = UserModel::query()
->where('1 = 1')
->andWhere('name = :name:', ['name' => ''])
->execute();
foreach ($users as $user) {
$this->handleUserInfo($user->id);
echo "finished user: {$user->id}" . PHP_EOL;
sleep(1);
}
}
protected function handleUserInfo($id)
{
$url = 'http://www.imooc.com/u/'. $id;
$ql = QueryList::getInstance()->get($url);
$data = [];
$data['id'] = $id;
$data['avatar'] = $ql->find('.user-pic-bg>img')->attr('src');
$data['name'] = $ql->find('h3.user-name>span')->text();
$data['about'] = $ql->find('p.user-desc')->text();
$user = new UserModel();
$user->save($data);
}
protected function handleTeacherInfo($id)
{
$url = 'http://www.imooc.com/t/'. $id;
$ql = QueryList::getInstance()->get($url);
$data = [];
$data['id'] = $id;
$data['avatar'] = $ql->find('img.tea-header')->attr('src');
$data['name'] = $ql->find('p.tea-nickname')->text();
$data['title'] = $ql->find('p.tea-professional')->text();
$data['about'] = $ql->find('p.tea-desc')->text();
$user = new UserModel();
$user->create($data);
}
protected function handleCourseInfo(CourseModel $course, QueryList $ql)
{
$data = [];
$data['user_id'] = $ql->find('img.js-usercard-dialog')->attr('data-userid');
$data['description'] = $ql->find('.course-description')->text();
$data['duration'] = $ql->find('.static-item:eq(1)>.meta-value')->text();
$data['score'] = $ql->find('.score-btn>.meta-value')->text();
if (empty($data['user_id'])) {
return false;
}
$data['duration'] = $this->getCourseDuration($data['duration']);
return $course->update($data);
}
protected function handleCourseChapters(CourseModel $course, QueryList $ql)
{
$topChapters = $ql->rules([
'title' => ['.chapter>h3', 'text'],
'sub_chapter_html' => ['.chapter>.video', 'html'],
])->query()->getData();
if ($topChapters->count() == 0) {
return false;
}
foreach ($topChapters->all() as $item) {
$data = [
'course_id' => $course->id,
'title' => $item['title'],
];
// create top chapter
$chapter = new ChapterModel();
$chapter->create($data);
// create sub chapter
if (!empty($item['sub_chapter_html'])) {
$this->handleSubChapters($chapter, $item['sub_chapter_html']);
}
}
}
protected function handleSubChapters(ChapterModel $topChapter, $subChapterHtml)
{
$ql = QueryList::html($subChapterHtml);
$chapters = $ql->find('li')->texts();
if ($chapters->count() == 0) {
return false;
}
foreach ($chapters->all() as $item) {
preg_match('/(\d{1,}-\d{1,})\s{1,}(.*?)\((.*?)\)/s', $item, $matches);
if (!isset($matches[3]) || empty($matches[3])) {
continue;
}
$data = [
'course_id' => $topChapter->course_id,
'parent_id' => $topChapter->id,
'title' => $matches[2],
'duration' => $this->getChapterDuration($matches[3]),
];
$model = new ChapterModel();
$model->create($data);
}
}
protected function getCourseDuration($duration)
{
$hours = 0;
$minutes = 0;
if (preg_match('/(.*?)小时(.*?)分/s', $duration, $matches)) {
$hours = trim($matches[1]);
$minutes = trim($matches[2]);
} elseif (preg_match('/(.*?)分/s', $duration, $matches)) {
$minutes = trim($matches[1]);
}
return 3600 * $hours + 60 * $minutes;
}
protected function getChapterDuration($duration)
{
if (strpos($duration, ':') === false) {
return 0;
}
list($minutes, $seconds) = explode(':', trim($duration));
return 60 * $minutes + $seconds;
}
protected function getLevel($type)
{
$mapping = [
'入门' => CourseModel::LEVEL_ENTRY,
'初级' => CourseModel::LEVEL_JUNIOR,
'中级' => CourseModel::LEVEL_MIDDLE,
'高级' => CourseModel::LEVEL_SENIOR,
];
return $mapping[$type] ?? CourseModel::LEVEL_ENTRY;
}
protected function getCategoryId($type)
{
$mapping = [
'html' => 1, 'javascript' => 2, 'vuejs' => 10, 'reactjs' => 19,
'angular' => 18, 'nodejs' => 16, 'jquery' => 15,
'bootstrap' => 17, 'sassless' => 21, 'webapp' => 22, 'fetool' => 23,
'html5' => 13, 'css3' => 14,
'php' => 24, 'java' => 25, 'python' => 26, 'c' => 27, 'cplusplus' => 28, 'ruby' => 29, 'go' => 30, 'csharp' => 31,
'android' => 32, 'ios' => 33,
'mysql' => 36, 'mongodb' => 37, 'redis' => 38, 'oracle' => 39, 'pgsql' => 40,
'cloudcomputing' => 42, 'bigdata' => 43,
'unity3d' => 34, 'cocos2dx' => 35,
'dxdh' => 46, 'uitool' => 47, 'uijc' => 48,
];
return $mapping[$type] ?? 0;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Console\Tasks;
use App\Repos\Course as CourseRepo;
use App\Services\Search\CourseDocument;
use App\Services\Search\CourseSearcher;
use App\Services\Sync\CourseIndex as CourseIndexSync;
class SyncCourseIndexTask 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;
$document = new CourseDocument();
$handler = new CourseSearcher();
$index = $handler->getXS()->getIndex();
$index->openBuffer();
foreach ($courses as $course) {
$doc = $document->setDocument($course);
if ($course->published == 1) {
$index->update($doc);
} else {
$index->del($course->id);
}
}
$index->closeBuffer();
$redis->sRem($key, ...$courseIds);
}
protected function getSyncKey()
{
$sync = new CourseIndexSync();
return $sync->getSyncKey();
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Console\Tasks;
use App\Repos\ImGroup as GroupRepo;
use App\Services\Search\GroupDocument;
use App\Services\Search\GroupSearcher;
use App\Services\Sync\GroupIndex as GroupIndexSync;
class SyncGroupIndexTask extends Task
{
public function mainAction()
{
$redis = $this->getRedis();
$key = $this->getSyncKey();
$groupIds = $redis->sRandMember($key, 1000);
if (!$groupIds) return;
$groupRepo = new GroupRepo();
$groups = $groupRepo->findByIds($groupIds);
if ($groups->count() == 0) return;
$document = new GroupDocument();
$handler = new GroupSearcher();
$index = $handler->getXS()->getIndex();
$index->openBuffer();
foreach ($groups as $group) {
$doc = $document->setDocument($group);
if ($group->published == 1) {
$index->update($doc);
} else {
$index->del($group->id);
}
}
$index->closeBuffer();
$redis->sRem($key, ...$groupIds);
}
protected function getSyncKey()
{
$sync = new GroupIndexSync();
return $sync->getSyncKey();
}
}

View File

@ -0,0 +1,177 @@
<?php
namespace App\Console\Tasks;
use App\Models\Course as CourseModel;
use App\Models\Learning as LearningModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\ChapterUser as ChapterUserRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Learning as LearningRepo;
use App\Services\Sync\Learning as LearningSync;
class SyncLearningTask extends Task
{
public function mainAction()
{
$redis = $this->getRedis();
$sync = new LearningSync();
$syncKey = $sync->getSyncKey();
$requestIds = $redis->sMembers($syncKey);
if (!$requestIds) return;
foreach ($requestIds as $requestId) {
$itemKey = $sync->getItemKey($requestId);
$this->handleLearning($itemKey);
$redis->sRem($syncKey, $requestId);
}
}
/**
* @param string $itemKey
*/
protected function handleLearning($itemKey)
{
/**
* @var LearningModel $cacheLearning
*/
$cacheLearning = $this->cache->get($itemKey);
if (!$cacheLearning) return;
$learningRepo = new LearningRepo();
$dbLearning = $learningRepo->findByRequestId($cacheLearning->request_id);
if (!$dbLearning) {
$cacheLearning->create();
$this->updateChapterUser($cacheLearning);
} else {
$dbLearning->duration += $cacheLearning->duration;
$dbLearning->position = $cacheLearning->position;
$dbLearning->active_time = $cacheLearning->active_time;
$dbLearning->update();
$this->updateChapterUser($dbLearning);
}
$this->cache->delete($itemKey);
}
/**
* @param LearningModel $learning
*/
protected function updateChapterUser(LearningModel $learning)
{
$chapterUserRepo = new ChapterUserRepo();
$chapterUser = $chapterUserRepo->findPlanChapterUser($learning->chapter_id, $learning->user_id, $learning->plan_id);
if (!$chapterUser) return;
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($learning->chapter_id);
if (!$chapter) return;
$chapterUser->duration += $learning->duration;
/**
* 消费规则
*
* 1.点播观看时间大于时长30%
* 2.直播观看时间超过10分钟
* 3.图文浏览即消费
*/
if ($chapter->model == CourseModel::MODEL_VOD) {
$duration = $chapter->attrs['duration'] ?: 300;
$progress = floor(100 * $chapterUser->duration / $duration);
$chapterUser->position = floor($learning->position);
$chapterUser->progress = $progress < 100 ? $progress : 100;
$chapterUser->consumed = $chapterUser->duration > 0.3 * $duration ? 1 : 0;
} elseif ($chapter->model == CourseModel::MODEL_LIVE) {
$chapterUser->consumed = $chapterUser->duration > 600 ? 1 : 0;
} elseif ($chapter->model == CourseModel::MODEL_READ) {
$chapterUser->consumed = 1;
}
$chapterUser->update();
if ($chapterUser->consumed == 1) {
$this->updateCourseUser($learning);
}
}
/**
* @param LearningModel $learning
*/
protected function updateCourseUser(LearningModel $learning)
{
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findPlanCourseUser($learning->course_id, $learning->user_id, $learning->plan_id);
if (!$courseUser) return;
$courseRepo = new CourseRepo();
$courseLessons = $courseRepo->findLessons($learning->course_id);
if ($courseLessons->count() == 0) return;
$userLearnings = $courseRepo->findUserLearnings($learning->course_id, $learning->user_id, $learning->plan_id);
if ($userLearnings->count() == 0) return;
$consumedUserLearnings = [];
foreach ($userLearnings->toArray() as $userLearning) {
if ($userLearning['consumed'] == 1) {
$consumedUserLearnings[] = $userLearning;
}
}
if (count($consumedUserLearnings) == 0) return;
$duration = 0;
foreach ($consumedUserLearnings as $userLearning) {
$duration += $userLearning['duration'];
}
$courseLessonIds = kg_array_column($courseLessons->toArray(), 'id');
$consumedUserLessonIds = kg_array_column($consumedUserLearnings, 'chapter_id');
$consumedLessonIds = array_intersect($courseLessonIds, $consumedUserLessonIds);
$totalCount = count($courseLessonIds);
$consumedCount = count($consumedLessonIds);
$progress = intval(100 * $consumedCount / $totalCount);
$courseUser->progress = $progress;
$courseUser->duration = $duration;
$courseUser->update();
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Console\Tasks;
use App\Repos\User as UserRepo;
use App\Services\Search\UserDocument;
use App\Services\Search\UserSearcher;
use App\Services\Sync\UserIndex as UserIndexSync;
class SyncUserIndexTask extends Task
{
public function mainAction()
{
$redis = $this->getRedis();
$key = $this->getSyncKey();
$userIds = $redis->sRandMember($key, 1000);
if (!$userIds) return;
$userRepo = new UserRepo();
$users = $userRepo->findByIds($userIds);
if ($users->count() == 0) return;
$document = new UserDocument();
$handler = new UserSearcher();
$index = $handler->getXS()->getIndex();
$index->openBuffer();
foreach ($users as $user) {
$doc = $document->setDocument($user);
if ($user->deleted == 0) {
$index->update($doc);
} else {
$index->del($user->id);
}
}
$index->closeBuffer();
$redis->sRem($key, ...$userIds);
}
protected function getSyncKey()
{
$sync = new UserIndexSync();
return $sync->getSyncKey();
}
}

View File

@ -2,16 +2,46 @@
namespace App\Console\Tasks;
use App\Library\Logger;
use App\Services\Service as AppService;
class Task extends \Phalcon\Cli\Task
{
public function getConfig()
{
$appService = new AppService();
return $appService->getConfig();
}
public function getCache()
{
$appService = new AppService();
return $appService->getCache();
}
public function getRedis()
{
$appService = new AppService();
return $appService->getRedis();
}
public function getLogger($channel = null)
{
$logger = new Logger();
$appService = new AppService();
return $logger->getInstance($channel);
return $appService->getLogger($channel);
}
public function getSettings($section)
{
$appService = new AppService();
return $appService->getLogger($section);
}
}

View File

@ -3,7 +3,8 @@
namespace App\Console\Tasks;
use App\Models\User as UserModel;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class UnlockUserTask extends Task
{
@ -12,34 +13,28 @@ class UnlockUserTask extends Task
{
$users = $this->findUsers();
if ($users->count() == 0) {
return;
}
if ($users->count() == 0) return;
foreach ($users as $user) {
$user->locked = 0;
$user->locked_expiry = 0;
$user->update();
$user->update(['locked' => 0]);
}
}
/**
* 查找待解锁用户
*
* @param integer $limit
* @return \Phalcon\Mvc\Model\ResultsetInterface
* @param int $limit
* @return ResultsetInterface|Resultset|UserModel[]
*/
protected function findUsers($limit = 1000)
{
$time = time() - 6 * 3600;
$users = UserModel::query()
return UserModel::query()
->where('locked = 1')
->andWhere('locked_expiry < :time:', ['time' => $time])
->andWhere('lock_expiry_time < :time:', ['time' => $time])
->limit($limit)
->execute();
return $users;
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace App\Console\Tasks;
use App\Caches\Setting as SettingCache;
use App\Models\Setting as SettingModel;
class UpgradeTask extends Task
{
public function mainAction()
{
$this->resetSettingAction();
$this->resetAnnotationAction();
$this->resetMetadataAction();
$this->resetVoltAction();
}
/**
* 重置系统设置
*
* @command: php console.php upgrade reset_setting
*/
public function resetSettingAction()
{
echo "start reset setting..." . PHP_EOL;
$rows = SettingModel::query()->columns('section')->distinct(true)->execute();
foreach ($rows as $row) {
$cache = new SettingCache();
$cache->rebuild($row->section);
}
echo "end reset setting..." . PHP_EOL;
}
/**
* 重置注解
*
* @command: php console.php upgrade reset_annotation
*/
public function resetAnnotationAction()
{
$config = $this->getConfig();
$redis = $this->getRedis();
$dbIndex = $config->path('annotation.db');
$statsKey = $config->path('annotation.statsKey');
$redis->select($dbIndex);
$keys = $redis->sMembers($statsKey);
echo "start reset annotation..." . PHP_EOL;
if (count($keys) > 0) {
$keys = $this->handlePhKeys($keys);
$redis->del(...$keys);
$redis->del($statsKey);
}
echo "end reset annotation..." . PHP_EOL;
}
/**
* 重置元数据
*
* @command: php console.php upgrade reset_metadata
*/
public function resetMetadataAction()
{
$config = $this->getConfig();
$redis = $this->getRedis();
$dbIndex = $config->path('metadata.db');
$statsKey = $config->path('metadata.statsKey');
$redis->select($dbIndex);
$keys = $redis->sMembers($statsKey);
echo "start reset metadata..." . PHP_EOL;
if (count($keys) > 0) {
$keys = $this->handlePhKeys($keys);
$redis->del(...$keys);
$redis->del($statsKey);
}
echo "start reset metadata..." . PHP_EOL;
}
/**
* 重置模板
*
* @command: php console.php upgrade reset_volt
*/
public function resetVoltAction()
{
echo "start reset volt..." . PHP_EOL;
$dir = cache_path('volt');
foreach (scandir($dir) as $file) {
if (strpos($file, '.php')) {
unlink($dir . '/' . $file);
}
}
echo "end reset volt..." . PHP_EOL;
}
protected function handlePhKeys($keys)
{
return array_map(function ($key) {
return "_PHCR{$key}";
}, $keys);
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More