1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-08-02 20:45:52 +08:00

Compare commits

..

No commits in common. "master" and "v1.7.5" have entirely different histories.

132 changed files with 1227 additions and 1186 deletions

View File

@ -1,39 +1,4 @@
### [v1.7.8](https://gitee.com/koogua/course-tencent-cloud/releases/v1.7.8)(2025-06-20) ### [v1.7.5](https://gitee.com/koogua/course-tencent-cloud/releases/v1.7.5)(2024-02-22)
- 移除ThrottleLimit
- 增加CloseLiveTask
- 增加搜索页图片alt属性striptags过滤
- 后台增加返回顶部快捷方式
- 前台fixbar增加联系电话
- 优化安装脚本
- 优化课时列表直播提示
- 优化后台返回链接
- 优化统计分析代码位置
- 直播回调后更新课时缓存
- 后台清空头像->上传头像
- sitemap.xml直接写入网站根目录
### [v1.7.7](https://gitee.com/koogua/course-tencent-cloud/releases/v1.7.7)(2025-04-20)
- 优化索引管理工具
- 优化章节等页面UI
- 修正workerman中onMessage问题
- 修正非免费课程试听问题
- 优化layer窗口中的表单跳转
- 文件清理以及命名优化
- 优化倒计时
### [v1.7.6](https://gitee.com/koogua/course-tencent-cloud/releases/v1.7.6)(2025-03-22)
- 升级layui-v2.9.25
- 去除发货中不必要的异常抛出
- 去除文章和问题缓存重建
- 去除多余的文件引用
- 修正每日访问站点积分问题
- 限制全文搜索关键字长度
- 统一规划二维码样式
### [v1.7.5](https://gitee.com/koogua/course-tencent-cloud/releases/v1.7.5)(2025-02-22)
- 优化后台统计图表 - 优化后台统计图表
- 优化图片放大查看 - 优化图片放大查看

View File

@ -1,6 +1,6 @@
## 酷瓜云课堂 ## 酷瓜云课堂
[![酷瓜云课堂-开源知识付费解决方案](https://portal-1255691183.file.myqcloud.com/img/content/63ec392618bd5.png)](https://www.koogua.com) ![酷瓜云课堂](https://portal-1255691183.file.myqcloud.com/img/content/61dd395c053e5.png)
### 系统介绍 ### 系统介绍
@ -63,4 +63,4 @@ Tips: 请用手机注册一个新账号,用户中心 -> 关注订阅,扫码
- [码云平台](https://gitee.com/koogua/course-tencent-cloud/issues) - [码云平台](https://gitee.com/koogua/course-tencent-cloud/issues)
- [官方社区](https://www.koogua.com/community) - [官方社区](https://www.koogua.com/community)
- QQ交流群: 787363898 - QQ交流群: 788459713

View File

@ -18,13 +18,6 @@ abstract class Migration
abstract public function run(); abstract public function run();
protected function saveSettings(array $settings)
{
foreach ($settings as $setting) {
$this->saveSetting($setting);
}
}
protected function saveSetting(array $setting) protected function saveSetting(array $setting)
{ {
$settingRepo = new SettingRepo(); $settingRepo = new SettingRepo();
@ -39,4 +32,4 @@ abstract class Migration
} }
} }
} }

View File

@ -20,6 +20,8 @@ class ArticleIndexTask extends Task
* 搜索测试 * 搜索测试
* *
* @command: php console.php article_index search {query} * @command: php console.php article_index search {query}
* @param array $params
* @throws \XSException
*/ */
public function searchAction($params) public function searchAction($params)
{ {
@ -29,9 +31,7 @@ class ArticleIndexTask extends Task
exit('please special a query word' . PHP_EOL); exit('please special a query word' . PHP_EOL);
} }
$handler = new ArticleSearcher(); $result = $this->searchArticles($query);
$result = $handler->search($query);
var_export($result); var_export($result);
} }
@ -42,6 +42,24 @@ class ArticleIndexTask extends Task
* @command: php console.php article_index clean * @command: php console.php article_index clean
*/ */
public function cleanAction() public function cleanAction()
{
$this->cleanArticleIndex();
}
/**
* 重建索引
*
* @command: php console.php article_index rebuild
*/
public function rebuildAction()
{
$this->rebuildArticleIndex();
}
/**
* 清空索引
*/
protected function cleanArticleIndex()
{ {
$handler = new ArticleSearcher(); $handler = new ArticleSearcher();
@ -56,10 +74,8 @@ class ArticleIndexTask extends Task
/** /**
* 重建索引 * 重建索引
*
* @command: php console.php article_index rebuild
*/ */
public function rebuildAction() protected function rebuildArticleIndex()
{ {
$articles = $this->findArticles(); $articles = $this->findArticles();
@ -67,7 +83,7 @@ class ArticleIndexTask extends Task
$handler = new ArticleSearcher(); $handler = new ArticleSearcher();
$doc = new ArticleDocument(); $documenter = new ArticleDocument();
$index = $handler->getXS()->getIndex(); $index = $handler->getXS()->getIndex();
@ -76,7 +92,7 @@ class ArticleIndexTask extends Task
$index->beginRebuild(); $index->beginRebuild();
foreach ($articles as $article) { foreach ($articles as $article) {
$document = $doc->setDocument($article); $document = $documenter->setDocument($article);
$index->add($document); $index->add($document);
} }
@ -86,39 +102,17 @@ class ArticleIndexTask extends Task
} }
/** /**
* 刷新索引缓存 * 搜索文章
* *
* @command: php console.php article_index flush_index * @param string $query
* @return array
* @throws \XSException
*/ */
public function flushIndexAction() protected function searchArticles($query)
{ {
$handler = new ArticleSearcher(); $handler = new ArticleSearcher();
$index = $handler->getXS()->getIndex(); return $handler->search($query);
echo '------ start flush article index ------' . PHP_EOL;
$index->flushIndex();
echo '------ end flush article index ------' . PHP_EOL;
}
/**
* 刷新搜索日志
*
* @command: php console.php article_index flush_logging
*/
public function flushLoggingAction()
{
$handler = new ArticleSearcher();
$index = $handler->getXS()->getIndex();
echo '------ start flush article logging ------' . PHP_EOL;
$index->flushLogging();
echo '------ end flush article logging ------' . PHP_EOL;
} }
/** /**

View File

@ -1,72 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2024 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Console\Tasks;
use App\Caches\CourseChapterList as CourseChapterListCache;
use App\Models\Chapter as ChapterModel;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Repos\Chapter as ChapterRepo;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class CloseLiveTask extends Task
{
public function mainAction()
{
$chapterLives = $this->findChapterLives();
echo sprintf('pending lives: %s', $chapterLives->count()) . PHP_EOL;
if ($chapterLives->count() == 0) return;
echo '------ start close live task ------' . PHP_EOL;
foreach ($chapterLives as $chapterLive) {
$chapterLive->status = ChapterLiveModel::STATUS_INACTIVE;
$chapterLive->update();
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterLive->chapter_id);
$attrs = $chapter->attrs;
$attrs['stream']['status'] = ChapterModel::SS_INACTIVE;
$chapter->attrs = $attrs;
$chapter->update();
$cache = new CourseChapterListCache();
$cache->rebuild($chapterLive->course_id);
}
echo '------ end close live task ------' . PHP_EOL;
}
/**
* 查找待关闭直播
*
* @param int $limit
* @return ResultsetInterface|Resultset|ChapterLiveModel[]
*/
protected function findChapterLives(int $limit = 100)
{
$status = ChapterLiveModel::STATUS_ACTIVE;
$endTime = time() - 3600;
return ChapterLiveModel::query()
->where('status = :status:', ['status' => $status])
->andWhere('end_time < :end_time:', ['end_time' => $endTime])
->limit($limit)
->execute();
}
}

View File

@ -20,6 +20,8 @@ class CourseIndexTask extends Task
* 搜索测试 * 搜索测试
* *
* @command: php console.php course_index search {query} * @command: php console.php course_index search {query}
* @param array $params
* @throws \XSException
*/ */
public function searchAction($params) public function searchAction($params)
{ {
@ -29,9 +31,7 @@ class CourseIndexTask extends Task
exit('please special a query word' . PHP_EOL); exit('please special a query word' . PHP_EOL);
} }
$handler = new CourseSearcher(); $result = $this->searchCourses($query);
$result = $handler->search($query);
var_export($result); var_export($result);
} }
@ -42,6 +42,24 @@ class CourseIndexTask extends Task
* @command: php console.php course_index clean * @command: php console.php course_index clean
*/ */
public function cleanAction() public function cleanAction()
{
$this->cleanCourseIndex();
}
/**
* 重建索引
*
* @command: php console.php course_index rebuild
*/
public function rebuildAction()
{
$this->rebuildCourseIndex();
}
/**
* 清空索引
*/
protected function cleanCourseIndex()
{ {
$handler = new CourseSearcher(); $handler = new CourseSearcher();
@ -56,10 +74,8 @@ class CourseIndexTask extends Task
/** /**
* 重建索引 * 重建索引
*
* @command: php console.php course_index rebuild
*/ */
public function rebuildAction() protected function rebuildCourseIndex()
{ {
$courses = $this->findCourses(); $courses = $this->findCourses();
@ -67,7 +83,7 @@ class CourseIndexTask extends Task
$handler = new CourseSearcher(); $handler = new CourseSearcher();
$doc = new CourseDocument(); $documenter = new CourseDocument();
$index = $handler->getXS()->getIndex(); $index = $handler->getXS()->getIndex();
@ -76,7 +92,7 @@ class CourseIndexTask extends Task
$index->beginRebuild(); $index->beginRebuild();
foreach ($courses as $course) { foreach ($courses as $course) {
$document = $doc->setDocument($course); $document = $documenter->setDocument($course);
$index->add($document); $index->add($document);
} }
@ -86,39 +102,17 @@ class CourseIndexTask extends Task
} }
/** /**
* 刷新索引缓存 * 搜索课程
* *
* @command: php console.php course_index flush_index * @param string $query
* @return array
* @throws \XSException
*/ */
public function flushIndexAction() protected function searchCourses($query)
{ {
$handler = new CourseSearcher(); $handler = new CourseSearcher();
$index = $handler->getXS()->getIndex(); return $handler->search($query);
echo '------ start flush course index ------' . PHP_EOL;
$index->flushIndex();
echo '------ end flush course index ------' . PHP_EOL;
}
/**
* 刷新搜索日志
*
* @command: php console.php course_index flush_logging
*/
public function flushLoggingAction()
{
$handler = new CourseSearcher();
$index = $handler->getXS()->getIndex();
echo '------ start flush course logging ------' . PHP_EOL;
$index->flushLogging();
echo '------ end flush course logging ------' . PHP_EOL;
} }
/** /**
@ -130,7 +124,7 @@ class CourseIndexTask extends Task
{ {
return CourseModel::query() return CourseModel::query()
->where('published = 1') ->where('published = 1')
->andWhere('deleted = 0') ->where('deleted = 0')
->execute(); ->execute();
} }

View File

@ -58,6 +58,8 @@ class DeliverTask extends Task
case OrderModel::ITEM_VIP: case OrderModel::ITEM_VIP:
$this->handleVipOrder($order); $this->handleVipOrder($order);
break; break;
default:
$this->noMatchedHandler($order);
} }
$order->status = OrderModel::STATUS_FINISHED; $order->status = OrderModel::STATUS_FINISHED;
@ -153,6 +155,11 @@ class DeliverTask extends Task
$this->closePendingOrders($user->id); $this->closePendingOrders($user->id);
} }
protected function noMatchedHandler(OrderModel $order)
{
throw new \RuntimeException("No Matched Handler For Order: {$order->id}");
}
protected function closePendingOrders($userId) protected function closePendingOrders($userId)
{ {
$orders = $this->findUserPendingOrders($userId); $orders = $this->findUserPendingOrders($userId);

View File

@ -20,6 +20,8 @@ class QuestionIndexTask extends Task
* 搜索测试 * 搜索测试
* *
* @command: php console.php question_index search {query} * @command: php console.php question_index search {query}
* @param array $params
* @throws \XSException
*/ */
public function searchAction($params) public function searchAction($params)
{ {
@ -29,9 +31,7 @@ class QuestionIndexTask extends Task
exit('please special a query word' . PHP_EOL); exit('please special a query word' . PHP_EOL);
} }
$handler = new QuestionSearcher(); $result = $this->searchQuestions($query);
$result = $handler->search($query);
var_export($result); var_export($result);
} }
@ -42,6 +42,24 @@ class QuestionIndexTask extends Task
* @command: php console.php question_index clean * @command: php console.php question_index clean
*/ */
public function cleanAction() public function cleanAction()
{
$this->cleanQuestionIndex();
}
/**
* 重建索引
*
* @command: php console.php question_index rebuild
*/
public function rebuildAction()
{
$this->rebuildQuestionIndex();
}
/**
* 清空索引
*/
protected function cleanQuestionIndex()
{ {
$handler = new QuestionSearcher(); $handler = new QuestionSearcher();
@ -56,10 +74,8 @@ class QuestionIndexTask extends Task
/** /**
* 重建索引 * 重建索引
*
* @command: php console.php question_index rebuild
*/ */
public function rebuildAction() protected function rebuildQuestionIndex()
{ {
$questions = $this->findQuestions(); $questions = $this->findQuestions();
@ -67,7 +83,7 @@ class QuestionIndexTask extends Task
$handler = new QuestionSearcher(); $handler = new QuestionSearcher();
$doc = new QuestionDocument(); $documenter = new QuestionDocument();
$index = $handler->getXS()->getIndex(); $index = $handler->getXS()->getIndex();
@ -76,7 +92,7 @@ class QuestionIndexTask extends Task
$index->beginRebuild(); $index->beginRebuild();
foreach ($questions as $question) { foreach ($questions as $question) {
$document = $doc->setDocument($question); $document = $documenter->setDocument($question);
$index->add($document); $index->add($document);
} }
@ -86,39 +102,17 @@ class QuestionIndexTask extends Task
} }
/** /**
* 刷新索引缓存 * 搜索文章
* *
* @command: php console.php question_index flush_index * @param string $query
* @return array
* @throws \XSException
*/ */
public function flushIndexAction() protected function searchQuestions($query)
{ {
$handler = new QuestionSearcher(); $handler = new QuestionSearcher();
$index = $handler->getXS()->getIndex(); return $handler->search($query);
echo '------ start flush question index ------' . PHP_EOL;
$index->flushIndex();
echo '------ end flush question index ------' . PHP_EOL;
}
/**
* 刷新搜索日志
*
* @command: php console.php question_index flush_logging
*/
public function flushLoggingAction()
{
$handler = new QuestionSearcher();
$index = $handler->getXS()->getIndex();
echo '------ start flush question logging ------' . PHP_EOL;
$index->flushLogging();
echo '------ end flush question logging ------' . PHP_EOL;
} }
/** /**

View File

@ -37,7 +37,7 @@ class SitemapTask extends Task
$this->sitemap = new Sitemap(); $this->sitemap = new Sitemap();
$filename = public_path('sitemap.xml'); $filename = tmp_path('sitemap.xml');
echo '------ start sitemap task ------' . PHP_EOL; echo '------ start sitemap task ------' . PHP_EOL;

View File

@ -13,12 +13,12 @@ use GuzzleHttp\Client;
class SyncAppInfoTask extends Task class SyncAppInfoTask extends Task
{ {
const API_BASE_URL = 'https://www.koogua.com/api';
public function mainAction() public function mainAction()
{ {
echo '------ start sync app info ------' . PHP_EOL; echo '------ start sync app info ------' . PHP_EOL;
$url = 'https://www.koogua.com/api/instance/collect';
$site = $this->getSettings('site'); $site = $this->getSettings('site');
$serverHost = parse_url($site['url'], PHP_URL_HOST); $serverHost = parse_url($site['url'], PHP_URL_HOST);
@ -38,8 +38,6 @@ class SyncAppInfoTask extends Task
$client = new Client(); $client = new Client();
$url = sprintf('%s/instance/collect', self::API_BASE_URL);
$client->request('POST', $url, ['form_params' => $params]); $client->request('POST', $url, ['form_params' => $params]);
echo '------ end sync app info ------' . PHP_EOL; echo '------ end sync app info ------' . PHP_EOL;

View File

@ -71,6 +71,28 @@ class UploadController extends Controller
return $this->jsonSuccess(['data' => $data]); return $this->jsonSuccess(['data' => $data]);
} }
/**
* @Post("/avatar/img", name="admin.upload.avatar_img")
*/
public function uploadAvatarImageAction()
{
$service = new StorageService();
$file = $service->uploadAvatarImage();
if (!$file) {
return $this->jsonError(['msg' => '上传文件失败']);
}
$data = [
'id' => $file->id,
'name' => $file->name,
'url' => $service->getImageUrl($file->path),
];
return $this->jsonSuccess(['data' => $data]);
}
/** /**
* @Post("/content/img", name="admin.upload.content_img") * @Post("/content/img", name="admin.upload.content_img")
*/ */

View File

@ -9,8 +9,10 @@ namespace App\Http\Admin\Services;
use App\Builders\ArticleList as ArticleListBuilder; use App\Builders\ArticleList as ArticleListBuilder;
use App\Builders\ReportList as ReportListBuilder; use App\Builders\ReportList as ReportListBuilder;
use App\Caches\Article as ArticleCache;
use App\Http\Admin\Services\Traits\AccountSearchTrait; use App\Http\Admin\Services\Traits\AccountSearchTrait;
use App\Library\Paginator\Query as PagerQuery; use App\Library\Paginator\Query as PagerQuery;
use App\Library\Utils\Word as WordUtil;
use App\Models\Article as ArticleModel; use App\Models\Article as ArticleModel;
use App\Models\Category as CategoryModel; use App\Models\Category as CategoryModel;
use App\Models\Reason as ReasonModel; use App\Models\Reason as ReasonModel;
@ -135,6 +137,7 @@ class Article extends Service
$article->create(); $article->create();
$this->saveDynamicAttrs($article); $this->saveDynamicAttrs($article);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
$this->recountUserArticles($user); $this->recountUserArticles($user);
@ -205,6 +208,7 @@ class Article extends Service
$owner = $this->findUser($article->owner_id); $owner = $this->findUser($article->owner_id);
$this->saveDynamicAttrs($article); $this->saveDynamicAttrs($article);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
$this->recountUserArticles($owner); $this->recountUserArticles($owner);
@ -224,6 +228,7 @@ class Article extends Service
$owner = $this->findUser($article->owner_id); $owner = $this->findUser($article->owner_id);
$this->saveDynamicAttrs($article); $this->saveDynamicAttrs($article);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
$this->recountUserArticles($owner); $this->recountUserArticles($owner);
@ -243,6 +248,7 @@ class Article extends Service
$owner = $this->findUser($article->owner_id); $owner = $this->findUser($article->owner_id);
$this->saveDynamicAttrs($article); $this->saveDynamicAttrs($article);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
$this->recountUserArticles($owner); $this->recountUserArticles($owner);
@ -281,6 +287,7 @@ class Article extends Service
$owner = $this->findUser($article->owner_id); $owner = $this->findUser($article->owner_id);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
$this->recountUserArticles($owner); $this->recountUserArticles($owner);
@ -316,6 +323,7 @@ class Article extends Service
$owner = $this->findUser($article->owner_id); $owner = $this->findUser($article->owner_id);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
$this->recountUserArticles($owner); $this->recountUserArticles($owner);
} }
@ -354,6 +362,7 @@ class Article extends Service
$owner = $this->findUser($article->owner_id); $owner = $this->findUser($article->owner_id);
$this->recountUserArticles($owner); $this->recountUserArticles($owner);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
} }
} }
@ -380,6 +389,7 @@ class Article extends Service
$owner = $this->findUser($article->owner_id); $owner = $this->findUser($article->owner_id);
$this->recountUserArticles($owner); $this->recountUserArticles($owner);
$this->rebuildArticleCache($article);
$this->rebuildArticleIndex($article); $this->rebuildArticleIndex($article);
} }
} }
@ -398,6 +408,13 @@ class Article extends Service
return $userRepo->findById($id); return $userRepo->findById($id);
} }
protected function rebuildArticleCache(ArticleModel $article)
{
$cache = new ArticleCache();
$cache->rebuild($article->id);
}
protected function rebuildArticleIndex(ArticleModel $article) protected function rebuildArticleIndex(ArticleModel $article)
{ {
$sync = new ArticleIndexSync(); $sync = new ArticleIndexSync();

View File

@ -251,16 +251,14 @@ class ChapterContent extends Service
$content = $validator->checkContent($post['content']); $content = $validator->checkContent($post['content']);
$read->content = $content; $read->update(['content' => $content]);
$read->update();
$attrs = $chapter->attrs; $attrs = $chapter->attrs;
$attrs['word_count'] = WordUtil::getWordCount($content); $attrs['word_count'] = WordUtil::getWordCount($content);
$attrs['duration'] = WordUtil::getWordDuration($content); $attrs['duration'] = WordUtil::getWordDuration($content);
$chapter->attrs = $attrs;
$chapter->update(); $chapter->update(['attrs' => $attrs]);
$this->updateCourseReadAttrs($read->course_id); $this->updateCourseReadAttrs($read->course_id);
} }

