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

Merge pull request #9 from xiaochong0302/develop

v1.2.0阶段性合并
This commit is contained in:
jacky huang 2020-11-30 12:28:58 +08:00 committed by GitHub
commit 0f4bbad4eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 135 additions and 17834 deletions

View File

@ -1,5 +0,0 @@
使用协议
1. 本系统属于强业务类型,非通用类库框架,不适合再次衍生发布。
2. 在保留我们版权标识的前提下,用户可以修改以满足自己的需求,可以用于商业用途。
3. 有限社区支持,用户对自己的行为负责。

View File

@ -1,24 +1,45 @@
## 酷瓜云课堂
![酷瓜云网课GPL协议开源](https://images.gitee.com/uploads/images/2020/1127/092621_3805cf8f_23592.png)
#### 项目介绍
酷瓜云课堂,依托腾讯云基础服务架构,采用 C 扩展框架 Phalcon 开发,致力网络教育软件。
酷瓜云课堂依托腾讯云基础服务架构采用C扩展框架Phalcon开发GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。
![](https://img.shields.io/static/v1?label=release&message=1.2.0&color=blue)
![](https://img.shields.io/static/v1?label=stars&message=101&color=blue)
![](https://img.shields.io/static/v1?label=forks&message=40&color=blue)
![](https://img.shields.io/static/v1?label=license&message=GPL-2.0&color=blue)
#### 系统功能
实现了点播、直播、专栏、会员、微聊等,是一个完整的产品,具体功能我也不想写一大堆,自己体验吧!
- [前台演示](https://ctc.koogua.com)
- [后台演示](https://ctc.koogua.com/admin)
帐号100015@163.com / 123456 (前后台通用)
友情提示:
- 系统配置低1核 1G 1M 跑多个容器),切莫压测
- 课程数据来源于网络(无实质内容),切莫购买
- 管理后台已禁止数据提交,私密配置已过滤
演示帐号:**13507083515 / 123456** (前后台通用)
桌面端演示:
- [前台演示](https://ctc.koogua.com)
- [后台演示](https://ctc.koogua.com/admin)
移动端演示:
![移动端二维码](https://images.gitee.com/uploads/images/2020/1127/093203_265221a2_23592.png)
支付流程演示:
- [MySQL提升课程全面讲解MySQL架构设计0.01元)](https://ctc.koogua.com/order/confirm?item_id=1390&item_type=1)
- [Nginx入门到实践Nginx中间件0.01元)](https://ctc.koogua.com/order/confirm?item_id=1286&item_type=1)
- [数据库与中间件的基础必修课0.02元)](https://ctc.koogua.com/order/confirm?item_id=80&item_type=2)
Tips: 测试支付请用手机号注册一个新账户,以便接收订单通知,以及避免课程无法购买
#### 项目组件
- 后台框架:[phalcon 3.4.5](https://phalcon.io)
@ -27,14 +48,6 @@
- 即时通讯:[workerman 3.5.22](https://workerman.net)
- 基础依赖:[php7.3](https://php.net) [mysql5.7](https://mysql.com) [redis5.0](https://redis.io)
#### 使用协议
虽然尝试了解过开源协议,但是理解的模棱两可,干脆用自己的协议吧。
1. 本系统属于强业务类型,非通用类库框架,不适合再次衍生发布。
2. 在保留我们版权标识的前提下,用户可以修改以满足自己的需求,可以用于商业用途。
3. 有限社区支持,用户对自己的行为负责。
#### 安装指南
- [运行环境搭建](https://gitee.com/koogua/course-tencent-cloud-docker)
@ -43,20 +56,13 @@
#### 开发计划
- 桌面端:进行中
- 移动端:待启动
- 移动端:进行中
- 小程序:待启动
#### 意见反馈
- [在线反馈](https://gitee.com/koogua/course-tencent-cloud/issues)(推荐)
- QQ邮箱: 76632555@qq.com
- QQ群组: 787363898
#### 加入我们
这是一个创业项目,个人能力和精力有限,要兼顾产品规划以及开发,还要处理很多琐碎事情。目前在南山科技园某个众创空间,希望有 **深圳前端同学** 加入我们。
联系邮箱76632555@qq.com
- QQ交流群: 787363898
#### 通过这个项目能学到什么?

View File

@ -55,6 +55,10 @@
<td>cover_270</td>
<td>mageMogr2/thumbnail/270x/interlace/0</td>
</tr>
<tr>
<td>content_800</td>
<td>mageMogr2/thumbnail/800x/interlace/0</td>
</tr>
<tr>
<td>slide_1100</td>
<td>imageMogr2/thumbnail/1100x/interlace/0</td>

View File

@ -47,6 +47,10 @@ class ChapterController extends Controller
$chapter = $service->handle($id);
if ($chapter['me']['owned'] == 0) {
return $this->jsonError(['msg' => '没有访问章节权限']);
}
return $this->jsonSuccess(['chapter' => $chapter]);
}

View File

@ -50,10 +50,6 @@ class Account extends Service
$user = $validator->checkUserLogin($post['account'], $post['password']);
//$validator = new CaptchaValidator();
//$validator->checkCode($post['ticket'], $post['rand']);
return $this->auth->saveAuthInfo($user);
}

View File

@ -44,8 +44,6 @@ class IndexController extends Controller
{
$service = new IndexService();
dd($service->getLives());
$this->view->pick('index/full');
$this->view->setVar('lives', $service->getLives());
$this->view->setVar('slides', $service->getSlides());

View File

@ -10,7 +10,6 @@ use App\Services\Pay\Wxpay as WxpayService;
use App\Services\Storage as StorageService;
use App\Traits\Response as ResponseTrait;
use App\Traits\Security as SecurityTrait;
use Phalcon\Text;
use PHPQRCode\QRcode;
class PublicController extends \Phalcon\Mvc\Controller
@ -44,31 +43,6 @@ class PublicController extends \Phalcon\Mvc\Controller
}
}
/**
* @Get("/img/{id:[0-9]+}", name="home.img")
*/
public function imageAction($id)
{
$repo = new UploadRepo();
$file = $repo->findById($id);
if ($file && Text::startsWith($file->mime, 'image')) {
$service = new StorageService();
$location = $service->getImageUrl($file->path);
$this->response->redirect($location);
} else {
$this->response->setStatusCode(404);
return $this->response;
}
}
/**
* @Get("/qrcode", name="home.qrcode")
*/

View File

@ -1,9 +1,9 @@
<?php
namespace App\Http\Home\Controllers;
use App\Services\MyStorage as StorageService;
use App\Validators\Validator as AppValidator;
/**
* @RoutePrefix("/upload")
@ -11,6 +11,15 @@ use App\Services\MyStorage as StorageService;
class UploadController extends Controller
{
public function initialize()
{
$authUser = $this->getAuthUser();
$validator = new AppValidator();
$validator->checkAuthUser($authUser->id);
}
/**
* @Post("/avatar/img", name="home.upload.avatar_img")
*/

View File

@ -7,11 +7,10 @@
<ul class="sidebar-lesson-list">
{% for lesson in item.children %}
{% set url = url({'for':'home.chapter.show','id':lesson.id}) %}
{% set free_flag = lesson.free == 1 ? '<span class="layui-badge">免费</span>' : '' %}
{% set active = (chapter.id == lesson.id) ? 'active' : 'normal' %}
<li class="lesson-title layui-elip">
{% if lesson.me.owned == 1 %}
<a class="{{ active }}" href="{{ url }}" title="{{ lesson.title }}">{{ lesson.title }} {{ free_flag }}</a>
<a class="{{ active }}" href="{{ url }}" title="{{ lesson.title }}">{{ lesson.title }}</a>
{% else %}
<span class="deny" title="{{ lesson.title }}">{{ lesson.title }}</span>
{% endif %}

View File

@ -16,7 +16,6 @@
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="{{ course_url }}"><i class="layui-icon layui-icon-return"></i> 返回课程</a>
<a><cite>{{ chapter.course.title }}</cite></a>
<a><cite>{{ chapter.title }}</cite></a>
</span>
<span class="share">

View File

@ -7,7 +7,6 @@
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="{{ course_url }}"><i class="layui-icon layui-icon-return"></i> 返回课程</a>
<a><cite>{{ chapter.course.title }}</cite></a>
<a><cite>{{ chapter.title }}</cite></a>
</span>
</div>

View File

@ -8,7 +8,6 @@
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="{{ course_url }}"><i class="layui-icon layui-icon-return"></i> 返回课程</a>
<a><cite>{{ chapter.course.title }}</cite></a>
<a><cite>{{ chapter.title }}</cite></a>
</span>
</div>

View File

@ -13,7 +13,6 @@
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="{{ course_url }}"><i class="layui-icon layui-icon-return"></i> 返回课程</a>
<a><cite>{{ chapter.course.title }}</cite></a>
<a><cite>{{ chapter.title }}</cite></a>
</span>
<span class="share">

View File

@ -14,7 +14,6 @@
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="{{ course_url }}"><i class="layui-icon layui-icon-return"></i> 返回课程</a>
<a><cite>{{ chapter.course.title }}</cite></a>
<a><cite>{{ chapter.title }}</cite></a>
</span>
<span class="share">

View File

@ -8,7 +8,7 @@
{% set teacher.title = teacher.title ? teacher.title : '小小教书匠' %}
<div class="sidebar-teacher-card clearfix">
<div class="avatar">
<img src="{{ teacher.avatar }}" alt="{{ teacher.name }}">
<img src="{{ teacher.avatar }}!avatar_160" alt="{{ teacher.name }}">
</div>
<div class="info">
<div class="name layui-elip">

View File

@ -25,7 +25,7 @@
<div class="user-profile wrap clearfix">
{{ vip_info(user.vip) }}
<div class="avatar">
<img src="{{ user.avatar }}" alt="{{ user.name }}">
<img src="{{ user.avatar }}!avatar_160" alt="{{ user.name }}">
</div>
<div class="info">
<p><span class="name">{{ user.name }}</span><span>{{ gender_icon(user.gender) }}</span></p>

View File

@ -1,20 +0,0 @@
<?php
namespace App\Http\Mobile\Controllers;
use App\Traits\Response as ResponseTrait;
use App\Traits\Security as SecurityTrait;
use Phalcon\Mvc\Dispatcher;
class Controller extends \Phalcon\Mvc\Controller
{
use ResponseTrait;
use SecurityTrait;
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
$this->checkRateLimit();
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace App\Http\Mobile\Controllers;
/**
* @RoutePrefix("/mobile")
*/
class IndexController extends Controller
{
/**
* @Get("/", name="mobile.index")
*/
public function indexAction()
{
}
/**
* @Get("/routes", name="mobile.routes")
*/
public function routesAction()
{
$definitions = [];
$routes = $this->router->getRoutes();
foreach ($routes as $route) {
if (strpos($route->getPattern(), '/api') !== false) {
$definitions[] = [
'pattern' => $route->getPattern(),
'methods' => $route->getHttpMethods(),
];
}
}
return $this->jsonSuccess(['routes' => $definitions]);
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace App\Http\Mobile\Controllers;
use App\Traits\Response as ResponseTrait;
/**
* @RoutePrefix("/mobile")
*/
class PublicController extends \Phalcon\Mvc\Controller
{
use ResponseTrait;
}

View File

@ -1,31 +0,0 @@
<?php
namespace App\Http\Mobile;
use App\Library\Mvc\View as MyView;
use App\Services\Auth\Mobile as MobileAuth;
use Phalcon\DiInterface;
use Phalcon\Mvc\ModuleDefinitionInterface;
class Module implements ModuleDefinitionInterface
{
public function registerAutoLoaders(DiInterface $di = null)
{
}
public function registerServices(DiInterface $di)
{
$di->setShared('view', function () {
$view = new MyView();
$view->disable();
return $view;
});
$di->setShared('auth', function () {
return new MobileAuth();
});
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace App\Http\Mobile\Services;
use App\Validators\Account as AccountValidator;
class Login extends Service
{
public function loginByPassword($account, $password)
{
$validator = new AccountValidator();
$user = $validator->checkUserLogin($account, $password);
}
public function loginByVerify($account, $code)
{
$validator = new AccountValidator();
$user = $validator->checkVerifyLogin($account, $code);
}
protected function grantAuthToken()
{
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Http\Mobile\Services;
class Logout extends Service
{
public function logoutBySession()
{
}
public function logoutByToken()
{
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace App\Http\Mobile\Services;
class Service extends \App\Services\Service
{
}

View File

@ -246,6 +246,42 @@ function kg_cos_cover_url($path, $style = null)
return kg_cos_img_url($path, $style);
}
/**
* 获取幻灯片URL
*
* @param string $path
* @param string $style
* @return string
*/
function kg_cos_slide_url($path, $style = null)
{
return kg_cos_img_url($path, $style);
}
/**
* 清除存储图片处理样式
*
* @param $path
* @return string
*/
function kg_cos_img_style_trim($path)
{
return preg_replace('/!\w+/', '', $path);
}
/**
* 解析markdown内容
*
* @param $content
* @return string
*/
function kg_parse_markdown($content)
{
return preg_replace_callback('/\/img\/content\/(.*?)\)/', function ($matches) {
return '/img/content/' . trim($matches[1]) . '!content_800';
}, $content);
}
/**
* 隐藏部分字符
*

View File

@ -143,7 +143,7 @@ class Slide extends Model
public function afterFetch()
{
if (!Text::startsWith($this->cover, 'http')) {
$this->cover = kg_cos_cover_url($this->cover);
$this->cover = kg_cos_slide_url($this->cover);
}
}

View File

@ -67,7 +67,10 @@ class ChapterInfo extends Service
'liked' => 0,
];
if ($user->id) {
$me['joined'] = $this->joinedChapter ? 1 : 0;
$me['owned'] = $this->ownedChapter ? 1 : 0;
if ($user->id > 0) {
$likeRepo = new ChapterLikeRepo();
@ -84,9 +87,6 @@ class ChapterInfo extends Service
if ($this->chapterUser) {
$me['position'] = $this->chapterUser->position;
}
$me['joined'] = $this->joinedChapter ? 1 : 0;
$me['owned'] = $this->ownedChapter ? 1 : 0;
}
$result['me'] = $me;

View File

@ -117,6 +117,7 @@ class ConsultCreate extends Service
protected function getPriority(CourseModel $course, UserModel $user)
{
$charge = $course->market_price > 0;
$vip = $user->vip == 1;
if ($vip && $charge) {

View File

@ -21,10 +21,6 @@ class ConsultDelete extends Service
{
$consult = $this->checkConsult($id);
$course = $this->checkCourse($consult->course_id);
$chapter = $this->checkChapter($consult->chapter_id);
$user = $this->getLoginUser();
$validator = new ConsultValidator();
@ -33,10 +29,20 @@ class ConsultDelete extends Service
$consult->update(['deleted' => 1]);
if ($consult->course_id > 0) {
$course = $this->checkCourse($consult->course_id);
$this->decrCourseConsultCount($course);
}
if ($consult->chapter_id > 0) {
$chapter = $this->checkChapter($consult->chapter_id);
$this->decrChapterConsultCount($chapter);
}
}
protected function decrCourseConsultCount(CourseModel $course)
{

View File

@ -22,6 +22,8 @@ class BasicInfo extends Service
public function handleBasicInfo(CourseModel $course)
{
$course->details = kg_parse_markdown($course->details);
$teachers = $this->handleTeachers($course);
$ratings = $this->handleRatings($course);

View File

@ -20,10 +20,14 @@ class HelpInfo extends Service
protected function handleHelp(HelpModel $help)
{
$help->content = kg_parse_markdown($help->content);
return [
'id' => $help->id,
'title' => $help->title,
'content' => $help->content,
'create_time' => $help->create_time,
'update_time' => $help->update_time,
];
}

View File

@ -20,6 +20,8 @@ class PageInfo extends Service
protected function handlePage(PageModel $page)
{
$page->content = kg_parse_markdown($page->content);
return [
'id' => $page->id,
'title' => $page->title,

View File

@ -52,7 +52,7 @@ class ReviewCreate extends Service
$this->incrCourseReviewCount($course);
$this->updateCourseRating($course->id);
$this->updateCourseRating($course);
return $review;
}
@ -71,11 +71,11 @@ class ReviewCreate extends Service
$course->update();
}
public function updateCourseRating($courseId)
public function updateCourseRating(CourseModel $course)
{
$service = new CourseStatService();
$service->updateRating($courseId);
$service->updateRating($course->id);
}
}

View File

@ -31,7 +31,7 @@ class ReviewDelete extends Service
$this->decrCourseReviewCount($course);
$this->updateCourseRating($course->id);
$this->updateCourseRating($course);
}
protected function decrCourseReviewCount(CourseModel $course)
@ -42,11 +42,11 @@ class ReviewDelete extends Service
}
}
protected function updateCourseRating($courseId)
protected function updateCourseRating(CourseModel $course)
{
$service = new CourseStatService();
$service->updateRating($courseId);
$service->updateRating($course->id);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Services\Logic\Review;
use App\Models\Course as CourseModel;
use App\Services\CourseStat as CourseStatService;
use App\Services\Logic\CourseTrait;
use App\Services\Logic\ReviewTrait;
@ -39,14 +40,14 @@ class ReviewUpdate extends Service
$review->update($data);
$this->updateCourseRating($course->id);
$this->updateCourseRating($course);
}
protected function updateCourseRating($courseId)
protected function updateCourseRating(CourseModel $course)
{
$service = new CourseStatService();
$service->updateRating($courseId);
$service->updateRating($course->id);
}
}

View File

@ -102,6 +102,11 @@ class Wxpay extends PayService
'body' => $trade->subject,
]);
/**
* 微信H5支付会检查Referer构造Referer头信息
*/
$result->headers->set('Referer', kg_site_url());
} catch (\Exception $e) {
Log::error('Wxpay Wap Exception', [

View File

@ -46,7 +46,7 @@ trait Auth
$validator = new AppValidator();
$validator->checkAuthUser($authUser);
$validator->checkAuthUser($authUser['id']);
$userRepo = new UserRepo();

View File

@ -90,7 +90,7 @@ class Course extends Validator
throw new BadRequestException('course.invalid_cover');
}
return $value;
return kg_cos_img_style_trim($value);
}
public function checkTitle($title)

View File

@ -98,7 +98,7 @@ class ImGroup extends Validator
throw new BadRequestException('im_group.invalid_avatar');
}
return $value;
return kg_cos_img_style_trim($value);
}
public function checkType($type)

View File

@ -64,18 +64,7 @@ class Slide extends Validator
throw new BadRequestException('slide.invalid_cover');
}
return $value;
}
public function checkBgColor($bgColor)
{
$value = $this->filter->sanitize($bgColor, ['trim', 'string']);
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $bgColor)) {
throw new BadRequestException('slide.invalid_bg_color');
}
return $value;
return kg_cos_img_style_trim($value);
}
public function checkPlatform($platform)

View File

@ -137,7 +137,7 @@ class User extends Validator
throw new BadRequestException('user.invalid_avatar');
}
return $value;
return kg_cos_img_style_trim($value);
}
public function checkEduRole($value)

View File

@ -9,9 +9,9 @@ use Phalcon\Mvc\User\Component;
class Validator extends Component
{
public function checkAuthUser($authInfo)
public function checkAuthUser($userId)
{
if (empty($authInfo['id'])) {
if (empty($userId)) {
throw new UnauthorizedException('sys.unauthorized');
}
}

View File

@ -113,10 +113,6 @@ class HttpKernel extends Kernel
'className' => 'App\Http\Home\Module',
'path' => app_path('Http/Home/Module.php'),
],
'mobile' => [
'className' => 'App\Http\Mobile\Module',
'path' => app_path('Http/Mobile/Module.php'),
],
];
$this->app->registerModules($modules);

View File

@ -23,15 +23,6 @@ foreach ($webFiles as $file) {
}
}
$mobileFiles = scandir(app_path('Http/Mobile/Controllers'));
foreach ($mobileFiles as $file) {
if (strpos($file, 'Controller.php')) {
$className = str_replace('Controller.php', '', $file);
$router->addModuleResource('mobile', 'App\Http\Mobile\Controllers\\' . $className);
}
}
$apiFiles = scandir(app_path('Http/Api/Controllers'));
foreach ($apiFiles as $file) {

View File

@ -24,7 +24,7 @@ layui.use(['jquery'], function () {
max: 30000
},
upload: {
url: '/admin/upload/img/editor',
url: '/admin/upload/content/img',
max: 10 * 1024 * 1024,
accept: 'image/*',
headers: {

File diff suppressed because it is too large Load Diff