1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-22 11:41:27 +08:00
This commit is contained in:
xiaochong0302 2020-10-13 10:16:45 +08:00
commit 034a6803f0
104 changed files with 2767 additions and 15187 deletions

1
.gitignore vendored
View File

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

49
CHANGELOG.md Normal file
View File

@ -0,0 +1,49 @@
### [v1.1.0](https://gitee.com/koogua/course-tencent-cloud/releases/v1.1.0)(2020-10-08)
- 增加运营统计功能
- 增加课程资料功能
- 增加changelog
- 忽略schema
- 账户安全页面调整
- 简化部分路由
- 修复相关课程列表undefined问题
### [v1.0.0-beta1](https://gitee.com/koogua/course-tencent-cloud/releases/v1.0.0-beta1)(2020-09-26)
前台功能:
- 注册、登录、忘记密码
- 首页:轮播、新上课程、免费课程、会员课程
- 课程列表:多维度筛选,多维度排序
- 课程详情:章节,咨询,评价,相关课程,推荐课程,课程套餐
- 课时详情:点播,直播,图文
- 购买支付:课程,套餐,赞赏,会员
- 教师列表
- 群组列表
- 即时通讯
- 在线客服
- 全文检索:课程、群组、用户
- 个人主页:我的课程,我的收藏,我的好友,我的群组
- 会员中心:我的课程,我的收藏,我的咨询,我的评价,我的好友,我的群组,我的订单,我的退款,个人信息,账户安全
- 教学中心 :我的课程,我的直播,我的咨询
后台功能:
- 课程管理:课程列表,课程搜索,添加课程,编辑课程,删除课程,课程分类
- 套餐管理:套餐列表,添加套餐,编辑套餐,删除套餐
- 话题管理:话题列表,添加话题,编辑话题,删除话题
- 单页管理:单页列表,添加单页,编辑单页,删除单页
- 帮助管理:帮助列表,添加帮助,编辑帮助,删除帮助,帮助分类
- 学员管理:学员列表,搜索学员,添加学员,编辑学员,学习记录
- 咨询管理:咨询列表,搜索咨询,编辑咨询,删除咨询
- 评价管理:评价列表,搜索评价,编辑评价,删除评价
- 群组管理:群组列表,搜索群组,编辑群组,删除群组
- 轮播管理:轮播列表,编辑轮播,删除轮播
- 导航管理:导航列表,编辑导航,删除导航
- 订单管理:订单列表,搜索订单,订单详情
- 交易管理:交易列表,搜索交易,交易详情
- 退款管理:退款列表,搜索退款,退款详情,退款审核
- 用户管理:用户列表,编辑用户,添加用户
- 角色管理:角色列表,编辑角色,删除角色
- 操作记录:记录列表,搜索记录,记录详情
- 系统配置:网站,密钥,存储,点播,直播,短信,邮件,验证码,支付,会员,微聊

View File

@ -0,0 +1,40 @@
<?php
namespace App\Builders;
use App\Repos\Upload as UploadRepo;
class ResourceList extends Builder
{
public function handleUploads($relations)
{
$uploads = $this->getUploads($relations);
foreach ($relations as $key => $value) {
$relations[$key]['upload'] = $uploads[$value['upload_id']] ?? new \stdClass();
}
return $relations;
}
public function getUploads($relations)
{
$ids = kg_array_column($relations, 'upload_id');
$uploadRepo = new UploadRepo();
$columns = ['id', 'name', 'path', 'mime', 'md5', 'size'];
$uploads = $uploadRepo->findByIds($ids, $columns);
$result = [];
foreach ($uploads->toArray() as $upload) {
$result[$upload['id']] = $upload;
}
return $result;
}
}

View File

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

View File

@ -2,8 +2,7 @@
namespace App\Caches;
use App\Models\Order as OrderModel;
use App\Models\User as UserModel;
use App\Repos\Stat as StatRepo;
class SiteTodayStat extends Cache
{
@ -22,42 +21,21 @@ class SiteTodayStat extends Cache
public function getContent($id = null)
{
$statRepo = new StatRepo();
$date = date('Y-m-d');
$saleCount = $statRepo->countDailySales($date);
$saleAmount = $statRepo->sumDailySales($date);
$refundAmount = $statRepo->sumDailyRefunds($date);
$registerCount = $statRepo->countDailyRegisteredUser($date);
return [
'user_count' => $this->countUsers(),
'order_count' => $this->countOrders(),
'sale_amount' => $this->sumSales(),
'sale_count' => $saleCount,
'sale_amount' => $saleAmount,
'refund_amount' => $refundAmount,
'register_count' => $registerCount,
];
}
protected function countUsers()
{
return (int)UserModel::count([
'conditions' => 'create_time > :time:',
'bind' => ['time' => strtotime('today')],
]);
}
protected function countOrders()
{
return (int)OrderModel::count([
'conditions' => 'create_time > :time: AND status = :status:',
'bind' => [
'time' => strtotime('today'),
'status' => OrderModel::STATUS_FINISHED,
],
]);
}
protected function sumSales()
{
return (float)OrderModel::sum([
'column' => 'amount',
'conditions' => 'create_time > :time: AND status = :status:',
'bind' => [
'time' => strtotime('today'),
'status' => OrderModel::STATUS_FINISHED,
],
]);
}
}

View File