View File

@ -9,6 +9,7 @@ namespace App\Http\Admin\Services;
use App\Builders\QuestionList as QuestionListBuilder; use App\Builders\QuestionList as QuestionListBuilder;
use App\Builders\ReportList as ReportListBuilder; use App\Builders\ReportList as ReportListBuilder;
use App\Caches\Question as QuestionCache;
use App\Http\Admin\Services\Traits\AccountSearchTrait; use App\Http\Admin\Services\Traits\AccountSearchTrait;
use App\Library\Paginator\Query as PagerQuery; use App\Library\Paginator\Query as PagerQuery;
use App\Models\Category as CategoryModel; use App\Models\Category as CategoryModel;
@ -130,6 +131,7 @@ class Question extends Service
$question->create(); $question->create();
$this->saveDynamicAttrs($question); $this->saveDynamicAttrs($question);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
$this->recountUserQuestions($user); $this->recountUserQuestions($user);
@ -193,6 +195,7 @@ class Question extends Service
$owner = $this->findUser($question->owner_id); $owner = $this->findUser($question->owner_id);
$this->saveDynamicAttrs($question); $this->saveDynamicAttrs($question);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
$this->recountUserQuestions($owner); $this->recountUserQuestions($owner);
@ -216,6 +219,7 @@ class Question extends Service
$owner = $this->findUser($question->owner_id); $owner = $this->findUser($question->owner_id);
$this->saveDynamicAttrs($question); $this->saveDynamicAttrs($question);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
$this->recountUserQuestions($owner); $this->recountUserQuestions($owner);
@ -234,6 +238,7 @@ class Question extends Service
$owner = $this->findUser($question->owner_id); $owner = $this->findUser($question->owner_id);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
$this->recountUserQuestions($owner); $this->recountUserQuestions($owner);
@ -273,6 +278,7 @@ class Question extends Service
$owner = $this->findUser($question->owner_id); $owner = $this->findUser($question->owner_id);
$this->recountUserQuestions($owner); $this->recountUserQuestions($owner);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
return $question; return $question;
@ -307,6 +313,7 @@ class Question extends Service
$owner = $this->findUser($question->owner_id); $owner = $this->findUser($question->owner_id);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
$this->recountUserQuestions($owner); $this->recountUserQuestions($owner);
} }
@ -345,6 +352,7 @@ class Question extends Service
$owner = $this->findUser($question->owner_id); $owner = $this->findUser($question->owner_id);
$this->recountUserQuestions($owner); $this->recountUserQuestions($owner);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
} }
} }
@ -371,6 +379,7 @@ class Question extends Service
$owner = $this->findUser($question->owner_id); $owner = $this->findUser($question->owner_id);
$this->recountUserQuestions($owner); $this->recountUserQuestions($owner);
$this->rebuildQuestionCache($question);
$this->rebuildQuestionIndex($question); $this->rebuildQuestionIndex($question);
} }
} }
@ -389,6 +398,13 @@ class Question extends Service
return $userRepo->findById($id); return $userRepo->findById($id);
} }
protected function rebuildQuestionCache(QuestionModel $question)
{
$cache = new QuestionCache();
$cache->rebuild($question->id);
}
protected function rebuildQuestionIndex(QuestionModel $question) protected function rebuildQuestionIndex(QuestionModel $question)
{ {
$sync = new QuestionIndexSync(); $sync = new QuestionIndexSync();

View File

@ -52,8 +52,8 @@ class Setting extends Service
{ {
$alipay = $this->getSettings('pay.alipay'); $alipay = $this->getSettings('pay.alipay');
$alipay['return_url'] = $alipay['return_url'] ?: kg_full_url(['for' => 'home.alipay.callback']); $alipay['return_url'] = $alipay['return_url'] ?: kg_full_url(['for' => 'home.alipay_callback']);
$alipay['notify_url'] = $alipay['notify_url'] ?: kg_full_url(['for' => 'home.alipay.notify']); $alipay['notify_url'] = $alipay['notify_url'] ?: kg_full_url(['for' => 'home.alipay_notify']);
return $alipay; return $alipay;
} }
@ -62,8 +62,8 @@ class Setting extends Service
{ {
$wxpay = $this->getSettings('pay.wxpay'); $wxpay = $this->getSettings('pay.wxpay');
$wxpay['return_url'] = $wxpay['return_url'] ?: kg_full_url(['for' => 'home.wxpay.callback']); $wxpay['return_url'] = $wxpay['return_url'] ?: kg_full_url(['for' => 'home.wxpay_callback']);
$wxpay['notify_url'] = $wxpay['notify_url'] ?: kg_full_url(['for' => 'home.wxpay.notify']); $wxpay['notify_url'] = $wxpay['notify_url'] ?: kg_full_url(['for' => 'home.wxpay_notify']);
return $wxpay; return $wxpay;
} }
@ -109,11 +109,11 @@ class Setting extends Service
$result = $this->getSettings($section); $result = $this->getSettings($section);
if ($section == 'live.notify') { if ($section == 'live.notify') {
$result['stream_begin_url'] = $result['stream_begin_url'] ?: kg_full_url(['for' => 'home.live.notify'], ['action' => 'streamBegin']); $result['stream_begin_url'] = $result['stream_begin_url'] ?: kg_full_url(['for' => 'home.live_notify'], ['action' => 'streamBegin']);
$result['stream_end_url'] = $result['stream_end_url'] ?: kg_full_url(['for' => 'home.live.notify'], ['action' => 'streamEnd']); $result['stream_end_url'] = $result['stream_end_url'] ?: kg_full_url(['for' => 'home.live_notify'], ['action' => 'streamEnd']);
$result['record_url'] = $result['record_url'] ?: kg_full_url(['for' => 'home.live.notify'], ['action' => 'record']); $result['record_url'] = $result['record_url'] ?: kg_full_url(['for' => 'home.live_notify'], ['action' => 'record']);
$result['snapshot_url'] = $result['snapshot_url'] ?: kg_full_url(['for' => 'home.live.notify'], ['action' => 'snapshot']); $result['snapshot_url'] = $result['snapshot_url'] ?: kg_full_url(['for' => 'home.live_notify'], ['action' => 'snapshot']);
$result['porn_url'] = $result['porn_url'] ?: kg_full_url(['for' => 'home.live.notify'], ['action' => 'porn']); $result['porn_url'] = $result['porn_url'] ?: kg_full_url(['for' => 'home.live_notify'], ['action' => 'porn']);
} }
return $result; return $result;

View File

@ -9,7 +9,7 @@
<div class="kg-nav-left"> <div class="kg-nav-left">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
{% if parent.id > 0 %} {% if parent.id > 0 %}
<a href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a> <a class="kg-back" href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a>
<a><cite>{{ parent.name }}</cite></a> <a><cite>{{ parent.name }}</cite></a>
{% endif %} {% endif %}
<a><cite>分类管理</cite></a> <a><cite>分类管理</cite></a>
@ -87,4 +87,4 @@
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}

View File

@ -88,7 +88,7 @@
layer.open({ layer.open({
type: 2, type: 2,
title: '推流测试', title: '推流测试',
area: ['720px', '540px'], area: ['720px', '500px'],
content: [url, 'no'] content: [url, 'no']
}); });
}); });
@ -97,4 +97,4 @@
</script> </script>
{% endblock %} {% endblock %}

View File

@ -43,7 +43,7 @@
<div class="kg-nav"> <div class="kg-nav">
<div class="kg-nav-left"> <div class="kg-nav-left">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
<a href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a> <a class="kg-back" href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a>
<a><cite>{{ course.title }}</cite></a> <a><cite>{{ course.title }}</cite></a>
<a><cite>{{ chapter.title }}</cite></a> <a><cite>{{ chapter.title }}</cite></a>
<a><cite>课时管理</cite></a> <a><cite>课时管理</cite></a>
@ -126,4 +126,4 @@
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@
<div class="kg-nav"> <div class="kg-nav">
<div class="kg-nav-left"> <div class="kg-nav-left">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
<a href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a> <a class="kg-back" href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a>
<a><cite>{{ course.title }}</cite></a> <a><cite>{{ course.title }}</cite></a>
<a><cite>章节管理</cite></a> <a><cite>章节管理</cite></a>
</span> </span>
@ -79,4 +79,4 @@
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}

View File

@ -3,6 +3,7 @@
<table class="kg-table layui-table"> <table class="kg-table layui-table">
<tr> <tr>
<th>名称</th> <th>名称</th>
<th>类型</th>
<th>大小</th> <th>大小</th>
<th>日期</th> <th>日期</th>
<th width="15%">操作</th> <th width="15%">操作</th>
@ -12,6 +13,7 @@
{% set delete_url = url({'for':'admin.resource.delete','id':item.id}) %} {% set delete_url = url({'for':'admin.resource.delete','id':item.id}) %}
<tr> <tr>
<td><input class="layui-input res-name" type="text" value="{{ item.upload.name }}" data-url="{{ update_url }}"></td> <td><input class="layui-input res-name" type="text" value="{{ item.upload.name }}" data-url="{{ update_url }}"></td>
<td>{{ item.upload.mime }}</td>
<td>{{ item.upload.size|human_size }}</td> <td>{{ item.upload.size|human_size }}</td>
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td> <td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
<td> <td>
@ -25,4 +27,4 @@
{% else %} {% else %}
<div class="kg-center">没有相关资料</div> <div class="kg-center">没有相关资料</div>
<br> <br>
{% endif %} {% endif %}

View File

@ -6,7 +6,7 @@
<div class="layui-card-body"> <div class="layui-card-body">
<table class="layui-table"> <table class="layui-table">
<colgroup> <colgroup>
<col width="25%"> <col width="100">
<col> <col>
</colgroup> </colgroup>
<tbody> <tbody>
@ -24,4 +24,4 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@ -1,9 +1,9 @@
<div class="layui-card layui-text"> <div class="layui-card layui-text" xmlns="http://www.w3.org/1999/html">
<div class="layui-card-header">服务器信息</div> <div class="layui-card-header">服务器信息</div>
<div class="layui-card-body"> <div class="layui-card-body">
<table class="layui-table"> <table class="layui-table">
<colgroup> <colgroup>
<col width="25%"> <col width="100">
<col> <col>
</colgroup> </colgroup>
<tbody> <tbody>
@ -22,4 +22,4 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@
<div class="layui-card-body"> <div class="layui-card-body">
<table class="layui-table"> <table class="layui-table">
<colgroup> <colgroup>
<col width="25%"> <col width="100">
<col> <col>
</colgroup> </colgroup>
<tbody> <tbody>
@ -26,4 +26,4 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@ -11,11 +11,9 @@
积分兑换 积分兑换
{% elseif value == 6 %} {% elseif value == 6 %}
抽奖兑换 抽奖兑换
{% elseif value == 7 %}
教师
{% elseif value == 10 %} {% elseif value == 10 %}
试听 试听
{% else %} {% else %}
N/A N/A
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}

View File

@ -25,7 +25,9 @@
<div class="kg-nav-left"> <div class="kg-nav-left">
<span class="layui-breadcrumb"> <span class="layui-breadcrumb">
{% if parent.id > 0 %} {% if parent.id > 0 %}
<a href="{{ back_url }}"><i class="layui-icon layui-icon-return"></i>返回</a> <a class="kg-back" href="{{ back_url }}">
<i class="layui-icon layui-icon-return"></i> 返回
</a>
<a><cite>{{ parent.name }}</cite></a> <a><cite>{{ parent.name }}</cite></a>
{% endif %} {% endif %}
<a><cite>导航管理</cite></a> <a><cite>导航管理</cite></a>

View File

@ -65,7 +65,7 @@
layer.open({ layer.open({
type: 2, type: 2,
title: '推流测试', title: '推流测试',
area: ['720px', '540px'], area: ['720px', '500px'],
content: [url, 'no'] content: [url, 'no']
}); });
}); });
@ -85,4 +85,4 @@
</script> </script>
{% endblock %} {% endblock %}

View File

@ -34,7 +34,7 @@
type: 2, type: 2,
title: '支付宝 - 支付测试', title: '支付宝 - 支付测试',
resize: false, resize: false,
area: ['640px', '320px'], area: ['640px', '300px'],
content: [url, 'no'] content: [url, 'no']
}); });
}); });
@ -45,7 +45,7 @@
type: 2, type: 2,
title: '微信 - 支付测试', title: '微信 - 支付测试',
resize: false, resize: false,
area: ['640px', '320px'], area: ['640px', '300px'],
content: [url, 'no'] content: [url, 'no']
}); });
}); });
@ -54,4 +54,4 @@
</script> </script>
{% endblock %} {% endblock %}

View File

@ -20,10 +20,9 @@
{{ js_include('lib/layui/layui.js') }} {{ js_include('lib/layui/layui.js') }}
{{ js_include('admin/js/common.js') }} {{ js_include('admin/js/common.js') }}
{{ js_include('admin/js/fixbar.js') }}
{% block include_js %}{% endblock %} {% block include_js %}{% endblock %}
{% block inline_js %}{% endblock %} {% block inline_js %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -21,11 +21,12 @@
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label" style="padding-top:30px;">头像</label> <label class="layui-form-label" style="padding-top:30px;">头像</label>
<div class="layui-input-inline" style="width:80px;"> <div class="layui-input-inline" style="width:80px;">
<img id="img-avatar" class="kg-avatar" src="{{ user.avatar }}"> <img id="avatar" class="kg-avatar" src="{{ user.avatar }}">
<input type="hidden" name="avatar" value="{{ user.avatar }}"> <input type="hidden" name="avatar" value="{{ user.avatar }}">
<input type="hidden" name="default_avatar" value="{{ default_avatar }}">
</div> </div>
<div class="layui-input-inline" style="padding-top:25px;"> <div class="layui-input-inline" style="padding-top:25px;">
<button id="change-avatar" class="layui-btn layui-btn-sm" type="button">更换</button> <button id="clear-avatar" class="layui-btn layui-btn-sm" type="button">清空</button>
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
@ -152,12 +153,6 @@
{% endblock %} {% endblock %}
{% block include_js %}
{{ js_include('admin/js/avatar.upload.js') }}
{% endblock %}
{% block inline_js %} {% block inline_js %}
<script> <script>
@ -168,6 +163,12 @@
var form = layui.form; var form = layui.form;
var laydate = layui.laydate; var laydate = layui.laydate;
$('#clear-avatar').on('click', function () {
var defaultAvatar = $('input[name=default_avatar]').val();
$('input[name=avatar]').val(defaultAvatar);
$('#avatar').attr('src', defaultAvatar);
});
laydate.render({ laydate.render({
elem: 'input[name=vip_expiry_time]', elem: 'input[name=vip_expiry_time]',
type: 'datetime' type: 'datetime'
@ -200,4 +201,4 @@
</script> </script>
{% endblock %} {% endblock %}

View File

@ -30,6 +30,8 @@ class Controller extends \Phalcon\Mvc\Controller
$this->setCors(); $this->setCors();
} }
$this->checkRateLimit();
return true; return true;
} }

View File

@ -7,7 +7,7 @@
namespace App\Http\Api\Controllers; namespace App\Http\Api\Controllers;
use App\Services\Logic\Live\LiveChat as LiveChatService; use App\Services\Logic\Live\LiveChapter as LiveChapterService;
use App\Services\Logic\Live\LiveList as LiveListService; use App\Services\Logic\Live\LiveList as LiveListService;
/** /**
@ -33,7 +33,7 @@ class LiveController extends Controller
*/ */
public function chatsAction($id) public function chatsAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$chats = $service->getRecentChats($id); $chats = $service->getRecentChats($id);
@ -45,7 +45,7 @@ class LiveController extends Controller
*/ */
public function statsAction($id) public function statsAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$stats = $service->getStats($id); $stats = $service->getStats($id);
@ -57,7 +57,7 @@ class LiveController extends Controller
*/ */
public function statusAction($id) public function statusAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$status = $service->getStatus($id); $status = $service->getStatus($id);
@ -69,7 +69,7 @@ class LiveController extends Controller
*/ */
public function bindUserAction($id) public function bindUserAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$service->bindUser($id); $service->bindUser($id);
@ -81,7 +81,7 @@ class LiveController extends Controller
*/ */
public function sendMessageAction($id) public function sendMessageAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$message = $service->sendMessage($id); $message = $service->sendMessage($id);

View File

@ -34,7 +34,7 @@ class AccountController extends Controller
} }
if ($this->authUser->id > 0) { if ($this->authUser->id > 0) {
return $this->response->redirect(['for' => 'home.index']); return $this->response->redirect('/');
} }
$returnUrl = $this->request->getHTTPReferer(); $returnUrl = $this->request->getHTTPReferer();
@ -62,7 +62,7 @@ class AccountController extends Controller
} }
if ($this->authUser->id > 0) { if ($this->authUser->id > 0) {
return $this->response->redirect(['for' => 'home.index']); return $this->response->redirect('/');
} }
$service = new OAuthProviderService(); $service = new OAuthProviderService();
@ -105,7 +105,7 @@ class AccountController extends Controller
return $this->response->redirect(['for' => 'home.index']); return $this->response->redirect(['for' => 'home.index']);
} }
$this->seo->prependTitle('忘记密码'); $this->seo->prependTitle('重置密码');
} }
/** /**
@ -119,11 +119,9 @@ class AccountController extends Controller
$returnUrl = $this->request->getPost('return_url', 'string'); $returnUrl = $this->request->getPost('return_url', 'string');
$location = $returnUrl ?: $this->url->get(['for' => 'home.index']);
$content = [ $content = [
'location' => $location, 'location' => $returnUrl ?: '/',
'msg' => '注册账号成功', 'msg' => '注册成功',
]; ];
return $this->jsonSuccess($content); return $this->jsonSuccess($content);
@ -142,12 +140,7 @@ class AccountController extends Controller
$location = $returnUrl ?: $this->url->get(['for' => 'home.index']); $location = $returnUrl ?: $this->url->get(['for' => 'home.index']);
$content = [ return $this->jsonSuccess(['location' => $location]);
'location' => $location,
'msg' => '登录账号成功',
];
return $this->jsonSuccess($content);
} }
/** /**
@ -163,12 +156,7 @@ class AccountController extends Controller
$location = $returnUrl ?: $this->url->get(['for' => 'home.index']); $location = $returnUrl ?: $this->url->get(['for' => 'home.index']);
$content = [ return $this->jsonSuccess(['location' => $location]);
'location' => $location,
'msg' => '登录账号成功',
];
return $this->jsonSuccess($content);
} }
/** /**

View File

@ -120,21 +120,27 @@ class ConnectController extends Controller
$service = new ConnectService(); $service = new ConnectService();
$openUser = $service->getOpenUserInfo($code, $state, $provider); $openUser = $service->getOpenUserInfo($code, $state, $provider);
$connect = $service->getConnectRelation($openUser['id'], $openUser['provider']); $connect = $service->getConnectRelation($openUser['id'], $openUser['provider']);
if ($this->authUser->id > 0 && $openUser) { if ($this->authUser->id > 0) {
$service->bindUser($openUser); if ($openUser) {
return $this->response->redirect(['for' => 'home.uc.account']); $service->bindUser($openUser);
return $this->response->redirect(['for' => 'home.uc.account']);
}
} else {
if ($connect) {
$service->authConnectLogin($connect);
return $this->response->redirect(['for' => 'home.index']);
}
} }
if ($this->authUser->id == 0 && $connect) { $captcha = $service->getSettings('captcha');
$service->authConnectLogin($connect);
return $this->response->redirect(['for' => 'home.index']);
}
$this->seo->prependTitle('绑定帐号'); $this->seo->prependTitle('绑定帐号');
$this->view->pick('connect/bind'); $this->view->pick('connect/bind');
$this->view->setVar('captcha', $captcha);
$this->view->setVar('provider', $provider); $this->view->setVar('provider', $provider);
$this->view->setVar('open_user', $openUser); $this->view->setVar('open_user', $openUser);
} }

View File

@ -77,14 +77,8 @@ class ConsultController extends Controller
$consult = $service->handle($consult->id); $consult = $service->handle($consult->id);
$location = $this->url->get([
'for' => 'home.course.show',
'id' => $consult['course']['id'],
]);
$content = [ $content = [
'location' => $location, 'consult' => $consult,
'target' => 'parent',
'msg' => '提交咨询成功', 'msg' => '提交咨询成功',
]; ];
@ -98,13 +92,14 @@ class ConsultController extends Controller
{ {
$service = new ConsultUpdateService(); $service = new ConsultUpdateService();
$service->handle($id); $consult = $service->handle($id);
$location = $this->url->get(['for' => 'home.uc.consults']); $service = new ConsultInfoService();
$consult = $service->handle($consult->id);
$content = [ $content = [
'location' => $location, 'consult' => $consult,
'target' => 'parent',
'msg' => '更新咨询成功', 'msg' => '更新咨询成功',
]; ];
@ -132,13 +127,14 @@ class ConsultController extends Controller
$service = new ConsultReplyService(); $service = new ConsultReplyService();
$service->handle($id); $consult = $service->handle($id);
$location = $this->url->get(['for' => 'home.tc.consults']); $service = new ConsultInfoService();
$consult = $service->handle($consult->id);
$content = [ $content = [
'location' => $location, 'consult' => $consult,
'target' => 'parent',
'msg' => '回复咨询成功', 'msg' => '回复咨询成功',
]; ];

View File

@ -77,6 +77,8 @@ class Controller extends \Phalcon\Mvc\Controller
$this->checkCsrfToken(); $this->checkCsrfToken();
} }
$this->checkRateLimit();
return true; return true;
} }

View File

@ -37,6 +37,8 @@ class LayerController extends \Phalcon\Mvc\Controller
$this->checkCsrfToken(); $this->checkCsrfToken();
} }
$this->checkRateLimit();
return true; return true;
} }

View File

@ -7,7 +7,7 @@
namespace App\Http\Home\Controllers; namespace App\Http\Home\Controllers;
use App\Services\Logic\Live\LiveChat as LiveChatService; use App\Services\Logic\Live\LiveChapter as LiveChapterService;
use Phalcon\Mvc\View; use Phalcon\Mvc\View;
/** /**
@ -21,7 +21,7 @@ class LiveController extends Controller
*/ */
public function chatsAction($id) public function chatsAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$chats = $service->getRecentChats($id); $chats = $service->getRecentChats($id);
@ -35,7 +35,7 @@ class LiveController extends Controller
*/ */
public function statsAction($id) public function statsAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$stats = $service->getStats($id); $stats = $service->getStats($id);
@ -47,7 +47,7 @@ class LiveController extends Controller
*/ */
public function statusAction($id) public function statusAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$status = $service->getStatus($id); $status = $service->getStatus($id);
@ -59,7 +59,7 @@ class LiveController extends Controller
*/ */
public function bindUserAction($id) public function bindUserAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$service->bindUser($id); $service->bindUser($id);
@ -71,7 +71,7 @@ class LiveController extends Controller
*/ */
public function sendMessageAction($id) public function sendMessageAction($id)
{ {
$service = new LiveChatService(); $service = new LiveChapterService();
$response = $service->sendMessage($id); $response = $service->sendMessage($id);

View File

@ -104,7 +104,7 @@ class PublicController extends \Phalcon\Mvc\Controller
} }
/** /**
* @Get("/alipay/callback", name="home.alipay.callback") * @Get("/alipay/callback", name="home.alipay_callback")
*/ */
public function alipayCallbackAction() public function alipayCallbackAction()
{ {
@ -112,7 +112,7 @@ class PublicController extends \Phalcon\Mvc\Controller
} }
/** /**
* @Get("/wxpay/callback", name="home.wxpay.callback") * @Get("/wxpay/callback", name="home.wxpay_callback")
*/ */
public function wxpayCallbackAction() public function wxpayCallbackAction()
{ {
@ -120,7 +120,7 @@ class PublicController extends \Phalcon\Mvc\Controller
} }
/** /**
* @Post("/alipay/notify", name="home.alipay.notify") * @Post("/alipay/notify", name="home.alipay_notify")
*/ */
public function alipayNotifyAction() public function alipayNotifyAction()
{ {
@ -136,7 +136,7 @@ class PublicController extends \Phalcon\Mvc\Controller
} }
/** /**
* @Post("/wxpay/notify", name="home.wxpay.notify") * @Post("/wxpay/notify", name="home.wxpay_notify")
*/ */
public function wxpayNotifyAction() public function wxpayNotifyAction()
{ {
@ -188,7 +188,7 @@ class PublicController extends \Phalcon\Mvc\Controller
} }
/** /**
* @Post("/live/notify", name="home.live.notify") * @Post("/live/notify", name="home.live_notify")
*/ */
public function liveNotifyAction() public function liveNotifyAction()
{ {
@ -198,7 +198,6 @@ class PublicController extends \Phalcon\Mvc\Controller
return $this->jsonSuccess(); return $this->jsonSuccess();
} else { } else {
$this->response->setStatusCode(403); $this->response->setStatusCode(403);
return $this->jsonError();
} }
} }