@ -7,6 +7,7 @@ use App\Http\Admin\Services\ChapterContent as ChapterContentService;
use App\Http\Admin\Services\Course as CourseService;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Models\Course as CourseModel;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/admin/chapter")
@ -14,6 +15,19 @@ use App\Models\Course as CourseModel;
class ChapterController extends Controller
{
/**
* @Get("/{id:[0-9]+}/resources", name="admin.chapter.resources")
*/
public function resourcesAction($id)
{
$chapterService = new ChapterService();
$resources = $chapterService->getResources($id);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->setVar('resources', $resources);
}
/**
* @Get("/{id:[0-9]+}/lessons", name="admin.chapter.lessons")
*/
@ -96,6 +110,12 @@ class ChapterController extends Controller
$this->view->pick('chapter/edit_lesson');
$resources = $chapterService->getResources($chapter->id);
$cos = $chapterService->getSettings('cos');
$this->view->setVar('cos', $cos);
switch ($course->model) {
case CourseModel::MODEL_VOD:
$vod = $contentService->getChapterVod($chapter->id);

View File

@ -49,7 +49,7 @@ class Controller extends \Phalcon\Mvc\Controller
* 特例白名单
*/
$whitelist = [
'controllers' => ['public', 'index', 'vod', 'test', 'xm_course'],
'controllers' => ['public', 'index', 'vod', 'upload', 'test', 'xm_course'],
'routes' => ['admin.package.guiding'],
];

View File

@ -0,0 +1,49 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Resource as ResourceService;
/**
* @RoutePrefix("/admin/resource")
*/
class ResourceController extends Controller
{
/**
* @Post("/create", name="admin.resource.create")
*/
public function createAction()
{
$resourceService = new ResourceService();
$resourceService->createResource();
return $this->jsonSuccess(['msg' => '上传资源成功']);
}
/**
* @Post("/{id:[0-9]+}/update", name="admin.resource.update")
*/
public function updateAction($id)
{
$resourceService = new ResourceService();
$resourceService->updateResource($id);
return $this->jsonSuccess(['msg' => '更新资源成功']);
}
/**
* @Post("/{id:[0-9]+}/delete", name="admin.resource.delete")
*/
public function deleteAction($id)
{
$resourceService = new ResourceService();
$resourceService->deleteResource($id);
return $this->jsonSuccess(['msg' => '删除资源成功']);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Stat as StatService;
/**
* @RoutePrefix("/admin/stat")
*/
class StatController extends Controller
{
/**
* @Get("/sales/hot", name="admin.stat.hot_sales")
*/
public function hotSalesAction()
{
$statService = new StatService();
$years = $statService->getYearOptions();
$months = $statService->getMonthOptions();
$items = $statService->hotSales();
$this->view->pick('stat/hot_sales');
$this->view->setVar('years', $years);
$this->view->setVar('months', $months);
$this->view->setVar('items', $items);
}
/**
* @Get("/sales", name="admin.stat.sales")
*/
public function salesAction()
{
$statService = new StatService();
$years = $statService->getYearOptions();
$months = $statService->getMonthOptions();
$data = $statService->sales();
$this->view->pick('stat/sales');
$this->view->setVar('years', $years);
$this->view->setVar('months', $months);
$this->view->setVar('data', $data);
}
/**
* @Get("/refunds", name="admin.stat.refunds")
*/
public function refundsAction()
{
$statService = new StatService();
$years = $statService->getYearOptions();
$months = $statService->getMonthOptions();
$data = $statService->refunds();
$this->view->pick('stat/refunds');
$this->view->setVar('years', $years);
$this->view->setVar('months', $months);
$this->view->setVar('data', $data);
}
/**
* @Get("/users/registered", name="admin.stat.reg_users")
*/
public function registeredUsersAction()
{
$statService = new StatService();
$years = $statService->getYearOptions();
$months = $statService->getMonthOptions();
$data = $statService->registeredUsers();
$this->view->pick('stat/registered_users');
$this->view->setVar('years', $years);
$this->view->setVar('months', $months);
$this->view->setVar('data', $data);
}
/**
* @Get("/users/online", name="admin.stat.online_users")
*/
public function onlineUsersAction()
{
$statService = new StatService();
$years = $statService->getYearOptions();
$months = $statService->getMonthOptions();
$data = $statService->onlineUsers();
$this->view->pick('stat/online_users');
$this->view->setVar('years', $years);
$this->view->setVar('months', $months);
$this->view->setVar('data', $data);
}
}

View File

@ -73,4 +73,22 @@ class UploadController extends Controller
}
}
/**
* @Get("/sign", name="admin.upload.sign")
*/
public function signatureAction()
{
$service = new StorageService();
$token = $service->getFederationToken();
$data = [
'credentials' => $token->getCredentials(),
'expiredTime' => $token->getExpiredTime(),
'startTime' => time(),
];
return $this->jsonSuccess($data);
}
}

View File

@ -84,7 +84,7 @@ class UserController extends Controller
$roles = $userService->getRoles();
if ($user->admin_role == RoleModel::ROLE_ROOT) {
$this->response->redirect(['action' => 'list']);
$this->response->redirect(['for' => 'admin.user.list']);
}
$this->view->setVar('user', $user);

View File

@ -11,7 +11,7 @@ class VodController extends Controller
{
/**
* @Post("/upload/sign", name="admin.vod.upload_sign")
* @Get("/upload/sign", name="admin.vod.upload_sign")
*/
public function uploadSignatureAction()
{

View File

@ -438,6 +438,43 @@ class AuthNode extends Service
],
],
],
[
'id' => '2-7',
'title' => '数据统计',
'type' => 'menu',
'children' => [
[
'id' => '2-7-1',
'title' => '热卖商品',
'type' => 'menu',
'route' => 'admin.stat.hot_sales',
],
[
'id' => '2-7-2',
'title' => '成交订单',
'type' => 'menu',
'route' => 'admin.stat.sales',
],
[
'id' => '2-7-3',
'title' => '售后退款',
'type' => 'menu',
'route' => 'admin.stat.refunds',
],
[
'id' => '2-7-4',
'title' => '注册用户',
'type' => 'menu',
'route' => 'admin.stat.reg_users',
],
[
'id' => '2-7-5',
'title' => '活跃用户',
'type' => 'menu',
'route' => 'admin.stat.online_users',
],
],
],
],
];
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Admin\Services;
use App\Builders\ResourceList as ResourceListBuilder;
use App\Caches\Chapter as ChapterCache;
use App\Caches\CourseChapterList as CatalogCache;
use App\Models\Chapter as ChapterModel;
@ -11,12 +12,32 @@ use App\Models\ChapterVod as ChapterVodModel;
use App\Models\Course as CourseModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\Resource as ResourceRepo;
use App\Services\CourseStat as CourseStatService;
use App\Validators\Chapter as ChapterValidator;
class Chapter extends Service
{
public function getResources($id)
{
$resourceRepo = new ResourceRepo();
$resources = $resourceRepo->findByChapterId($id);
if ($resources->count() == 0) {
return [];
}
$builder = new ResourceListBuilder();
$items = $resources->toArray();
$items = $builder->handleUploads($items);
return $builder->objects($items);
}
public function getLessons($parentId)
{
$deleted = $this->request->getQuery('deleted', 'int', 0);

View File

@ -0,0 +1,119 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Resource as ResourceModel;
use App\Models\Upload as UploadModel;
use App\Repos\Upload as UploadRepo;
use App\Services\Storage as StorageService;
use App\Validators\Chapter as ChapterValidator;
use App\Validators\Resource as ResourceValidator;
use App\Validators\Upload as UploadValidator;
class Resource extends Service
{
public function createResource()
{
$post = $this->request->getPost();
$validator = new ChapterValidator();
$chapter = $validator->checkChapter($post['chapter_id']);
$course = $validator->checkCourse($chapter->course_id);
$uploadRepo = new UploadRepo();
$upload = $uploadRepo->findByMd5($post['upload']['md5']);
if (!$upload) {
$upload = new UploadModel();
$upload->type = UploadModel::TYPE_RESOURCE;
$upload->name = $post['upload']['name'];
$upload->size = $post['upload']['size'];
$upload->path = $post['upload']['path'];
$upload->md5 = $post['upload']['md5'];
$upload->mime = $post['upload']['mime'];
$upload->create();
}
$resource = new ResourceModel();
$resource->course_id = $course->id;
$resource->chapter_id = $chapter->id;
$resource->upload_id = $upload->id;
$resource->create();
$chapter->resource_count += 1;
$chapter->update();
$course->resource_count += 1;
$course->update();
return $upload;
}
public function updateResource($id)
{
$post = $this->request->getPost();
$resource = $this->findOrFail($id);
$validator = new UploadValidator();
$upload = $validator->checkUpload($resource->upload_id);
$data = [];
if (isset($post['name'])) {
$data['name'] = $validator->checkName($post['name']);
}
$upload->update($data);
$resource->update();
}
public function deleteResource($id)
{
$resource = $this->findOrFail($id);
$validator = new ResourceValidator();
$course = $validator->checkCourse($resource->course_id);
$chapter = $validator->checkChapter($resource->chapter_id);
$validator = new UploadValidator();
$upload = $validator->checkUpload($resource->upload_id);
$storageService = new StorageService();
$storageService->deleteObject($upload->path);
$resource->delete();
if ($course->resource_count > 1) {
$course->resource_count -= 1;
$course->update();
}
if ($chapter->resource_count > 1) {
$chapter->resource_count -= 1;
$chapter->update();
}
}
protected function findOrFail($id)
{
$validator = new ResourceValidator();
return $validator->checkResource($id);
}
}

View File

@ -148,6 +148,12 @@ class Role extends Service
$list[] = 'admin.chapter.content';
}
if (array_intersect(['admin.chapter.add', 'admin.chapter.edit'], $routes)) {
$list[] = 'admin.resource.create';
$list[] = 'admin.resource.update';
$list[] = 'admin.resource.delete';
}
if (in_array('admin.course.delete', $routes)) {
$list[] = 'admin.chapter.delete';
$list[] = 'admin.chapter.restore';

View File

@ -0,0 +1,405 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Order as OrderModel;
use App\Repos\Stat as StatRepo;
class Stat extends Service
{
public function hotSales()
{
$type = $this->request->getQuery('type', 'int', OrderModel::ITEM_COURSE);
$year = $this->request->getQuery('year', 'int', date('Y'));
$month = $this->request->getQuery('month', 'int', date('m'));
$prev = $this->getPrevMonth($year, $month);
return [
[
'title' => "{$year}-{$month}",
'sales' => $this->handleHotSales($type, $year, $month),
],
[
'title' => "{$prev['year']}-{$prev['month']}",
'sales' => $this->handleHotSales($type, $prev['year'], $prev['month']),
],
];
}
public function sales()
{
$year = $this->request->getQuery('year', 'int', date('Y'));
$month = $this->request->getQuery('month', 'int', date('m'));
$prev = $this->getPrevMonth($year, $month);
$currSales = $this->handleSales($year, $month);
$prevSales = $this->handleSales($prev['year'], $prev['month']);
$items = [];
foreach (range(1, 31) as $day) {
$date = sprintf('%02d', $day);
$prevMonth = "{$prev['year']}-{$prev['month']}";
$currMonth = "{$year}-{$month}";
$items[] = [
'date' => $date,
$currMonth => $currSales[$date] ?? 0,
$prevMonth => $prevSales[$date] ?? 0,
];
}
return $items;
}
public function refunds()
{
$year = $this->request->getQuery('year', 'int', date('Y'));
$month = $this->request->getQuery('month', 'int', date('m'));
$prev = $this->getPrevMonth($year, $month);
$currRefunds = $this->handleRefunds($year, $month);
$prevRefunds = $this->handleRefunds($prev['year'], $prev['month']);
$items = [];
foreach (range(1, 31) as $day) {
$date = sprintf('%02d', $day);
$prevMonth = "{$prev['year']}-{$prev['month']}";
$currMonth = "{$year}-{$month}";
$items[] = [
'date' => $date,
$currMonth => $currRefunds[$date] ?? 0,
$prevMonth => $prevRefunds[$date] ?? 0,
];
}
return $items;
}
public function registeredUsers()
{
$year = $this->request->getQuery('year', 'int', date('Y'));
$month = $this->request->getQuery('month', 'int', date('m'));
$prev = $this->getPrevMonth($year, $month);
$currUsers = $this->handleRegisteredUsers($year, $month);
$prevUsers = $this->handleRegisteredUsers($prev['year'], $prev['month']);
$items = [];
foreach (range(1, 31) as $day) {
$date = sprintf('%02d', $day);
$prevMonth = "{$prev['year']}-{$prev['month']}";
$currMonth = "{$year}-{$month}";
$items[] = [
'date' => $date,
$currMonth => $currUsers[$date] ?? 0,
$prevMonth => $prevUsers[$date] ?? 0,
];
}
return $items;
}
public function onlineUsers()
{
$year = $this->request->getQuery('year', 'int', date('Y'));
$month = $this->request->getQuery('month', 'int', date('m'));
$prev = $this->getPrevMonth($year, $month);
$currUsers = $this->handleOnlineUsers($year, $month);
$prevUsers = $this->handleOnlineUsers($prev['year'], $prev['month']);
$items = [];
foreach (range(1, 31) as $day) {
$date = sprintf('%02d', $day);
$prevMonth = "{$prev['year']}-{$prev['month']}";
$currMonth = "{$year}-{$month}";
$items[] = [
'date' => $date,
$currMonth => $currUsers[$date] ?? 0,
$prevMonth => $prevUsers[$date] ?? 0,
];
}
return $items;
}
public function getYearOptions()
{
$end = date('Y');
$start = $end - 3;
return range($start, $end);
}
public function getMonthOptions()
{
$options = [];
foreach (range(1, 12) as $value) {
$options[] = sprintf('%02d', $value);
}
return $options;
}
protected function isCurrMonth($year, $month)
{
return date('Y-m') == "{$year}-{$month}";
}
protected function getPrevMonth($year, $month)
{
$currentMonthTime = strtotime("{$year}-{$month}");
$prevMonthTime = strtotime('-1 month', $currentMonthTime);
return [
'year' => date('Y', $prevMonthTime),
'month' => date('m', $prevMonthTime),
];
}
protected function getMonthDates($year, $month)
{
$startTime = strtotime("{$year}-{$month}-01");
$days = date('t', $startTime);
$result = [];
foreach (range(1, $days) as $day) {
$result[] = sprintf('%04d-%02d-%02d', $year, $month, $day);
}
return $result;
}
protected function handleHotSales($type, $year, $month)
{
$keyName = "stat_hot_sales:{$type}_{$year}_{$month}";
$cache = $this->getCache();
$items = $cache->get($keyName);
if (!$items) {
$statRepo = new StatRepo();
$orders = $statRepo->findMonthlyOrders($type, $year, $month);
$items = [];
if ($orders->count() > 0) {
foreach ($orders as $order) {
$key = $order->item_id;
if (!isset($items[$key])) {
$items[$key] = [
'title' => $order->subject,
'total_count' => 1,
'total_amount' => $order->amount,
];
} else {
$items[$key]['total_count'] += 1;
$items[$key]['total_amount'] += $order->amount;
}
}
$totalCount = array_column($items, 'total_count');
array_multisort($totalCount, SORT_DESC, $items);
}
$queryMonth = "{$year}-{$month}";
$currMonth = date('Y-m');
if ($queryMonth < $currMonth) {
$cache->save($keyName, $items, 7 * 86400);
} else {
$cache->save($keyName, $items, 2 * 3600);
}
}
return $items;
}
protected function handleSales($year, $month)
{
$keyName = "stat_sales:{$year}_{$month}";
$redis = $this->getRedis();
$list = $redis->hGetAll($keyName);
$statRepo = new StatRepo();
$currDate = date('Y-m-d');
$currDay = date('d');
if (!$list) {
$dates = $this->getMonthDates($year, $month);
foreach ($dates as $date) {
$key = substr($date, -2);
if ($date < $currDate) {
$list[$key] = $statRepo->sumDailySales($date);
} elseif ($date == $currDate) {
$list[$key] = -999;
} else {
$list[$key] = 0;
}
}
$redis->hMSet($keyName, $list);
$redis->expire($keyName, 7 * 86400);
}
foreach ($list as $key => $value) {
if ($value < 0) {
$list[$key] = $statRepo->sumDailySales("{$year}-{$month}-{$key}");
$redis->hSet($keyName, $key, $list[$key]);
}
}
if ($this->isCurrMonth($year, $month)) {
$list[$currDay] = $statRepo->sumDailySales($currDate);
}
return $list;
}
protected function handleRefunds($year, $month)
{
$keyName = "stat_refunds:{$year}_{$month}";
$redis = $this->getRedis();
$list = $redis->hGetAll($keyName);
$statRepo = new StatRepo();
$currDate = date('Y-m-d');
$currDay = date('d');
if (!$list) {
$dates = $this->getMonthDates($year, $month);
foreach ($dates as $date) {
$key = substr($date, -2);
if ($date < $currDate) {
$list[$key] = $statRepo->sumDailyRefunds($date);
} elseif ($date == $currDate) {
$list[$key] = -999;
} else {
$list[$key] = 0;
}
}
$redis->hMSet($keyName, $list);
$redis->expire($keyName, 7 * 86400);
}
foreach ($list as $key => $value) {
if ($value < 0) {
$list[$key] = $statRepo->sumDailyRefunds("{$year}-{$month}-{$key}");
$redis->hSet($keyName, $key, $list[$key]);
}
}
if ($this->isCurrMonth($year, $month)) {
$list[$currDay] = $statRepo->sumDailyRefunds($currDate);
}
return $list;
}
protected function handleRegisteredUsers($year, $month)
{
$keyName = "stat_reg_users:{$year}_{$month}";
$redis = $this->getRedis();
$list = $redis->hGetAll($keyName);
$statRepo = new StatRepo();
$currDate = date('Y-m-d');
$currDay = date('d');
if (!$list) {
$dates = $this->getMonthDates($year, $month);
foreach ($dates as $date) {
$key = substr($date, -2);
if ($date < $currDate) {
$list[$key] = $statRepo->countDailyRegisteredUser($date);
} elseif ($date == $currDate) {
$list[$key] = -999;
} else {
$list[$key] = 0;
}
}
$redis->hMSet($keyName, $list);
$redis->expire($keyName, 7 * 86400);
}
foreach ($list as $key => $value) {
if ($value < 0) {
$list[$key] = $statRepo->countDailyRegisteredUser("{$year}-{$month}-{$key}");
$redis->hSet($keyName, $key, $list[$key]);
}
}
if ($this->isCurrMonth($year, $month)) {
$list[$currDay] = $statRepo->countDailyRegisteredUser($currDate);
}
return $list;
}
protected function handleOnlineUsers($year, $month)
{
$keyName = "stat_online_users:{$year}_{$month}";
$redis = $this->getRedis();
$list = $redis->hGetAll($keyName);
$statRepo = new StatRepo();
$currDate = date('Y-m-d');
$currDay = date('d');
if (!$list) {
$dates = $this->getMonthDates($year, $month);
foreach ($dates as $date) {
$key = substr($date, -2);
if ($date < $currDate) {
$list[$key] = $statRepo->countDailyOnlineUser($date);
} elseif ($date == $currDate) {
$list[$key] = -999;
} else {
$list[$key] = 0;
}
}
$redis->hMSet($keyName, $list);
$redis->expire($keyName, 7 * 86400);
}
foreach ($list as $key => $value) {
if ($value < 0) {
$list[$key] = $statRepo->countDailyOnlineUser("{$year}-{$month}-{$key}");
$redis->hSet($keyName, $key, $list[$key]);
}
}
if ($this->isCurrMonth($year, $month)) {
$list[$currDay] = $statRepo->countDailyOnlineUser($currDate);
}
return $list;
}
}

View File

@ -20,6 +20,7 @@
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">基本信息</li>
<li>{{ content_title(course.model) }}</li>
<li>学习资料</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
@ -34,6 +35,9 @@
{{ partial('chapter/edit_lesson_read') }}
{% endif %}
</div>
<div class="layui-tab-item">
{{ partial('chapter/edit_resource') }}
</div>
</div>
</div>
@ -64,6 +68,9 @@
{% endif %}
{{ js_include('lib/cos-js-sdk-v5.min.js') }}
{{ js_include('admin/js/chapter.resource.js') }}
{% endblock %}
{% block inline_js %}

View File

@ -0,0 +1,34 @@
{% set res_list_url = url({'for':'admin.chapter.resources','id':chapter.id}) %}
<fieldset class="layui-elem-field layui-field-title">
<legend>资料列表</legend>
</fieldset>
<div id="res-list" data-url="{{ res_list_url }}"></div>
<fieldset class="layui-elem-field layui-field-title">
<legend>上传资料</legend>
</fieldset>
<form class="layui-form kg-form" id="res-form">
<div class="layui-form-item" id="res-upload-block">
<label class="layui-form-label">资源文件</label>
<div class="layui-input-block">
<span class="layui-btn" id="res-upload-btn">选择文件</span>
<input class="layui-hide" type="file" name="res_file" accept="*/*">
</div>
</div>
<div class="layui-form-item layui-hide" id="res-progress-block">
<label class="layui-form-label">上传进度</label>
<div class="layui-input-block">
<div class="layui-progress layui-progress-big" lay-showpercent="yes" lay-filter="res-upload-progress" style="top:10px;">
<div class="layui-progress-bar" lay-percent="0%"></div>
</div>
</div>
</div>
<div class="layui-hide">
<input type="hidden" name="chapter_id" value="{{ chapter.id }}">
<input type="hidden" name="bucket" value="{{ cos.bucket }}">
<input type="hidden" name="region" value="{{ cos.region }}">
</div>
</form>

View File

@ -0,0 +1,30 @@
{% if resources %}
<table class="kg-table layui-table">
<tr>
<th>名称</th>
<th>类型</th>
<th>大小</th>
<th>日期</th>
<th width="15%">操作</th>
</tr>
{% for item in resources %}
{% set update_url = url({'for':'admin.resource.update','id':item.id}) %}
{% set delete_url = url({'for':'admin.resource.delete','id':item.id}) %}
{% set download_url = url({'for':'home.download','md5':item.upload.md5}) %}
<tr>
<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>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
<td>
<a class="layui-btn layui-btn-sm layui-bg-red res-btn-delete" href="javascript:" data-url="{{ delete_url }}">删除</a>
<a class="layui-btn layui-btn-sm" href="{{ download_url }}" target="_blank">下载</a>
</td>
</tr>
{% endfor %}
</table>
<br>
{% else %}
<div class="kg-center">没有相关资料</div>
<br>
{% endif %}

View File

@ -13,11 +13,14 @@
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo">COURSE ADMIN</div>
<div class="layui-logo">酷瓜云课堂</div>
<div class="kg-side-menu-bar">
<a href="javascript:"><i class="layui-icon layui-icon-spread-left"></i></a>
</div>
<ul class="layui-nav layui-layout-left kg-nav-module">
<li class="layui-nav-item">
<a href="/admin">首页</a>
</li>
{% for item in top_menus %}
<li data-module="module-{{ item.id }}" class="layui-nav-item">
<a href="javascript:">{{ item.title }}</a>

View File

@ -2,22 +2,28 @@
<div class="layui-card-header">今日统计</div>
<div class="layui-card-body">
<div class="layui-row layui-col-space10">
<div class="layui-col-md4">
<div class="layui-col-md3">
<div class="kg-stat-card">
<div class="name">用户注册</div>
<div class="count">{{ today_stat.user_count }}</div>
<div class="count">{{ today_stat.register_count }}</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-col-md3">
<div class="kg-stat-card">
<div class="name">成交订单</div>
<div class="count">{{ today_stat.order_count }}</div>
<div class="count">{{ today_stat.sale_count }}</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-col-md3">
<div class="kg-stat-card">
<div class="name">销售金额</div>
<div class="count">{{ today_stat.sale_amount }}</div>
<div class="count">{{ '¥%0.2f'|format(today_stat.sale_amount) }}</div>
</div>
</div>
<div class="layui-col-md3">
<div class="kg-stat-card">
<div class="name">退款金额</div>
<div class="count">{{ '¥%0.2f'|format(today_stat.refund_amount) }}</div>
</div>
</div>
</div>

View File

@ -4,17 +4,17 @@
{%- macro position_info(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-green">顶部</span>
顶部
{% elseif value == 2 %}
<span class="layui-badge layui-bg-blue">底部</span>
底部
{% endif %}
{%- endmacro %}
{%- macro target_info(value) %}
{% if value == '_blank' %}
<span class="layui-badge layui-bg-green">新窗口</span>
新窗口
{% elseif value == '_self' %}
<span class="layui-badge layui-bg-blue">原窗口</span>
原窗口
{% endif %}
{%- endmacro %}

View File

@ -38,28 +38,28 @@
{%- macro item_type(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-green">课程</span>
课程
{% elseif value == 2 %}
<span class="layui-badge layui-bg-blue">套餐</span>
套餐
{% elseif value == 3 %}
<span class="layui-badge layui-bg-red">赞赏</span>
赞赏
{% elseif value == 4 %}
<span class="layui-badge layui-bg-orange">会员</span>
会员
{% elseif value == 99 %}
<span class="layui-badge layui-bg-black">测试</span>
测试
{% endif %}
{%- endmacro %}
{%- macro order_status(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-blue">待支付</span>
待支付
{% elseif value == 2 %}
<span class="layui-badge layui-bg-gray">发货中</span>
发货中
{% elseif value == 3 %}
<span class="layui-badge layui-bg-green">已完成</span>
已完成
{% elseif value == 4 %}
<span class="layui-badge layui-bg-cyan">已关闭</span>
已关闭
{% elseif value == 5 %}
<span class="layui-badge layui-bg-red">已退款</span>
已退款
{% endif %}
{%- endmacro %}

View File

@ -1,16 +1,16 @@
{%- macro refund_status(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-blue">待处理</span>
待处理
{% elseif value == 2 %}
<span class="layui-badge layui-bg-gray">已取消</span>
已取消
{% elseif value == 3 %}
<span class="layui-badge layui-bg-orange">已审核</span>
已审核
{% elseif value == 4 %}
<span class="layui-badge layui-bg-red">已拒绝</span>
已拒绝
{% elseif value == 5 %}
<span class="layui-badge layui-bg-green">已完成</span>
已完成
{% elseif value == 6 %}
<span class="layui-badge layui-bg-cyan">已失败</span>
已失败
{% endif %}
{%- endmacro %}

View File

@ -3,10 +3,10 @@
{% block content %}
{%- macro type_info(value) %}
{% if value == 'system' %}
<span class="layui-badge layui-bg-green">内置</span>
{% elseif value == 'custom' %}
<span class="layui-badge layui-bg-blue">自定</span>
{% if value == 1 %}
内置
{% elseif value == 2 %}
自定义
{% endif %}
{%- endmacro %}
@ -43,7 +43,7 @@
{% set restore_url = url({'for':'admin.role.restore','id':item.id}) %}
<tr>
<td>{{ item.id }}</td>
<td><a href="javascript:" title="{{ item.summary }}">{{ item.name }}</a></td>
<td><a href="{{ edit_url }}" title="{{ item.summary }}">{{ item.name }}</a></td>
<td>{{ type_info(item.type) }}</td>
<td>
<a href="{{ user_list_url }}">

View File

@ -4,11 +4,11 @@
{%- macro target_info(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-green">课程</span>
课程
{% elseif value == 2 %}
<span class="layui-badge layui-bg-blue">单页</span>
单页
{% elseif value == 3 %}
<span class="layui-badge layui-bg-gray">链接</span>
链接
{% endif %}
{%- endmacro %}

View File

@ -0,0 +1,101 @@
{% extends 'templates/main.volt' %}
{% block content %}
{%- macro show_sales(sales) %}
<table class="layui-table kg-table">
<colgroup>
<col>
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th>排序</th>
<th>名称</th>
<th>数量</th>
<th>金额</th>
</tr>
</thead>
<tbody>
{% for sale in sales %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ sale.title }}</td>
<td>{{ sale.total_count }}</td>
<td>{{ '¥%0.2f'|format(sale.total_amount) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{%- endmacro %}
{% set types = {'1':'课程','2':'套餐','3':'赞赏','4':'会员'} %}
{% set year = request.get('year','int',date('Y')) %}
{% set month = request.get('month','int',date('m')) %}
{% set type = request.get('type','int',1) %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>热卖商品统计</cite></a>
</span>
</div>
</div>
<form class="layui-form kg-search-form" method="GET" action="{{ url({'for':'admin.stat.hot_sales'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">选择类型</label>
<div class="layui-input-inline">
<select name="type">
{% for key,value in types %}
<option value="{{ key }}" {% if key == type %}selected{% endif %}>{{ value }}</option>
{% endfor %}
</select>
</div>
<label class="layui-form-label">选择年份</label>
<div class="layui-input-inline">
<select name="year">
{% for value in years %}
<option value="{{ value }}" {% if value == year %}selected{% endif %}>{{ value }}年</option>
{% endfor %}
</select>
</div>
<label class="layui-form-label">选择月份</label>
<div class="layui-input-inline">
<select name="month">
{% for value in months %}
<option value="{{ value }}" {% if value == month %}selected{% endif %}>{{ value }}月</option>
{% endfor %}
</select>
</div>
<div class="layui-input-inline">
<button class="layui-btn" lay-submit="true">查询</button>
</div>
</div>
</form>
<div class="kg-sale-list layui-row layui-col-space15">
{% for item in items %}
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">{{ item.title }}</div>
<div class="layui-card-body">{{ show_sales(item.sales) }}</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
{% block inline_css %}
<style>
.kg-sale-list {
padding: 10px;
background: #f2f2f2;
}
</style>
{% endblock %}

View File

@ -0,0 +1,74 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set year = request.get('year','int',date('Y')) %}
{% set month = request.get('month','int',date('m')) %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>活跃用户统计</cite></a>
</span>
</div>
</div>
<form class="layui-form kg-search-form" method="GET" action="{{ url({'for':'admin.stat.online_users'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">选择年份</label>
<div class="layui-input-inline">
<select name="year">
{% for value in years %}
<option value="{{ value }}" {% if value == year %}selected{% endif %}>{{ value }}年</option>
{% endfor %}
</select>
</div>
<label class="layui-form-label">选择月份</label>
<div class="layui-input-inline">
<select name="month">
{% for value in months %}
<option value="{{ value }}" {% if value == month %}selected{% endif %}>{{ value }}月</option>
{% endfor %}
</select>
</div>
<div class="layui-input-inline">
<button class="layui-btn" lay-submit="true">查询</button>
</div>
</div>
</form>
<div class="kg-chart" id="chart"></div>
{% endblock %}
{% block include_js %}
{{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }}
{% endblock %}
{% block inline_js %}
<script>
var myChart = echarts.init(document.getElementById('chart'));
var option = {
legend: {},
tooltip: {},
dataset: {
source: {{ data|json_encode }}
},
xAxis: {type: 'category'},
yAxis: {},
series: [
{type: 'line'},
{type: 'line'}
]
};
myChart.setOption(option);
</script>
{% endblock %}

View File

@ -0,0 +1,74 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set year = request.get('year','int',date('Y')) %}
{% set month = request.get('month','int',date('m')) %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>售后退款统计</cite></a>
</span>
</div>
</div>
<form class="layui-form kg-search-form" method="GET" action="{{ url({'for':'admin.stat.refunds'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">选择年份</label>
<div class="layui-input-inline">
<select name="year">
{% for value in years %}
<option value="{{ value }}" {% if value == year %}selected{% endif %}>{{ value }}年</option>
{% endfor %}
</select>
</div>
<label class="layui-form-label">选择月份</label>
<div class="layui-input-inline">
<select name="month">
{% for value in months %}
<option value="{{ value }}" {% if value == month %}selected{% endif %}>{{ value }}月</option>
{% endfor %}
</select>
</div>
<div class="layui-input-inline">
<button class="layui-btn" lay-submit="true">查询</button>
</div>
</div>
</form>
<div class="kg-chart" id="chart"></div>
{% endblock %}
{% block include_js %}
{{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }}
{% endblock %}
{% block inline_js %}
<script>
var myChart = echarts.init(document.getElementById('chart'));
var option = {
legend: {},
tooltip: {},
dataset: {
source: {{ data|json_encode }}
},
xAxis: {type: 'category'},
yAxis: {},
series: [
{type: 'line'},
{type: 'line'}
]
};
myChart.setOption(option);
</script>
{% endblock %}

View File

@ -0,0 +1,74 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set year = request.get('year','int',date('Y')) %}
{% set month = request.get('month','int',date('m')) %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>注册用户统计</cite></a>
</span>
</div>
</div>
<form class="layui-form kg-search-form" method="GET" action="{{ url({'for':'admin.stat.reg_users'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">选择年份</label>
<div class="layui-input-inline">
<select name="year">
{% for value in years %}
<option value="{{ value }}" {% if value == year %}selected{% endif %}>{{ value }}年</option>
{% endfor %}
</select>
</div>
<label class="layui-form-label">选择月份</label>
<div class="layui-input-inline">
<select name="month">
{% for value in months %}
<option value="{{ value }}" {% if value == month %}selected{% endif %}>{{ value }}月</option>
{% endfor %}
</select>
</div>
<div class="layui-input-inline">
<button class="layui-btn" lay-submit="true">查询</button>
</div>
</div>
</form>
<div class="kg-chart" id="chart"></div>
{% endblock %}
{% block include_js %}
{{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }}
{% endblock %}
{% block inline_js %}
<script>
var myChart = echarts.init(document.getElementById('chart'));
var option = {
legend: {},
tooltip: {},
dataset: {
source: {{ data|json_encode }}
},
xAxis: {type: 'category'},
yAxis: {},
series: [
{type: 'line'},
{type: 'line'}
]
};
myChart.setOption(option);
</script>
{% endblock %}

View File

@ -0,0 +1,74 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set year = request.get('year','int',date('Y')) %}
{% set month = request.get('month','int',date('m')) %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>成交订单统计</cite></a>
</span>
</div>
</div>
<form class="layui-form kg-search-form" method="GET" action="{{ url({'for':'admin.stat.sales'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">选择年份</label>
<div class="layui-input-inline">
<select name="year">
{% for value in years %}
<option value="{{ value }}" {% if value == year %}selected{% endif %}>{{ value }}年</option>
{% endfor %}
</select>
</div>
<label class="layui-form-label">选择月份</label>
<div class="layui-input-inline">
<select name="month">
{% for value in months %}
<option value="{{ value }}" {% if value == month %}selected{% endif %}>{{ value }}月</option>
{% endfor %}
</select>
</div>
<div class="layui-input-inline">
<button class="layui-btn" lay-submit="true">查询</button>
</div>
</div>
</form>
<div class="kg-chart" id="chart"></div>
{% endblock %}
{% block include_js %}
{{ js_include('https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js', false) }}
{% endblock %}
{% block inline_js %}
<script>
var myChart = echarts.init(document.getElementById('chart'));
var option = {
legend: {},
tooltip: {},
dataset: {
source: {{ data|json_encode }}
},
xAxis: {type: 'category'},
yAxis: {},
series: [
{type: 'line'},
{type: 'line'}
]
};
myChart.setOption(option);
</script>
{% endblock %}

View File

@ -2,6 +2,18 @@
{% block content %}
{%- macro client_type_info(value) %}
{% if value == 1 %}
desktop
{% elseif value == 2 %}
mobile
{% elseif value == 3 %}
app
{% elseif value == 4 %}
小程序
{% endif %}
{%- endmacro %}
<table class="layui-table kg-table">
<colgroup>
<col>
@ -26,7 +38,7 @@
<p class="layui-elip">章节:{{ item.chapter.title }}</p>
</td>
<td>
<p>类型:{{ item.client_type }}</p>
<p>类型:{{ client_type_info(item.client_type) }}</p>
<p>地址:<a href="javascript:" class="kg-ip2region" title="查看位置" data-ip="{{ item.client_ip }}">{{ item.client_ip }}</a></p>
</td>
<td>{{ item.duration|duration }}</td>

View File

@ -4,11 +4,11 @@
{%- macro source_type_info(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-green">免费</span>
免费
{% elseif value == 2 %}
<span class="layui-badge layui-bg-orange">付费</span>
{% elseif value == 3 %}
<span class="layui-badge layui-bg-blue">导入</span>
导入
{% endif %}
{%- endmacro %}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="zh-CN-Hans">
<head>
<meta 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 name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="csrf-token" content="{{ csrfToken.getToken() }}">

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="zh-CN-Hans">
<head>
<meta 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 name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="csrf-token" content="{{ csrfToken.getToken() }}">

View File

@ -1,20 +1,20 @@
{%- macro channel_type(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-blue">支付宝</span>
支付宝
{% elseif value == 2 %}
<span class="layui-badge layui-bg-green">微信</span>
微信
{% endif %}
{%- endmacro %}
{%- macro trade_status(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-blue">待支付</span>
待支付
{% elseif value == 2 %}
<span class="layui-badge layui-bg-green">已完成</span>
已完成
{% elseif value == 3 %}
<span class="layui-badge layui-bg-cyan">已关闭</span>
已关闭
{% elseif value == 4 %}
<span class="layui-badge layui-bg-red">已退款</span>
已退款
{% endif %}
{%- endmacro %}

View File

@ -4,25 +4,25 @@
{%- macro gender_info(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-gray"></span>
{% elseif value == 2 %}
<span class="layui-badge layui-bg-gray"></span>
{% elseif value == 3 %}
<span class="layui-badge layui-bg-gray"></span>
{% endif %}
{%- endmacro %}
{%- macro edu_role_info(user) %}
{% if user.edu_role.id == 1 %}
<span class="layui-badge layui-bg-gray">学员</span>
学员
{% elseif user.edu_role.id == 2 %}
<span class="layui-badge layui-bg-blue">讲师</span>
<a href="{{ url({'for':'admin.user.list'},{'edu_role':user.edu_role.id}) }}">讲师</a>
{% endif %}
{%- endmacro %}
{%- macro admin_role_info(user) %}
{% if user.admin_role.id %}
<span class="layui-badge layui-bg-gray">{{ user.admin_role.name }}</span>
{% if user.admin_role.id > 0 %}
<a href="{{ url({'for':'admin.user.list'},{'admin_role':user.admin_role.id}) }}">{{ user.admin_role.name }}</a>
{% endif %}
{%- endmacro %}

View File

@ -7,11 +7,7 @@ use App\Services\Logic\Account\EmailUpdate as EmailUpdateService;
use App\Services\Logic\Account\PasswordReset as PasswordResetService;
use App\Services\Logic\Account\PasswordUpdate as PasswordUpdateService;
use App\Services\Logic\Account\PhoneUpdate as PhoneUpdateService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/account")
*/
class AccountController extends Controller
{
@ -24,12 +20,12 @@ class AccountController extends Controller
$this->response->redirect('/');
}
$returnUrl = $this->request->getHTTPReferer();
$service = new AccountService();
$captcha = $service->getSettings('captcha');
$returnUrl = $this->request->getHTTPReferer();
$this->view->setVar('return_url', $returnUrl);
$this->view->setVar('captcha', $captcha);
}
@ -137,60 +133,6 @@ class AccountController extends Controller
$this->view->setVar('captcha', $captcha);
}
/**
* @Get("/password/edit", name="home.account.edit_pwd")
*/
public function editPasswordAction()
{
if ($this->authUser->id == 0) {
$this->response->redirect(['for' => 'home.account.login']);
}
$service = new AccountService();
$captcha = $service->getSettings('captcha');
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('account/edit_password');
$this->view->setVar('captcha', $captcha);
}
/**
* @Get("/phone/edit", name="home.account.edit_phone")
*/
public function editPhoneAction()
{
if ($this->authUser->id == 0) {
$this->response->redirect(['for' => 'home.account.login']);
}
$service = new AccountService();
$captcha = $service->getSettings('captcha');
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('account/edit_phone');
$this->view->setVar('captcha', $captcha);
}
/**
* @Get("/email/edit", name="home.account.edit_email")
*/
public function editEmailAction()
{
if ($this->authUser->id == 0) {
$this->response->redirect(['for' => 'home.account.login']);
}
$service = new AccountService();
$captcha = $service->getSettings('captcha');
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('account/edit_email');
$this->view->setVar('captcha', $captcha);
}
/**
* @Post("/password/reset", name="home.account.reset_pwd")
*/
@ -219,7 +161,12 @@ class AccountController extends Controller
$service->handle();
$content = ['msg' => '更新手机成功'];
$location = $this->url->get(['for' => 'home.uc.account']);
$content = [
'location' => $location,
'msg' => '更新手机成功',
];
return $this->jsonSuccess($content);
}
@ -233,7 +180,12 @@ class AccountController extends Controller
$service->handle();
$content = ['msg' => '更新邮箱成功'];
$location = $this->url->get(['for' => 'home.uc.account']);
$content = [
'location' => $location,
'msg' => '更新邮箱成功',
];
return $this->jsonSuccess($content);
}
@ -247,7 +199,12 @@ class AccountController extends Controller
$service->handle();
$content = ['msg' => '更新密码成功'];
$location = $this->url->get(['for' => 'home.uc.account']);
$content = [
'location' => $location,
'msg' => '更新密码成功',
];
return $this->jsonSuccess($content);
}

View File

@ -8,6 +8,7 @@ use App\Services\Logic\Chapter\ChapterInfo as ChapterInfoService;
use App\Services\Logic\Chapter\ChapterLike as ChapterLikeService;
use App\Services\Logic\Chapter\DanmuList as ChapterDanmuListService;
use App\Services\Logic\Chapter\Learning as ChapterLearningService;
use App\Services\Logic\Chapter\ResourceList as ChapterResourceListService;
use App\Services\Logic\Course\ChapterList as CourseChapterListService;
/**
@ -16,6 +17,18 @@ use App\Services\Logic\Course\ChapterList as CourseChapterListService;
class ChapterController extends Controller
{
/**
* @Get("/{id:[0-9]+}/resources", name="home.chapter.resources")
*/
public function resourcesAction($id)
{
$service = new ChapterResourceListService();
$items = $service->handle($id);
$this->view->setVar('items', $items);
}
/**
* @Get("/{id:[0-9]+}", name="home.chapter.show")
*/

View File

@ -24,7 +24,7 @@ class ImController extends Controller
}
/**
* @Get("/index", name="home.im.index")
* @Get("/", name="home.im.index")
*/
public function indexAction()
{

View File

@ -19,6 +19,31 @@ class PublicController extends \Phalcon\Mvc\Controller
use ResponseTrait;
use SecurityTrait;
/**
* @Get("/download/{md5}", name="home.download")
*/
public function downloadAction($md5)
{
$repo = new UploadRepo();
$file = $repo->findByMd5($md5);
if ($file) {
$service = new StorageService();
$location = $service->getFileUrl($file->path);
$this->response->redirect($location, true);
} else {
$this->response->setStatusCode(404);
return $this->response;
}
}
/**
* @Get("/img/{id:[0-9]+}", name="home.img")
*/

View File

@ -1,6 +1,5 @@
<?php
namespace App\Http\Home\Controllers;
use App\Services\Logic\Teacher\Console\ConsultList as ConsultListService;
@ -28,7 +27,7 @@ class TeacherConsoleController extends Controller
}
/**
* @Get("/index", name="home.tc.index")
* @Get("/", name="home.tc.index")
*/
public function indexAction()
{
@ -95,7 +94,7 @@ class TeacherConsoleController extends Controller
'stream_code' => substr($pushUrl, $pos + 1),
];
$this->view->pick('teacher/console/live_push');
$this->view->pick('teacher/console/live');
$this->view->setVar('qrcode', $qrcode);
$this->view->setVar('obs', $obs);
}

View File

@ -34,7 +34,7 @@ class UserConsoleController extends Controller
}
/**
* @Get("/index", name="home.uc.index")
* @Get("/", name="home.uc.index")
*/
public function indexAction()
{
@ -61,9 +61,23 @@ class UserConsoleController extends Controller
{
$service = new AccountInfoService();
$captcha = $service->getSettings('captcha');
$account = $service->handle();
$this->view->pick('user/console/account');
$type = $this->request->getQuery('type', 'string', 'info');
if ($type == 'info') {
$this->view->pick('user/console/account_info');
} elseif ($type == 'phone') {
$this->view->pick('user/console/account_phone');
} elseif ($type == 'email') {
$this->view->pick('user/console/account_email');
} elseif ($type == 'password') {
$this->view->pick('user/console/account_password');
}
$this->view->setVar('captcha', $captcha);
$this->view->setVar('account', $account);
}
@ -183,8 +197,10 @@ class UserConsoleController extends Controller
$service->handle();
$location = $this->url->get(['for' => 'home.uc.profile']);
$content = [
'location' => $this->request->getHTTPReferer(),
'location' => $location,
'msg' => '更新资料成功',
];

View File

@ -1,42 +0,0 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.account.update_email'}) }}">
<br><br>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="login_password" placeholder="登录密码" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input id="cv-account" class="layui-input" type="text" name="email" placeholder="邮箱地址" data-type="email" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="verify-input-inline">
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div>
<div class="verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">立即绑定</button>
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">
<input id="cv-rand" type="hidden" name="rand">
</div>
</div>
</form>
{% endblock %}
{% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.js') }}
{% endblock %}

View File

@ -1,29 +0,0 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.account.update_pwd'}) }}">
<br>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="origin_password" placeholder="原始密码" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="new_password" placeholder="新设密码" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="confirm_password" placeholder="确认密码" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-fluid" lay-submit="true" lay-filter="go">提交</button>
</div>
</div>
</form>
{% endblock %}

View File

@ -1,42 +0,0 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.account.update_phone'}) }}">
<br><br>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="login_password" placeholder="登录密码" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input id="cv-account" class="layui-input" type="text" name="phone" placeholder="手机号码" data-type="phone" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="verify-input-inline">
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div>
<div class="verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">立即绑定</button>
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">
<input id="cv-rand" type="hidden" name="rand">
</div>
</div>
</form>
{% endblock %}
{% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.js') }}
{% endblock %}

View File

@ -16,20 +16,20 @@
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="new_password" autocomplete="off" placeholder="新密码" lay-verify="required">
<input class="layui-input" type="password" name="new_password" autocomplete="off" placeholder="新密码字母数字特殊字符6-16位" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="verify-input-inline">
<div class="layui-input-inline verify-input-inline">
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div>
<div class="verify-btn-inline">
<div class="layui-input-inline verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">立即重置</button>
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">重置密码</button>
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">
<input id="cv-rand" type="hidden" name="rand">

View File

@ -5,10 +5,10 @@
</div>
</div>
<div class="layui-form-item">
<div class="verify-input-inline">
<div class="layui-input-inline verify-input-inline">
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div>
<div class="verify-btn-inline">
<div class="layui-input-inline verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>

View File

@ -16,20 +16,20 @@
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码字母数字特殊字符6-16位" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<div class="verify-input-inline">
<div class="layui-input-inline verify-input-inline">
<input class="layui-input" type="text" name="verify_code" placeholder="验证码" lay-verify="required">
</div>
<div class="verify-btn-inline">
<div class="layui-input-inline verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">立即注册</button>
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">注册帐号</button>
<input type="hidden" name="return_url" value="{{ return_url }}">
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">

View File

@ -4,6 +4,7 @@
{% set full_chapter_url = full_url({'for':'home.chapter.show','id':chapter.id}) %}
{% set course_url = url({'for':'home.course.show','id':chapter.course.id}) %}
{% set resources_url = url({'for':'home.chapter.resources','id':chapter.id}) %}
{% set learning_url = url({'for':'home.chapter.learning','id':chapter.id}) %}
{% set live_chats_url = url({'for':'home.live.chats','id':chapter.id}) %}
{% set live_stats_url = url({'for':'home.live.stats','id':chapter.id}) %}
@ -19,8 +20,11 @@
<a><cite>{{ chapter.title }}</cite></a>
</span>
<span class="share">
<a href="javascript:" title="点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="我要点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="在线人数"><i class="layui-icon layui-icon-user"></i><em>0</em></a>
{% if chapter.resource_count > 0 and chapter.me.owned == 1 %}
<a href="javascript:" title="资料下载" data-url="{{ resources_url }}"><i class="layui-icon layui-icon-download-circle icon-resource"></i></a>
{% endif %}
<a href="javascript:" title="分享到微信" data-url=""><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>

View File

@ -4,6 +4,7 @@
{% set full_chapter_url = full_url({'for':'home.chapter.show','id':chapter.id}) %}
{% set course_url = url({'for':'home.course.show','id':chapter.course.id}) %}
{% set resources_url = url({'for':'home.chapter.resources','id':chapter.id}) %}
{% set learning_url = url({'for':'home.chapter.learning','id':chapter.id}) %}
{% set like_url = url({'for':'home.chapter.like','id':chapter.id}) %}
{% set consult_url = url({'for':'home.consult.add'},{'chapter_id':chapter.id}) %}
@ -16,9 +17,12 @@
<a><cite>{{ chapter.title }}</cite></a>
</span>
<span class="share">
<a href="javascript:" title="点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="我要点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="学习人次"><i class="layui-icon layui-icon-user"></i><em>{{ chapter.user_count }}</em></a>
<a href="javascript:" title="我要提问" data-url="{{ consult_url }}"><i class="layui-icon layui-icon-help icon-help"></i></a>
{% if chapter.resource_count > 0 and chapter.me.owned == 1 %}
<a href="javascript:" title="资料下载" data-url="{{ resources_url }}"><i class="layui-icon layui-icon-download-circle icon-resource"></i></a>
{% endif %}
<a href="javascript:" title="分享到微信" data-url=""><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>

View File

@ -0,0 +1,32 @@
{% extends 'templates/layer.volt' %}
{% block content %}
<table class="kg-table layui-table">
<tr>
<th>名称</th>
<th>类型</th>
<th>大小</th>
<th width="15%">操作</th>
</tr>
{% for item in items %}
{% set download_url = url({'for':'home.download','md5':item.md5}) %}
<tr>
<td>{{ item.name }}</td>
<td>{{ item.mime }}</td>
<td>{{ item.size|human_size }}</td>
<td><a class="layui-btn layui-btn-sm" href="{{ download_url }}" target="_blank">下载</a></td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% block inline_js %}
<script>
var index = parent.layer.getFrameIndex(window.name);
parent.layer.iframeAuto(index);
</script>
{% endblock %}

View File

@ -4,6 +4,7 @@
{% set full_chapter_url = full_url({'for':'home.chapter.show','id':chapter.id}) %}
{% set course_url = url({'for':'home.course.show','id':chapter.course.id}) %}
{% set resources_url = url({'for':'home.chapter.resources','id':chapter.id}) %}
{% set learning_url = url({'for':'home.chapter.learning','id':chapter.id}) %}
{% set like_url = url({'for':'home.chapter.like','id':chapter.id}) %}
{% set qrcode_url = url({'for':'home.qrcode'},{'text':full_chapter_url}) %}
@ -17,9 +18,12 @@
<a><cite>{{ chapter.title }}</cite></a>
</span>
<span class="share">
<a href="javascript:" title="学习人次"><i class="layui-icon layui-icon-user"></i><em>{{ chapter.user_count }}</em></a>
<a href="javascript:" title="我要点赞" data-url="{{ like_url }}"><i class="layui-icon layui-icon-praise icon-praise {{ liked_class }}"></i><em class="like-count">{{ chapter.like_count }}</em></a>
<a href="javascript:" title="学习人次"><i class="layui-icon layui-icon-user"></i><em>{{ chapter.user_count }}</em></a>
<a href="javascript:" title="我要提问" data-url="{{ consult_url }}"><i class="layui-icon layui-icon-help icon-help"></i></a>
{% if chapter.resource_count > 0 and chapter.me.owned == 1 %}
<a href="javascript:" title="资料下载" data-url="{{ resources_url }}"><i class="layui-icon layui-icon-download-circle icon-resource"></i></a>
{% endif %}
<a href="javascript:" title="分享到微信"><i class="layui-icon layui-icon-login-wechat icon-wechat"></i></a>
<a href="javascript:" title="分享到QQ空间"><i class="layui-icon layui-icon-login-qq icon-qq"></i></a>
<a href="javascript:" title="分享到微博"><i class="layui-icon layui-icon-login-weibo icon-weibo"></i></a>

View File

@ -30,6 +30,7 @@
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>

View File

@ -27,6 +27,7 @@
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>

View File

@ -4,7 +4,7 @@
{% set consult.answer = consult.answer ? consult.answer : '请耐心等待回复吧' %}
<div class="consult-info">
<div class="consult-info clearfix">
{% if consult.course.id is defined %}
<div class="item">
<div class="label">课程:</div>

View File

@ -8,7 +8,8 @@
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="{{ url({'for':'home.group.list'}) }}">群组列表</a>
<a href="/">首页</a>
<a href="{{ url({'for':'home.group.list'}) }}">群组</a>
<a><cite>{{ group.name }}</cite></a>
</span>
</div>

View File

@ -1,7 +1,8 @@
<!DOCTYPE html>
<html lang="zh-CN-Hans">
<head>
<meta 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 name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>出错啦</title>
{{ icon_link("favicon.ico") }}

View File

@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="zh-CN-Hans">
<head>
<meta 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 name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="csrf-token" content="{{ csrfToken.getToken() }}">
<title>即时通讯</title>
<title>酷瓜云课堂</title>
{{ icon_link('favicon.ico') }}
{{ css_link('lib/layui/css/layui.css') }}
{{ css_link('home/css/common.css') }}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="zh-CN-Hans">
<head>
<meta 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 name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="keywords" content="{{ seo.getKeywords() }}">

View File

@ -0,0 +1,54 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layout-main clearfix">
<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>
<form class="layui-form security-form" method="POST" action="{{ url({'for':'home.account.update_email'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">登录密码</label>
<div class="layui-input-block">
<input class="layui-input" type="password" name="login_password" autocomplete="off" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">邮箱地址</label>
<div class="layui-input-block">
<input id="cv-account" class="layui-input" type="text" name="email" data-type="email" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">验证码</label>
<div class="layui-input-inline verify-input-inline">
<input class="layui-input" type="text" name="verify_code" lay-verify="required">
</div>
<div class="layui-input-inline verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">提交修改</button>
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">
<input id="cv-rand" type="hidden" name="rand">
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.js') }}
{% endblock %}

View File

@ -2,9 +2,9 @@
{% block content %}
{% set edit_pwd_url = url({'for':'home.account.edit_pwd'}) %}
{% set edit_phone_url = url({'for':'home.account.edit_phone'}) %}
{% set edit_email_url = url({'for':'home.account.edit_email'}) %}
{% set edit_pwd_url = url({'for':'home.uc.account'},{'type':'password'}) %}
{% set edit_phone_url = url({'for':'home.uc.account'},{'type':'phone'}) %}
{% set edit_email_url = url({'for':'home.uc.account'},{'type':'email'}) %}
<div class="layout-main clearfix">
<div class="my-sidebar">{{ partial('user/console/menu') }}</div>
@ -18,17 +18,17 @@
<span class="icon"><i class="layui-icon layui-icon-password"></i></span>
<span class="title">登录密码</span>
<span class="summary">经常更改密码有助于保护您的帐号安全</span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-pwd" href="javascript:" data-url="{{ edit_pwd_url }}">修改</a></span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-pwd" href="{{ edit_pwd_url }}">修改</a></span>
</div>
<div class="security-item">
<span class="icon"><i class="layui-icon layui-icon-cellphone"></i></span>
<span class="title">手机绑定</span>
{% if account.phone %}
<span class="summary">已绑定手机:{{ account.phone|anonymous }}</span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-phone" href="javascript:" data-url="{{ edit_phone_url }}">修改</a></span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-phone" href="{{ edit_phone_url }}">修改</a></span>
{% else %}
<span class="summary">可用于登录和重置密码</span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-phone" href="javascript:" data-url="{{ edit_phone_url }}">绑定</a></span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-phone" href="{{ edit_phone_url }}">绑定</a></span>
{% endif %}
</div>
<div class="security-item">
@ -36,10 +36,10 @@
<span class="title">邮箱绑定</span>
{% if account.phone %}
<span class="summary">已绑定邮箱:{{ account.email|anonymous }}</span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-email" href="javascript:" data-url="{{ edit_email_url }}">修改</a></span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-email" href="{{ edit_email_url }}">修改</a></span>
{% else %}
<span class="summary">可用于登录和重置密码</span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-email" href="javascript:" data-url="{{ edit_email_url }}">绑定</a></span>
<span class="action"><a class="layui-btn layui-btn-sm btn-edit-email" href="{{ edit_email_url }}">绑定</a></span>
{% endif %}
</div>
</div>
@ -48,9 +48,3 @@
</div>
{% endblock %}
{% block include_js %}
{{ js_include('home/js/user.console.account.js') }}
{% endblock %}

View File

@ -0,0 +1,44 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layout-main clearfix">
<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>
<form class="layui-form security-form" method="POST" action="{{ url({'for':'home.account.update_pwd'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">原始密码</label>
<div class="layui-input-block">
<input class="layui-input" type="password" name="origin_password" autocomplete="off" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新设密码</label>
<div class="layui-input-block">
<input class="layui-input" type="password" name="new_password" placeholder="字母数字特殊字符6-16位" autocomplete="off" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">确认密码</label>
<div class="layui-input-block">
<input class="layui-input" type="password" name="confirm_password" placeholder="字母数字特殊字符6-16位" autocomplete="off" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button class="layui-btn layui-btn-primary" type="reset">重置</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,54 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layout-main clearfix">
<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>
<form class="layui-form security-form" method="POST" action="{{ url({'for':'home.account.update_phone'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">登录密码</label>
<div class="layui-input-block">
<input class="layui-input" type="password" name="login_password" autocomplete="off" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手机号码</label>
<div class="layui-input-block">
<input id="cv-account" class="layui-input" type="text" name="phone" data-type="phone" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">验证码</label>
<div class="layui-input-inline verify-input-inline">
<input class="layui-input" type="text" name="verify_code" lay-verify="required">
</div>
<div class="layui-input-inline verify-btn-inline">
<button id="cv-verify-emit" class="layui-btn layui-btn-primary layui-btn-disabled" type="button" disabled="disabled">获取验证码</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button id="cv-submit-btn" class="layui-btn layui-btn-fluid layui-btn-disabled" disabled="disabled" lay-submit="true" lay-filter="go">提交修改</button>
<input id="cv-app-id" type="hidden" value="{{ captcha.app_id }}">
<input id="cv-ticket" type="hidden" name="ticket">
<input id="cv-rand" type="hidden" name="rand">
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('https://ssl.captcha.qq.com/TCaptcha.js',false) }}
{{ js_include('home/js/captcha.verify.js') }}
{% endblock %}

View File

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

View File

@ -137,7 +137,7 @@ function kg_site_url()
* @param string $key
* @return mixed
*/
function kg_site_setting($section, $key = null)
function kg_setting($section, $key = null)
{
$cache = new SettingCache();
@ -148,6 +148,24 @@ function kg_site_setting($section, $key = null)
return $settings[$key] ?? null;
}
/**
* 获取站点配置
*
* @param string $path
* @param mixed $defaultValue
* @return mixed
*/
function kg_config($path, $defaultValue = null)
{
/**
* @var Config $config
*/
$config = Di::getDefault()->getShared('config');
return $config->path($path, $defaultValue);
}
/**
* 获取默认头像路径
*
@ -282,6 +300,23 @@ function kg_human_number($number)
return $result;
}
/**
* 格式化大小
*
* @param int $bytes
* @return string
*/
function kg_human_size($bytes)
{
if (!$bytes) return 0;
$symbols = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
$exp = floor(log($bytes) / log(1024));
return sprintf('%.2f ' . $symbols[$exp], ($bytes / pow(1024, floor($exp))));
}
/**
* 格式化之前时间
*

View File

@ -90,7 +90,7 @@ class Common
public static function password($str)
{
$pattern = '/^[A-Za-z0-9]{6,16}$/';
$pattern = '/^[[:graph:]]{6,16}$/';
return preg_match($pattern, $str) ? true : false;
}

52
app/Listeners/User.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace App\Listeners;
use App\Models\Online as OnlineModel;
use App\Models\User as UserModel;
use App\Repos\Online as OnlineRepo;
use App\Traits\Client as ClientTrait;
use Phalcon\Events\Event;
class User extends Listener
{
use ClientTrait;
public function online(Event $event, $source, UserModel $user)
{
$now = time();
if ($now - $user->active_time > 600) {
$user->active_time = $now;
$user->update();
$onlineRepo = new OnlineRepo();
$online = $onlineRepo->findByUserDate($user->id, date('Y-m-d'));
if ($online) {
$online->active_time = $now;
$online->client_type = $this->getClientType();
$online->client_ip = $this->getClientIp();
$online->update();
} else {
$online = new OnlineModel();
$online->user_id = $user->id;
$online->active_time = $now;
$online->client_type = $this->getClientType();
$online->client_ip = $this->getClientIp();
$online->create();
}
}
}
}

View File

@ -72,6 +72,9 @@ class Audit extends Model
if (is_array($this->req_data) && !empty($this->req_data)) {
foreach ($this->req_data as $key => $value) {
if (!is_scalar($value)) {
$value = kg_json_encode($value);
}
if (kg_strlen($value) > 255) {
$this->req_data[$key] = kg_substr($value, 0, 255);
}

View File

@ -132,6 +132,13 @@ class Chapter extends Model
*/
public $deleted;
/**
* 资源数
*
* @var int
*/
public $resource_count;
/**
* 课时数
*
@ -160,13 +167,6 @@ class Chapter extends Model
*/
public $like_count;
/**
* 资源数
*
* @var int
*/
public $res_count;
/**
* 创建时间
*

View File

@ -8,7 +8,7 @@ class Client
/**
* 类型
*/
const TYPE_DESKTOP = 1; // home
const TYPE_DESKTOP = 1; // desktop
const TYPE_MOBILE = 2; // mobile
const TYPE_APP = 3; // app
const TYPE_MINI = 4; // 小程序
@ -16,7 +16,7 @@ class Client
public static function types()
{
return [
self::TYPE_DESKTOP => 'home',
self::TYPE_DESKTOP => 'desktop',
self::TYPE_MOBILE => 'mobile',
self::TYPE_APP => 'app',
self::TYPE_MINI => 'mini',

View File

@ -179,6 +179,13 @@ class Course extends Model
*/
public $deleted;
/**
* 资源数
*
* @var int
*/
public $resource_count;
/**
* 学员数
*
@ -221,13 +228,6 @@ class Course extends Model
*/
public $favorite_count;
/**
* 资源数
*
* @var int
*/
public $res_count;
/**
* 创建时间
*

79
app/Models/Online.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace App\Models;
class Online extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 计划编号
*
* @var string
*/
public $date;
/**
* 客户端类型
*
* @var int
*/
public $client_type;
/**
* 客户端IP
*
* @var string
*/
public $client_ip;
/**
* 活跃时间
*
* @var int
*/
public $active_time;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_online';
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -2,9 +2,7 @@
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class ChapterResource extends Model
class Resource extends Model
{
/**
@ -35,13 +33,6 @@ class ChapterResource extends Model
*/
public $upload_id;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
@ -58,19 +49,7 @@ class ChapterResource extends Model
public function getSource(): string
{
return 'kg_chapter_resource';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
return 'kg_resource';
}
public function beforeCreate()

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Caches\MaxUploadId as MaxUploadIdCache;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class Upload extends Model
@ -13,7 +14,7 @@ class Upload extends Model
const TYPE_COVER_IMG = 1; // 封面图
const TYPE_CONTENT_IMG = 2; // 内容图
const TYPE_AVATAR_IMG = 3; // 头像
const TYPE_COURSE_RES = 4; // 课件资源
const TYPE_RESOURCE = 4; // 课件资源
const TYPE_IM_IMG = 5; // IM图片
const TYPE_IM_FILE = 6; // IM文件
@ -114,4 +115,11 @@ class Upload extends Model
$this->update_time = time();
}
public function afterCreate()
{
$cache = new MaxUploadIdCache();
$cache->rebuild();
}
}

View File

@ -22,8 +22,12 @@ class Volt extends Provider
$compiler = $volt->getCompiler();
$compiler->addFunction('site_setting', function ($resolvedArgs) {
return 'kg_site_setting(' . $resolvedArgs . ')';
$compiler->addFunction('config', function ($resolvedArgs) {
return 'kg_config(' . $resolvedArgs . ')';
});
$compiler->addFunction('setting', function ($resolvedArgs) {
return 'kg_setting(' . $resolvedArgs . ')';
});
$compiler->addFunction('full_url', function ($resolvedArgs) {
@ -62,6 +66,10 @@ class Volt extends Provider
return 'kg_human_number(' . $resolvedArgs . ')';
});
$compiler->addFilter('human_size', function ($resolvedArgs) {
return 'kg_human_size(' . $resolvedArgs . ')';
});
$compiler->addFilter('time_ago', function ($resolvedArgs) {
return 'kg_time_ago(' . $resolvedArgs . ')';
});

View File

@ -63,7 +63,7 @@ class Audit extends Repository
}
/**
* @param string $id
* @param int $id
* @return AuditModel|Model|bool
*/
public function findById($id)

View File

@ -8,7 +8,6 @@ use App\Models\ChapterLive as ChapterLiveModel;
use App\Models\ChapterRead as ChapterReadModel;
use App\Models\ChapterUser as ChapterUserModel;
use App\Models\ChapterVod as ChapterVodModel;
use App\Models\Comment as CommentModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
@ -153,14 +152,6 @@ class Chapter extends Repository
]);
}
public function countComments($chapterId)
{
return (int)CommentModel::count([
'conditions' => 'chapter_id = :chapter_id: AND deleted = 0',
'bind' => ['chapter_id' => $chapterId],
]);
}
public function countLikes($chapterId)
{
return (int)ChapterLikeModel::count([

30
app/Repos/Online.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Repos;
use App\Models\Online as OnlineModel;
use Phalcon\Mvc\Model;
class Online extends Repository
{
/**
* @param int $userId
* @param string $activeDate
* @return OnlineModel|Model|bool
*/
public function findByUserDate($userId, $activeDate)
{
$activeTime = strtotime($activeDate);
return OnlineModel::findFirst([
'conditions' => 'user_id = ?1 AND active_time BETWEEN ?2 AND ?3',
'bind' => [
1 => $userId,
2 => $activeTime,
3 => $activeTime + 86400,
],
]);
}
}

57
app/Repos/Resource.php Normal file
View File

@ -0,0 +1,57 @@
<?php
namespace App\Repos;
use App\Models\Resource as ResourceModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class Resource extends Repository
{
/**
* @param int $id
* @return ResourceModel|Model|bool
*/
public function findById($id)
{
return ResourceModel::findFirst($id);
}
/**
* @param array $ids
* @param array|string $columns
* @return ResultsetInterface|Resultset|ResourceModel[]
*/
public function findByIds($ids, $columns = '*')
{
return ResourceModel::query()
->columns($columns)
->inWhere('id', $ids)
->execute();
}
/**
* @param int $courseId
* @return ResultsetInterface|Resultset|ResourceModel[]
*/
public function findByCourseId($courseId)
{
return ResourceModel::query()
->where('course_id = :course_id:', ['course_id' => $courseId])
->execute();
}
/**
* @param int $chapterId
* @return ResultsetInterface|Resultset|ResourceModel[]
*/
public function findByChapterId($chapterId)
{
return ResourceModel::query()
->where('chapter_id = :chapter_id:', ['chapter_id' => $chapterId])
->execute();
}
}

139
app/Repos/Stat.php Normal file
View File

@ -0,0 +1,139 @@
<?php
namespace App\Repos;
use App\Models\Online as OnlineModel;
use App\Models\Order as OrderModel;
use App\Models\OrderStatus as OrderStatusModel;
use App\Models\Refund as RefundModel;
use App\Models\User as UserModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class Stat extends Repository
{
public function countDailyRegisteredUser($date)
{
$startTime = strtotime($date);
$endTime = $startTime + 86400;
return (int)UserModel::count([
'conditions' => 'create_time BETWEEN :start_time: AND :end_time:',
'bind' => ['start_time' => $startTime, 'end_time' => $endTime],
]);
}
public function countDailyOnlineUser($date)
{
$startTime = strtotime($date);
$endTime = $startTime + 86400;
return (int)OnlineModel::count([
'conditions' => 'active_time BETWEEN :start_time: AND :end_time:',
'bind' => ['start_time' => $startTime, 'end_time' => $endTime],
]);
}
public function countDailySales($date)
{
$sql = "SELECT count(*) AS total_count FROM %s AS os JOIN %s AS o ON os.order_id = o.id ";
$sql .= "WHERE os.status = ?1 AND o.create_time BETWEEN ?2 AND ?3";
$phql = sprintf($sql, OrderStatusModel::class, OrderModel::class);
$startTime = strtotime($date);
$endTime = $startTime + 86400;
$result = $this->modelsManager->executeQuery($phql, [
1 => OrderModel::STATUS_FINISHED,
2 => $startTime,
3 => $endTime,
]);
return (float)$result[0]['total_count'];
}
public function countDailyRefunds($date)
{
$startTime = strtotime($date);
$endTime = $startTime + 86400;
return (int)RefundModel::count([
'conditions' => 'status = ?1 AND create_time BETWEEN ?2 AND ?3',
'bind' => [
1 => RefundModel::STATUS_FINISHED,
2 => $startTime,
3 => $endTime,
],
]);
}
public function sumDailySales($date)
{
$sql = "SELECT sum(o.amount) AS total_amount FROM %s AS os JOIN %s AS o ON os.order_id = o.id ";
$sql .= "WHERE os.status = ?1 AND o.create_time BETWEEN ?2 AND ?3";
$phql = sprintf($sql, OrderStatusModel::class, OrderModel::class);
$startTime = strtotime($date);
$endTime = $startTime + 86400;
$result = $this->modelsManager->executeQuery($phql, [
1 => OrderModel::STATUS_FINISHED,
2 => $startTime,
3 => $endTime,
]);
return (float)$result[0]['total_amount'];
}
public function sumDailyRefunds($date)
{
$startTime = strtotime($date);
$endTime = $startTime + 86400;
return (float)RefundModel::sum([
'column' => 'amount',
'conditions' => 'status = ?1 AND create_time BETWEEN ?2 AND ?3',
'bind' => [
1 => RefundModel::STATUS_FINISHED,
2 => $startTime,
3 => $endTime,
],
]);
}
/**
* @param int $type
* @param int $year
* @param int $month
* @return ResultsetInterface|Resultset|OrderModel[]
*/
public function findMonthlyOrders($type, $year, $month)
{
$startTime = strtotime("{$year}-{$month}");
$endTime = strtotime('+1 month', $startTime);
$status = OrderModel::STATUS_FINISHED;
return $this->modelsManager->createBuilder()
->addFrom(OrderStatusModel::class, 'os')
->join(OrderModel::class, 'os.order_id = o.id', 'o')
->columns('o.*')
->where('o.item_type = :type:', ['type' => $type])
->andWhere('os.status = :status:', ['status' => $status])
->betweenWhere('o.create_time', $startTime, $endTime)
->getQuery()->execute();
}
}

View File

@ -4,6 +4,8 @@ namespace App\Repos;
use App\Models\Upload as UploadModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class Upload extends Repository
{
@ -29,4 +31,17 @@ class Upload extends Repository
]);
}
/**
* @param array $ids
* @param string|array $columns
* @return ResultsetInterface|Resultset|UploadModel[]
*/
public function findByIds($ids, $columns = '*')
{
return UploadModel::query()
->columns($columns)
->inWhere('id', $ids)
->execute();
}
}

View File

@ -71,6 +71,7 @@ class BasicInfo extends Service
'summary' => $chapter->summary,
'model' => $chapter->model,
'play_urls' => $playUrls,
'resource_count' => $chapter->resource_count,
'user_count' => $chapter->user_count,
'like_count' => $chapter->like_count,
];
@ -97,6 +98,7 @@ class BasicInfo extends Service
'start_time' => $live->start_time,
'end_time' => $live->end_time,
'status' => $live->status,
'resource_count' => $chapter->resource_count,
'user_count' => $chapter->user_count,
'like_count' => $chapter->like_count,
];
@ -114,6 +116,7 @@ class BasicInfo extends Service
'summary' => $chapter->summary,
'model' => $chapter->model,
'content' => $read->content,
'resource_count' => $chapter->resource_count,
'user_count' => $chapter->user_count,
'like_count' => $chapter->like_count,
];

View File

@ -0,0 +1,34 @@
<?php
namespace App\Services\Logic\Chapter;
use App\Builders\ResourceList as ResourceListBuilder;
use App\Repos\Resource as ResourceRepo;
use App\Services\Logic\ChapterTrait;
use App\Services\Logic\Service;
class ResourceList extends Service
{
use ChapterTrait;
public function handle($id)
{
$chapter = $this->checkChapter($id);
$resourceRepo = new ResourceRepo();
$resources = $resourceRepo->findByChapterId($chapter->id);
if ($resources->count() == 0) {
return [];
}
$builder = new ResourceListBuilder();
$relations = $resources->toArray();
return $builder->getUploads($relations);
}
}

View File

@ -78,9 +78,9 @@ class MyStorage extends Storage
*
* @return UploadModel|bool
*/
public function uploadCourseResource()
public function uploadResource()
{
return $this->upload('/res/course/', self::MIME_FILE, UploadModel::TYPE_COURSE_RES);
return $this->upload('/resource/', self::MIME_FILE, UploadModel::TYPE_RESOURCE);
}
/**

View File

@ -4,6 +4,13 @@ namespace App\Services;
use Phalcon\Logger\Adapter\File as FileLogger;
use Qcloud\Cos\Client as CosClient;
use TencentCloud\Common\Credential;
use TencentCloud\Common\Exception\TencentCloudSDKException;
use TencentCloud\Common\Profile\ClientProfile;
use TencentCloud\Common\Profile\HttpProfile;
use TencentCloud\Sts\V20180813\Models\GetFederationTokenRequest;
use TencentCloud\Sts\V20180813\Models\GetFederationTokenResponse;
use TencentCloud\Sts\V20180813\StsClient;
class Storage extends Service
{
@ -32,6 +39,74 @@ class Storage extends Service
$this->client = $this->getCosClient();
}
/**
* 获取临时凭证
*
* @return GetFederationTokenResponse
*/
public function getFederationToken()
{
$secret = $this->getSettings('secret');
$resource = sprintf('qcs::cos:%s:uid/%s:%s/*',
$this->settings['region'],
$secret['app_id'],
$this->settings['bucket']
);
$policy = json_encode([
'version' => '2.0',
'statement' => [
'effect' => 'allow',
'action' => [
'name/cos:PutObject',
'name/cos:PostObject',
'name/cos:InitiateMultipartUpload',
'name/cos:ListMultipartUploads',
'name/cos:ListParts',
'name/cos:UploadPart',
'name/cos:CompleteMultipartUpload',
],
'resource' => [$resource],
],
]);
try {
$credential = new Credential($secret['secret_id'], $secret['secret_key']);
$httpProfile = new HttpProfile();
$httpProfile->setEndpoint('sts.tencentcloudapi.com');
$clientProfile = new ClientProfile();
$clientProfile->setHttpProfile($httpProfile);
$client = new StsClient($credential, $this->settings['region'], $clientProfile);
$request = new GetFederationTokenRequest();
$params = json_encode([
'Name' => 'foo',
'Policy' => urlencode($policy),
]);
$request->fromJsonString($params);
return $client->GetFederationToken($request);
} catch (TencentCloudSDKException $e) {
$this->logger->error('Get Tmp Token Exception' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
]));
throw new \Exception('Get Tmp Token Exception');
}
}
/**
* 上传字符内容
*

View File

@ -6,7 +6,8 @@ use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Auth as AuthService;
use App\Validators\Validator as AppValidator;
use Phalcon\Di;
use Phalcon\Di as Di;
use Phalcon\Events\Manager as EventsManager;
trait Auth
{
@ -24,7 +25,16 @@ trait Auth
$userRepo = new UserRepo();
return $userRepo->findById($authUser['id']);
$user = $userRepo->findById($authUser['id']);
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('user:online', $this, $user);
return $user;
}
/**
@ -40,13 +50,7 @@ trait Auth
$userRepo = new UserRepo();
$user = $userRepo->findById($authUser['id']);
if (time() - $user->active_time > 600) {
$user->update(['active_time' => time()]);
}
return $user;
return $userRepo->findById($authUser['id']);
}
/**

View File

@ -0,0 +1,45 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Repos\Resource as ResourceRepo;
class Resource extends Validator
{
public function checkResource($id)
{
$resourceRepo = new ResourceRepo();
$resource = $resourceRepo->findById($id);
if (!$resource) {
throw new BadRequestException('resource.not_found');
}
return $resource;
}
public function checkCourse($id)
{
$validator = new Course();
return $validator->checkCourse($id);
}
public function checkChapter($id)
{
$validator = new Chapter();
return $validator->checkChapter($id);
}
public function checkUpload($id)
{
$validator = new Upload();
return $validator->checkUpload($id);
}
}

57
app/Validators/Upload.php Normal file
View File

@ -0,0 +1,57 @@
<?php
namespace App\Validators;
use App\Caches\MaxUploadId as MaxUploadIdCache;
use App\Exceptions\BadRequest as BadRequestException;
use App\Repos\Upload as UploadRepo;
class Upload extends Validator
{
public function checkUpload($id)
{
$this->checkId($id);
$uploadRepo = new UploadRepo();
$upload = $uploadRepo->findById($id);
if (!$upload) {
throw new BadRequestException('upload.not_found');
}
return $upload;
}
public function checkId($id)
{
$id = intval($id);
$maxIdCache = new MaxUploadIdCache();
$maxId = $maxIdCache->get();
if ($id < 1 || $id > $maxId) {
throw new BadRequestException('upload.not_found');
}
}
public function checkName($name)
{
$value = $this->filter->sanitize($name, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 2) {
throw new BadRequestException('upload.name_too_short');
}
if ($length > 100) {
throw new BadRequestException('upload.name_too_long');
}
return $value;
}
}

View File

@ -8,6 +8,7 @@ use App\Exceptions\NotFound as NotFoundException;
use App\Exceptions\ServiceUnavailable as ServiceUnavailableException;
use App\Exceptions\Unauthorized as UnauthorizedException;
use App\Library\Logger as AppLogger;
use Phalcon\Config;
use Phalcon\Mvc\User\Component;
class HttpErrorHandler extends Component
@ -127,6 +128,14 @@ class HttpErrorHandler extends Component
];
}
/**
* @return Config
*/
protected function getConfig()
{
return $this->getDI()->getShared('config');
}
protected function getLogger()
{
$logger = new AppLogger();

View File

@ -1,9 +1,11 @@
<?php
use App\Listeners\Pay;
use App\Listeners\User;
use App\Listeners\UserDailyCounter;
return [
'pay' => Pay::class,
'user' => User::class,
'userDailyCounter' => UserDailyCounter::class,
];

View File

@ -640,7 +640,7 @@ class InitTable extends Phinx\Migration\AbstractMigration
'unique' => false,
])
->create();
$this->table('kg_chapter_resource', [
$this->table('kg_resource', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
@ -676,13 +676,6 @@ class InitTable extends Phinx\Migration\AbstractMigration
'comment' => '上传编号',
'after' => 'chapter_id',
])
->addColumn('deleted', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '删除标识',
'after' => 'upload_id',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
@ -2517,7 +2510,7 @@ class InitTable extends Phinx\Migration\AbstractMigration
->addColumn('client_ip', 'string', [
'null' => false,
'default' => '',
'limit' => 30,
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '终端IP',

View File

@ -0,0 +1,107 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class CreateOnlineTable extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('kg_online', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('id', 'integer', [
'null' => false,
'limit' => MysqlAdapter::INT_REGULAR,
'identity' => 'enable',
'comment' => '主键编号',
])
->addColumn('user_id', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '用户编号',
'after' => 'id',
])
->addColumn('client_type', 'integer', [
'null' => false,
'default' => '1',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '终端类型',
'after' => 'user_id',
])
->addColumn('client_ip', 'string', [
'null' => false,
'default' => '',
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '终端IP',
'after' => 'client_type',
])
->addColumn('active_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '活跃时间',
'after' => 'client_ip',
])
->addColumn('create_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '创建时间',
'after' => 'active_time',
])
->addColumn('update_time', 'integer', [
'null' => false,
'default' => '0',
'limit' => MysqlAdapter::INT_REGULAR,
'comment' => '更新时间',
'after' => 'create_time',
])
->addIndex(['active_time'], [
'name' => 'active_time',
'unique' => false,
])
->addIndex(['user_id'], [
'name' => 'user_id',
'unique' => false,
])
->create();
$this->table('kg_task', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->save();
$this->table('kg_trade', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->changeColumn('channel_sn', 'string', [
'null' => false,
'default' => '',
'limit' => 64,
'collation' => 'utf8mb4_general_ci',
'encoding' => 'utf8mb4',
'comment' => '平台序号',
'after' => 'channel',
])
->save();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -60,10 +60,17 @@
}
.kg-login-wrap {
width: 400px;
width: 500px;
margin: 100px auto;
}
.kg-login-wrap .layui-card-header {
height: 50px;
line-height: 50px;
text-align: center;
font-size: 16px;
}
.kg-login-form {
padding: 15px 10px 5px 10px;
}
@ -258,3 +265,20 @@ img.kg-qrcode {
font-size: 20px;
color: rgb(0, 150, 136);
}
.kg-search-form {
margin-bottom: 30px;
}
.kg-search-form .layui-form-label {
width: auto;
}
.kg-search-form .layui-input-inline:last-child {
margin-left: 10px;
}
.kg-chart {
width: 100%;
height: 480px;
}

View File

@ -0,0 +1,133 @@
layui.use(['jquery', 'element', 'layer'], function () {
var $ = layui.jquery;
var element = layui.element;
var layer = layui.layer;
var $uploadBtn = $('#res-upload-btn');
var $resFile = $('input[name=res_file]');
var $uploadBlock = $('#res-upload-block');
var $progressBlock = $('#res-progress-block');
var chapterId = $('input[name=chapter_id]').val();
var myConfig = {
bucket: $('input[name=bucket]').val(),
region: $('input[name=region]').val(),
storageClass: 'STANDARD'
};
var cos = new COS({
getAuthorization: function (options, callback) {
$.get('/admin/upload/sign', {
bucket: options.Bucket,
region: options.Region,
}, function (data) {
console.log(data);
var credentials = data && data.credentials;
if (!data || !credentials) {
layer.msg('获取临时凭证失败', {icon: 2});
return console.error('invalid credentials');
}
callback({
TmpSecretId: credentials.TmpSecretId,
TmpSecretKey: credentials.TmpSecretKey,
XCosSecurityToken: credentials.Token,
ExpiredTime: data.expiredTime,
StartTime: data.startTime
});
});
}
});
loadResourceList();
$uploadBtn.on('click', function () {
$resFile.trigger('click');
});
$resFile.on('change', function (e) {
var file = this.files[0];
var keyName = getKeyName(file.name);
cos.putObject({
StorageClass: myConfig.storageClass,
Bucket: myConfig.bucket,
Region: myConfig.region,
Key: keyName,
Body: file,
onProgress: function (info) {
if (!isNaN(info.percent)) {
var percent = Math.ceil(100 * info.percent);
element.progress('res-upload-progress', percent + '%');
}
console.log(info);
}
}, function (err, data) {
if (data && data.statusCode === 200) {
$.post('/admin/resource/create', {
upload: {
name: file.name,
mime: file.type,
size: file.size,
path: keyName,
md5: data.ETag.replace(/"/g, '')
},
chapter_id: chapterId,
}, function () {
$uploadBlock.removeClass('layui-hide');
$progressBlock.addClass('layui-hide');
loadResourceList();
});
}
console.log(err || data);
});
$uploadBlock.addClass('layui-hide');
$progressBlock.removeClass('layui-hide');
});
$('body').on('change', '.res-name', function () {
var url = $(this).data('url');
$.post(url, {
name: $(this).val()
}, function (res) {
layer.msg(res.msg, {icon: 1});
});
});
$('body').on('click', '.res-btn-delete', function () {
var url = $(this).data('url');
layer.confirm('确定要删除吗?', function () {
$.post(url, function (res) {
layer.msg(res.msg, {icon: 1});
loadResourceList();
});
});
});
function getKeyName(filename) {
var ext = getFileExtension(filename);
var date = new Date();
var name = [
date.getFullYear(),
date.getMonth() + 1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
Math.round(10000 * Math.random())
].join('');
return '/resource/' + name + '.' + ext;
}
function getFileExtension(filename) {
var index = filename.lastIndexOf('.');
return filename.substr(index + 1);
}
function loadResourceList() {
var url = $('#res-list').data('url');
$.get(url, function (html) {
$('#res-list').html(html);
});
}
});

View File

@ -6,7 +6,7 @@ layui.use(['jquery', 'element'], function () {
var getSignature = function () {
var result = '';
$.ajax({
type: 'POST',
type: 'GET',
url: '/admin/vod/upload/sign',
async: false,
success: function (res) {

View File

@ -43,24 +43,24 @@ function xmCourse(data, url) {
{field: 'title', title: '标题', width: 390},
{
field: 'model', title: '类型', width: 50, templet: function (d) {
if (d.model === 'vod') {
if (d.model === 1) {
return '点播';
} else if (d.model === 'live') {
} else if (d.model === 2) {
return '直播';
} else if (d.model === 'read') {
} else if (d.model === 3) {
return '图文';
}
}
},
{
field: 'level', title: '难度', width: 50, templet: function (d) {
if (d.level === 'entry') {
if (d.level === 1) {
return '入门';
} else if (d.level === 'junior') {
} else if (d.level === 2) {
return '初级';
} else if (d.level === 'medium') {
} else if (d.level === 3) {
return '中级';
} else if (d.level === 'senior') {
} else if (d.level === 4) {
return '高级';
}
}

View File

@ -116,7 +116,7 @@
#main {
margin-top: 80px;
margin-bottom: 30px;
min-height: 560px;
min-height: 760px;
}
#footer {
@ -1161,18 +1161,18 @@
}
.account-form .verify-input-inline {
display: inline-block;
width: 283px;
margin-right: 5px;
width: 280px;
margin-right: 10px;
}
.account-form .verify-btn-inline {
display: inline-block;
vertical-align: top;
width: 110px;
margin-right: 0;
}
.verify-btn-inline button {
min-width: 108px;
min-width: 100px;
width: 100%;
}
.login-tab {
@ -1500,14 +1500,15 @@
}
.consult-info .item {
clear: both;
margin-bottom: 15px;
line-height: 1.5em;
height: 1.5em;
clear: both;
}
.consult-info .item .label {
float: left;
width: 50px;
height: 32px;
text-align: right;
}
@ -1523,6 +1524,19 @@
word-wrap: break-word;
}
.security-form {
width: 600px;
}
.security-form .verify-input-inline {
width: 360px;
}
.security-form .verify-btn-inline {
width: 120px;
margin-right: 0;
}
.security-item {
padding: 15px;
line-height: 50px;

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