View File

@ -49,15 +49,7 @@ class RefundController extends Controller
$service->handle(); $service->handle();
$location = $this->url->get(['for' => 'home.uc.refunds']); return $this->jsonSuccess(['msg' => '申请退款成功']);
$content = [
'location' => $location,
'target' => 'parent',
'msg' => '提交申请成功',
];
return $this->jsonSuccess($content);
} }
/** /**

View File

@ -55,6 +55,13 @@ class ReviewController extends Controller
$this->notFound(); $this->notFound();
} }
$approved = $review['published'] == ReviewModel::PUBLISH_APPROVED;
$owned = $review['me']['owned'] == 1;
if (!$approved && !$owned) {
$this->notFound();
}
return $this->jsonSuccess(['review' => $review]); return $this->jsonSuccess(['review' => $review]);
} }
@ -65,13 +72,14 @@ class ReviewController extends Controller
{ {
$service = new ReviewCreateService(); $service = new ReviewCreateService();
$service->handle(); $review = $service->handle();
$location = $this->url->get(['for' => 'home.uc.reviews']); $service = new ReviewInfoService();
$review = $service->handle($review->id);
$content = [ $content = [
'location' => $location, 'review' => $review,
'target' => 'parent',
'msg' => '发布评价成功', 'msg' => '发布评价成功',
]; ];
@ -87,11 +95,12 @@ class ReviewController extends Controller
$service->handle($id); $service->handle($id);
$location = $this->url->get(['for' => 'home.uc.reviews']); $service = new ReviewInfoService();
$review = $service->handle($id);
$content = [ $content = [
'location' => $location, 'review' => $review,
'target' => 'parent',
'msg' => '更新评价成功', 'msg' => '更新评价成功',
]; ];

View File

@ -108,6 +108,8 @@ class UserConsoleController extends Controller
$service = new AccountInfoService(); $service = new AccountInfoService();
$captcha = $service->getSettings('captcha');
$account = $service->handle(); $account = $service->handle();
$service = new OAuthProviderService(); $service = new OAuthProviderService();
@ -135,6 +137,7 @@ class UserConsoleController extends Controller
$this->view->setVar('wechat_oa_connected', $wechatOAConnect ? 1 : 0); $this->view->setVar('wechat_oa_connected', $wechatOAConnect ? 1 : 0);
$this->view->setVar('oauth_provider', $oauthProvider); $this->view->setVar('oauth_provider', $oauthProvider);
$this->view->setVar('connects', $connects); $this->view->setVar('connects', $connects);
$this->view->setVar('captcha', $captcha);
$this->view->setVar('account', $account); $this->view->setVar('account', $account);
} }

View File

@ -24,9 +24,12 @@ class WeChatOfficialAccountController extends Controller
*/ */
public function bindAction() public function bindAction()
{ {
$captcha = $this->getSettings('captcha');
$this->seo->prependTitle('绑定帐号'); $this->seo->prependTitle('绑定帐号');
$this->view->pick('wechat/oa/bind'); $this->view->pick('wechat/oa/bind');
$this->view->setVar('captcha', $captcha);
} }
/** /**

View File

@ -14,7 +14,6 @@ use App\Repos\User as UserRepo;
use App\Services\Auth\Home as AuthService; use App\Services\Auth\Home as AuthService;
use App\Services\Logic\Account\Register as RegisterService; use App\Services\Logic\Account\Register as RegisterService;
use App\Services\Logic\Notice\External\AccountLogin as AccountLoginNotice; use App\Services\Logic\Notice\External\AccountLogin as AccountLoginNotice;
use App\Services\Logic\WeChat\OfficialAccount as WeChatOAService;
use App\Validators\Account as AccountValidator; use App\Validators\Account as AccountValidator;
use App\Validators\WeChatOfficialAccount as WeChatOAValidator; use App\Validators\WeChatOfficialAccount as WeChatOAValidator;
@ -64,13 +63,10 @@ class WeChatOfficialAccount extends Service
$openId = $validator->checkLoginOpenId($post['ticket']); $openId = $validator->checkLoginOpenId($post['ticket']);
$unionId = $this->getUnionId($openId);
$connect = new ConnectModel(); $connect = new ConnectModel();
$connect->user_id = $user->id; $connect->user_id = $user->id;
$connect->open_id = $openId; $connect->open_id = $openId;
$connect->union_id = $unionId;
$connect->provider = ConnectModel::PROVIDER_WECHAT_OA; $connect->provider = ConnectModel::PROVIDER_WECHAT_OA;
$connect->create(); $connect->create();
@ -90,8 +86,6 @@ class WeChatOfficialAccount extends Service
$openId = $validator->checkLoginOpenId($post['ticket']); $openId = $validator->checkLoginOpenId($post['ticket']);
$unionId = $this->getUnionId($openId);
$registerService = new RegisterService(); $registerService = new RegisterService();
$account = $registerService->handle(); $account = $registerService->handle();
@ -104,7 +98,6 @@ class WeChatOfficialAccount extends Service
$connect->user_id = $user->id; $connect->user_id = $user->id;
$connect->open_id = $openId; $connect->open_id = $openId;
$connect->union_id = $unionId;
$connect->provider = ConnectModel::PROVIDER_WECHAT_OA; $connect->provider = ConnectModel::PROVIDER_WECHAT_OA;
$connect->create(); $connect->create();
@ -118,17 +111,6 @@ class WeChatOfficialAccount extends Service
$this->eventsManager->fire('Account:afterRegister', $this, $user); $this->eventsManager->fire('Account:afterRegister', $this, $user);
} }
protected function getUnionId($openId)
{
$service = new WeChatOAService();
$app = $service->getOfficialAccount();
$user = $app->user->get($openId);
return $user['unionid'] ?: '';
}
protected function getAppAuth() protected function getAppAuth()
{ {
/** /**

View File

@ -36,6 +36,6 @@
</form> </form>
{% else %} {% else %}
<div class="register-close-tips"> <div class="register-close-tips">
<i class="layui-icon layui-icon-lock"></i> 邮箱注册已关闭 <i class="layui-icon layui-icon-tips"></i> 邮箱注册已关闭
</div> </div>
{% endif %} {% endif %}

View File

@ -36,6 +36,6 @@
</form> </form>
{% else %} {% else %}
<div class="register-close-tips"> <div class="register-close-tips">
<i class="layui-icon layui-icon-lock"></i> 手机注册已关闭 <i class="layui-icon layui-icon-tips"></i> 手机注册已关闭
</div> </div>
{% endif %} {% endif %}

View File

@ -1,44 +1,35 @@
{%- macro model_icon(model) %}
{% if model == 1 %}
<i class="iconfont icon-video"></i>
{% elseif model == 2 %}
<i class="iconfont icon-live"></i>
{% elseif model == 3 %}
<i class="iconfont icon-article"></i>
{% elseif model == 4 %}
<i class="layui-icon layui-icon-user"></i>
{% endif %}
{%- endmacro %}
{%- macro show_lesson_list(parent,chapter) %} {%- macro show_lesson_list(parent,chapter) %}
<ul class="sidebar-lesson-list"> <ul class="sidebar-lesson-list">
{% for lesson in parent.children %} {% for lesson in parent.children %}
{% set url = url({'for':'home.chapter.show','id':lesson.id}) %} {% set url = url({'for':'home.chapter.show','id':lesson.id}) %}
{% set active = chapter.id == lesson.id ? 'active' : 'normal' %} {% set active = (chapter.id == lesson.id) ? 'active' : 'normal' %}
{% set priv = lesson.me.owned == 1 ? 'allow' : 'deny' %} <li class="lesson-title layui-elip">
<li class="sidebar-lesson layui-elip {{ priv }} {{ active }}" data-url="{{ url }}"> {% if lesson.me.owned == 1 %}
<span class="model">{{ model_icon(lesson.model) }}</span> <a class="{{ active }}" href="{{ url }}" title="{{ lesson.title }}">{{ lesson.title }}</a>
<span class="title" title="{{ lesson.title }}">{{ lesson.title }}</span> {% else %}
{% if lesson.me.owned == 0 %} <span class="deny" title="{{ lesson.title }}">{{ lesson.title }}</span>
<span class="lock"><i class="iconfont icon-lock"></i></span>
{% endif %} {% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
{%- endmacro %} {%- endmacro %}
<div class="layui-card sidebar-card sidebar-catalog"> <div class="layui-card sidebar-card sidebar-chapter">
<div class="layui-card-header">课程目录</div> <div class="layui-card-header">课程目录</div>
<div class="layui-card-body"> <div class="layui-card-body">
{% if catalog|length > 1 %} {% if catalog|length > 1 %}
<div class="sidebar-chapter-list"> <div class="sidebar-chapter-list">
{% for item in catalog %} {% for item in catalog %}
<div class="sidebar-chapter layui-elip">{{ item.title }}</div> <div class="chapter-title layui-elip">{{ item.title }}</div>
{{ show_lesson_list(item,chapter) }} <div class="sidebar-lesson-list">
{{ show_lesson_list(item,chapter) }}
</div>
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
{{ show_lesson_list(catalog[0],chapter) }} <div class="sidebar-lesson-list">
{{ show_lesson_list(catalog[0],chapter) }}
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -35,7 +35,11 @@
<div class="layui-card-body"> <div class="layui-card-body">
<div class="chat-msg-list" id="chat-msg-list" data-url="{{ live_chats_url }}"></div> <div class="chat-msg-list" id="chat-msg-list" data-url="{{ live_chats_url }}"></div>
<form class="layui-form chat-msg-form" method="post" action="{{ send_msg_url }}"> <form class="layui-form chat-msg-form" method="post" action="{{ send_msg_url }}">
<input class="layui-input" type="text" name="content" maxlength="50" placeholder="快来一起互动吧" lay-vertype="tips" lay-verify="required"> {% if auth_user.id > 0 %}
<input class="layui-input" type="text" name="content" maxlength="50" placeholder="快来一起互动吧" lay-verType="tips" lay-verify="required">
{% else %}
<input class="layui-input" type="text" placeholder="登录后才可以发言哦" readonly="readonly">
{% endif %}
<button class="layui-hide" type="submit" lay-submit="true" lay-filter="chat">发送</button> <button class="layui-hide" type="submit" lay-submit="true" lay-filter="chat">发送</button>
</form> </form>
</div> </div>
@ -76,4 +80,4 @@
{{ js_include('home/js/course.share.js') }} {{ js_include('home/js/course.share.js') }}
{{ js_include('home/js/copy.js') }} {{ js_include('home/js/copy.js') }}
{% endblock %} {% endblock %}

View File

@ -1,15 +1,9 @@
{% for chat in chats %} {% for chat in chats %}
{% if chat.user.vip == 1 %} <div class="chat">
<div class="chat chat-vip"> {% if chat.user.vip == 1 %}
<span class="icon"><i class="layui-icon layui-icon-diamond"></i></span> <span class="layui-icon layui-icon-diamond icon-vip"></span>
<span class="user layui-badge layui-bg-orange">{{ chat.user.name }}</span> {% endif %}
<span class="content">{{ chat.content }}</span> <span class="user">{{ chat.user.name }}</span>
</div> <span class="content">{{ chat.content }}</span>
{% else %} </div>
<div class="chat chat-normal"> {% endfor %}
<span class="icon"><i class="layui-icon layui-icon-username"></i></span>
<span class="user layui-badge layui-bg-blue">{{ chat.user.name }}</span>
<span class="content">{{ chat.content }}</span>
</div>
{% endif %}
{% endfor %}

View File

@ -11,9 +11,9 @@
</span> </span>
</div> </div>
<div class="live-preview"> <div class="preview">
<div class="icon"><i class="layui-icon layui-icon-face-cry"></i></div> <div class="icon"><i class="layui-icon layui-icon-face-cry"></i></div>
<div class="tips">直播已禁止,谢谢关注!</div> <div class="tips">直播已禁止,谢谢关注!</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -13,19 +13,19 @@
</div> </div>
{% if time() < chapter.start_time %} {% if time() < chapter.start_time %}
<div class="live-preview countdown wrap"> <div class="preview countdown">
<div class="icon"><i class="layui-icon layui-icon-time"></i></div> <div class="icon"><i class="layui-icon layui-icon-time"></i></div>
<div class="timer"></div> <div class="timer"></div>
<div class="tips">直播倒计时开始啦,敬请关注!</div> <div class="tips">直播倒计时开始啦,敬请关注!</div>
</div> </div>
{% elseif chapter.start_time < time() and chapter.end_time > time() %} {% elseif chapter.start_time < time() and chapter.end_time > time() %}
<div class="live-preview countdown wrap"> <div class="preview countdown">
<div class="icon"><i class="layui-icon layui-icon-face-surprised"></i></div> <div class="icon"><i class="layui-icon layui-icon-face-surprised"></i></div>
<div class="timer"></div> <div class="timer"></div>
<div class="tips">直播时间到了,老师去哪了?</div> <div class="tips">直播时间到了,老师去哪了?</div>
</div> </div>
{% else %} {% else %}
<div class="live-preview wrap"> <div class="preview">
<div class="icon"><i class="layui-icon layui-icon-tree"></i></div> <div class="icon"><i class="layui-icon layui-icon-tree"></i></div>
<div class="tips">直播已结束,谢谢关注!</div> <div class="tips">直播已结束,谢谢关注!</div>
</div> </div>
@ -43,4 +43,4 @@
{{ js_include('home/js/chapter.live.countdown.js') }} {{ js_include('home/js/chapter.live.countdown.js') }}
{% endblock %} {% endblock %}

View File

@ -7,7 +7,7 @@
<div class="icon" title="{{ like_title }}" data-url="{{ like_url }}"> <div class="icon" title="{{ like_title }}" data-url="{{ like_url }}">
<i class="layui-icon layui-icon-praise icon-praise {{ like_class }}"></i> <i class="layui-icon layui-icon-praise icon-praise {{ like_class }}"></i>
</div> </div>
<div class="text" data-count="{{ chapter.like_count }}">{{ chapter.like_count }}</div> <div class="text">{{ chapter.like_count }}</div>
</div> </div>
<div class="item" id="toolbar-online"> <div class="item" id="toolbar-online">
<div class="icon" title="在线人数"> <div class="icon" title="在线人数">
@ -15,4 +15,4 @@
</div> </div>
<div class="text">0</div> <div class="text">0</div>
</div> </div>
</div> </div>

View File

@ -1,118 +1,103 @@
{%- macro show_lesson_list(chapter) %} {%- macro show_lesson_list(chapter) %}
<ul class="lesson-list"> <ul class="lesson-list">
{% for lesson in chapter.children %} {% for lesson in chapter.children %}
{% set url = url({'for':'home.chapter.show','id':lesson.id}) %}
{% set priv = lesson.me.owned ? 'allow' : 'deny' %}
{% if lesson.model == 1 %} {% if lesson.model == 1 %}
<li class="lesson-item {{ priv }}" data-url="{{ url }}">{{ vod_lesson_info(lesson) }}</li> <li class="lesson-item">{{ vod_lesson_info(lesson) }}</li>
{% elseif lesson.model == 2 %} {% elseif lesson.model == 2 %}
<li class="lesson-item {{ priv }}" data-url="{{ url }}">{{ live_lesson_info(lesson) }}</li> <li class="lesson-item">{{ live_lesson_info(lesson) }}</li>
{% elseif lesson.model == 3 %} {% elseif lesson.model == 3 %}
<li class="lesson-item {{ priv }}" data-url="{{ url }}">{{ read_lesson_info(lesson) }}</li> <li class="lesson-item">{{ read_lesson_info(lesson) }}</li>
{% elseif lesson.model == 4 %} {% elseif lesson.model == 4 %}
<li class="lesson-item deny" data-url="{{ url }}">{{ offline_lesson_info(lesson) }}</li> <li class="lesson-item">{{ offline_lesson_info(lesson) }}</li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
{%- endmacro %} {%- endmacro %}
{%- macro vod_lesson_info(lesson) %} {%- macro vod_lesson_info(lesson) %}
<div class="left"> {% set url = lesson.me.owned ? url({'for':'home.chapter.show','id':lesson.id}) : '' %}
<span class="model"><i class="iconfont icon-video"></i></span> {% set priv = lesson.me.owned ? 'allow' : 'deny' %}
<a class="{{ priv }} view-lesson" href="javascript:" data-url="{{ url }}">
<i class="layui-icon layui-icon-play"></i>
<span class="title">{{ lesson.title }}</span> <span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="iconfont icon-trial"></span>
{% endif %}
{% if lesson.me.duration > 0 %} {% if lesson.me.duration > 0 %}
<span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span> <span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span>
{% endif %} {% endif %}
{% if lesson.me.owned == 0 %} {% if priv == 'deny' %}
<span class="lock"><i class="iconfont icon-lock"></i></span> <span class="iconfont icon-lock"></span>
{% endif %} {% endif %}
{% if lesson.free == 1 %}
<span class="flag flag-free">试听</span>
{% endif %}
</div>
<div class="right">
<span class="duration">{{ lesson.attrs.duration|duration }}</span> <span class="duration">{{ lesson.attrs.duration|duration }}</span>
</div> </a>
{%- endmacro %} {%- endmacro %}
{%- macro live_lesson_info(lesson) %} {%- macro live_lesson_info(lesson) %}
<div class="left"> {% set url = lesson.me.owned ? url({'for':'home.chapter.show','id':lesson.id}) : '' %}
<span class="model"><i class="iconfont icon-live"></i></span> {% set priv = lesson.me.owned ? 'allow' : 'deny' %}
<a class="{{ priv }} view-lesson" href="javascript:" data-url="{{ url }}">
<i class="layui-icon layui-icon-video"></i>
<span class="title">{{ lesson.title }}</span> <span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="iconfont icon-trial"></span>
{% endif %}
{% if lesson.me.duration > 0 %} {% if lesson.me.duration > 0 %}
<span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span> <span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span>
{% endif %} {% endif %}
{% if lesson.me.owned == 0 %} {% if priv == 'deny' %}
<span class="lock"><i class="iconfont icon-lock"></i></span> <span class="iconfont icon-lock"></span>
{% endif %} {% endif %}
{% if lesson.attrs.playback.ready == 1 %} <span class="live" title="{{ date('Y-m-d H:i',lesson.attrs.start_time) }}">{{ live_status_info(lesson) }}</span>
<span class="flag flag-playback">回放</span> </a>
{% endif %}
{% if lesson.free == 1 %}
<span class="flag flag-free">试听</span>
{% endif %}
</div>
<div class="right">
<span class="live-status">{{ live_status_info(lesson) }}</span>
<span class="live-time">{{ date('Y-m-d H:i',lesson.attrs.start_time) }}</span>
</div>
{%- endmacro %} {%- endmacro %}
{%- macro read_lesson_info(lesson) %} {%- macro read_lesson_info(lesson) %}
<div class="left"> {% set url = lesson.me.owned ? url({'for':'home.chapter.show','id':lesson.id}) : '' %}
<span class="model"><i class="iconfont icon-article"></i></span> {% set priv = lesson.me.owned ? 'allow' : 'deny' %}
<a class="{{ priv }} view-lesson" href="javascript:" data-url="{{ url }}">
<i class="layui-icon layui-icon-read"></i>
<span class="title">{{ lesson.title }}</span> <span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="iconfont icon-trial"></span>
{% endif %}
{% if lesson.me.duration > 0 %} {% if lesson.me.duration > 0 %}
<span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span> <span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span>
{% endif %} {% endif %}
{% if lesson.me.owned == 0 %} {% if priv == 'deny' %}
<span class="lock"><i class="iconfont icon-lock"></i></span> <span class="iconfont icon-lock"></span>
{% endif %} {% endif %}
{% if lesson.free == 1 %} </a>
<span class="flag flag-free">试读</span>
{% endif %}
</div>
<div class="right">
<span class="size"></span>
</div>
{%- endmacro %} {%- endmacro %}
{%- macro offline_lesson_info(lesson) %} {%- macro offline_lesson_info(lesson) %}
<div class="left"> <a class="deny view-lesson" href="javascript:">
<span class="model"><i class="layui-icon layui-icon-user"></i></span> <i class="layui-icon layui-icon-user"></i>
<span class="title">{{ lesson.title }}</span> <span class="title">{{ lesson.title }}</span>
{% if lesson.me.owned == 0 %}
<span class="lock"><i class="iconfont icon-lock"></i></span>
{% endif %}
{% if lesson.free == 1 %} {% if lesson.free == 1 %}
<span class="flag flag-free">试听</span> <span class="layui-badge free-badge">试听</span>
{% endif %} {% endif %}
</div> <span class="live" title="{{ date('Y-m-d H:i',lesson.attrs.start_time) }}">{{ offline_status_info(lesson) }}</span>
<div class="right"> </a>
<span class="live-status">{{ offline_status_info(lesson) }}</span>
<span class="live-time">{{ date('Y-m-d H:i',lesson.attrs.start_time) }}</span>
</div>
{%- endmacro %} {%- endmacro %}
{%- macro live_status_info(lesson) %} {%- macro live_status_info(lesson) %}
{% if lesson.attrs.stream.status == 'active' %} {% if lesson.attrs.stream.status == 'active' %}
<span class="flag flag-active">直播中</span> <span class="active">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 直播中</span>
{% elseif lesson.attrs.start_time > time() %} {% elseif lesson.attrs.start_time > time() %}
<span class="flag flag-scheduled">倒计时</span> <span class="pending">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 倒计时</span>
{% elseif lesson.attrs.end_time < time() %} {% elseif lesson.attrs.end_time < time() %}
<span class="flag flag-ended">已结束</span> <span class="finished">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 已结束</span>
{% elseif lesson.attrs.stream.status == 'inactive' %}
<span class="flag flag-inactive">未推流</span>
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}
{%- macro offline_status_info(lesson) %} {%- macro offline_status_info(lesson) %}
{% if lesson.attrs.start_time < time() and lesson.attrs.end_time > time() %} {% if lesson.attrs.start_time < time() and lesson.attrs.end_time > time() %}
<span class="flag flag-active">授课中</span> <span class="active">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 授课中</span>
{% elseif lesson.attrs.start_time > time() %} {% elseif lesson.attrs.start_time > time() %}
<span class="flag flag-scheduled">未开始</span> <span class="pending">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 未开始</span>
{% elseif lesson.attrs.end_time < time() %} {% elseif lesson.attrs.end_time < time() %}
<span class="flag flag-ended">已结束</span> <span class="finished">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 已结束</span>
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}
@ -134,4 +119,4 @@
{% else %} {% else %}
{{ show_lesson_list(chapters[0]) }} {{ show_lesson_list(chapters[0]) }}
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@ -105,21 +105,21 @@
{{ offline_meta_info(course) }} {{ offline_meta_info(course) }}
{% endif %} {% endif %}
</div> </div>
<div class="ratings"> <div class="rating">
<p class="item"> <p class="item">
<span class="name">内容实用</span> <span class="name">内容实用</span>
<span class="star" id="rating1" data-value="{{ course.ratings.rating1 }}"></span> <span class="star">{{ star_info(course.ratings.rating1) }}</span>
<span class="score">{{ "%0.1f"|format(course.ratings.rating1) }} 分</span> <span class="score">{{ "%0.1f"|format(course.ratings.rating1) }} 分</span>
</p> </p>
<p class="item"> <p class="item">
<span class="name">简洁易懂</span> <span class="name">简洁易懂</span>
<span class="star" id="rating2" data-value="{{ course.ratings.rating2 }}"></span> <span class="star">{{ star_info(course.ratings.rating2) }}</span>
<span class="score">{{ "%0.1f"|format(course.ratings.rating2) }} 分</span> <span class="score">{{ "%0.1f"|format(course.ratings.rating2) }} 分</span>
</p> </p>
<p class="item"> <p class="item">
<span class="name">逻辑清晰</span> <span class="name">逻辑清晰</span>
<span class="star" id="rating3" data-value="{{ course.ratings.rating3 }}"></span> <span class="star">{{ star_info(course.ratings.rating3) }}</span>
<span class="score">{{ "%0.1f"|format(course.ratings.rating3) }} 分</span> <span class="score">{{ "%0.1f"|format(course.ratings.rating3) }} 分</span>
</p> </p>
</div> </div>
</div> </div>

View File

@ -26,8 +26,8 @@
{% endif %} {% endif %}
<div class="item" id="toolbar-favorite"> <div class="item" id="toolbar-favorite">
<div class="icon" title="{{ favorite_title }}" data-url="{{ favorite_url }}"> <div class="icon" title="{{ favorite_title }}" data-url="{{ favorite_url }}">
<i class="layui-icon icon-star {{ favorite_class }}"></i> <i class="layui-icon layui-icon-star icon-star {{ favorite_class }}"></i>
</div> </div>
<div class="text" data-count="{{ course.favorite_count }}">{{ course.favorite_count }}</div> <div class="text" data-count="{{ course.favorite_count }}">{{ course.favorite_count }}</div>
</div> </div>
</div> </div>

View File

@ -31,10 +31,17 @@
{% endblock %} {% endblock %}
{% block include_js %} {% block inline_js %}
{{ js_include('lib/clipboard.min.js') }} <script>
{{ js_include('home/js/help.show.js') }} layui.use(['jquery', 'helper'], function () {
{{ js_include('home/js/copy.js') }} var $ = layui.jquery;
var helper = layui.helper;
var $courseList = $('#course-list');
if ($courseList.length > 0) {
helper.ajaxLoadHtml($courseList.data('url'), $courseList.attr('id'));
}
});
</script>
{% endblock %} {% endblock %}

View File

@ -11,11 +11,5 @@
积分 积分
{% elseif value == 6 %} {% elseif value == 6 %}
抽奖 抽奖
{% elseif value == 7 %}
教师
{% elseif value == 10 %}
试听
{% else %}
N/A
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}

View File

@ -10,7 +10,7 @@
{% if course.model in [1,2,3] %} {% if course.model in [1,2,3] %}
<p> <p>
<span>学习期限:{{ date('Y-m-d',course.study_expiry_time) }}</span> <span>学习期限:{{ date('Y-m-d',course.study_expiry_time) }}</span>
<span>退款期限:{{ course.refund_expiry > 0 ? date('Y-m-d',course.refund_expiry_time) : '不支持' }}</span> <span>退款期限:{{ date('Y-m-d',course.refund_expiry_time) }}</span>
</p> </p>
{% elseif course.model == 4 %} {% elseif course.model == 4 %}
<p>上课时间:{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</p> <p>上课时间:{{ course.attrs.start_date }} ~ {{ course.attrs.end_date }}</p>
@ -23,13 +23,13 @@
<div class="order-item"> <div class="order-item">
<p>课程名称:{{ course.title }}</p> <p>课程名称:{{ course.title }}</p>
<p> <p>
<span>市场价格:<em class="price">{{ '¥%0.2f'|format(course.market_price) }}</em></span> <span>市场价格:{{ '¥%0.2f'|format(course.market_price) }}</span>
<span>会员价格:<em class="price">{{ '¥%0.2f'|format(course.vip_price) }}</em></span> <span>会员价格:<em class="price">{{ '¥%0.2f'|format(course.vip_price) }}</em></span>
</p> </p>
{% if course.model in [1,2,3] %} {% if course.model in [1,2,3] %}
<p> <p>
<span>学习期限:{{ date('Y-m-d',course.study_expiry_time) }}</span> <span>学习期限:{{ date('Y-m-d',course.study_expiry_time) }}</span>
<span>退款期限:{{ course.refund_expiry > 0 ? date('Y-m-d',course.refund_expiry_time) : '不支持' }}</span> <span>退款期限:{{ date('Y-m-d',course.refund_expiry_time) }}</span>
</p> </p>
{% endif %} {% endif %}
</div> </div>

View File

@ -26,7 +26,7 @@
<a class="layui-btn layui-bg-blue" href="{{ order_pay_url }}" target="_top">立即支付</a> <a class="layui-btn layui-bg-blue" href="{{ order_pay_url }}" target="_top">立即支付</a>
{% endif %} {% endif %}
{% if order.me.allow_cancel == 1 %} {% if order.me.allow_cancel == 1 %}
<button class="layui-btn layui-bg-red btn-order-cancel" data-sn="{{ order.sn }}" data-url="{{ order_cancel_url }}">立即取消</button> <a class="layui-btn layui-bg-red order-cancel" href="javascript:" data-sn="{{ order.sn }}" data-url="{{ order_cancel_url }}">立即取消</a>
{% endif %} {% endif %}
{% if order.me.allow_refund == 1 %} {% if order.me.allow_refund == 1 %}
<a class="layui-btn layui-bg-blue" href="{{ refund_confirm_url }}">申请退款</a> <a class="layui-btn layui-bg-blue" href="{{ refund_confirm_url }}">申请退款</a>
@ -39,4 +39,4 @@
{{ js_include('home/js/order.info.js') }} {{ js_include('home/js/order.info.js') }}
{% endblock %} {% endblock %}

View File

@ -30,10 +30,17 @@
{% endblock %} {% endblock %}
{% block include_js %} {% block inline_js %}
{{ js_include('lib/clipboard.min.js') }} <script>
{{ js_include('home/js/page.show.js') }} layui.use(['jquery', 'helper'], function () {
{{ js_include('home/js/copy.js') }} var $ = layui.jquery;
var helper = layui.helper;
var $courseList = $('#course-list');
if ($courseList.length > 0) {
helper.ajaxLoadHtml($courseList.data('url'), $courseList.attr('id'));
}
});
</script>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@
<div class="order-item"> <div class="order-item">
<p>课程名称:<span>{{ course.title }}</span></p> <p>课程名称:<span>{{ course.title }}</span></p>
<p>退款期限:<span>{{ date('Y-m-d H:i:s',course.refund_expiry_time) }} {{ expiry_flag }}</span></p> <p>退款期限:<span>{{ date('Y-m-d H:i:s',course.refund_expiry_time) }} {{ expiry_flag }}</span></p>
<p>退款金额:<span class="price">{{ '¥%0.2f'|format(course.refund_amount) }}</span>退款比例:<span class="rate">{{ 100 * course.refund_rate }}%</span></p> <p>退款金额:<span class="price">{{ '¥%0.2f'|format(course.refund_amount) }}</span>退款比例:<span class="price">{{ 100 * course.refund_percent }}%</span></p>
</div> </div>
{% elseif confirm.item_type == 2 %} {% elseif confirm.item_type == 2 %}
{% set courses = confirm.item_info.courses %} {% set courses = confirm.item_info.courses %}
@ -18,7 +18,7 @@
<div class="order-item"> <div class="order-item">
<p>课程名称:<span>{{ course.title }}</span></p> <p>课程名称:<span>{{ course.title }}</span></p>
<p>退款期限:<span>{{ date('Y-m-d H:i:s',course.refund_expiry_time) }} {{ expiry_flag }}</span></p> <p>退款期限:<span>{{ date('Y-m-d H:i:s',course.refund_expiry_time) }} {{ expiry_flag }}</span></p>
<p>退款金额:<span class="price">{{ '¥%0.2f'|format(course.refund_amount) }}</span>退款比例:<span class="rate">{{ 100 * course.refund_rate }}%</span></p> <p>退款金额:<span class="price">{{ '¥%0.2f'|format(course.refund_amount) }}</span>退款比例:<span class="price">{{ 100 * course.refund_percent }}%</span></p>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -40,9 +40,12 @@
</table> </table>
<br> <br>
{% if confirm.refund_amount > 0 %} {% if confirm.refund_amount > 0 %}
<form class="layui-form" method="post" action="{{ url({'for':'home.refund.create'}) }}"> <form class="layui-form layui-form-pane" method="post" action="{{ url({'for':'home.refund.create'}) }}">
<div class="layui-form-item"> <div class="layui-form-item">
<input class="layui-input" name="apply_note" placeholder="请告知我们退款原因,让我们做的更好..." lay-verify="required"> <label class="layui-form-label">退款原因</label>
<div class="layui-input-block">
<input class="layui-input" name="apply_note" lay-verify="required">
</div>
</div> </div>
<div class="layui-form-item center"> <div class="layui-form-item center">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交申请</button> <button class="layui-btn" lay-submit="true" lay-filter="go">提交申请</button>
@ -65,4 +68,4 @@
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -9,9 +9,9 @@
<table class="layui-table order-table" lay-size="lg"> <table class="layui-table order-table" lay-size="lg">
<tr> <tr>
<td colspan="2"> <td colspan="2">
<span>订单金额:<em class="price">{{ '¥%0.2f'|format(refund.order.amount) }}</em></span> 订单金额:<span class="price">{{ '¥%0.2f'|format(refund.order.amount) }}</span>
<span>退款金额:<em class="price">{{ '¥%0.2f'|format(refund.amount) }}</em></span> 退款金额:<span class="price">{{ '¥%0.2f'|format(refund.amount) }}</span>
<span>退款状态:{{ refund_status(refund.status) }}</span> 退款状态:<span class="status">{{ refund_status(refund.status) }}</span>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -22,8 +22,41 @@
<br> <br>
<div class="center"> <div class="center">
{% if refund.me.allow_cancel == 1 %} {% if refund.me.allow_cancel == 1 %}
<button class="layui-btn btn-refund-cancel" data-sn="{{ refund.sn }}" data-url="{{ cancel_url }}">取消退款</button> <button class="kg-refund layui-btn" data-sn="{{ refund.sn }}" data-url="{{ cancel_url }}">取消退款</button>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}
{% block inline_js %}
<script>
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var index = parent.layer.getFrameIndex(window.name);
parent.layer.iframeAuto(index);
$('.kg-refund').on('click', function () {
var url = $(this).data('url');
var data = {sn: $(this).data('sn')};
layer.confirm('确定要取消退款吗?', function () {
$.ajax({
type: 'POST',
url: url,
data: data,
success: function (res) {
layer.msg(res.msg, {icon: 1});
setTimeout(function () {
parent.window.location.href = '/uc/refunds';
}, 1500);
}
});
});
});
});
</script>
{% endblock %}

View File

@ -24,9 +24,9 @@
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">评价内容</label> <label class="layui-form-label" for="content">评价内容</label>
<div class="layui-input-block"> <div class="layui-input-block">
<textarea name="content" class="layui-textarea" placeholder="请描述你的学习经历,例如学习成果、课程内容、讲师风格、教学服务等。" lay-verify="required"></textarea> <textarea name="content" id="content" class="layui-textarea" placeholder="请描述你的学习经历,例如学习成果、课程内容、讲师风格、教学服务等。"></textarea>
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
@ -54,4 +54,4 @@
{{ js_include('home/js/user.console.review.js') }} {{ js_include('home/js/user.console.review.js') }}
{% endblock %} {% endblock %}

View File

@ -26,7 +26,7 @@
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">评价内容</label> <label class="layui-form-label">评价内容</label>
<div class="layui-input-block"> <div class="layui-input-block">
<textarea name="content" class="layui-textarea" lay-verify="required">{{ review.content }}</textarea> <textarea name="content" class="layui-textarea">{{ review.content }}</textarea>
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
@ -53,4 +53,4 @@
{{ js_include('home/js/user.console.review.js') }} {{ js_include('home/js/user.console.review.js') }}
{% endblock %} {% endblock %}

View File

@ -19,7 +19,7 @@
{% if item.cover %} {% if item.cover %}
<div class="cover"> <div class="cover">
<a href="{{ article_url }}" target="_blank"> <a href="{{ article_url }}" target="_blank">
<img src="{{ item.cover }}!cover_270" alt="{{ item.title|striptags }}"> <img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -6,7 +6,7 @@
<div class="search-course-card"> <div class="search-course-card">
<div class="cover"> <div class="cover">
<a href="{{ course_url }}" target="_blank"> <a href="{{ course_url }}" target="_blank">
<img src="{{ item.cover }}!cover_270" alt="{{ item.title|striptags }}"> <img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
</a> </a>
</div> </div>
<div class="info"> <div class="info">

View File

@ -19,7 +19,7 @@
{% if item.cover %} {% if item.cover %}
<div class="cover"> <div class="cover">
<a href="{{ question_url }}" target="_blank"> <a href="{{ question_url }}" target="_blank">
<img src="{{ item.cover }}!cover_270" alt="{{ item.title|striptags }}"> <img src="{{ item.cover }}!cover_270" alt="{{ item.title }}">
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -1,9 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN-Hans"> <html lang="zh-CN-Hans">
<head> <head>
{% if site_info.analytics_enabled == 1 %}
{{ site_info.analytics_script }}
{% endif %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
@ -41,5 +38,12 @@
{% block include_js %}{% endblock %} {% block include_js %}{% endblock %}
{% block inline_js %}{% endblock %} {% block inline_js %}{% endblock %}
{% if site_info.analytics_enabled == 1 %}
<div class="layui-hide">
{{ site_info.analytics_script }}
</div>
{% endif %}
</body> </body>
</html> </html>

View File

@ -10,11 +10,10 @@
{% set point_enabled = setting('point','enabled') %} {% set point_enabled = setting('point','enabled') %}
<div class="my-profile-card wrap"> <div class="my-profile-card wrap">
<div class="vip">{{ vip_info(auth_user) }}</div>
<div class="avatar"> <div class="avatar">
<img class="my-avatar" src="{{ auth_user.avatar }}" alt="{{ auth_user.name }}"> <img class="my-avatar" src="{{ auth_user.avatar }}" alt="{{ auth_user.name }}">
</div> </div>
<div class="name">{{ auth_user.name }}</div> <div class="name">{{ auth_user.name }} {{ vip_info(auth_user) }}</div>
</div> </div>
<div class="layui-card"> <div class="layui-card">
@ -72,4 +71,4 @@
<li><a href="{{ url({'for':'home.uc.account'}) }}">帐号安全</a></li> <li><a href="{{ url({'for':'home.uc.account'}) }}">帐号安全</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -79,6 +79,7 @@
{% block include_js %} {% block include_js %}
{{ js_include('home/js/user.avatar.upload.js') }}
{{ js_include('home/js/user.console.profile.js') }} {{ js_include('home/js/user.console.profile.js') }}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,33 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layout-main">
<div class="my-sidebar">{{ partial('user/console/menu') }}</div>
<div class="my-content">
<div class="wrap">
<div class="my-nav">
<span class="title">关注订阅</span>
</div>
<div class="my-subscribe">
{% if subscribed == 0 %}
<div id="sub-qrcode" class="qrcode"></div>
<div id="sub-tips" class="tips">订阅官方公众号,接收重要通知!</div>
{% else %}
<div class="tips">你已经订阅官方公众号</div>
{% endif %}
</div>
<div class="layui-hide">
<input type="hidden" name="subscribed" value="{{ subscribed }}">
</div>
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('home/js/wechat.oa.subscribe.js') }}
{% endblock %}

View File

@ -4,13 +4,13 @@
<div class="vip-header">会员权益</div> <div class="vip-header">会员权益</div>
<div class="vip-priv-list wrap"> <div class="vip-reason-list wrap">
<button class="layui-btn layui-bg-blue">好课畅学</button> <span class="layui-badge reason-badge">好课畅学</span>
<button class="layui-btn layui-bg-blue">会员折扣</button> <span class="layui-badge reason-badge">会员折扣</span>
<button class="layui-btn layui-bg-blue">高清视频</button> <span class="layui-badge reason-badge">高清视频</span>
<button class="layui-btn layui-bg-blue">广告免疫</button> <span class="layui-badge reason-badge">广告免疫</span>
<button class="layui-btn layui-bg-blue">会员标识</button> <span class="layui-badge reason-badge">会员标识</span>
<button class="layui-btn layui-bg-blue">优先服务</button> <span class="layui-badge reason-badge">贴心服务</span>
</div> </div>
<div class="vip-header">开通会员</div> <div class="vip-header">开通会员</div>
@ -57,4 +57,4 @@
{{ js_include('home/js/vip.index.js') }} {{ js_include('home/js/vip.index.js') }}
{% endblock %} {% endblock %}

View File

@ -3,7 +3,7 @@
<div class="layui-row layui-col-space20"> <div class="layui-row layui-col-space20">
{% for item in pager.items %} {% for item in pager.items %}
{% set user_url = url({'for':'home.user.show','id':item.id}) %} {% set user_url = url({'for':'home.user.show','id':item.id}) %}
<div class="layui-col-md3"> <div class="layui-col-md2">
<div class="user-card"> <div class="user-card">
<div class="avatar"> <div class="avatar">
<a href="{{ user_url }}" title="{{ item.about }}" target="_blank"> <a href="{{ user_url }}" title="{{ item.about }}" target="_blank">

View File

@ -16,7 +16,7 @@ class AppInfo
protected $link = 'https://www.koogua.com'; protected $link = 'https://www.koogua.com';
protected $version = '1.7.8'; protected $version = '1.7.5';
public function __get($name) public function __get($name)
{ {

View File

@ -1,51 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2024 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Library\Paginator\Adapter;
use App\Library\Paginator\Query as PaginatorQuery;
use Phalcon\Paginator\Adapter\NativeArray as PhNativeArray;
use stdClass;
class NativeArray extends PhNativeArray
{
/**
* @var string
*/
protected $baseUrl;
/**
* @var array
*/
protected $params = [];
public function paginate(): stdClass
{
$pager = parent::paginate();
$query = new PaginatorQuery();
$this->baseUrl = $query->getBaseUrl();
$this->params = $query->getParams();
$pager->first = $this->buildPageUrl($pager->first);
$pager->previous = $this->buildPageUrl($pager->previous);
$pager->next = $this->buildPageUrl($pager->next);
$pager->last = $this->buildPageUrl($pager->last);
return $pager;
}
protected function buildPageUrl($page)
{
$this->params['page'] = $page;
return $this->baseUrl . '?' . http_build_query($this->params);
}
}

View File

@ -19,7 +19,6 @@ class CourseUser extends Model
const SOURCE_MANUAL = 4; // 分配 const SOURCE_MANUAL = 4; // 分配
const SOURCE_POINT_REDEEM = 5; // 积分兑换 const SOURCE_POINT_REDEEM = 5; // 积分兑换
const SOURCE_LUCKY_REDEEM = 6; // 抽奖兑换 const SOURCE_LUCKY_REDEEM = 6; // 抽奖兑换
const SOURCE_TEACHER = 7; // 教师
const SOURCE_TRIAL = 10; // 试听 const SOURCE_TRIAL = 10; // 试听
/** /**
@ -138,7 +137,6 @@ class CourseUser extends Model
self::SOURCE_TRIAL => '试听', self::SOURCE_TRIAL => '试听',
self::SOURCE_VIP => '畅学', self::SOURCE_VIP => '畅学',
self::SOURCE_MANUAL => '分配', self::SOURCE_MANUAL => '分配',
self::SOURCE_TEACHER => '教师',
self::SOURCE_POINT_REDEEM => '积分兑换', self::SOURCE_POINT_REDEEM => '积分兑换',
self::SOURCE_LUCKY_REDEEM => '抽奖兑换', self::SOURCE_LUCKY_REDEEM => '抽奖兑换',
]; ];

View File

@ -19,7 +19,7 @@ class Order extends Model
const ITEM_PACKAGE = 2; // 套餐 const ITEM_PACKAGE = 2; // 套餐
const ITEM_REWARD = 3; // 赞赏(已弃用) const ITEM_REWARD = 3; // 赞赏(已弃用)
const ITEM_VIP = 4; // 会员 const ITEM_VIP = 4; // 会员
const ITEM_TEST = 99; // 支付测试 const ITEM_TEST = 99; // 测试
/** /**
* 状态类型 * 状态类型
@ -191,7 +191,7 @@ class Order extends Model
self::ITEM_COURSE => '课程', self::ITEM_COURSE => '课程',
self::ITEM_PACKAGE => '套餐', self::ITEM_PACKAGE => '套餐',
self::ITEM_VIP => '会员', self::ITEM_VIP => '会员',
self::ITEM_TEST => '支付测试', self::ITEM_TEST => '测试',
]; ];
} }

View File

@ -20,7 +20,7 @@ class PointHistory extends Model
const EVENT_SITE_VISIT = 5; // 站点访问 const EVENT_SITE_VISIT = 5; // 站点访问
const EVENT_CHAPTER_STUDY = 6; // 课时学习 const EVENT_CHAPTER_STUDY = 6; // 课时学习
const EVENT_COURSE_REVIEW = 7; // 课程评价 const EVENT_COURSE_REVIEW = 7; // 课程评价
const EVENT_IM_DISCUSS = 8; // 微聊讨论(已弃用) const EVENT_IM_DISCUSS = 8; // 微聊讨论
const EVENT_COMMENT_POST = 9; // 发布评论 const EVENT_COMMENT_POST = 9; // 发布评论
const EVENT_ARTICLE_POST = 10; // 发布文章 const EVENT_ARTICLE_POST = 10; // 发布文章
const EVENT_QUESTION_POST = 11; // 发布问题 const EVENT_QUESTION_POST = 11; // 发布问题
@ -132,6 +132,7 @@ class PointHistory extends Model
self::EVENT_SITE_VISIT => '用户登录', self::EVENT_SITE_VISIT => '用户登录',
self::EVENT_CHAPTER_STUDY => '课时学习', self::EVENT_CHAPTER_STUDY => '课时学习',
self::EVENT_COURSE_REVIEW => '课程评价', self::EVENT_COURSE_REVIEW => '课程评价',
self::EVENT_IM_DISCUSS => '微聊讨论',
]; ];
} }

View File

@ -29,16 +29,8 @@ class Connect extends Repository
$query->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]); $query->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
} }
if (!empty($where['open_id'])) {
$query->andWhere('open_id = :open_id:', ['open_id' => $where['open_id']]);
}
if (!empty($where['provider'])) { if (!empty($where['provider'])) {
if (is_array($where['provider'])) { $query->andWhere('provider = :provider:', ['provider' => $where['provider']]);
$query->inWhere('provider', $where['provider']);
} else {
$query->andWhere('provider = :provider:', ['provider' => $where['provider']]);
}
} }
if (isset($where['deleted'])) { if (isset($where['deleted'])) {
@ -75,6 +67,19 @@ class Connect extends Repository
]); ]);
} }
/**
* @param string $openId
* @param int $provider
* @return ConnectModel|Model|bool
*/
public function findByOpenIdShallow($openId, $provider)
{
return ConnectModel::findFirst([
'conditions' => 'open_id = ?1 AND provider = ?2',
'bind' => [1 => $openId, 2 => $provider],
]);
}
/** /**
* @param int $userId * @param int $userId
* @param int $provider * @param int $provider

View File

@ -7,7 +7,7 @@
namespace App\Services; namespace App\Services;
use App\Caches\CourseChapterList as CourseChapterListCache; use App\Caches\CourseChapterList as CatalogCache;
use App\Models\Chapter as ChapterModel; use App\Models\Chapter as ChapterModel;
use App\Models\ChapterLive as ChapterLiveModel; use App\Models\ChapterLive as ChapterLiveModel;
use App\Repos\Chapter as ChapterRepo; use App\Repos\Chapter as ChapterRepo;
@ -175,7 +175,7 @@ class LiveNotify extends Service
protected function rebuildCatalogCache(ChapterModel $chapter) protected function rebuildCatalogCache(ChapterModel $chapter)
{ {
$cache = new CourseChapterListCache(); $cache = new CatalogCache();
$cache->rebuild($chapter->course_id); $cache->rebuild($chapter->course_id);
} }
@ -216,4 +216,4 @@ class LiveNotify extends Service
return $sign == $mySign; return $sign == $mySign;
} }
} }

View File

@ -36,8 +36,6 @@ class Register extends LogicService
$accountValidator = new AccountValidator(); $accountValidator = new AccountValidator();
$accountValidator->checkRegisterStatus($post['account']);
$accountValidator->checkLoginName($post['account']); $accountValidator->checkLoginName($post['account']);
$data = []; $data = [];

View File

@ -39,10 +39,10 @@ class ChapterList extends LogicService
if (count($chapters) == 0) return []; if (count($chapters) == 0) return [];
if ($user->id > 0) { if ($user->id > 0 && $this->courseUser) {
$chapters = $this->handleLoginUserChapters($chapters, $course, $user); $chapters = $this->handleLoginUserChapters($chapters, $course, $user);
} else { } else {
$chapters = $this->handleGuestUserChapters($chapters, $course); $chapters = $this->handleGuestUserChapters($chapters);
} }
return $chapters; return $chapters;
@ -50,11 +50,7 @@ class ChapterList extends LogicService
protected function handleLoginUserChapters(array $chapters, CourseModel $course, UserModel $user) protected function handleLoginUserChapters(array $chapters, CourseModel $course, UserModel $user)
{ {
$mappings = []; $mappings = $this->getLearningMappings($course->id, $user->id, $this->courseUser->plan_id);
if ($this->courseUser) {
$mappings = $this->getLearningMappings($course->id, $user->id, $this->courseUser->plan_id);
}
foreach ($chapters as &$chapter) { foreach ($chapters as &$chapter) {
foreach ($chapter['children'] as &$lesson) { foreach ($chapter['children'] as &$lesson) {
@ -65,30 +61,23 @@ class ChapterList extends LogicService
'owned' => $owned ? 1 : 0, 'owned' => $owned ? 1 : 0,
'logged' => 1, 'logged' => 1,
]; ];
// 如果课程是免费的,但又设置了课时试听,清除试听标识
if ($course->market_price == 0 && $lesson['free'] == 1) {
$lesson['free'] = 0;
}
} }
} }
return $chapters; return $chapters;
} }
protected function handleGuestUserChapters(array $chapters, CourseModel $course) protected function handleGuestUserChapters(array $chapters)
{ {
foreach ($chapters as &$chapter) { foreach ($chapters as &$chapter) {
foreach ($chapter['children'] as &$lesson) { foreach ($chapter['children'] as &$lesson) {
$owned = ($this->ownedCourse || $lesson['free'] == 1) && $lesson['published'] == 1;
$lesson['me'] = [ $lesson['me'] = [
'progress' => 0, 'progress' => 0,
'duration' => 0, 'duration' => 0,
'logged' => 0, 'logged' => 0,
'owned' => 0, 'owned' => $owned ? 1 : 0,
]; ];
// 如果课程是免费的,但又设置了课时试听,清除试听标识
if ($course->market_price == 0 && $lesson['free'] == 1) {
$lesson['free'] = 0;
}
} }
} }

View File

@ -100,7 +100,7 @@ class CourseInfo extends LogicService
if ($this->courseUser) { if ($this->courseUser) {
$me['reviewed'] = $this->courseUser->reviewed ? 1 : 0; $me['reviewed'] = $this->courseUser->reviewed ? 1 : 0;
$me['progress'] = $this->courseUser->progress; $me['progress'] = $this->courseUser->progress ? 1 : 0;
$me['plan_id'] = $this->courseUser->plan_id; $me['plan_id'] = $this->courseUser->plan_id;
} }
} }

View File

@ -46,11 +46,7 @@ trait CourseUserTrait
$this->joinedCourse = true; $this->joinedCourse = true;
} }
if ($course->teacher_id == $user->id) { if ($course->market_price == 0) {
$this->ownedCourse = true;
} elseif ($course->market_price == 0) {
$this->ownedCourse = true; $this->ownedCourse = true;
@ -100,7 +96,6 @@ trait CourseUserTrait
case CourseUserModel::SOURCE_FREE: case CourseUserModel::SOURCE_FREE:
case CourseUserModel::SOURCE_TRIAL: case CourseUserModel::SOURCE_TRIAL:
case CourseUserModel::SOURCE_VIP: case CourseUserModel::SOURCE_VIP:
case CourseUserModel::SOURCE_TEACHER:
$this->createCourseUser($course, $user, $expiryTime, $sourceType); $this->createCourseUser($course, $user, $expiryTime, $sourceType);
$this->deleteCourseUser($relation); $this->deleteCourseUser($relation);
break; break;
@ -174,8 +169,6 @@ trait CourseUserTrait
$result = true; $result = true;
} elseif ($course->vip_price == 0 && $user->vip == 1) { } elseif ($course->vip_price == 0 && $user->vip == 1) {
$result = true; $result = true;
} elseif($course->teacher_id == $user->id) {
$result = true;
} }
return $result; return $result;
@ -183,10 +176,6 @@ trait CourseUserTrait
protected function getFreeSourceType(CourseModel $course, UserModel $user) protected function getFreeSourceType(CourseModel $course, UserModel $user)
{ {
if ($course->teacher_id == $user->id) {
return CourseUserModel::SOURCE_TEACHER;
}
$sourceType = CourseUserModel::SOURCE_FREE; $sourceType = CourseUserModel::SOURCE_FREE;
if ($course->market_price > 0) { if ($course->market_price > 0) {

View File

@ -12,7 +12,7 @@ use App\Services\Logic\Service as LogicService;
use App\Validators\Live as LiveValidator; use App\Validators\Live as LiveValidator;
use GatewayClient\Gateway; use GatewayClient\Gateway;
class LiveChat extends LogicService class LiveChapter extends LogicService
{ {
use ChapterTrait; use ChapterTrait;

View File

@ -121,6 +121,7 @@ class OrderConfirm extends LogicService
'lesson_count' => $course->lesson_count, 'lesson_count' => $course->lesson_count,
'study_expiry' => $course->study_expiry, 'study_expiry' => $course->study_expiry,
'refund_expiry' => $course->refund_expiry, 'refund_expiry' => $course->refund_expiry,
'origin_price' => $course->origin_price,
'market_price' => $course->market_price, 'market_price' => $course->market_price,
'vip_price' => $course->vip_price, 'vip_price' => $course->vip_price,
]; ];

View File

@ -0,0 +1,63 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Services\Logic\Point\History;
use App\Models\ImMessage as ImMessageModel;
use App\Models\PointHistory as PointHistoryModel;
use App\Repos\PointHistory as PointHistoryRepo;
use App\Repos\User as UserRepo;
use App\Services\Logic\Point\PointHistory;
class ImDiscuss extends PointHistory
{
public function handle(ImMessageModel $message)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$eventRule = json_decode($setting['event_rule'], true);
$eventEnabled = $eventRule['im_discuss']['enabled'] ?? 0;
if ($eventEnabled == 0) return;
$eventPoint = $eventRule['im_discuss']['point'] ?? 0;
if ($eventPoint <= 0) return;
$eventId = $message->sender_id;
$eventType = PointHistoryModel::EVENT_IM_DISCUSS;
$eventInfo = new \stdClass();
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findDailyEventHistory($eventId, $eventType, date('Ymd'));
if ($history) return;
$userRepo = new UserRepo();
$user = $userRepo->findById($message->sender_id);
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_info = $eventInfo;
$history->event_point = $eventPoint;
$this->handlePointHistory($history);
}
}

View File

@ -35,7 +35,7 @@ class SiteVisit extends PointHistory
$eventId = $user->id; $eventId = $user->id;
$eventType = PointHistoryModel::EVENT_SITE_VISIT; $eventType = PointHistoryModel::EVENT_SITE_VISIT;
$eventInfo = []; $eventInfo = new \stdClass();
$historyRepo = new PointHistoryRepo(); $historyRepo = new PointHistoryRepo();

View File

@ -28,12 +28,12 @@ class Article extends Handler
$paginator = new XunSearchPaginator([ $paginator = new XunSearchPaginator([
'xs' => $searcher->getXS(), 'xs' => $searcher->getXS(),
'highlight' => $searcher->getHighlightFields(), 'highlight' => $searcher->getHighlightFields(),
'query' => $this->handleKeywords($params['query']), 'query' => $params['query'],
'page' => $page, 'page' => $page,
'limit' => $limit, 'limit' => $limit,
]); ]);
$pager = $paginator->paginate(); $pager = $paginator->getPaginate();
return $this->handleArticles($pager); return $this->handleArticles($pager);
} }

View File

@ -28,12 +28,12 @@ class Course extends Handler
$paginator = new XunSearchPaginator([ $paginator = new XunSearchPaginator([
'xs' => $searcher->getXS(), 'xs' => $searcher->getXS(),
'highlight' => $searcher->getHighlightFields(), 'highlight' => $searcher->getHighlightFields(),
'query' => $this->handleKeywords($params['query']), 'query' => $params['query'],
'page' => $page, 'page' => $page,
'limit' => $limit, 'limit' => $limit,
]); ]);
$pager = $paginator->paginate(); $pager = $paginator->getPaginate();
return $this->handleCourses($pager); return $this->handleCourses($pager);
} }

View File

@ -18,9 +18,4 @@ abstract class Handler extends LogicService
abstract function getRelatedQuery($query, $limit); abstract function getRelatedQuery($query, $limit);
protected function handleKeywords($str) }
{
return kg_substr($str, 0, 50, '');
}
}

View File

@ -28,12 +28,12 @@ class Question extends Handler
$paginator = new XunSearchPaginator([ $paginator = new XunSearchPaginator([
'xs' => $searcher->getXS(), 'xs' => $searcher->getXS(),
'highlight' => $searcher->getHighlightFields(), 'highlight' => $searcher->getHighlightFields(),
'query' => $this->handleKeywords($params['query']), 'query' => $params['query'],
'page' => $page, 'page' => $page,
'limit' => $limit, 'limit' => $limit,
]); ]);
$pager = $paginator->paginate(); $pager = $paginator->getPaginate();
return $this->handleQuestions($pager); return $this->handleQuestions($pager);
} }

View File

@ -111,9 +111,6 @@ class OfficialAccount extends AppService
$logger->debug('Received Message: ' . json_encode($message)); $logger->debug('Received Message: ' . json_encode($message));
/**
* 事件类型文档https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
*/
switch ($message['MsgType']) { switch ($message['MsgType']) {
case 'event': case 'event':
switch ($message['Event']) { switch ($message['Event']) {
@ -164,24 +161,44 @@ class OfficialAccount extends AppService
if ($connect) return null; if ($connect) return null;
$loginScene = sprintf('qrscene_%s', self::QR_SCENE_LOGIN); /**
* 尼玛不知道为什么又多了个"qrscene_"前缀SCAN事件里面又不带这个前缀
*/
$subscribeScene = sprintf('qrscene_%s', self::QR_SCENE_SUBSCRIBE); $subscribeScene = sprintf('qrscene_%s', self::QR_SCENE_SUBSCRIBE);
/** $userId = 0;
* 未关注过服务号,在登录页扫登录场景码,关注服务号
*/ if (Text::startsWith($eventKey, $subscribeScene)) {
if (Text::startsWith($eventKey, $loginScene)) {
$ticket = str_replace($loginScene, '', $eventKey); $userId = str_replace($subscribeScene, '', $eventKey);
$this->handleLoginPageSubscribe($ticket, $openId);
} else {
$connect = $connectRepo->findByOpenIdShallow($openId, ConnectModel::PROVIDER_WECHAT_OA);
if ($connect) $userId = $connect->user_id;
} }
/** if ($userId > 0) {
* 未关注过服务号,在用户中心扫关注场景码,关注服务号
*/ $userRepo = new UserRepo();
if (Text::startsWith($eventKey, $subscribeScene)) {
$userId = str_replace($subscribeScene, '', $eventKey); $user = $userRepo->findById($userId);
$this->handleAccountPageSubscribe($userId, $openId);
if (!$user) return null;
$userInfo = $this->getUserInfo($openId);
$unionId = $userInfo['unionid'] ?: '';
$connect = new ConnectModel();
$connect->user_id = $userId;
$connect->open_id = $openId;
$connect->union_id = $unionId;
$connect->provider = ConnectModel::PROVIDER_WECHAT_OA;
$connect->create();
} }
return new TextMessage('开心呀,我们又多了一个小伙伴!'); return new TextMessage('开心呀,我们又多了一个小伙伴!');
@ -211,19 +228,19 @@ class OfficialAccount extends AppService
$eventKey = $message['EventKey'] ?? ''; $eventKey = $message['EventKey'] ?? '';
if (Text::startsWith($eventKey, self::QR_SCENE_LOGIN)) { if (Text::startsWith($eventKey, self::QR_SCENE_LOGIN)) {
$ticket = str_replace(self::QR_SCENE_LOGIN, '', $eventKey); return $this->handleLoginScanEvent($eventKey, $openId);
$this->handleLoginPageSubscribe($ticket, $openId);
} elseif (Text::startsWith($eventKey, self::QR_SCENE_SUBSCRIBE)) { } elseif (Text::startsWith($eventKey, self::QR_SCENE_SUBSCRIBE)) {
$userId = str_replace(self::QR_SCENE_SUBSCRIBE, '', $eventKey); return $this->handleSubscribeScanEvent($eventKey, $openId);
$this->handleAccountPageSubscribe($userId, $openId);
} }
return $this->emptyReply(); return $this->emptyReply();
} }
protected function handleLoginPageSubscribe($ticket, $openId) protected function handleLoginScanEvent($eventKey, $openId)
{ {
if (empty($ticket) || empty($openId)) return; $ticket = str_replace(self::QR_SCENE_LOGIN, '', $eventKey);
if (empty($ticket) || empty($openId)) return null;
$connectRepo = new ConnectRepo(); $connectRepo = new ConnectRepo();
@ -239,27 +256,31 @@ class OfficialAccount extends AppService
]; ];
$cache->save($keyName, $content, 30 * 60); $cache->save($keyName, $content, 30 * 60);
return $this->emptyReply();
} }
protected function handleAccountPageSubscribe($userId, $openId) protected function handleSubscribeScanEvent($eventKey, $openId)
{ {
if (empty($userId) || empty($openId)) return; $userId = str_replace(self::QR_SCENE_SUBSCRIBE, '', $eventKey);
if (empty($userId) || empty($openId)) return null;
$userRepo = new UserRepo(); $userRepo = new UserRepo();
$user = $userRepo->findById($userId); $user = $userRepo->findById($userId);
if (!$user) return; if (!$user) return null;
$userInfo = $this->getUserInfo($openId);
$unionId = $userInfo['unionid'] ?: '';
$connectRepo = new ConnectRepo(); $connectRepo = new ConnectRepo();
$connect = $connectRepo->findByOpenId($openId, ConnectModel::PROVIDER_WECHAT_OA); $connect = $connectRepo->findByOpenId($openId, ConnectModel::PROVIDER_WECHAT_OA);
if ($connect) return; if ($connect) return null;
$userInfo = $this->getUserInfo($openId);
$unionId = $userInfo['unionid'] ?: '';
$connect = new ConnectModel(); $connect = new ConnectModel();
@ -269,6 +290,8 @@ class OfficialAccount extends AppService
$connect->provider = ConnectModel::PROVIDER_WECHAT_OA; $connect->provider = ConnectModel::PROVIDER_WECHAT_OA;
$connect->create(); $connect->create();
return $this->emptyReply();
} }
protected function handleClickEvent($message) protected function handleClickEvent($message)

View File

@ -53,15 +53,15 @@ class Refund extends Service
$serviceFee = $this->getServiceFee($order); $serviceFee = $this->getServiceFee($order);
$serviceRate = $this->getServiceRate($order); $serviceRate = $this->getServiceRate($order);
$refundRate = 0.00; $refundPercent = 0.00;
$refundAmount = 0.00; $refundAmount = 0.00;
if ($itemInfo['course']['refund_expiry_time'] > time()) { if ($itemInfo['course']['refund_expiry_time'] > time()) {
$refundRate = $this->getCourseRefundRate($order->item_id, $order->owner_id); $refundPercent = $this->getCourseRefundPercent($order->item_id, $order->owner_id);
$refundAmount = round(($order->amount - $serviceFee) * $refundRate, 2); $refundAmount = round(($order->amount - $serviceFee) * $refundPercent, 2);
} }
$itemInfo['course']['refund_rate'] = $refundRate; $itemInfo['course']['refund_percent'] = $refundPercent;
$itemInfo['course']['refund_amount'] = $refundAmount; $itemInfo['course']['refund_amount'] = $refundAmount;
return [ return [
@ -95,17 +95,17 @@ class Refund extends Service
$course['cover'] = kg_cos_course_cover_url($course['cover']); $course['cover'] = kg_cos_course_cover_url($course['cover']);
$refundRate = 0.00; $refundPercent = 0.00;
$refundAmount = 0.00; $refundAmount = 0.00;
if ($course['refund_expiry_time'] > time()) { if ($course['refund_expiry_time'] > time()) {
$priceRate = round($course['market_price'] / $totalMarketPrice, 4); $pricePercent = round($course['market_price'] / $totalMarketPrice, 4);
$refundRate = $this->getCourseRefundRate($course['id'], $order->owner_id); $refundPercent = $this->getCourseRefundPercent($course['id'], $order->owner_id);
$refundAmount = round(($order->amount - $serviceFee) * $priceRate * $refundRate, 2); $refundAmount = round(($order->amount - $serviceFee) * $pricePercent * $refundPercent, 2);
$totalRefundAmount += $refundAmount; $totalRefundAmount += $refundAmount;
} }
$course['refund_rate'] = $refundRate; $course['refund_percent'] = $refundPercent;
$course['refund_amount'] = $refundAmount; $course['refund_amount'] = $refundAmount;
} }
@ -176,7 +176,7 @@ class Refund extends Service
return $serviceRate; return $serviceRate;
} }
protected function getCourseRefundRate($courseId, $userId) protected function getCourseRefundPercent($courseId, $userId)
{ {
$courseRepo = new CourseRepo(); $courseRepo = new CourseRepo();

View File

@ -10,7 +10,6 @@ namespace App\Services;
use Phalcon\Logger\Adapter\File as FileLogger; use Phalcon\Logger\Adapter\File as FileLogger;
use Qcloud\Cos\Client as CosClient; use Qcloud\Cos\Client as CosClient;
use TencentCloud\Common\Credential; use TencentCloud\Common\Credential;
use TencentCloud\Common\Exception\TencentCloudSDKException;
use TencentCloud\Common\Profile\ClientProfile; use TencentCloud\Common\Profile\ClientProfile;
use TencentCloud\Common\Profile\HttpProfile; use TencentCloud\Common\Profile\HttpProfile;
use TencentCloud\Sts\V20180813\Models\GetFederationTokenRequest; use TencentCloud\Sts\V20180813\Models\GetFederationTokenRequest;
@ -101,12 +100,11 @@ class Storage extends Service
$result = $client->GetFederationToken($request); $result = $client->GetFederationToken($request);
} catch (TencentCloudSDKException $e) { } catch (\Exception $e) {
$this->logger->error('Get Tmp Token Exception ' . kg_json_encode([ $this->logger->error('Get Tmp Token Exception' . kg_json_encode([
'code' => $e->getCode(), 'code' => $e->getCode(),
'message' => $e->getMessage(), 'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),
])); ]));
$result = false; $result = false;
@ -132,12 +130,11 @@ class Storage extends Service
$result = $response['Location'] ? $key : false; $result = $response['Location'] ? $key : false;
} catch (TencentCloudSDKException $e) { } catch (\Exception $e) {
$this->logger->error('Put String Exception ' . kg_json_encode([ $this->logger->error('Put String Exception ' . kg_json_encode([
'code' => $e->getCode(), 'code' => $e->getCode(),
'message' => $e->getMessage(), 'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),
])); ]));
$result = false; $result = false;
@ -165,12 +162,11 @@ class Storage extends Service
$result = $response['Location'] ? $key : false; $result = $response['Location'] ? $key : false;
} catch (TencentCloudSDKException $e) { } catch (\Exception $e) {
$this->logger->error('Put File Exception ' . kg_json_encode([ $this->logger->error('Put File Exception ' . kg_json_encode([
'code' => $e->getCode(), 'code' => $e->getCode(),
'message' => $e->getMessage(), 'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),
])); ]));
$result = false; $result = false;
@ -198,12 +194,11 @@ class Storage extends Service
$result = $response['Location'] ? $key : false; $result = $response['Location'] ? $key : false;
} catch (TencentCloudSDKException $e) { } catch (\Exception $e) {
$this->logger->error('Delete Object Exception ' . kg_json_encode([ $this->logger->error('Delete Object Exception ' . kg_json_encode([
'code' => $e->getCode(), 'code' => $e->getCode(),
'message' => $e->getMessage(), 'message' => $e->getMessage(),
'requestId' => $e->getRequestId(),
])); ]));
$result = false; $result = false;

65
app/Services/Throttle.php Normal file
View File

@ -0,0 +1,65 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Services;
class Throttle extends Service
{
public function checkRateLimit()
{
$config = $this->getConfig();
if (!$config->path('throttle.enabled')) {
return true;
}
$cache = $this->getCache();
$sign = $this->getRequestSignature();
$cacheKey = $this->getCacheKey($sign);
if ($cache->ttl($cacheKey) < 1) {
$cache->save($cacheKey, 0, $config->path('throttle.lifetime'));
}
$rateLimit = $cache->get($cacheKey);
if ($rateLimit >= $config->path('throttle.rate_limit')) {
return false;
}
$cache->increment($cacheKey, 1);
return true;
}
protected function getRequestSignature()
{
$authUser = $this->getAuthUser();
if (!empty($authUser['id'])) {
return md5($authUser['id']);
}
$httpHost = $this->request->getHttpHost();
$clientAddress = $this->request->getClientAddress();
if ($httpHost && $clientAddress) {
return md5($httpHost . '|' . $clientAddress);
}
throw new \RuntimeException('Unable to generate request signature');
}
protected function getCacheKey($sign)
{
return "throttle:{$sign}";
}
}

View File

@ -28,6 +28,13 @@ trait Security
$validator->checkHttpReferer(); $validator->checkHttpReferer();
} }
public function checkRateLimit()
{
$validator = new SecurityValidator();
$validator->checkRateLimit();
}
public function isNotSafeRequest() public function isNotSafeRequest()
{ {
/** /**

View File

@ -129,29 +129,6 @@ class Account extends Validator
} }
} }
public function checkRegisterStatus($account)
{
$local = $this->getSettings('oauth.local');
$allowPhone = $local['register_with_phone'] ?? false;
$allowEmail = $local['register_with_email'] ?? false;
$isEmail = CommonValidator::email($account);
$isPhone = CommonValidator::Phone($account);
if (!$allowPhone && !$allowEmail) {
throw new BadRequestException('account.register_disabled');
}
if ($isPhone && !$allowPhone) {
throw new BadRequestException('account.register_with_phone_disabled');
}
if ($isEmail && !$allowEmail) {
throw new BadRequestException('account.register_with_email_disabled');
}
}
public function checkVerifyLogin($name, $code) public function checkVerifyLogin($name, $code)
{ {
$this->checkLoginName($name); $this->checkLoginName($name);

View File

@ -11,7 +11,10 @@ use App\Exceptions\BadRequest as BadRequestException;
use App\Models\Order as OrderModel; use App\Models\Order as OrderModel;
use App\Models\Refund as RefundModel; use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel; use App\Models\Trade as TradeModel;
use App\Repos\Course as CourseRepo;
use App\Repos\Order as OrderRepo; use App\Repos\Order as OrderRepo;
use App\Repos\Package as PackageRepo;
use App\Repos\Vip as VipRepo;
class Order extends Validator class Order extends Validator
{ {
@ -47,36 +50,54 @@ class Order extends Validator
return $order; return $order;
} }
public function checkItemType($type) public function checkItemType($itemType)
{ {
$types = OrderModel::itemTypes(); $list = OrderModel::itemTypes();
if (!array_key_exists($type, $types)) { if (!array_key_exists($itemType, $list)) {
throw new BadRequestException('order.invalid_item_type'); throw new BadRequestException('order.invalid_item_type');
} }
return $type; return $itemType;
} }
public function checkCourse($id) public function checkCourse($itemId)
{ {
$validator = new Course(); $courseRepo = new CourseRepo();
return $validator->checkCourse($id); $course = $courseRepo->findById($itemId);
if (!$course || $course->published == 0) {
throw new BadRequestException('order.item_not_found');
}
return $course;
} }
public function checkPackage($id) public function checkPackage($itemId)
{ {
$validator = new Package(); $packageRepo = new PackageRepo();
return $validator->checkPackage($id); $package = $packageRepo->findById($itemId);
if (!$package || $package->published == 0) {
throw new BadRequestException('order.item_not_found');
}
return $package;
} }
public function checkVip($id) public function checkVip($itemId)
{ {
$validator = new Vip(); $vipRepo = new VipRepo();
return $validator->checkVip($id); $vip = $vipRepo->findById($itemId);
if (!$vip || $vip->deleted == 1) {
throw new BadRequestException('order.item_not_found');
}
return $vip;
} }
public function checkAmount($amount) public function checkAmount($amount)
@ -84,7 +105,7 @@ class Order extends Validator
$value = $this->filter->sanitize($amount, ['trim', 'float']); $value = $this->filter->sanitize($amount, ['trim', 'float']);
if ($value < 0.01 || $value > 100000) { if ($value < 0.01 || $value > 100000) {
throw new BadRequestException('order.invalid_amount'); throw new BadRequestException('order.invalid_pay_amount');
} }
return $value; return $value;
@ -127,7 +148,7 @@ class Order extends Validator
]; ];
if (!in_array($order->item_type, $types)) { if (!in_array($order->item_type, $types)) {
throw new BadRequestException('order.refund_not_supported'); throw new BadRequestException('order.refund_item_unsupported');
} }
$orderRepo = new OrderRepo(); $orderRepo = new OrderRepo();
@ -146,7 +167,7 @@ class Order extends Validator
]; ];
if ($refund && in_array($refund->status, $scopes)) { if ($refund && in_array($refund->status, $scopes)) {
throw new BadRequestException('order.refund_request_existed'); throw new BadRequestException('order.refund_apply_existed');
} }
} }

View File

@ -8,7 +8,9 @@
namespace App\Validators; namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException; use App\Exceptions\BadRequest as BadRequestException;
use App\Exceptions\ServiceUnavailable as ServiceUnavailableException;
use App\Library\CsrfToken as CsrfTokenService; use App\Library\CsrfToken as CsrfTokenService;
use App\Services\Throttle as ThrottleService;
class Security extends Validator class Security extends Validator
{ {
@ -51,6 +53,17 @@ class Security extends Validator
} }
} }
public function checkRateLimit()
{
$service = new ThrottleService();
$result = $service->checkRateLimit();
if (!$result) {
throw new ServiceUnavailableException('security.too_many_requests');
}
}
protected function getCsrfWhitelist() protected function getCsrfWhitelist()
{ {
return []; return [];

View File

@ -91,7 +91,7 @@ class Trade extends Validator
]; ];
if ($refund && in_array($refund->status, $scopes)) { if ($refund && in_array($refund->status, $scopes)) {
throw new BadRequestException('trade.refund_request_existed'); throw new BadRequestException('trade.refund_apply_existed');
} }
} }

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