1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-16 23:40:01 +08:00

first commit

This commit is contained in:
xiaochong0302 2019-12-13 00:10:03 +08:00
parent ab8bd706c3
commit 9b0e09c6ca
539 changed files with 37684 additions and 36 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.idea
/config/config.php
/vendor

0
.phalcon/.gitkeep Normal file
View File

View File

@ -1,36 +0,0 @@
# course-tencent-cloud
#### Description
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

75
app/Caches/Cache.php Normal file
View File

@ -0,0 +1,75 @@
<?php
namespace App\Caches;
use Phalcon\Mvc\User\Component as UserComponent;
abstract class Cache extends UserComponent
{
/**
* @var \Phalcon\Cache\Backend
*/
protected $cache;
public function __construct()
{
$this->cache = $this->getDI()->get('cache');
}
/**
* 获取缓存内容
*
* @param mixed $params
* @return mixed
*/
public function get($params = null)
{
$key = $this->getKey($params);
$content = $this->cache->get($key);
$lifetime = $this->getLifetime();
if (!$content) {
$content = $this->getContent($params);
$this->cache->save($key, $content, $lifetime);
$content = $this->cache->get($key);
}
return $content;
}
/**
* 删除缓存内容
*
* @param mixed $params
*/
public function delete($params = null)
{
$key = $this->getKey($params);
$this->cache->delete($key);
}
/**
* 获取缓存有效期
*
* @return integer
*/
abstract protected function getLifetime();
/**
* 获取键值
*
* @param mixed $params
* @return string
*/
abstract protected function getKey($params = null);
/**
* 获取原始内容
*
* @param mixed $params
* @return mixed
*/
abstract protected function getContent($params = null);
}

57
app/Caches/Category.php Normal file
View File

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

70
app/Caches/Config.php Normal file
View File

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

57
app/Caches/Course.php Normal file
View File

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

53
app/Caches/Nav.php Normal file
View File

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

57
app/Caches/User.php Normal file
View File

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

View File

@ -0,0 +1,149 @@
<?php
namespace App\Console\Tasks;
use Phalcon\Cli\Task;
class CleanLogTask extends Task
{
public function mainAction()
{
$this->cleanCommonLog();
$this->cleanConsoleLog();
$this->cleanSqlLog();
$this->cleanListenerLog();
$this->cleanCaptchaLog();
$this->cleanMailerLog();
$this->cleanSmserLog();
$this->cleanVodLog();
$this->cleanStorageLog();
$this->cleanAlipayLog();
$this->cleanWxpayLog();
$this->cleanRefundLog();
}
/**
* 清理通用日志
*/
protected function cleanCommonLog()
{
$this->cleanLog('common', 7);
}
/**
* 清理Console日志
*/
protected function cleanConsoleLog()
{
$this->cleanLog('console', 7);
}
/**
* 清理SQL日志
*/
protected function cleanSqlLog()
{
$this->cleanLog('sql', 3);
}
/**
* 清理监听者日志
*/
protected function cleanListenerLog()
{
$this->cleanLog('listener', 7);
}
/**
* 清理验证码服务日志
*/
protected function cleanCaptchaLog()
{
$this->cleanLog('captcha', 7);
}
/**
* 清理点播服务日志
*/
protected function cleanVodLog()
{
$this->cleanLog('vod', 7);
}
/**
* 清理存储服务日志
*/
protected function cleanStorageLog()
{
$this->cleanLog('storage', 7);
}
/**
* 清理短信服务日志
*/
protected function cleanSmserLog()
{
$this->cleanLog('smser', 7);
}
/**
* 清理邮件服务日志
*/
protected function cleanMailerLog()
{
$this->cleanLog('mailer', 7);
}
/**
* 清理阿里支付服务日志
*/
protected function cleanAlipayLog()
{
$this->cleanLog('alipay', 30);
}
/**
* 清理微信支付服务日志
*/
protected function cleanWxpayLog()
{
$this->cleanLog('wxpay', 30);
}
/**
* 清理退款日志
*/
protected function cleanRefundLog()
{
$this->cleanLog('refund', 30);
}
/**
* 清理日志文件
*
* @param string $prefix
* @param integer $keepDays 保留天数
* @return mixed
*/
protected function cleanLog($prefix, $keepDays)
{
$files = glob(log_path() . "/{$prefix}-*.log");
if (!$files) return false;
foreach ($files as $file) {
$date = substr($file, -14, 10);
$today = date('Y-m-d');
if (strtotime($today) - strtotime($date) >= $keepDays * 86400) {
$deleted = unlink($file);
if ($deleted) {
echo "Delete {$file} success" . PHP_EOL;
} else {
echo "Delete {$file} failed" . PHP_EOL;
}
}
}
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Console\Tasks;
use App\Models\Order as OrderModel;
use Phalcon\Cli\Task;
class CloseOrderTask extends Task
{
public function mainAction()
{
$orders = $this->findOrders();
if ($orders->count() == 0) {
return;
}
foreach ($orders as $order) {
$order->status = OrderModel::STATUS_CLOSED;
$order->update();
}
}
/**
* 查找待关闭订单
*
* @param integer $limit
* @return \Phalcon\Mvc\Model\ResultsetInterface
*/
protected function findOrders($limit = 1000)
{
$status = OrderModel::STATUS_PENDING;
$createdAt = time() - 12 * 3600;
$orders = OrderModel::query()
->where('status = :status:', ['status' => $status])
->andWhere('created_at < :created_at:', ['created_at' => $createdAt])
->limit($limit)
->execute();
return $orders;
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Console\Tasks;
use App\Models\Trade as TradeModel;
use App\Services\Alipay as AlipayService;
use Phalcon\Cli\Task;
class CloseTradeTask extends Task
{
public function mainAction()
{
$trades = $this->findTrades();
if ($trades->count() == 0) {
return;
}
foreach ($trades as $trade) {
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$this->closeAlipayTrade($trade);
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$this->closeWxpayTrade($trade);
}
}
}
/**
* 关闭支付宝交易
*
* @param TradeModel $trade
*/
protected function closeAlipayTrade($trade)
{
$service = new AlipayService();
$alyOrder = $service->findOrder($trade->sn);
if ($alyOrder) {
if ($alyOrder->trade_status == 'WAIT_BUYER_PAY') {
$service->closeOrder($trade->sn);
}
}
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
}
/**
* 关闭微信交易
*
* @param TradeModel $trade
*/
protected function closeWxpayTrade($trade)
{
$service = new WxpayService();
$wxOrder = $service->findOrder($trade->sn);
if ($wxOrder) {
if ($wxOrder->trade_state == 'NOTPAY') {
$service->closeOrder($trade->sn);
}
}
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
}
/**
* 查找待关闭交易
*
* @param integer $limit
* @return \Phalcon\Mvc\Model\ResultsetInterface
*/
protected function findTrades($limit = 5)
{
$status = TradeModel::STATUS_PENDING;
$createdAt = time() - 15 * 60;
$trades = TradeModel::query()
->where('status = :status:', ['status' => $status])
->andWhere('created_at < :created_at:', ['created_at' => $createdAt])
->limit($limit)
->execute();
return $trades;
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,24 @@
<?php
namespace App\Console\Tasks;
use App\Models\Chapter;
class MainTask extends Task
{
public function mainAction()
{
echo "You are now flying with Phalcon CLI!";
}
public function okAction()
{
$chapter = Chapter::findFirstById(15224);
$chapter->duration = 123;
echo $chapter->duration;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Console\Tasks;
use Phalcon\Cli\Task;
class MaintainTask extends Task
{
public function mainAction()
{
}
public function resetAnnotationsAction()
{
$dir = cache_path('annotations');
foreach (scandir($dir) as $file) {
if (strpos($file, '.php')) {
unlink($dir . '/' . $file);
}
}
}
public function resetModelsMetaDataAction()
{
$dir = cache_path('metadata');
foreach (scandir($dir) as $file) {
if (strpos($file, '.php')) {
unlink($dir . '/' . $file);
}
}
}
}

View File

@ -0,0 +1,273 @@
<?php
namespace App\Console\Tasks;
use App\Models\Order as OrderModel;
use App\Models\Refund as RefundModel;
use App\Models\Task as TaskModel;
use App\Models\Trade as TradeModel;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\Refund as RefundRepo;
use App\Repos\Trade as TradeRepo;
use App\Services\Alipay as AlipayService;
use App\Services\Wxpay as WxpayService;
class RefundTask extends Task
{
const TRY_COUNT = 5;
public function mainAction()
{
$logger = $this->getLogger('refund');
$tasks = $this->findTasks();
if ($tasks->count() == 0) {
return;
}
$tradeRepo = new TradeRepo();
$orderRepo = new OrderRepo();
$refundRepo = new RefundRepo();
foreach ($tasks as $task) {
$refund = $refundRepo->findBySn($task->item_info['refund']['sn']);
$trade = $tradeRepo->findBySn($task->item_info['refund']['trade_sn']);
$order = $orderRepo->findBySn($task->item_info['refund']['order_sn']);
try {
$this->db->begin();
$this->handleTradeRefund($trade, $refund);
$this->handleOrderRefund($order);
$refund->status = RefundModel::STATUS_FINISHED;
if ($refund->update() === false) {
throw new \RuntimeException('Update Refund Status Failed');
}
$trade->status = TradeModel::STATUS_REFUNDED;
if ($trade->update() === false) {
throw new \RuntimeException('Update Trade Status Failed');
}
$order->status = OrderModel::STATUS_REFUNDED;
if ($order->update() === false) {
throw new \RuntimeException('Update Order Status Failed');
}
$task->status = TaskModel::STATUS_FINISHED;
if ($task->update() === false) {
throw new \RuntimeException('Update Task Status Failed');
}
$this->db->commit();
} catch (\Exception $e) {
$this->db->rollback();
$task->try_count += 1;
if ($task->try_count > self::TRY_COUNT) {
$task->status = TaskModel::STATUS_FAILED;
$refund->status = RefundModel::STATUS_FAILED;
$refund->update();
}
$task->update();
$logger->info('Refund Task Exception ' . kg_json_encode([
'message' => $e->getMessage(),
'task' => $task->toArray(),
]));
}
}
}
/**
* 处理交易退款
*
* @param TradeModel $trade
* @param RefundModel $refund
*/
protected function handleTradeRefund(TradeModel $trade, RefundModel $refund)
{
$response = false;
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$alipay = new AlipayService();
$response = $alipay->refundOrder([
'out_trade_no' => $trade->sn,
'out_request_no' => $refund->sn,
'refund_amount' => $refund->amount,
]);
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$wxpay = new WxpayService();
$response = $wxpay->refundOrder([
'out_trade_no' => $trade->sn,
'out_refund_no' => $refund->sn,
'total_fee' => 100 * $trade->order_amount,
'refund_fee' => 100 * $refund->amount,
]);
}
if (!$response) {
throw new \RuntimeException('Payment Refund Failed');
}
}
/**
* 处理订单退款
*
* @param OrderModel $order
*/
protected function handleOrderRefund(OrderModel $order)
{
switch ($order->item_type) {
case OrderModel::TYPE_COURSE:
$this->handleCourseOrderRefund($order);
break;
case OrderModel::TYPE_PACKAGE:
$this->handlePackageOrderRefund($order);
break;
case OrderModel::TYPE_REWARD:
$this->handleRewardOrderRefund($order);
break;
case OrderModel::TYPE_VIP:
$this->handleVipOrderRefund($order);
break;
case OrderModel::TYPE_TEST:
$this->handleTestOrderRefund($order);
break;
}
}
/**
* 处理课程订单退款
*
* @param OrderModel $order
*/
protected function handleCourseOrderRefund(OrderModel $order)
{
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseStudent($order->item_id, $order->user_id);
if ($courseUser) {
$courseUser->deleted = 1;
if ($courseUser->update() === false) {
throw new \RuntimeException('Delete Course User Failed');
}
}
}
/**
* 处理套餐订单退款
*
* @param OrderModel $order
*/
protected function handlePackageOrderRefund(OrderModel $order)
{
$courseUserRepo = new CourseUserRepo();
foreach ($order->item_info['courses'] as $course) {
$courseUser = $courseUserRepo->findCourseStudent($course['id'], $order->user_id);
if ($courseUser) {
$courseUser->deleted = 1;
if ($courseUser->update() === false) {
throw new \RuntimeException('Delete Course User Failed');
}
}
}
}
/**
* 处理会员订单退款
*
* @param OrderModel $order
*/
protected function handleVipOrderRefund(OrderModel $order)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($order->user_id);
$baseTime = $user->vip_expiry;
switch ($order->item_info['vip']['duration']) {
case 'one_month':
$user->vip_expiry = strtotime('-1 months', $baseTime);
break;
case 'three_month':
$user->vip_expiry = strtotime('-3 months', $baseTime);
break;
case 'six_month':
$user->vip_expiry = strtotime('-6 months', $baseTime);
break;
case 'twelve_month':
$user->vip_expiry = strtotime('-12 months', $baseTime);
break;
}
if ($user->vip_expiry < time()) {
$user->vip = 0;
}
if ($user->update() === false) {
throw new \RuntimeException('Update User Vip Failed');
}
}
/**
* 处理打赏订单退款
*
* @param OrderModel $order
*/
protected function handleRewardOrderRefund(OrderModel $order)
{
}
/**
* 处理测试订单退款
*
* @param OrderModel $order
*/
protected function handleTestOrderRefund(OrderModel $order)
{
}
/**
* 查找退款任务
*
* @param integer $limit
* @return \Phalcon\Mvc\Model\ResultsetInterface
*/
protected function findTasks($limit = 5)
{
$itemType = TaskModel::TYPE_REFUND;
$status = TaskModel::STATUS_PENDING;
$tryCount = self::TRY_COUNT;
$tasks = TaskModel::query()
->where('item_type = :item_type:', ['item_type' => $itemType])
->andWhere('status = :status:', ['status' => $status])
->andWhere('try_count < :try_count:', ['try_count' => $tryCount])
->orderBy('priority ASC,try_count DESC')
->limit($limit)
->execute();
return $tasks;
}
}

View File

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

View File

@ -0,0 +1,17 @@
<?php
namespace App\Console\Tasks;
use App\Library\Logger;
class Task extends \Phalcon\Cli\Task
{
public function getLogger($channel = null)
{
$logger = new Logger();
return $logger->getInstance($channel);
}
}

View File

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

View File

@ -0,0 +1,157 @@
<?php
namespace App\Console\Tasks;
use App\Models\Chapter as ChapterModel;
use App\Repos\Chapter as ChapterRepo;
use App\Services\CourseStats as CourseStatsService;
use App\Services\Vod as VodService;
use Phalcon\Cli\Task;
class VodEventTask extends Task
{
public function mainAction()
{
$events = $this->pullEvents();
if (!$events) {
return;
}
$handles = [];
$count = 0;
foreach ($events as $event) {
$handles[] = $event['EventHandle'];
if ($event['EventType'] == 'NewFileUpload') {
$this->handleNewFileUploadEvent($event);
} elseif ($event['EventType'] == 'ProcedureStateChanged') {
$this->handleProcedureStateChangedEvent($event);
}
$count++;
if ($count >= 12) {
break;
}
}
$this->confirmEvents($handles);
}
protected function handleNewFileUploadEvent($event)
{
$fileId = $event['FileUploadEvent']['FileId'];
$format = $event['FileUploadEvent']['MediaBasicInfo']['Type'];
$duration = $event['FileUploadEvent']['MetaData']['Duration'];
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findByFileId($fileId);
if (!$chapter) {
return;
}
$vodService = new VodService();
if ($this->isAudioFile($format)) {
$vodService->createTransAudioTask($fileId);
} else {
$vodService->createTransVideoTask($fileId);
}
/**
* @var \stdClass $attrs
*/
$attrs = $chapter->attrs;
$attrs->file_status = ChapterModel::FS_TRANSLATING;
$attrs->duration = $duration;
$chapter->update(['attrs' => $attrs]);
$this->updateCourseDuration($chapter->course_id);
}
protected function handleProcedureStateChangedEvent($event)
{
$fileId = $event['ProcedureStateChangeEvent']['FileId'];
$processResult = $event['ProcedureStateChangeEvent']['MediaProcessResultSet'];
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findByFileId($fileId);
if (!$chapter) {
return;
}
$failCount = $successCount = 0;
foreach ($processResult as $item) {
if ($item['Type'] == 'Transcode') {
if ($item['TranscodeTask']['Status'] == 'SUCCESS') {
$successCount++;
} elseif ($item['TranscodeTask']['Status'] == 'FAIL') {
$failCount++;
}
}
}
$status = ChapterModel::FS_TRANSLATING;
/**
* 当有一个成功标记为成功
*/
if ($successCount > 0) {
$status = ChapterModel::FS_TRANSLATED;
} elseif ($failCount > 0) {
$status = ChapterModel::FS_FAILED;
}
if ($status == ChapterModel::FS_TRANSLATING) {
return;
}
/**
* @var \stdClass $attrs
*/
$attrs = $chapter->attrs;
$attrs->file_status = $status;
$chapter->update(['attrs' => $attrs]);
}
protected function pullEvents()
{
$vodService = new VodService();
$result = $vodService->pullEvents();
return $result;
}
protected function confirmEvents($handles)
{
$vodService = new VodService();
$result = $vodService->confirmEvents($handles);
return $result;
}
protected function isAudioFile($format)
{
$formats = ['mp3', 'm4a', 'wav', 'flac', 'ogg'];
$result = in_array(strtolower($format), $formats);
return $result;
}
protected function updateCourseDuration($courseId)
{
$courseStats = new CourseStatsService();
$courseStats->updateVodDuration($courseId);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class BadRequest extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class Forbidden extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class NotFound extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class ServerError extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class Unauthorized extends \Exception
{
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Audit as AuditService;
/**
* @RoutePrefix("/admin/audit")
*/
class AuditController extends Controller
{
/**
* @Get("/search", name="admin.audit.search")
*/
public function searchAction()
{
}
/**
* @Get("/list", name="admin.audit.list")
*/
public function listAction()
{
$auditService = new AuditService();
$pager = $auditService->getAudits();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id}/show", name="admin.audit.show")
*/
public function showAction($id)
{
$auditService = new AuditService();
$audit = $auditService->getAudit($id);
$this->view->setVar('audit', $audit);
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Category as CategoryService;
/**
* @RoutePrefix("/admin/category")
*/
class CategoryController extends Controller
{
/**
* @Get("/list", name="admin.category.list")
*/
public function listAction()
{
$parentId = $this->request->get('parent_id', 'int', 0);
$service = new CategoryService();
$parent = $service->getParentCategory($parentId);
$categories = $service->getChildCategories($parentId);
$this->view->setVar('parent', $parent);
$this->view->setVar('categories', $categories);
}
/**
* @Get("/add", name="admin.category.add")
*/
public function addAction()
{
$parentId = $this->request->get('parent_id', 'int', 0);
$service = new CategoryService();
$topCategories = $service->getTopCategories();
$this->view->setVar('parent_id', $parentId);
$this->view->setVar('top_categories', $topCategories);
}
/**
* @Post("/create", name="admin.category.create")
*/
public function createAction()
{
$service = new CategoryService();
$category = $service->createCategory();
$location = $this->url->get(
['for' => 'admin.category.list'],
['parent_id' => $category->parent_id]
);
$content = [
'location' => $location,
'msg' => '创建分类成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.category.edit")
*/
public function editAction($id)
{
$service = new CategoryService();
$category = $service->getCategory($id);
$this->view->setVar('category', $category);
}
/**
* @Post("/{id}/update", name="admin.category.update")
*/
public function updateAction($id)
{
$service = new CategoryService();
$category = $service->getCategory($id);
$service->updateCategory($id);
$location = $this->url->get(
['for' => 'admin.category.list'],
['parent_id' => $category->parent_id]
);
$content = [
'location' => $location,
'msg' => '更新分类成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.category.delete")
*/
public function deleteAction($id)
{
$service = new CategoryService();
$service->deleteCategory($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除分类成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.category.restore")
*/
public function restoreAction($id)
{
$service = new CategoryService();
$service->restoreCategory($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原分类成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,215 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Chapter as ChapterService;
use App\Http\Admin\Services\ChapterContent as ChapterContentService;
use App\Http\Admin\Services\Config as ConfigService;
use App\Http\Admin\Services\Course as CourseService;
use App\Models\Course as CourseModel;
/**
* @RoutePrefix("/admin/chapter")
*/
class ChapterController extends Controller
{
/**
* @Get("/{id}/lessons", name="admin.chapter.lessons")
*/
public function lessonsAction($id)
{
$chapterService = new ChapterService();
$courseService = new CourseService();
$chapter = $chapterService->getChapter($id);
$course = $courseService->getCourse($chapter->course_id);
$lessons = $chapterService->getLessons($chapter->id);
$this->view->setVar('lessons', $lessons);
$this->view->setVar('chapter', $chapter);
$this->view->setVar('course', $course);
}
/**
* @Get("/add", name="admin.chapter.add")
*/
public function addAction()
{
$courseId = $this->request->getQuery('course_id');
$parentId = $this->request->getQuery('parent_id');
$type = $this->request->getQuery('type');
$chapterService = new ChapterService();
$course = $chapterService->getCourse($courseId);
$courseChapters = $chapterService->getCourseChapters($courseId);
$this->view->setVar('course', $course);
$this->view->setVar('parent_id', $parentId);
$this->view->setVar('course_chapters', $courseChapters);
if ($type == 'chapter') {
$this->view->pick('chapter/add_chapter');
} else {
$this->view->pick('chapter/add_lesson');
}
}
/**
* @Post("/create", name="admin.chapter.create")
*/
public function createAction()
{
$service = new ChapterService();
$chapter = $service->createChapter();
$location = $this->url->get([
'for' => 'admin.course.chapters',
'id' => $chapter->course_id,
]);
$content = [
'location' => $location,
'msg' => '创建章节成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.chapter.edit")
*/
public function editAction($id)
{
$contentService = new ChapterContentService();
$chapterService = new ChapterService();
$configService = new ConfigService();
$chapter = $chapterService->getChapter($id);
$course = $chapterService->getCourse($chapter->course_id);
$storage = $configService->getSectionConfig('storage');
switch ($course->model) {
case CourseModel::MODEL_VOD:
$vod = $contentService->getChapterVod($chapter->id);
$translatedFiles = $contentService->getTranslatedFiles($vod->file_id);
$this->view->setVar('vod', $vod);
$this->view->setVar('translated_files', $translatedFiles);
break;
case CourseModel::MODEL_LIVE:
$live = $contentService->getChapterLive($chapter->id);
$this->view->setVar('live', $live);
break;
case CourseModel::MODEL_ARTICLE:
$article = $contentService->getChapterArticle($chapter->id);
$this->view->setVar('article', $article);
break;
}
$this->view->setVar('storage', $storage);
$this->view->setVar('chapter', $chapter);
$this->view->setVar('course', $course);
if ($chapter->parent_id > 0) {
$this->view->pick('chapter/edit_lesson');
} else {
$this->view->pick('chapter/edit_chapter');
}
}
/**
* @Post("/{id}/update", name="admin.chapter.update")
*/
public function updateAction($id)
{
$service = new ChapterService();
$chapter = $service->updateChapter($id);
if ($chapter->parent_id > 0) {
$location = $this->url->get([
'for' => 'admin.chapter.lessons',
'id' => $chapter->parent_id,
]);
} else {
$location = $this->url->get([
'for' => 'admin.course.chapters',
'id' => $chapter->course_id,
]);
}
$content = [
'location' => $location,
'msg' => '更新章节成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.chapter.delete")
*/
public function deleteAction($id)
{
$service = new ChapterService();
$service->deleteChapter($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除章节成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.chapter.restore")
*/
public function restoreAction($id)
{
$service = new ChapterService();
$service->restoreChapter($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除章节成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/content", name="admin.chapter.content")
*/
public function contentAction($id)
{
$contentService = new ChapterContentService();
$contentService->updateChapterContent($id);
$chapterService = new ChapterService();
$chapter = $chapterService->getChapter($id);
$location = $this->url->get([
'for' => 'admin.chapter.lessons',
'id' => $chapter->parent_id,
]);
$content = [
'location' => $location,
'msg' => '更新课时内容成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,275 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Config as ConfigService;
/**
* @RoutePrefix("/admin/config")
*/
class ConfigController extends Controller
{
/**
* @Route("/website", name="admin.config.website")
*/
public function websiteAction()
{
$section = 'website';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateSectionConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$website = $service->getSectionConfig($section);
$this->view->setVar('website', $website);
}
}
/**
* @Route("/secret", name="admin.config.secret")
*/
public function secretAction()
{
$section = 'secret';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateStorageConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$secret = $service->getSectionConfig($section);
$this->view->setVar('secret', $secret);
}
}
/**
* @Route("/storage", name="admin.config.storage")
*/
public function storageAction()
{
$section = 'storage';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateStorageConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$storage = $service->getSectionConfig($section);
$this->view->setVar('storage', $storage);
}
}
/**
* @Route("/vod", name="admin.config.vod")
*/
public function vodAction()
{
$section = 'vod';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateVodConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$vod = $service->getSectionConfig($section);
$this->view->setVar('vod', $vod);
}
}
/**
* @Route("/live", name="admin.config.live")
*/
public function liveAction()
{
$section = 'live';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateLiveConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$live = $service->getSectionConfig($section);
$ptt = json_decode($live->pull_trans_template);
$this->view->setVar('live', $live);
$this->view->setVar('ptt', $ptt);
}
}
/**
* @Route("/payment", name="admin.config.payment")
*/
public function paymentAction()
{
$service = new ConfigService();
if ($this->request->isPost()) {
$section = $this->request->getPost('section');
$data = $this->request->getPost();
$service->updateSectionConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$alipay = $service->getSectionConfig('payment.alipay');
$wxpay = $service->getSectionConfig('payment.wxpay');
$this->view->setVar('alipay', $alipay);
$this->view->setVar('wxpay', $wxpay);
}
}
/**
* @Route("/smser", name="admin.config.smser")
*/
public function smserAction()
{
$section = 'smser';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateSmserConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$smser = $service->getSectionConfig($section);
$template = json_decode($smser->template);
$this->view->setVar('smser', $smser);
$this->view->setVar('template', $template);
}
}
/**
* @Route("/mailer", name="admin.config.mailer")
*/
public function mailerAction()
{
$section = 'mailer';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateSectionConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$mailer = $service->getSectionConfig($section);
$this->view->setVar('mailer', $mailer);
}
}
/**
* @Route("/captcha", name="admin.config.captcha")
*/
public function captchaAction()
{
$section = 'captcha';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateSectionConfig($section, $data);
$content = [
'location' => $this->request->getHTTPReferer(),
'msg' => '更新配置成功',
];
return $this->ajaxSuccess($content);
} else {
$captcha = $service->getSectionConfig($section);
$this->view->setVar('captcha', $captcha);
}
}
/**
* @Route("/vip", name="admin.config.vip")
*/
public function vipAction()
{
$section = 'vip';
$service = new ConfigService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$service->updateSectionConfig($section, $data);
return $this->ajaxSuccess(['msg' => '更新配置成功']);
} else {
$vip = $service->getSectionConfig($section);
$this->view->setVar('vip', $vip);
}
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Models\Audit as AuditModel;
use App\Traits\Ajax as AjaxTrait;
use App\Traits\Security as SecurityTrait;
use Phalcon\Mvc\Dispatcher;
class Controller extends \Phalcon\Mvc\Controller
{
protected $authUser;
use AjaxTrait, SecurityTrait;
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
if ($this->notSafeRequest()) {
if (!$this->checkHttpReferer() || !$this->checkCsrfToken()) {
$dispatcher->forward([
'controller' => 'public',
'action' => 'csrf',
]);
return false;
}
}
$this->authUser = $this->getDI()->get('auth')->getAuthUser();
if (!$this->authUser) {
$dispatcher->forward([
'controller' => 'public',
'action' => 'auth',
]);
return false;
}
$controller = $dispatcher->getControllerName();
$route = $this->router->getMatchedRoute();
/**
* 管理员忽略权限检查
*/
if ($this->authUser->admin) {
return true;
}
/**
* 特例白名单
*/
$whitelist = [
'controller' => [
'public', 'index', 'storage', 'vod', 'test',
'xm_course',
],
'route' => [
'admin.package.guiding',
],
];
/**
* 特定控制器忽略权限检查
*/
if (in_array($controller, $whitelist['controller'])) {
return true;
}
/**
* 特定路由忽略权限检查
*/
if (in_array($route->getName(), $whitelist['route'])) {
return true;
}
/**
* 执行路由权限检查
*/
if (!in_array($route->getName(), $this->authUser->routes)) {
$dispatcher->forward([
'controller' => 'public',
'action' => 'forbidden',
]);
return false;
}
return true;
}
public function initialize()
{
$this->view->setVar('auth_user', $this->authUser);
}
public function afterExecuteRoute(Dispatcher $dispatcher)
{
if ($this->request->isPost()) {
$audit = new AuditModel();
$audit->user_id = $this->authUser->id;
$audit->user_name = $this->authUser->name;
$audit->user_ip = $this->request->getClientAddress();
$audit->req_route = $this->router->getMatchedRoute()->getName();
$audit->req_path = $this->request->getServer('REQUEST_URI');
$audit->req_data = $this->request->getPost();
$audit->create();
}
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Course as CourseService;
/**
* @RoutePrefix("/admin/course")
*/
class CourseController extends Controller
{
/**
* @Get("/search", name="admin.course.search")
*/
public function searchAction()
{
$courseService = new CourseService();
$xmCategories = $courseService->getXmCategories(0);
$this->view->setVar('xm_categories', $xmCategories);
}
/**
* @Get("/list", name="admin.course.list")
*/
public function listAction()
{
$courseService = new CourseService();
$pager = $courseService->getCourses();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/add", name="admin.course.add")
*/
public function addAction()
{
}
/**
* @Post("/create", name="admin.course.create")
*/
public function createAction()
{
$courseService = new CourseService();
$course = $courseService->createCourse();
$location = $this->url->get([
'for' => 'admin.course.edit',
'id' => $course->id,
]);
$content = [
'location' => $location,
'msg' => '创建课程成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.course.edit")
*/
public function editAction($id)
{
$courseService = new CourseService();
$course = $courseService->getCourse($id);
$xmTeachers = $courseService->getXmTeachers($id);
$xmCategories = $courseService->getXmCategories($id);
$xmCourses = $courseService->getXmCourses($id);
$this->view->setVar('course', $course);
$this->view->setVar('xm_teachers', $xmTeachers);
$this->view->setVar('xm_categories', $xmCategories);
$this->view->setVar('xm_courses', $xmCourses);
}
/**
* @Post("/{id}/update", name="admin.course.update")
*/
public function updateAction($id)
{
$courseService = new CourseService();
$courseService->updateCourse($id);
$content = ['msg' => '更新课程成功'];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.course.delete")
*/
public function deleteAction($id)
{
$courseService = new CourseService();
$courseService->deleteCourse($id);
$content = [
'location' => $this->request->getHTTPReferer(),
'msg' => '删除课程成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.course.restore")
*/
public function restoreAction($id)
{
$courseService = new CourseService();
$courseService->restoreCourse($id);
$content = [
'location' => $this->request->getHTTPReferer(),
'msg' => '还原课程成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/chapters", name="admin.course.chapters")
*/
public function chaptersAction($id)
{
$courseService = new CourseService();
$course = $courseService->getCourse($id);
$chapters = $courseService->getChapters($id);
$this->view->setVar('course', $course);
$this->view->setVar('chapters', $chapters);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\AuthMenu as AuthMenuService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/admin")
*/
class IndexController extends Controller
{
/**
* @Get("/", name="admin.index")
*/
public function indexAction()
{
$authMenu = new AuthMenuService();
$topMenus = $authMenu->getTopMenus();
$leftMenus = $authMenu->getLeftMenus();
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->setVar('top_menus', $topMenus);
$this->view->setVar('left_menus', $leftMenus);
}
/**
* @Get("/main", name="admin.main")
*/
public function mainAction()
{
/*
$service = new \App\Services\Order();
$course = \App\Models\Course::findFirstById(1152);
$service->createCourseOrder($course);
*/
/*
$service = new \App\Services\Order();
$package = \App\Models\Package::findFirstById(5);
$service->createPackageOrder($package);
*/
$refund = new \App\Services\Refund();
$order = \App\Models\Order::findFirstById(131);
$amount = $refund->getRefundAmount($order);
dd($amount);
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Nav as NavService;
/**
* @RoutePrefix("/admin/nav")
*/
class NavController extends Controller
{
/**
* @Get("/list", name="admin.nav.list")
*/
public function listAction()
{
$parentId = $this->request->get('parent_id', 'int', 0);
$navService = new NavService();
$parent = $navService->getParentNav($parentId);
$navs = $navService->getChildNavs($parentId);
$this->view->setVar('parent', $parent);
$this->view->setVar('navs', $navs);
}
/**
* @Get("/add", name="admin.nav.add")
*/
public function addAction()
{
$parentId = $this->request->get('parent_id', 'int', 0);
$navService = new NavService();
$topNavs = $navService->getTopNavs();
$this->view->setVar('parent_id', $parentId);
$this->view->setVar('top_navs', $topNavs);
}
/**
* @Post("/create", name="admin.nav.create")
*/
public function createAction()
{
$navService = new NavService();
$nav = $navService->createNav();
$location = $this->url->get(
['for' => 'admin.nav.list'],
['parent_id' => $nav->parent_id]
);
$content = [
'location' => $location,
'msg' => '创建导航成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.nav.edit")
*/
public function editAction($id)
{
$navService = new NavService();
$nav = $navService->getNav($id);
$this->view->setVar('nav', $nav);
}
/**
* @Post("/{id}/update", name="admin.nav.update")
*/
public function updateAction($id)
{
$navService = new NavService();
$nav = $navService->getNav($id);
$navService->updateNav($id);
$location = $this->url->get(
['for' => 'admin.nav.list'],
['parent_id' => $nav->parent_id]
);
$content = [
'location' => $location,
'msg' => '更新导航成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.nav.delete")
*/
public function deleteAction($id)
{
$navService = new NavService();
$navService->deleteNav($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除导航成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.nav.restore")
*/
public function restoreAction($id)
{
$navService = new NavService();
$navService->restoreNav($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原导航成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Order as OrderService;
/**
* @RoutePrefix("/admin/order")
*/
class OrderController extends Controller
{
/**
* @Get("/search", name="admin.order.search")
*/
public function searchAction()
{
}
/**
* @Get("/list", name="admin.order.list")
*/
public function listAction()
{
$orderService = new OrderService();
$pager = $orderService->getOrders();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id}/show", name="admin.order.show")
*/
public function showAction($id)
{
$orderService = new OrderService();
$order = $orderService->getOrder($id);
$trades = $orderService->getTrades($order->sn);
$refunds = $orderService->getRefunds($order->sn);
$user = $orderService->getUser($order->user_id);
$this->view->setVar('order', $order);
$this->view->setVar('trades', $trades);
$this->view->setVar('refunds', $refunds);
$this->view->setVar('user', $user);
}
/**
* @Post("/{id}/close", name="admin.order.close")
*/
public function closeAction($id)
{
$orderService = new OrderService();
$orderService->closeOrder($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '关闭订单成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/refund", name="admin.order.refund")
*/
public function refundAction()
{
$tradeId = $this->request->getPost('trade_id', 'int');
$orderService = new OrderService;
$orderService->refundTrade($tradeId);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '订单退款成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,133 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Package as PackageService;
/**
* @RoutePrefix("/admin/package")
*/
class PackageController extends Controller
{
/**
* @Get("/guiding", name="admin.package.guiding")
*/
public function guidingAction()
{
$xmCourseIds = $this->request->getQuery('xm_course_ids');
$packageService = new PackageService();
$courses = $packageService->getGuidingCourses($xmCourseIds);
$guidingPrice = $packageService->getGuidingPrice($courses);
$this->view->setVar('courses', $courses);
$this->view->setVar('guiding_price', $guidingPrice);
}
/**
* @Get("/list", name="admin.package.list")
*/
public function listAction()
{
$packageService = new PackageService();
$pager = $packageService->getPackages();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/add", name="admin.package.add")
*/
public function addAction()
{
}
/**
* @Post("/create", name="admin.package.create")
*/
public function createAction()
{
$packageService = new PackageService();
$package = $packageService->createPackage();
$location = $this->url->get([
'for' => 'admin.package.edit',
'id' => $package->id,
]);
$content = [
'location' => $location,
'msg' => '创建套餐成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.package.edit")
*/
public function editAction($id)
{
$packageService = new PackageService();
$package = $packageService->getPackage($id);
$xmCourses = $packageService->getXmCourses($id);
$this->view->setVar('package', $package);
$this->view->setVar('xm_courses', $xmCourses);
}
/**
* @Post("/{id}/update", name="admin.package.update")
*/
public function updateAction($id)
{
$packageService = new PackageService();
$packageService->updatePackage($id);
$content = ['msg' => '更新套餐成功'];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.package.delete")
*/
public function deleteAction($id)
{
$packageService = new PackageService();
$packageService->deletePackage($id);
$content = [
'location' => $this->request->getHTTPReferer(),
'msg' => '删除套餐成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.package.restore")
*/
public function restoreAction($id)
{
$packageService = new PackageService();
$packageService->restorePackage($id);
$content = [
'location' => $this->request->getHTTPReferer(),
'msg' => '还原套餐成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Page as PageService;
/**
* @RoutePrefix("/admin/page")
*/
class PageController extends Controller
{
/**
* @Get("/list", name="admin.page.list")
*/
public function listAction()
{
$service = new PageService();
$pager = $service->getPages();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/add", name="admin.page.add")
*/
public function addAction()
{
}
/**
* @Post("/create", name="admin.page.create")
*/
public function createAction()
{
$service = new PageService();
$service->createPage();
$location = $this->url->get(['for' => 'admin.page.list']);
$content = [
'location' => $location,
'msg' => '创建单页成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.page.edit")
*/
public function editAction($id)
{
$service = new PageService;
$page = $service->getPage($id);
$this->view->setVar('page', $page);
}
/**
* @Post("/{id}/update", name="admin.page.update")
*/
public function updateAction($id)
{
$service = new PageService();
$service->updatePage($id);
$location = $this->url->get(['for' => 'admin.page.list']);
$content = [
'location' => $location,
'msg' => '更新单页成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.page.delete")
*/
public function deleteAction($id)
{
$service = new PageService();
$service->deletePage($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除单页成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.page.restore")
*/
public function restoreAction($id)
{
$service = new PageService();
$service->restorePage($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原单页成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Traits\Ajax as AjaxTrait;
/**
* @RoutePrefix("/admin")
*/
class PublicController extends \Phalcon\Mvc\Controller
{
use AjaxTrait;
/**
* @Route("/auth", name="admin.auth")
*/
public function authAction()
{
if ($this->request->isAjax()) {
return $this->ajaxError(['msg' => '会话已过期,请重新登录']);
}
$this->response->redirect(['for' => 'admin.login']);
}
/**
* @Route("/csrf", name="admin.csrf")
*/
public function csrfAction()
{
if ($this->request->isAjax()) {
return $this->ajaxError(['msg' => 'CSRF令牌验证失败']);
}
$this->view->pick('public/csrf');
}
/**
* @Route("/forbidden", name="admin.forbidden")
*/
public function forbiddenAction()
{
if ($this->request->isAjax()) {
return $this->ajaxError(['msg' => '无相关操作权限']);
}
$this->view->pick('public/forbidden');
}
/**
* @Route("/ip2region", name="admin.ip2region")
*/
public function ip2regionAction()
{
$ip = $this->request->getQuery('ip', 'trim');
$region = kg_ip2region($ip);
$this->view->setVar('region', $region);
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Refund as RefundService;
/**
* @RoutePrefix("/admin/refund")
*/
class RefundController extends Controller
{
/**
* @Get("/search", name="admin.refund.search")
*/
public function searchAction()
{
}
/**
* @Get("/list", name="admin.refund.list")
*/
public function listAction()
{
$refundService = new RefundService();
$pager = $refundService->getRefunds();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id}/show", name="admin.refund.show")
*/
public function showAction($id)
{
$refundService = new RefundService();
$refund = $refundService->getRefund($id);
$order = $refundService->getOrder($refund->order_sn);
$trade = $refundService->getTrade($refund->trade_sn);
$user = $refundService->getUser($trade->user_id);
$this->view->setVar('refund', $refund);
$this->view->setVar('order', $order);
$this->view->setVar('trade', $trade);
$this->view->setVar('user', $user);
}
/**
* @Post("/{id}/review", name="admin.refund.review")
*/
public function reviewAction($id)
{
$refundService = new RefundService;
$refundService->reviewRefund($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '审核退款成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Review as ReviewService;
/**
* @RoutePrefix("/admin/review")
*/
class ReviewController extends Controller
{
/**
* @Get("/search", name="admin.review.search")
*/
public function searchAction()
{
}
/**
* @Get("/list", name="admin.review.list")
*/
public function listAction()
{
$service = new ReviewService();
$pager = $service->getReviews();
$courseId = $this->request->getQuery('course_id', 'int', 0);
$course = null;
if ($courseId > 0) {
$course = $service->getCourse($courseId);
}
$this->view->setVar('pager', $pager);
$this->view->setVar('course', $course);
}
/**
* @Get("/{id}/edit", name="admin.review.edit")
*/
public function editAction($id)
{
$service = new ReviewService();
$review = $service->getReview($id);
$this->view->setVar('review', $review);
}
/**
* @Post("/{id}/update", name="admin.review.update")
*/
public function updateAction($id)
{
$service = new ReviewService();
$service->updateReview($id);
$content = [
'msg' => '更新评价成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.review.delete")
*/
public function deleteAction($id)
{
$service = new ReviewService();
$service->deleteReview($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除评价成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.review.restore")
*/
public function restoreAction($id)
{
$service = new ReviewService();
$service->restoreReview($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原评价成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\AuthNode;
use App\Http\Admin\Services\Role as RoleService;
/**
* @RoutePrefix("/admin/role")
*/
class RoleController extends Controller
{
/**
* @Get("/list", name="admin.role.list")
*/
public function listAction()
{
$roleService = new RoleService();
$roles = $roleService->getRoles();
$this->view->setVar('roles', $roles);
}
/**
* @Get("/add", name="admin.role.add")
*/
public function addAction()
{
}
/**
* @Post("/create", name="admin.role.create")
*/
public function createAction()
{
$roleService = new RoleService();
$role = $roleService->createRole();
$location = $this->url->get([
'for' => 'admin.role.edit',
'id' => $role->id,
]);
$content = [
'location' => $location,
'msg' => '创建角色成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.role.edit")
*/
public function editAction($id)
{
$roleService = new RoleService();
$role = $roleService->getRole($id);
//dd($role->routes);
$adminNode = new AuthNode();
$nodes = $adminNode->getAllNodes();
$this->view->setVar('role', $role);
$this->view->setVar('nodes', kg_array_object($nodes));
}
/**
* @Post("/{id}/update", name="admin.role.update")
*/
public function updateAction($id)
{
$roleService = new RoleService();
$roleService->updateRole($id);
$location = $this->url->get(['for' => 'admin.role.list']);
$content = [
'location' => $location,
'msg' => '更新角色成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.role.delete")
*/
public function deleteAction($id)
{
$roleService = new RoleService();
$roleService->deleteRole($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除角色成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.role.restore")
*/
public function restoreAction($id)
{
$roleService = new RoleService();
$roleService->restoreRole($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原角色成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Config as ConfigService;
use App\Http\Admin\Services\Session as SessionService;
use App\Traits\Ajax as AjaxTrait;
use App\Traits\Security as SecurityTrait;
/**
* @RoutePrefix("/admin")
*/
class SessionController extends \Phalcon\Mvc\Controller
{
use AjaxTrait;
use SecurityTrait;
/**
* @Route("/login", name="admin.login")
*/
public function loginAction()
{
if ($this->request->isPost()) {
if (!$this->checkHttpReferer() || !$this->checkCsrfToken()) {
$this->dispatcher->forward([
'controller' => 'public',
'action' => 'forbidden',
]);
return false;
}
$sessionService = new SessionService();
$sessionService->login();
$location = $this->url->get(['for' => 'admin.index']);
return $this->ajaxSuccess(['location' => $location]);
}
$configService = new ConfigService();
$captcha = $configService->getSectionConfig('captcha');
$this->view->pick('public/login');
$this->view->setVar('captcha', $captcha);
}
/**
* @Get("/logout", name="admin.logout")
*/
public function logoutAction()
{
$service = new SessionService();
$service->logout();
$this->response->redirect(['for' => 'admin.login']);
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Slide as SlideService;
/**
* @RoutePrefix("/admin/slide")
*/
class SlideController extends Controller
{
/**
* @Get("/list", name="admin.slide.list")
*/
public function listAction()
{
$service = new SlideService();
$pager = $service->getSlides();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/add", name="admin.slide.add")
*/
public function addAction()
{
}
/**
* @Post("/create", name="admin.slide.create")
*/
public function createAction()
{
$service = new SlideService();
$slide = $service->createSlide();
$location = $this->url->get([
'for' => 'admin.slide.edit',
'id' => $slide->id,
]);
$content = [
'location' => $location,
'msg' => '创建轮播成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.slide.edit")
*/
public function editAction($id)
{
$service = new SlideService();
$slide = $service->getSlide($id);
$this->view->setVar('slide', $slide);
}
/**
* @Post("/{id}/update", name="admin.slide.update")
*/
public function updateAction($id)
{
$service = new SlideService();
$service->updateSlide($id);
$location = $this->url->get(['for' => 'admin.slide.list']);
$content = [
'location' => $location,
'msg' => '更新轮播成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/delete", name="admin.slide.delete")
*/
public function deleteAction($id)
{
$service = new SlideService();
$service->deleteSlide($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除轮播成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/restore", name="admin.slide.restore")
*/
public function restoreAction($id)
{
$service = new SlideService();
$service->restoreSlide($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原轮播成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Services\Storage as StorageService;
/**
* @RoutePrefix("/admin/storage")
*/
class StorageController extends Controller
{
/**
* @Post("/cover/img/upload", name="admin.storage.cover.img.upload")
*/
public function uploadCoverImageAction()
{
$storageService = new StorageService();
$key = $storageService->uploadCoverImage();
$url = $storageService->getCiImageUrl($key);
if ($url) {
return $this->ajaxSuccess(['data' => ['src' => $url, 'title' => '']]);
} else {
return $this->ajaxError(['msg' => '上传文件失败']);
}
}
/**
* @Post("/content/img/upload", name="admin.content.img.upload")
*/
public function uploadContentImageAction()
{
$storageService = new StorageService();
$url = $storageService->uploadContentImage();
if ($url) {
return $this->ajaxSuccess(['data' => ['src' => $url, 'title' => '']]);
} else {
return $this->ajaxError(['msg' => '上传文件失败']);
}
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\CourseStudent as CourseStudentService;
/**
* @RoutePrefix("/admin/student")
*/
class StudentController extends Controller
{
/**
* @Get("/search", name="admin.student.search")
*/
public function searchAction()
{
}
/**
* @Get("/list", name="admin.student.list")
*/
public function listAction()
{
$courseId = $this->request->getQuery('course_id', 'int', '');
$service = new CourseStudentService();
$pager = $service->getCourseStudents();
$this->view->setVar('pager', $pager);
$this->view->setVar('course_id', $courseId);
}
/**
* @Get("/add", name="admin.student.add")
*/
public function addAction()
{
$courseId = $this->request->getQuery('course_id', 'int', '');
$this->view->setVar('course_id', $courseId);
}
/**
* @Post("/create", name="admin.student.create")
*/
public function createAction()
{
$service = new CourseStudentService();
$student = $service->createCourseStudent();
$location = $this->url->get(
['for' => 'admin.student.list'],
['course_id' => $student->course_id]
);
$content = [
'location' => $location,
'msg' => '添加学员成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/edit", name="admin.student.edit")
*/
public function editAction()
{
$courseId = $this->request->getQuery('course_id', 'int');
$userId = $this->request->getQuery('user_id', 'int');
$service = new CourseStudentService();
$courseStudent = $service->getCourseStudent($courseId, $userId);
$course = $service->getCourse($courseId);
$student = $service->getStudent($userId);
$this->view->setVar('course_student', $courseStudent);
$this->view->setVar('course', $course);
$this->view->setVar('student', $student);
}
/**
* @Post("/update", name="admin.student.update")
*/
public function updateAction()
{
$service = new CourseStudentService();
$student = $service->updateCourseStudent();
$location = $this->url->get(
['for' => 'admin.student.list'],
['course_id' => $student->course_id]
);
$content = [
'location' => $location,
'msg' => '更新学员成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/learning", name="admin.student.learning")
*/
public function learningAction()
{
$service = new CourseStudentService();
$pager = $service->getCourseLearnings();
$this->view->setVar('pager', $pager);
}
}

View File

@ -0,0 +1,201 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\AlipayTest as AlipayTestService;
use App\Http\Admin\Services\Config as ConfigService;
use App\Services\Captcha as CaptchaService;
use App\Services\Live as LiveService;
use App\Services\Mailer as MailerService;
use App\Services\Smser as SmserService;
use App\Services\Storage as StorageService;
use App\Services\Vod as VodService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/admin/test")
*/
class TestController extends Controller
{
/**
* @Post("/storage", name="admin.test.storage")
*/
public function storageTestAction()
{
$storageService = new StorageService();
$result = $storageService->uploadTestFile();
if ($result) {
return $this->ajaxSuccess(['msg' => '上传文件成功,请到控制台确认']);
} else {
return $this->ajaxError(['msg' => '上传文件失败,请检查相关配置']);
}
}
/**
* @Post("/vod", name="admin.test.vod")
*/
public function vodTestAction()
{
$vodService = new VodService();
$result = $vodService->test();
if ($result) {
return $this->ajaxSuccess(['msg' => '接口返回成功']);
} else {
return $this->ajaxError(['msg' => '接口返回失败,请检查相关配置']);
}
}
/**
* @Get("/live/push", name="admin.test.live.push")
*/
public function livePushTestAction()
{
$liveService = new LiveService();
$pushUrl = $liveService->getPushUrl('test');
$obs = new \stdClass();
$position = strrpos($pushUrl, '/');
$obs->fms_url = substr($pushUrl, 0, $position + 1);
$obs->stream_code = substr($pushUrl, $position + 1);
$this->view->pick('config/live_push_test');
$this->view->setVar('push_url', $pushUrl);
$this->view->setVar('obs', $obs);
}
/**
* @Get("/live/pull", name="admin.test.live.pull")
*/
public function livePullTestAction()
{
$liveService = new LiveService();
$m3u8PullUrls = $liveService->getPullUrls('test', 'm3u8');
$flvPullUrls = $liveService->getPullUrls('test', 'flv');
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('public/live_player');
$this->view->setVar('m3u8_pull_urls', $m3u8PullUrls);
$this->view->setVar('flv_pull_urls', $flvPullUrls);
}
/**
* @Post("/smser", name="admin.test.smser")
*/
public function smserTestAction()
{
$phone = $this->request->getPost('phone');
$smserService = new SmserService();
$response = $smserService->sendTestMessage($phone);
if ($response) {
return $this->ajaxSuccess(['msg' => '发送短信成功,请到收件箱确认']);
} else {
return $this->ajaxError(['msg' => '发送短信失败,请查看短信日志']);
}
}
/**
* @Post("/mailer", name="admin.test.mailer")
*/
public function mailerTestAction()
{
$email = $this->request->getPost('email');
$mailerService = new MailerService();
$result = $mailerService->sendTestMail($email);
if ($result) {
return $this->ajaxSuccess(['msg' => '发送邮件成功,请到收件箱确认']);
} else {
return $this->ajaxError(['msg' => '发送邮件失败,请检查配置']);
}
}
/**
* @Post("/captcha", name="admin.test.captcha")
*/
public function captchaTestAction()
{
$post = $this->request->getPost();
$captchaService = new CaptchaService();
$result = $captchaService->verify($post['ticket'], $post['rand']);
if ($result) {
$configService = new ConfigService();
$configService->updateSectionConfig('captcha', ['enabled' => 1]);
return $this->ajaxSuccess(['msg' => '后台验证成功']);
} else {
return $this->ajaxError(['msg' => '后台验证失败']);
}
}
/**
* @Get("/alipay", name="admin.test.alipay")
*/
public function alipayTestAction()
{
$alipayTestService = new AlipayTestService();
$this->db->begin();
$order = $alipayTestService->createTestOrder();
$trade = $alipayTestService->createTestTrade($order);
$qrcode = $alipayTestService->getTestQrCode($trade);
if ($order->id > 0 && $trade->id > 0 && $qrcode) {
$this->db->commit();
} else {
$this->db->rollback();
}
$this->view->pick('config/alipay_test');
$this->view->setVar('trade', $trade);
$this->view->setVar('qrcode', $qrcode);
}
/**
* @Post("/alipay/status", name="admin.test.alipay.status")
*/
public function alipayTestStatusAction()
{
$sn = $this->request->getPost('sn');
$alipayTestService = new AlipayTestService();
$status = $alipayTestService->getTestStatus($sn);
return $this->ajaxSuccess(['status' => $status]);
}
/**
* @Post("/alipay/cancel", name="admin.test.alipay.cancel")
*/
public function alipayTestCancelAction()
{
$sn = $this->request->getPost('sn');
$alipayTestService = new AlipayTestService();
$alipayTestService->cancelTestOrder($sn);
return $this->ajaxSuccess(['msg' => '取消订单成功']);
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Trade as TradeService;
/**
* @RoutePrefix("/admin/trade")
*/
class TradeController extends Controller
{
/**
* @Get("/search", name="admin.trade.search")
*/
public function searchAction()
{
}
/**
* @Get("/list", name="admin.trade.list")
*/
public function listAction()
{
$tradeService = new TradeService();
$pager = $tradeService->getTrades();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id}/show", name="admin.trade.show")
*/
public function showAction($id)
{
$tradeService = new TradeService();
$trade = $tradeService->getTrade($id);
$refunds = $tradeService->getRefunds($trade->sn);
$order = $tradeService->getOrder($trade->order_sn);
$user = $tradeService->getUser($trade->user_id);
$this->view->setVar('refunds', $refunds);
$this->view->setVar('trade', $trade);
$this->view->setVar('order', $order);
$this->view->setVar('user', $user);
}
/**
* @Post("/{id}/close", name="admin.trade.close")
*/
public function closeAction($id)
{
$tradeService = new TradeService();
$tradeService->closeTrade($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '关闭交易成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Post("/{id}/refund", name="admin.trade.refund")
*/
public function refundAction($id)
{
$tradeService = new TradeService();
$tradeService->refundTrade($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '申请退款成功,请到退款管理中审核确认',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\User as UserService;
/**
* @RoutePrefix("/admin/user")
*/
class UserController extends Controller
{
/**
* @Get("/search", name="admin.user.search")
*/
public function searchAction()
{
$userService = new UserService();
$roles = $userService->getRoles();
$this->view->setVar('roles', $roles);
}
/**
* @Get("/list", name="admin.user.list")
*/
public function listAction()
{
$userService = new UserService();
$pager = $userService->getUsers();
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id}/show", name="admin.user.show")
*/
public function showAction($id)
{
}
/**
* @Get("/add", name="admin.user.add")
*/
public function addAction()
{
$userService = new UserService();
$roles = $userService->getRoles();
$this->view->setVar('roles', $roles);
}
/**
* @Post("/create", name="admin.user.create")
*/
public function createAction()
{
$userService = new UserService();
$userService->createUser();
$location = $this->url->get(['for' => 'admin.user.list']);
$content = [
'location' => $location,
'msg' => '新增用户成功',
];
return $this->ajaxSuccess($content);
}
/**
* @Get("/{id}/edit", name="admin.user.edit")
*/
public function editAction($id)
{
$userService = new UserService();
$user = $userService->getUser($id);
$roles = $userService->getRoles();
$this->view->setVar('user', $user);
$this->view->setVar('roles', $roles);
}
/**
* @Post("/{id}/update", name="admin.user.update")
*/
public function updateAction($id)
{
$userService = new UserService();
$userService->updateUser($id);
$location = $this->url->get(['for' => 'admin.user.list']);
$content = [
'location' => $location,
'msg' => '更新用户成功',
];
return $this->ajaxSuccess($content);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Models\Learning as LearningModel;
use App\Services\Learning as LearningService;
use App\Services\Vod as VodService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/admin/vod")
*/
class VodController extends Controller
{
/**
* @Post("/upload/signature", name="admin.vod.upload.signature")
*/
public function uploadSignatureAction()
{
$service = new VodService();
$signature = $service->getUploadSignature();
return $this->ajaxSuccess(['signature' => $signature]);
}
/**
* @Get("/player", name="admin.vod.player")
*/
public function playerAction()
{
$courseId = $this->request->getQuery('course_id');
$chapterId = $this->request->getQuery('chapter_id');
$playUrl = $this->request->getQuery('play_url');
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('public/vod_player');
$this->view->setVar('course_id', $courseId);
$this->view->setVar('chapter_id', $chapterId);
$this->view->setVar('play_url', urldecode($playUrl));
}
/**
* @Get("/learning", name="admin.vod.learning")
*/
public function learningAction()
{
$query = $this->request->getQuery();
$learning = new LearningModel();
$learning->user_id = $this->authUser->id;
$learning->request_id = $query['request_id'];
$learning->course_id = $query['course_id'];
$learning->chapter_id = $query['chapter_id'];
$learning->position = $query['position'];
$learningService = new LearningService();
$learningService->save($learning, $query['timeout']);
return $this->ajaxSuccess();
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\XmCourse as XmCourseService;
/**
* @RoutePrefix("/admin/xm/course")
*/
class XmCourseController extends Controller
{
/**
* @Get("/all", name="admin.xm.course.all")
*/
public function allAction()
{
$xmCourseService = new XmCourseService();
$pager = $xmCourseService->getAllCourses();
return $this->ajaxSuccess([
'count' => $pager->total_items,
'data' => $pager->items,
]);
}
/**
* @Get("/paid", name="admin.xm.course.paid")
*/
public function paidAction()
{
$xmCourseService = new XmCourseService();
$pager = $xmCourseService->getPaidCourses();
return $this->ajaxSuccess([
'count' => $pager->total_items,
'data' => $pager->items,
]);
}
}

35
app/Http/Admin/Module.php Normal file
View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Admin;
use App\Http\Admin\Services\AuthUser;
use Phalcon\DiInterface;
use Phalcon\Mvc\ModuleDefinitionInterface;
use Phalcon\Mvc\View;
class Module implements ModuleDefinitionInterface
{
public function registerAutoLoaders(DiInterface $di = null)
{
}
public function registerServices(DiInterface $di)
{
$di->set('view', function () {
$view = new View();
$view->setViewsDir(__DIR__ . '/Views');
$view->registerEngines([
'.volt' => 'volt',
]);
return $view;
});
$di->setShared('auth', function () {
$authUser = new AuthUser();
return $authUser;
});
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Order as OrderModel;
use App\Models\Trade as TradeModel;
use App\Repos\Order as OrderRepo;
use App\Repos\Trade as TradeRepo;
use App\Services\Alipay as AlipayService;
class AlipayTest extends PaymentTest
{
/**
* 获取测试二维码
*
* @param TradeModel $trade
* @return mixed
*/
public function getTestQrCode($trade)
{
$outOrder = [
'out_trade_no' => $trade->sn,
'total_amount' => $trade->amount,
'subject' => $trade->subject,
];
$alipayService = new AlipayService();
$qrcode = $alipayService->getQrCode($outOrder);
$result = $qrcode ?: false;
return $result;
}
/**
* 取消测试订单
*
* @param string $sn
*/
public function cancelTestOrder($sn)
{
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findBySn($sn);
$orderRepo = new OrderRepo();
$order = $orderRepo->findBySn($trade->order_sn);
$alipayService = new AlipayService();
$response = $alipayService->cancelOrder($trade->sn);
if ($response) {
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
if ($order->status != OrderModel::STATUS_PENDING) {
$order->status = OrderModel::STATUS_PENDING;
$order->update();
}
}
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Repos\Audit as AuditRepo;
class Audit extends Service
{
public function getAudits()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$auditRepo = new AuditRepo();
$pager = $auditRepo->paginate($params, $sort, $page, $limit);
return $pager;
}
public function getAudit($id)
{
$auditRepo = new AuditRepo();
$audit = $auditRepo->findById($id);
return $audit;
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace App\Http\Admin\Services;
use Phalcon\Mvc\User\Component as UserComponent;
class AuthMenu extends UserComponent
{
protected $nodes = [];
protected $ownedRoutes = [];
protected $owned1stLevelIds = [];
protected $owned2ndLevelIds = [];
protected $owned3rdLevelIds = [];
protected $authUser;
public function __construct()
{
$this->authUser = $this->getAuthUser();
$this->nodes = $this->getAllNodes();
$this->setOwnedLevelIds();
}
public function getTopMenus()
{
$menus = [];
foreach ($this->nodes as $node) {
if ($this->authUser->admin || in_array($node['id'], $this->owned1stLevelIds)) {
$menus[] = [
'id' => $node['id'],
'label' => $node['label'],
];
}
}
return kg_array_object($menus);
}
public function getLeftMenus()
{
$menus = [];
foreach ($this->nodes as $key => $level) {
foreach ($level['child'] as $key2 => $level2) {
foreach ($level2['child'] as $key3 => $level3) {
$hasRight = $this->authUser->admin || in_array($level3['id'], $this->owned3rdLevelIds);
if ($level3['type'] == 'menu' && $hasRight) {
$menus[$key]['id'] = $level['id'];
$menus[$key]['label'] = $level['label'];
$menus[$key]['child'][$key2]['id'] = $level2['id'];
$menus[$key]['child'][$key2]['label'] = $level2['label'];
$menus[$key]['child'][$key2]['child'][$key3] = [
'id' => $level3['id'],
'label' => $level3['label'],
'url' => $this->url->get(['for' => $level3['route']]),
];
}
}
}
}
return kg_array_object($menus);
}
protected function setOwnedLevelIds()
{
$routeIdMapping = $this->getRouteIdMapping();
if (!$routeIdMapping) return;
$owned1stLevelIds = [];
$owned2ndLevelIds = [];
$owned3rdLevelIds = [];
foreach ($routeIdMapping as $key => $value) {
$ids = explode('-', $value);
if (in_array($key, $this->authUser->routes)) {
$owned1stLevelIds[] = $ids[0];
$owned2ndLevelIds[] = $ids[0] . '-' . $ids[1];
$owned3rdLevelIds[] = $value;
}
}
$this->owned1stLevelIds = array_unique($owned1stLevelIds);
$this->owned2ndLevelIds = array_unique($owned2ndLevelIds);
$this->owned3rdLevelIds = array_unique($owned3rdLevelIds);
}
protected function getRouteIdMapping()
{
$mapping = [];
foreach ($this->nodes as $level) {
foreach ($level['child'] as $level2) {
foreach ($level2['child'] as $level3) {
if ($level3['type'] == 'menu') {
$mapping[$level3['route']] = $level3['id'];
}
}
}
}
return $mapping;
}
protected function getAllNodes()
{
$authNode = new AuthNode();
return $authNode->getAllNodes();
}
protected function getAuthUser()
{
$auth = $this->getDI()->get('auth');
return $auth->getAuthUser();
}
}

View File

@ -0,0 +1,582 @@
<?php
namespace App\Http\Admin\Services;
class AuthNode extends Service
{
public function getAllNodes()
{
$nodes = [];
$nodes[] = $this->getContentNodes();
$nodes[] = $this->getOperationNodes();
$nodes[] = $this->getFinanceNodes();
$nodes[] = $this->getUserNodes();
$nodes[] = $this->getConfigNodes();
return $nodes;
}
protected function getContentNodes()
{
$nodes = [
'id' => '1',
'label' => '内容管理',
'child' => [
[
'id' => '1-1',
'label' => '课程管理',
'type' => 'menu',
'child' => [
[
'id' => '1-1-1',
'label' => '课程列表',
'type' => 'menu',
'route' => 'admin.course.list',
],
[
'id' => '1-1-2',
'label' => '搜索课程',
'type' => 'menu',
'route' => 'admin.course.search',
],
[
'id' => '1-1-3',
'label' => '添加课程',
'type' => 'menu',
'route' => 'admin.course.add',
],
[
'id' => '1-1-4',
'label' => '编辑课程',
'type' => 'button',
'route' => 'admin.course.edit',
],
[
'id' => '1-1-5',
'label' => '删除课程',
'type' => 'button',
'route' => 'admin.course.edit',
],
],
],
[
'id' => '1-2',
'label' => '分类管理',
'type' => 'menu',
'child' => [
[
'id' => '1-2-1',
'label' => '分类列表',
'type' => 'menu',
'route' => 'admin.category.list',
],
[
'id' => '1-2-2',
'label' => '添加分类',
'type' => 'menu',
'route' => 'admin.category.add',
],
[
'id' => '1-2-3',
'label' => '编辑分类',
'type' => 'button',
'route' => 'admin.category.edit',
],
[
'id' => '1-2-4',
'label' => '删除分类',
'type' => 'button',
'route' => 'admin.category.delete',
],
],
],
[
'id' => '1-3',
'label' => '套餐管理',
'type' => 'menu',
'child' => [
[
'id' => '1-3-1',
'label' => '套餐列表',
'type' => 'menu',
'route' => 'admin.package.list',
],
[
'id' => '1-3-2',
'label' => '添加套餐',
'type' => 'menu',
'route' => 'admin.package.add',
],
[
'id' => '1-3-3',
'label' => '编辑套餐',
'type' => 'button',
'route' => 'admin.package.edit',
],
[
'id' => '1-3-4',
'label' => '删除套餐',
'type' => 'button',
'route' => 'admin.package.delete',
],
],
],
],
];
return $nodes;
}
protected function getOperationNodes()
{
$nodes = [
'id' => '2',
'label' => '运营管理',
'child' => [
[
'id' => '2-5',
'label' => '学员管理',
'type' => 'menu',
'child' => [
[
'id' => '2-5-1',
'label' => '学员列表',
'type' => 'menu',
'route' => 'admin.student.list',
],
[
'id' => '2-5-2',
'label' => '搜索学员',
'type' => 'menu',
'route' => 'admin.student.search',
],
[
'id' => '2-5-3',
'label' => '添加学员',
'type' => 'menu',
'route' => 'admin.student.add',
],
[
'id' => '2-5-4',
'label' => '编辑学员',
'type' => 'button',
'route' => 'admin.student.edit',
],
],
],
[
'id' => '2-1',
'label' => '评价管理',
'type' => 'menu',
'child' => [
[
'id' => '2-1-1',
'label' => '评价列表',
'type' => 'menu',
'route' => 'admin.review.list',
],
[
'id' => '2-1-2',
'label' => '搜索评价',
'type' => 'menu',
'route' => 'admin.review.search',
],
[
'id' => '2-1-3',
'label' => '编辑评价',
'type' => 'button',
'route' => 'admin.review.edit',
],
[
'id' => '2-1-4',
'label' => '删除评价',
'type' => 'button',
'route' => 'admin.review.delete',
],
],
],
[
'id' => '2-3',
'label' => '轮播管理',
'type' => 'menu',
'child' => [
[
'id' => '2-3-1',
'label' => '轮播列表',
'type' => 'menu',
'route' => 'admin.slide.list',
],
[
'id' => '2-3-2',
'label' => '添加轮播',
'type' => 'menu',
'route' => 'admin.slide.add',
],
[
'id' => '2-3-3',
'label' => '编辑轮播',
'type' => 'button',
'route' => 'admin.slide.edit',
],
[
'id' => '2-3-4',
'label' => '删除轮播',
'type' => 'button',
'route' => 'admin.slide.delete',
],
],
],
[
'id' => '2-4',
'label' => '单页管理',
'type' => 'menu',
'child' => [
[
'id' => '2-4-1',
'label' => '单页列表',
'type' => 'menu',
'route' => 'admin.page.list',
],
[
'id' => '2-4-2',
'label' => '添加单页',
'type' => 'menu',
'route' => 'admin.page.add',
],
[
'id' => '2-4-3',
'label' => '编辑单页',
'type' => 'button',
'route' => 'admin.page.edit',
],
[
'id' => '2-4-4',
'label' => '删除单页',
'type' => 'button',
'route' => 'admin.page.delete',
],
],
],
[
'id' => '2-6',
'label' => '导航管理',
'type' => 'menu',
'child' => [
[
'id' => '2-6-1',
'label' => '导航列表',
'type' => 'menu',
'route' => 'admin.nav.list',
],
[
'id' => '2-6-2',
'label' => '添加导航',
'type' => 'menu',
'route' => 'admin.nav.add',
],
[
'id' => '2-6-3',
'label' => '编辑导航',
'type' => 'button',
'route' => 'admin.nav.edit',
],
[
'id' => '2-6-4',
'label' => '删除导航',
'type' => 'button',
'route' => 'admin.nav.delete',
],
],
],
],
];
return $nodes;
}
protected function getFinanceNodes()
{
$nodes = [
'id' => '3',
'label' => '财务管理',
'child' => [
[
'id' => '3-1',
'label' => '订单管理',
'type' => 'menu',
'child' => [
[
'id' => '3-1-1',
'label' => '订单列表',
'type' => 'menu',
'route' => 'admin.order.list',
],
[
'id' => '3-1-2',
'label' => '搜索订单',
'type' => 'menu',
'route' => 'admin.order.search',
],
[
'id' => '3-1-3',
'label' => '订单详情',
'type' => 'button',
'route' => 'admin.order.show',
],
[
'id' => '3-1-4',
'label' => '关闭订单',
'type' => 'button',
'route' => 'admin.order.close',
],
],
],
[
'id' => '3-2',
'label' => '交易管理',
'type' => 'menu',
'child' => [
[
'id' => '3-2-1',
'label' => '交易记录',
'type' => 'menu',
'route' => 'admin.trade.list',
],
[
'id' => '3-2-2',
'label' => '搜索交易',
'type' => 'menu',
'route' => 'admin.trade.search',
],
[
'id' => '3-2-3',
'label' => '关闭交易',
'type' => 'button',
'route' => 'admin.trade.close',
],
[
'id' => '3-2-4',
'label' => '交易退款',
'type' => 'button',
'route' => 'admin.trade.refund',
],
],
],
[
'id' => '3-3',
'label' => '退款管理',
'type' => 'menu',
'child' => [
[
'id' => '3-3-1',
'label' => '退款列表',
'type' => 'menu',
'route' => 'admin.refund.list',
],
[
'id' => '3-3-2',
'label' => '搜索退款',
'type' => 'menu',
'route' => 'admin.refund.search',
],
[
'id' => '3-3-3',
'label' => '退款详情',
'type' => 'button',
'route' => 'admin.refund.show',
],
[
'id' => '3-3-4',
'label' => '审核退款',
'type' => 'button',
'route' => 'admin.refund.review',
],
],
],
],
];
return $nodes;
}
protected function getUserNodes()
{
$nodes = [
'id' => '4',
'label' => '用户管理',
'child' => [
[
'id' => '4-1',
'label' => '用户管理',
'type' => 'menu',
'child' => [
[
'id' => '4-1-1',
'label' => '用户列表',
'type' => 'menu',
'route' => 'admin.user.list',
],
[
'id' => '4-1-2',
'label' => '搜索用户',
'type' => 'menu',
'route' => 'admin.user.search',
],
[
'id' => '4-1-3',
'label' => '添加用户',
'type' => 'menu',
'route' => 'admin.user.add',
],
[
'id' => '4-1-4',
'label' => '编辑用户',
'type' => 'button',
'route' => 'admin.user.edit',
]
],
],
[
'id' => '4-2',
'label' => '角色管理',
'type' => 'menu',
'child' => [
[
'id' => '4-2-1',
'label' => '角色列表',
'type' => 'menu',
'route' => 'admin.role.list',
],
[
'id' => '4-2-2',
'label' => '添加角色',
'type' => 'menu',
'route' => 'admin.role.add',
],
[
'id' => '4-2-3',
'label' => '编辑角色',
'type' => 'button',
'route' => 'admin.role.edit',
],
[
'id' => '4-2-4',
'label' => '删除角色',
'type' => 'button',
'route' => 'admin.role.delete',
]
],
],
[
'id' => '4-3',
'label' => '操作记录',
'type' => 'menu',
'child' => [
[
'id' => '4-3-1',
'label' => '记录列表',
'type' => 'menu',
'route' => 'admin.audit.list',
],
[
'id' => '4-3-2',
'label' => '搜索记录',
'type' => 'menu',
'route' => 'admin.audit.search',
],
[
'id' => '4-3-3',
'label' => '浏览记录',
'type' => 'button',
'route' => 'admin.audit.show',
],
],
],
],
];
return $nodes;
}
protected function getConfigNodes()
{
$nodes = [
'id' => '5',
'label' => '系统配置',
'child' => [
[
'id' => '5-1',
'label' => '配置管理',
'type' => 'menu',
'child' => [
[
'id' => '5-1-1',
'label' => '网站设置',
'type' => 'menu',
'route' => 'admin.config.website',
],
[
'id' => '5-1-2',
'label' => '密钥设置',
'type' => 'menu',
'route' => 'admin.config.secret',
],
[
'id' => '5-1-3',
'label' => '存储设置',
'type' => 'menu',
'route' => 'admin.config.storage',
],
[
'id' => '5-1-4',
'label' => '点播设置',
'type' => 'menu',
'route' => 'admin.config.vod',
],
[
'id' => '5-1-5',
'label' => '直播设置',
'type' => 'menu',
'route' => 'admin.config.live',
],
[
'id' => '5-1-6',
'label' => '短信设置',
'type' => 'menu',
'route' => 'admin.config.smser',
],
[
'id' => '5-1-7',
'label' => '邮件设置',
'type' => 'menu',
'route' => 'admin.config.mailer',
],
[
'id' => '5-1-8',
'label' => '验证码设置',
'type' => 'menu',
'route' => 'admin.config.captcha',
],
[
'id' => '5-1-9',
'label' => '支付设置',
'type' => 'menu',
'route' => 'admin.config.payment',
],
[
'id' => '5-1-10',
'label' => '会员设置',
'type' => 'menu',
'route' => 'admin.config.vip',
]
],
],
],
];
return $nodes;
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Role as RoleModel;
use App\Models\User as UserModel;
use Phalcon\Mvc\User\Component as UserComponent;
class AuthUser extends UserComponent
{
/**
* 判断权限
*
* @param string $route
* @return bool
*/
public function hasPermission($route)
{
$authUser = $this->getAuthUser();
if ($authUser->admin) return true;
if (in_array($route, $authUser->routes)) return true;
return false;
}
/**
* 写入会话
*
* @param UserModel $user
*/
public function setAuthUser(UserModel $user)
{
$role = RoleModel::findFirstById($user->admin_role);
if ($role->id == RoleModel::ROLE_ADMIN) {
$admin = 1;
$routes = [];
} else {
$admin = 0;
$routes = $role->routes;
}
$authKey = $this->getAuthKey();
$authUser = new \stdClass();
$authUser->id = $user->id;
$authUser->name = $user->name;
$authUser->avatar = $user->avatar;
$authUser->admin = $admin;
$authUser->routes = $routes;
$this->session->set($authKey, $authUser);
}
/**
* 清除会话
*/
public function removeAuthUser()
{
$authKey = $this->getAuthKey();
$this->session->remove($authKey);
}
/**
* 读取会话
*
* @return mixed
*/
public function getAuthUser()
{
$authKey = $this->getAuthKey();
return $this->session->get($authKey);
}
/**
* 获取会话键值
*
* @return string
*/
public function getAuthKey()
{
return 'admin';
}
}

View File

@ -0,0 +1,163 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Category as CategoryModel;
use App\Repos\Category as CategoryRepo;
use App\Validators\Category as CategoryValidator;
class Category extends Service
{
public function getCategory($id)
{
$category = $this->findOrFail($id);
return $category;
}
public function getParentCategory($id)
{
if ($id > 0) {
$parent = CategoryModel::findFirst($id);
} else {
$parent = new CategoryModel();
$parent->id = 0;
$parent->level = 0;
}
return $parent;
}
public function getTopCategories()
{
$categoryRepo = new CategoryRepo();
$categories = $categoryRepo->findAll([
'parent_id' => 0,
'deleted' => 0,
]);
return $categories;
}
public function getChildCategories($parentId)
{
$deleted = $this->request->getQuery('deleted', 'int', 0);
$categoryRepo = new CategoryRepo();
$categories = $categoryRepo->findAll([
'parent_id' => $parentId,
'deleted' => $deleted,
]);
return $categories;
}
public function createCategory()
{
$post = $this->request->getPost();
$validator = new CategoryValidator();
$data = [
'parent_id' => 0,
'published' => 1,
];
$parent = null;
if ($post['parent_id'] > 0) {
$parent = $validator->checkParent($post['parent_id']);
$data['parent_id'] = $parent->id;
}
$data['name'] = $validator->checkName($post['name']);
$data['priority'] = $validator->checkPriority($post['priority']);
$data['published'] = $validator->checkPublishStatus($post['published']);
$category = new CategoryModel();
$category->create($data);
if ($parent) {
$category->path = $parent->path . $category->id . ',';
$category->level = $parent->level + 1;
} else {
$category->path = ',' . $category->id . ',';
$category->level = 1;
}
$category->update();
return $category;
}
public function updateCategory($id)
{
$category = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new CategoryValidator();
$data = [];
if (isset($post['name'])) {
$data['name'] = $validator->checkName($post['name']);
}
if (isset($post['priority'])) {
$data['priority'] = $validator->checkPriority($post['priority']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
$category->update($data);
return $category;
}
public function deleteCategory($id)
{
$category = $this->findOrFail($id);
if ($category->deleted == 1) {
return false;
}
$category->deleted = 1;
$category->update();
return $category;
}
public function restoreCategory($id)
{
$category = $this->findOrFail($id);
if ($category->deleted == 0) {
return false;
}
$category->deleted = 0;
$category->update();
return $category;
}
protected function findOrFail($id)
{
$validator = new CategoryValidator();
$result = $validator->checkCategory($id);
return $result;
}
}

View File

@ -0,0 +1,248 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Chapter as ChapterModel;
use App\Models\Course as CourseModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Services\CourseStats as CourseStatsService;
use App\Validators\Chapter as ChapterValidator;
class Chapter extends Service
{
public function getCourse($courseId)
{
$courseRepo = new CourseRepo();
$result = $courseRepo->findById($courseId);
return $result;
}
public function getCourseChapters($courseId)
{
$chapterRepo = new ChapterRepo();
$result = $chapterRepo->findAll([
'course_id' => $courseId,
'parent_id' => 0,
'deleted' => 0,
]);
return $result;
}
public function getLessons($parentId)
{
$deleted = $this->request->getQuery('deleted', 'int', 0);
$chapterRepo = new ChapterRepo();
$result = $chapterRepo->findAll([
'parent_id' => $parentId,
'deleted' => $deleted,
]);
return $result;
}
public function getChapter($id)
{
$chapter = $this->findOrFail($id);
return $chapter;
}
public function createChapter()
{
$post = $this->request->getPost();
$validator = new ChapterValidator();
$data = [];
$data['course_id'] = $validator->checkCourseId($post['course_id']);
$data['title'] = $validator->checkTitle($post['title']);
$data['summary'] = $validator->checkSummary($post['summary']);
$data['free'] = $validator->checkFreeStatus($post['free']);
$chapterRepo = new ChapterRepo();
if (isset($post['parent_id'])) {
$data['parent_id'] = $validator->checkParentId($post['parent_id']);
$data['priority'] = $chapterRepo->maxLessonPriority($post['parent_id']);
} else {
$data['priority'] = $chapterRepo->maxChapterPriority($post['course_id']);
}
$data['priority'] += 1;
$chapter = new ChapterModel();
$chapter->create($data);
$this->updateChapterStats($chapter);
$this->updateCourseStats($chapter);
return $chapter;
}
public function updateChapter($id)
{
$chapter = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new ChapterValidator();
$data = [];
if (isset($post['title'])) {
$data['title'] = $validator->checkTitle($post['title']);
}
if (isset($post['summary'])) {
$data['summary'] = $validator->checkSummary($post['summary']);
}
if (isset($post['priority'])) {
$data['priority'] = $validator->checkPriority($post['priority']);
}
if (isset($post['free'])) {
$data['free'] = $validator->checkFreeStatus($post['free']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
if ($post['published'] == 1) {
$validator->checkPublishAbility($chapter);
}
}
$chapter->update($data);
$this->updateChapterStats($chapter);
$this->updateCourseStats($chapter);
return $chapter;
}
public function deleteChapter($id)
{
$chapter = $this->findOrFail($id);
if ($chapter->deleted == 1) {
return false;
}
$chapter->deleted = 1;
$chapter->update();
if ($chapter->parent_id == 0) {
$this->deleteChildChapters($chapter->id);
}
return $chapter;
}
public function restoreChapter($id)
{
$chapter = $this->findOrFail($id);
if ($chapter->deleted == 0) {
return false;
}
$chapter->deleted = 0;
$chapter->update();
if ($chapter->parent_id == 0) {
$this->restoreChildChapters($chapter->id);
}
$this->updateChapterStats($chapter);
$this->updateCourseStats($chapter);
return $chapter;
}
protected function deleteChildChapters($parentId)
{
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findAll(['parent_id' => $parentId]);
if ($chapters->count() == 0) {
return;
}
foreach ($chapters as $chapter) {
$chapter->deleted = 1;
$chapter->update();
}
}
protected function restoreChildChapters($parentId)
{
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findAll(['parent_id' => $parentId]);
if ($chapters->count() == 0) {
return;
}
foreach ($chapters as $chapter) {
$chapter->deleted = 0;
$chapter->update();
}
}
protected function updateChapterStats($chapter)
{
$chapterRepo = new ChapterRepo();
if ($chapter->parent_id > 0) {
$chapter = $chapterRepo->findById($chapter->parent_id);
}
$lessonCount = $chapterRepo->countLessons($chapter->id);
$chapter->lesson_count = $lessonCount;
$chapter->update();
}
protected function updateCourseStats($chapter)
{
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($chapter->course_id);
$courseStats = new CourseStatsService();
$courseStats->updateLessonCount($course->id);
if ($course->model == CourseModel::MODEL_VOD) {
$courseStats->updateVodDuration($course->id);
} elseif ($course->model == CourseModel::MODEL_LIVE) {
$courseStats->updateLiveDateRange($course->id);
} elseif ($course->model == CourseModel::MODEL_ARTICLE) {
$courseStats->updateArticleWordCount($course->id);
}
}
protected function findOrFail($id)
{
$validator = new ChapterValidator();
$result = $validator->checkChapter($id);
return $result;
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Util\Word as WordUtil;
use App\Models\Chapter as ChapterModel;
use App\Models\Course as CourseModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Services\CourseStats as CourseStatsService;
use App\Services\Vod as VodService;
use App\Validators\ChapterArticle as ChapterArticleValidator;
use App\Validators\ChapterLive as ChapterLiveValidator;
use App\Validators\ChapterVod as ChapterVodValidator;
class ChapterContent extends Service
{
public function getChapterVod($chapterId)
{
$chapterRepo = new ChapterRepo();
$result = $chapterRepo->findChapterVod($chapterId);
return $result;
}
public function getChapterLive($chapterId)
{
$chapterRepo = new ChapterRepo();
$result = $chapterRepo->findChapterLive($chapterId);
return $result;
}
public function getChapterArticle($chapterId)
{
$chapterRepo = new ChapterRepo();
$result = $chapterRepo->findChapterArticle($chapterId);
return $result;
}
public function getTranslatedFiles($fileId)
{
if (!$fileId) return;
$vodService = new VodService();
$mediaInfo = $vodService->getMediaInfo($fileId);
if (!$mediaInfo) return;
$result = [];
$files = $mediaInfo['MediaInfoSet'][0]['TranscodeInfo']['TranscodeSet'];
foreach ($files as $file) {
if ($file['Definition'] == 0) {
continue;
}
$result[] = [
'play_url' => $vodService->getPlayUrl($file['Url']),
'width' => $file['Width'],
'height' => $file['Height'],
'definition' => $file['Definition'],
'duration' => kg_play_duration($file['Duration']),
'format' => pathinfo($file['Url'], PATHINFO_EXTENSION),
'size' => sprintf('%0.2f', $file['Size'] / 1024 / 1024),
'bit_rate' => intval($file['Bitrate'] / 1024),
];
}
return kg_array_object($result);
}
public function updateChapterContent($chapterId)
{
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterId);
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($chapter->course_id);
switch ($course->model) {
case CourseModel::MODEL_VOD:
$this->updateChapterVod($chapter);
break;
case CourseModel::MODEL_LIVE:
$this->updateChapterLive($chapter);
break;
case CourseModel::MODEL_ARTICLE:
$this->updateChapterArticle($chapter);
break;
}
}
protected function updateChapterVod($chapter)
{
$post = $this->request->getPost();
$validator = new ChapterVodValidator();
$fileId = $validator->checkFileId($post['file_id']);
$chapterRepo = new ChapterRepo();
$vod = $chapterRepo->findChapterVod($chapter->id);
if ($fileId == $vod->file_id) {
return;
}
$vod->update(['file_id' => $fileId]);
$attrs = $chapter->attrs;
$attrs->file_id = $fileId;
$attrs->file_status = ChapterModel::FS_UPLOADED;
$chapter->update(['attrs' => $attrs]);
}
protected function updateChapterLive($chapter)
{
$post = $this->request->getPost();
$chapterRepo = new ChapterRepo();
$live = $chapterRepo->findChapterLive($chapter->id);
$validator = new ChapterLiveValidator();
$data = [];
$data['start_time'] = $validator->checkStartTime($post['start_time']);
$data['end_time'] = $validator->checkEndTime($post['end_time']);
$validator->checkTimeRange($post['start_time'], $post['end_time']);
$live->update($data);
$attrs = $chapter->attrs;
$attrs->start_time = $data['start_time'];
$attrs->end_time = $data['end_time'];
$chapter->update(['attrs' => $attrs]);
$courseStats = new CourseStatsService();
$courseStats->updateLiveDateRange($chapter->course_id);
}
protected function updateChapterArticle($chapter)
{
$post = $this->request->getPost();
$chapterRepo = new ChapterRepo();
$article = $chapterRepo->findChapterArticle($chapter->id);
$validator = new ChapterArticleValidator();
$data = [];
$data['content'] = $validator->checkContent($post['content']);
$article->update($data);
$attrs = $chapter->attrs;
$attrs->word_count = WordUtil::getWordCount($article->content);
$chapter->update(['attrs' => $attrs]);
$courseStats = new CourseStatsService();
$courseStats->updateArticleWordCount($chapter->course_id);
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Http\Admin\Services;
use App\Caches\Config as ConfigCache;
use App\Repos\Config as ConfigRepo;
class Config extends Service
{
public function getSectionConfig($section)
{
$configRepo = new ConfigRepo();
$items = $configRepo->findBySection($section);
$result = new \stdClass();
if ($items->count() > 0) {
foreach ($items as $item) {
$result->{$item->item_key} = $item->item_value;
}
}
return $result;
}
public function updateSectionConfig($section, $config)
{
$configRepo = new ConfigRepo();
foreach ($config as $key => $value) {
$item = $configRepo->findItem($section, $key);
if ($item) {
$item->item_value = trim($value);
$item->update();
}
}
$configCache = new ConfigCache();
$configCache->delete();
}
public function updateStorageConfig($section, $config)
{
$protocol = ['http://', 'https://'];
if (isset($config['bucket_domain'])) {
$config['bucket_domain'] = str_replace($protocol, '', $config['bucket_domain']);
}
if (isset($config['ci_domain'])) {
$config['ci_domain'] = str_replace($protocol, '', $config['ci_domain']);
}
$this->updateSectionConfig($section, $config);
}
public function updateVodConfig($section, $config)
{
$this->updateSectionConfig($section, $config);
}
public function updateLiveConfig($section, $config)
{
$protocol = ['http://', 'https://'];
if (isset($config['push_domain'])) {
$config['push_domain'] = str_replace($protocol, '', $config['push_domain']);
}
if (isset($config['pull_domain'])) {
$config['pull_domain'] = str_replace($protocol, '', $config['pull_domain']);
}
if (isset($config['ptt'])) {
$ptt = $config['ptt'];
$keys = array_keys($ptt['id']);
$myPtt = [];
foreach ($keys as $key) {
$myPtt[$key] = [
'id' => $ptt['id'][$key],
'bit_rate' => $ptt['bit_rate'][$key],
'summary' => $ptt['summary'][$key],
'height' => $ptt['height'][$key],
];
}
$config['pull_trans_template'] = kg_json_encode($myPtt);
}
$this->updateSectionConfig($section, $config);
}
public function updateSmserConfig($section, $config)
{
$template = $config['template'];
$keys = array_keys($template['id']);
$myTemplate = [];
foreach ($keys as $key) {
$myTemplate[$key] = [
'id' => $template['id'][$key],
'content' => $template['content'][$key],
];
}
$config['template'] = kg_json_encode($myTemplate);
$this->updateSectionConfig($section, $config);
}
}

View File

@ -0,0 +1,485 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Course as CourseModel;
use App\Models\CourseCategory as CourseCategoryModel;
use App\Models\CourseRelated as CourseRelatedModel;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\Category as CategoryRepo;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseCategory as CourseCategoryRepo;
use App\Repos\CourseRelated as CourseRelatedRepo;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\User as UserRepo;
use App\Transformers\CourseList as CourseListTransformer;
use App\Validators\Course as CourseValidator;
class Course extends Service
{
public function getCourses()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
if (isset($params['xm_category_ids'])) {
$params['id'] = $this->getCategoryCourseIds($params['xm_category_ids']);
}
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseRepo = new CourseRepo();
$pager = $courseRepo->paginate($params, $sort, $page, $limit);
return $this->handleCourses($pager);
}
public function getCourse($id)
{
$course = $this->findOrFail($id);
return $course;
}
public function createCourse()
{
$post = $this->request->getPost();
$validator = new CourseValidator();
$data = [];
$data['model'] = $validator->checkModel($post['model']);
$data['title'] = $validator->checkTitle($post['title']);
$data['published'] = 0;
$course = new CourseModel();
$course->create($data);
return $course;
}
public function updateCourse($id)
{
$course = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new CourseValidator();
$data = [];
if (isset($post['title'])) {
$data['title'] = $validator->checkTitle($post['title']);
}
if (isset($post['cover'])) {
$data['cover'] = $validator->checkCover($post['cover']);
}
if (isset($post['keywords'])) {
$data['keywords'] = $validator->checkKeywords($post['keywords']);
}
if (isset($post['summary'])) {
$data['summary'] = $validator->checkSummary($post['summary']);
}
if (isset($post['details'])) {
$data['details'] = $validator->checkDetails($post['details']);
}
if (isset($post['level'])) {
$data['level'] = $validator->checkLevel($post['level']);
}
if (isset($post['price_mode'])) {
if ($post['price_mode'] == 'free') {
$data['market_price'] = 0;
$data['vip_price'] = 0;
} else {
$data['market_price'] = $validator->checkMarketPrice($post['market_price']);
$data['vip_price'] = $validator->checkVipPrice($post['vip_price']);
$data['expiry'] = $validator->checkExpiry($post['expiry']);
}
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
if ($post['published'] == 1) {
$validator->checkPublishAbility($course);
}
}
if (isset($post['xm_category_ids'])) {
$this->saveCategories($course, $post['xm_category_ids']);
}
if (isset($post['xm_teacher_ids'])) {
$this->saveTeachers($course, $post['xm_teacher_ids']);
}
if (isset($post['xm_course_ids'])) {
$this->saveRelatedCourses($course, $post['xm_course_ids']);
}
$course->update($data);
return $course;
}
public function deleteCourse($id)
{
$course = $this->findOrFail($id);
if ($course->deleted == 1) {
return false;
}
$course->deleted = 1;
$course->update();
return $course;
}
public function restoreCourse($id)
{
$course = $this->findOrFail($id);
if ($course->deleted == 0) {
return false;
}
$course->deleted = 0;
$course->update();
return $course;
}
public function getXmCategories($id)
{
$categoryRepo = new CategoryRepo();
$allCategories = $categoryRepo->findAll(['deleted' => 0]);
if ($allCategories->count() == 0) {
return [];
}
$courseCategoryIds = [];
if ($id > 0) {
$courseRepo = new CourseRepo();
$courseCategories = $courseRepo->findCategories($id);
if ($courseCategories->count() > 0) {
foreach ($courseCategories as $category) {
$courseCategoryIds[] = $category->id;
}
}
}
$list = [];
foreach ($allCategories as $category) {
if ($category->level == 1) {
$list[$category->id] = [
'name' => $category->name,
'value' => $category->id,
'children' => [],
];
}
}
foreach ($allCategories as $category) {
$selected = in_array($category->id, $courseCategoryIds);
$parentId = $category->parent_id;
if ($category->level == 2) {
$list[$parentId]['children'][] = [
'id' => $category->id,
'name' => $category->name,
'selected' => $selected,
];
}
}
return array_values($list);
}
public function getXmTeachers($id)
{
$userRepo = new UserRepo();
$allTeachers = $userRepo->findTeachers();
if ($allTeachers->count() == 0) {
return [];
}
$courseTeacherIds = [];
if ($id > 0) {
$courseRepo = new CourseRepo();
$courseTeachers = $courseRepo->findTeachers($id);
if ($courseTeachers->count() > 0) {
foreach ($courseTeachers as $teacher) {
$courseTeacherIds[] = $teacher->id;
}
}
}
$list = [];
foreach ($allTeachers as $teacher) {
$selected = in_array($teacher->id, $courseTeacherIds);
$list[] = [
'id' => $teacher->id,
'name' => $teacher->name,
'selected' => $selected,
];
}
return $list;
}
public function getXmCourses($id)
{
$courseRepo = new CourseRepo();
$courses = $courseRepo->findRelatedCourses($id);
$list = [];
if ($courses->count() > 0) {
foreach ($courses as $course) {
$list[] = [
'id' => $course->id,
'title' => $course->title,
'selected' => true,
];
}
}
return $list;
}
public function getChapters($id)
{
$course = $this->findOrFail($id);
$deleted = $this->request->getQuery('deleted', 'int', 0);
$chapterRepo = new ChapterRepo();
$chapters = $chapterRepo->findAll([
'parent_id' => 0,
'course_id' => $course->id,
'deleted' => $deleted,
]);
return $chapters;
}
protected function findOrFail($id)
{
$validator = new CourseValidator();
$result = $validator->checkCourse($id);
return $result;
}
protected function saveTeachers($course, $teacherIds)
{
$courseRepo = new CourseRepo();
$courseTeachers = $courseRepo->findTeachers($course->id);
$originTeacherIds = [];
if ($courseTeachers->count() > 0) {
foreach ($courseTeachers as $teacher) {
$originTeacherIds[] = $teacher->id;
}
}
$newTeacherIds = explode(',', $teacherIds);
$addedTeacherIds = array_diff($newTeacherIds, $originTeacherIds);
if ($addedTeacherIds) {
foreach ($addedTeacherIds as $teacherId) {
$courseTeacher = new CourseUserModel();
$courseTeacher->create([
'course_id' => $course->id,
'user_id' => $teacherId,
'role_type' => CourseUserModel::ROLE_TEACHER,
'source_type' => CourseUserModel::SOURCE_IMPORT,
'expire_time' => strtotime('+10 years'),
]);
}
}
$deletedTeacherIds = array_diff($originTeacherIds, $newTeacherIds);
if ($deletedTeacherIds) {
$courseUserRepo = new CourseUserRepo();
foreach ($deletedTeacherIds as $teacherId) {
$courseTeacher = $courseUserRepo->findCourseTeacher($course->id, $teacherId);
if ($courseTeacher) {
$courseTeacher->delete();
}
}
}
}
protected function saveCategories($course, $categoryIds)
{
$courseRepo = new CourseRepo();
$courseCategories = $courseRepo->findCategories($course->id);
$originCategoryIds = [];
if ($courseCategories->count() > 0) {
foreach ($courseCategories as $category) {
$originCategoryIds[] = $category->id;
}
}
$newCategoryIds = explode(',', $categoryIds);
$addedCategoryIds = array_diff($newCategoryIds, $originCategoryIds);
if ($addedCategoryIds) {
foreach ($addedCategoryIds as $categoryId) {
$courseCategory = new CourseCategoryModel();
$courseCategory->create([
'course_id' => $course->id,
'category_id' => $categoryId,
]);
}
}
$deletedCategoryIds = array_diff($originCategoryIds, $newCategoryIds);
if ($deletedCategoryIds) {
$courseCategoryRepo = new CourseCategoryRepo();
foreach ($deletedCategoryIds as $categoryId) {
$courseCategory = $courseCategoryRepo->findCourseCategory($course->id, $categoryId);
if ($courseCategory) {
$courseCategory->delete();
}
}
}
}
protected function saveRelatedCourses($course, $courseIds)
{
$courseRepo = new CourseRepo();
$relatedCourses = $courseRepo->findRelatedCourses($course->id);
$originRelatedIds = [];
if ($relatedCourses->count() > 0) {
foreach ($relatedCourses as $relatedCourse) {
$originRelatedIds[] = $relatedCourse->id;
}
}
$newRelatedIds = explode(',', $courseIds);
$addedRelatedIds = array_diff($newRelatedIds, $originRelatedIds);
$courseRelatedRepo = new CourseRelatedRepo();
/**
* 双向关联
*/
if ($addedRelatedIds) {
foreach ($addedRelatedIds as $relatedId) {
if ($relatedId != $course->id) {
$record = $courseRelatedRepo->findCourseRelated($course->id, $relatedId);
if (!$record) {
$courseRelated = new CourseRelatedModel();
$courseRelated->create([
'course_id' => $course->id,
'related_id' => $relatedId,
]);
}
$record = $courseRelatedRepo->findCourseRelated($relatedId, $course->id);
if (!$record) {
$courseRelated = new CourseRelatedModel();
$courseRelated->create([
'course_id' => $relatedId,
'related_id' => $course->id,
]);
}
}
}
}
$deletedRelatedIds = array_diff($originRelatedIds, $newRelatedIds);
/**
* 单向删除
*/
if ($deletedRelatedIds) {
$courseRelatedRepo = new CourseRelatedRepo();
foreach ($deletedRelatedIds as $relatedId) {
$courseRelated = $courseRelatedRepo->findCourseRelated($course->id, $relatedId);
if ($courseRelated) {
$courseRelated->delete();
}
}
}
}
protected function getCategoryCourseIds($categoryIds)
{
if (empty($categoryIds)) return [];
$courseCategoryRepo = new CourseCategoryRepo();
$categoryIds = explode(',', $categoryIds);
$relations = $courseCategoryRepo->findByCategoryIds($categoryIds);
$result = [];
if ($relations->count() > 0) {
foreach ($relations as $relation) {
$result[] = $relation->course_id;
}
}
return $result;
}
protected function handleCourses($pager)
{
if ($pager->total_items > 0) {
$transformer = new CourseListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleCourses($pipeA);
$pipeC = $transformer->handleCategories($pipeB);
$pipeD = $transformer->arrayToObject($pipeC);
$pager->items = $pipeD;
}
return $pager;
}
}

View File

@ -0,0 +1,180 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Learning as LearningRepo;
use App\Repos\User as UserRepo;
use App\Transformers\CourseUserList as CourseUserListTransformer;
use App\Transformers\LearningList as LearningListTransformer;
use App\Validators\CourseUser as CourseUserValidator;
class CourseStudent extends Service
{
public function getCourse($courseId)
{
$repo = new CourseRepo();
return $repo->findById($courseId);
}
public function getStudent($userId)
{
$repo = new UserRepo();
return $repo->findById($userId);
}
public function getCourseStudents()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['role_type'] = CourseUserModel::ROLE_STUDENT;
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseUserRepo = new CourseUserRepo();
$pager = $courseUserRepo->paginate($params, $sort, $page, $limit);
return $this->handleCourseStudents($pager);
}
public function getCourseLearnings()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$learningRepo = new LearningRepo();
$pager = $learningRepo->paginate($params, $sort, $page, $limit);
return $this->handleCourseLearnings($pager);
}
public function getCourseStudent($courseId, $userId)
{
$result = $this->findOrFail($courseId, $userId);
return $result;
}
public function createCourseStudent()
{
$post = $this->request->getPost();
$validator = new CourseUserValidator();
$data = [
'role_type' => CourseUserModel::ROLE_STUDENT,
'source_type' => CourseUserModel::SOURCE_IMPORT,
];
$data['course_id'] = $validator->checkCourseId($post['course_id']);
$data['user_id'] = $validator->checkUserId($post['user_id']);
$data['expire_time'] = $validator->checkExpireTime($post['expire_time']);
$validator->checkIfJoined($post['course_id'], $post['user_id']);
$courseUser = new CourseUserModel();
$courseUser->create($data);
$this->updateStudentCount($data['course_id']);
return $courseUser;
}
public function updateCourseStudent()
{
$post = $this->request->getPost();
$courseStudent = $this->findOrFail($post['course_id'], $post['user_id']);
$validator = new CourseUserValidator();
$data = [];
if (isset($post['expire_time'])) {
$data['expire_time'] = $validator->checkExpireTime($post['expire_time']);
}
if (isset($post['locked'])) {
$data['locked'] = $validator->checkLockStatus($post['locked']);
}
$courseStudent->update($data);
return $courseStudent;
}
protected function updateStudentCount($courseId)
{
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($courseId);
$updater = new CourseStatsUpdater();
$updater->updateStudentCount($course);
}
protected function findOrFail($courseId, $userId)
{
$validator = new CourseUserValidator();
$result = $validator->checkCourseStudent($courseId, $userId);
return $result;
}
protected function handleCourseStudents($pager)
{
if ($pager->total_items > 0) {
$transformer = new CourseUserListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleCourses($pipeA);
$pipeC = $transformer->handleUsers($pipeB);
$pipeD = $transformer->arrayToObject($pipeC);
$pager->items = $pipeD;
}
return $pager;
}
protected function handleCourseLearnings($pager)
{
if ($pager->total_items > 0) {
$transformer = new LearningListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleCourses($pipeA);
$pipeC = $transformer->handleChapters($pipeB);
$pipeD = $transformer->handleUsers($pipeC);
$pipeE = $transformer->arrayToObject($pipeD);
$pager->items = $pipeE;
}
return $pager;
}
}

View File

@ -0,0 +1,179 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Nav as NavModel;
use App\Repos\Nav as NavRepo;
use App\Validators\Nav as NavValidator;
class Nav extends Service
{
public function getNav($id)
{
$nav = $this->findOrFail($id);
return $nav;
}
public function getParentNav($id)
{
if ($id > 0) {
$parent = NavModel::findFirst($id);
} else {
$parent = new NavModel();
$parent->id = 0;
$parent->level = 0;
}
return $parent;
}
public function getTopNavs()
{
$navRepo = new NavRepo();
$navs = $navRepo->findAll([
'parent_id' => 0,
'position' => 'top',
'deleted' => 0,
]);
return $navs;
}
public function getChildNavs($parentId)
{
$deleted = $this->request->getQuery('deleted', 'int', 0);
$navRepo = new NavRepo();
$navs = $navRepo->findAll([
'parent_id' => $parentId,
'deleted' => $deleted,
]);
return $navs;
}
public function createNav()
{
$post = $this->request->getPost();
$validator = new NavValidator();
$data = [
'parent_id' => 0,
'published' => 1,
];
$parent = null;
if ($post['parent_id'] > 0) {
$parent = $validator->checkParent($post['parent_id']);
$data['parent_id'] = $parent->id;
}
$data['name'] = $validator->checkName($post['name']);
$data['priority'] = $validator->checkPriority($post['priority']);
$data['url'] = $validator->checkUrl($post['url']);
$data['target'] = $validator->checkTarget($post['target']);
$data['position'] = $validator->checkPosition($post['position']);
$data['published'] = $validator->checkPublishStatus($post['published']);
$nav = new NavModel();
$nav->create($data);
if ($parent) {
$nav->path = $parent->path . $nav->id . ',';
$nav->level = $parent->level + 1;
} else {
$nav->path = ',' . $nav->id . ',';
$nav->level = 1;
}
$nav->update();
return $nav;
}
public function updateNav($id)
{
$nav = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new NavValidator();
$data = [];
if (isset($post['name'])) {
$data['name'] = $validator->checkName($post['name']);
}
if (isset($post['position'])) {
$data['position'] = $validator->checkPosition($post['position']);
}
if (isset($post['url'])) {
$data['url'] = $validator->checkUrl($post['url']);
}
if (isset($post['target'])) {
$data['target'] = $validator->checkTarget($post['target']);
}
if (isset($post['priority'])) {
$data['priority'] = $validator->checkPriority($post['priority']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
$nav->update($data);
return $nav;
}
public function deleteNav($id)
{
$nav = $this->findOrFail($id);
if ($nav->deleted == 1) {
return false;
}
$nav->deleted = 1;
$nav->update();
return $nav;
}
public function restoreNav($id)
{
$nav = $this->findOrFail($id);
if ($nav->deleted == 0) {
return false;
}
$nav->deleted = 0;
$nav->update();
return $nav;
}
protected function findOrFail($id)
{
$validator = new NavValidator();
$result = $validator->checkNav($id);
return $result;
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PaginateQuery;
use App\Models\Order as OrderModel;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
use App\Transformers\OrderList as OrderListTransformer;
use App\Validators\Order as OrderValidator;
class Order extends Service
{
public function getOrders()
{
$pageQuery = new PaginateQuery();
$params = $pageQuery->getParams();
$sort = $pageQuery->getSort();
$page = $pageQuery->getPage();
$limit = $pageQuery->getLimit();
$orderRepo = new OrderRepo();
$pager = $orderRepo->paginate($params, $sort, $page, $limit);
return $this->handleOrders($pager);
}
public function getTrades($sn)
{
$orderRepo = new OrderRepo();
$trades = $orderRepo->findTrades($sn);
return $trades;
}
public function getRefunds($sn)
{
$orderRepo = new OrderRepo();
$trades = $orderRepo->findRefunds($sn);
return $trades;
}
public function getUser($userId)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($userId);
return $user;
}
public function getOrder($id)
{
$order = $this->findOrFail($id);
return $order;
}
public function closeOrder($id)
{
$order = $this->findOrFail($id);
if ($order->status == OrderModel::STATUS_PENDING) {
$order->status = OrderModel::STATUS_CLOSED;
$order->update();
}
return $order;
}
protected function findOrFail($id)
{
$validator = new OrderValidator();
$result = $validator->checkOrder($id);
return $result;
}
protected function handleOrders($pager)
{
if ($pager->total_items > 0) {
$transformer = new OrderListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleItems($pipeA);
$pipeC = $transformer->handleUsers($pipeB);
$pipeD = $transformer->arrayToObject($pipeC);
$pager->items = $pipeD;
}
return $pager;
}
}

View File

@ -0,0 +1,246 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\CoursePackage as CoursePackageModel;
use App\Models\Package as PackageModel;
use App\Repos\Course as CourseRepo;
use App\Repos\CoursePackage as CoursePackageRepo;
use App\Repos\Package as PackageRepo;
use App\Validators\Package as PackageValidator;
class Package extends Service
{
public function getPackages()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$pageRepo = new PackageRepo();
$pager = $pageRepo->paginate($params, $sort, $page, $limit);
return $pager;
}
public function getPackage($id)
{
$package = $this->findOrFail($id);
return $package;
}
public function createPackage()
{
$post = $this->request->getPost();
$validator = new PackageValidator();
$data = [];
$data['title'] = $validator->checkTitle($post['title']);
$data['summary'] = $validator->checkSummary($post['summary']);
$package = new PackageModel();
$package->create($data);
return $package;
}
public function updatePackage($id)
{
$package = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new PackageValidator();
$data = [];
if (isset($post['title'])) {
$data['title'] = $validator->checkTitle($post['title']);
}
if (isset($post['summary'])) {
$data['summary'] = $validator->checkSummary($post['summary']);
}
if (isset($post['market_price'])) {
$data['market_price'] = $validator->checkMarketPrice($post['market_price']);
}
if (isset($post['vip_price'])) {
$data['vip_price'] = $validator->checkVipPrice($post['vip_price']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
if (isset($post['xm_course_ids'])) {
$this->saveCourses($package, $post['xm_course_ids']);
}
$package->update($data);
$this->updateCourseCount($package);
return $package;
}
public function deletePackage($id)
{
$package = $this->findOrFail($id);
if ($package->deleted == 1) {
return false;
}
$package->deleted = 1;
$package->update();
return $package;
}
public function restorePackage($id)
{
$package = $this->findOrFail($id);
if ($package->deleted == 0) {
return false;
}
$package->deleted = 0;
$package->update();
return $package;
}
public function getGuidingCourses($courseIds)
{
if (!$courseIds) return [];
$courseRepo = new CourseRepo();
$ids = explode(',', $courseIds);
$courses = $courseRepo->findByIds($ids);
return $courses;
}
public function getGuidingPrice($courses)
{
$totalMarketPrice = $totalVipPrice = 0;
if ($courses) {
foreach ($courses as $course) {
$totalMarketPrice += $course->market_price;
$totalVipPrice += $course->vip_price;
}
}
$sgtMarketPrice = sprintf('%0.2f', intval($totalMarketPrice * 0.9));
$sgtVipPrice = sprintf('%0.2f', intval($totalVipPrice * 0.8));
$price = new \stdClass();
$price->market_price = $sgtMarketPrice;
$price->vip_price = $sgtVipPrice;
return $price;
}
public function getXmCourses($id)
{
$packageRepo = new PackageRepo();
$courses = $packageRepo->findCourses($id);
$list = [];
if ($courses->count() > 0) {
foreach ($courses as $course) {
$list[] = [
'id' => $course->id,
'title' => $course->title,
'selected' => true,
];
}
}
return $list;
}
protected function saveCourses($package, $courseIds)
{
$packageRepo = new PackageRepo();
$courses = $packageRepo->findCourses($package->id);
$originCourseIds = [];
if ($courses->count() > 0) {
foreach ($courses as $course) {
$originCourseIds[] = $course->id;
}
}
$newCourseIds = explode(',', $courseIds);
$addedCourseIds = array_diff($newCourseIds, $originCourseIds);
if ($addedCourseIds) {
foreach ($addedCourseIds as $courseId) {
$coursePackage = new CoursePackageModel();
$coursePackage->create([
'course_id' => $courseId,
'package_id' => $package->id,
]);
}
}
$deletedCourseIds = array_diff($originCourseIds, $newCourseIds);
if ($deletedCourseIds) {
$coursePackageRepo = new CoursePackageRepo();
foreach ($deletedCourseIds as $courseId) {
$coursePackage = $coursePackageRepo->findCoursePackage($courseId, $package->id);
if ($coursePackage) {
$coursePackage->delete();
}
}
}
}
protected function updateCourseCount($package)
{
$packageRepo = new PackageRepo();
$courseCount = $packageRepo->countCourses($package->id);
$package->course_count = $courseCount;
$package->update();
}
protected function findOrFail($id)
{
$validator = new PackageValidator();
$result = $validator->checkPackage($id);
return $result;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Page as PageModel;
use App\Repos\Page as PageRepo;
use App\Validators\Page as PageValidator;
class Page extends Service
{
public function getPages()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$pageRepo = new PageRepo();
$pager = $pageRepo->paginate($params, $sort, $page, $limit);
return $pager;
}
public function getPage($id)
{
$page = $this->findOrFail($id);
return $page;
}
public function createPage()
{
$post = $this->request->getPost();
$validator = new PageValidator();
$data = [];
$data['title'] = $validator->checkTitle($post['title']);
$data['content'] = $validator->checkContent($post['content']);
$data['published'] = $validator->checkPublishStatus($post['published']);
$page = new PageModel();
$page->create($data);
return $page;
}
public function updatePage($id)
{
$page = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new PageValidator();
$data = [];
if (isset($post['title'])) {
$data['title'] = $validator->checkTitle($post['title']);
}
if (isset($post['content'])) {
$data['content'] = $validator->checkContent($post['content']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
$page->update($data);
return $page;
}
public function deletePage($id)
{
$page = $this->findOrFail($id);
if ($page->deleted == 1) {
return false;
}
$page->deleted = 1;
$page->update();
return $page;
}
public function restorePage($id)
{
$page = $this->findOrFail($id);
if ($page->deleted == 0) {
return false;
}
$page->deleted = 0;
$page->update();
return $page;
}
protected function findOrFail($id)
{
$validator = new PageValidator();
$result = $validator->checkPage($id);
return $result;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Order as OrderModel;
use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo;
abstract class PaymentTest extends Service
{
/**
* 创建测试订单
*
* @return OrderModel
*/
public function createTestOrder()
{
$authUser = $this->getDI()->get('auth')->getAuthUser();
$order = new OrderModel();
$order->subject = '测试 - 支付测试0.01元';
$order->amount = 0.01;
$order->user_id = $authUser->id;
$order->item_type = OrderModel::TYPE_TEST;
$order->create();
return $order;
}
/**
* 创建交易
*
* @param OrderModel $order
* @return TradeModel $trade
*/
public function createTestTrade($order)
{
$trade = new TradeModel();
$trade->user_id = $order->user_id;
$trade->order_sn = $order->sn;
$trade->subject = $order->subject;
$trade->amount = $order->amount;
$trade->channel = TradeModel::CHANNEL_ALIPAY;
$trade->create();
return $trade;
}
/**
* 获取订单状态
*
* @param string $sn
* @return string
*/
public function getTestStatus($sn)
{
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findBySn($sn);
return $trade->status;
}
/**
* 获取测试二维码
*
* @param TradeModel $trade
* @return mixed
*/
abstract public function getTestQrCode($trade);
/**
* 取消测试订单
*
* @param string $sn
*/
abstract public function cancelTestOrder($sn);
}

View File

@ -0,0 +1,122 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PaginateQuery;
use App\Models\Task as TaskModel;
use App\Repos\Order as OrderRepo;
use App\Repos\Refund as RefundRepo;
use App\Repos\Trade as TradeRepo;
use App\Repos\User as UserRepo;
use App\Transformers\RefundList as RefundListTransformer;
use App\Validators\Refund as RefundValidator;
class Refund extends Service
{
public function getRefunds()
{
$pageQuery = new PaginateQuery();
$params = $pageQuery->getParams();
$sort = $pageQuery->getSort();
$page = $pageQuery->getPage();
$limit = $pageQuery->getLimit();
$refundRepo = new RefundRepo();
$pager = $refundRepo->paginate($params, $sort, $page, $limit);
return $this->handleRefunds($pager);
}
public function getRefund($id)
{
$refund = $this->findOrFail($id);
return $refund;
}
public function getTrade($sn)
{
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findBySn($sn);
return $trade;
}
public function getOrder($sn)
{
$orderRepo = new OrderRepo();
$order = $orderRepo->findBySn($sn);
return $order;
}
public function getUser($id)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($id);
return $user;
}
public function reviewRefund($id)
{
$refund = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new RefundValidator();
$data = [];
$validator->checkIfAllowReview($refund);
$data['status'] = $validator->checkReviewStatus($post['status']);
$data['review_note'] = $validator->checkReviewNote($post['review_note']);
$refund->update($data);
$task = new TaskModel();
$task->item_id = $refund->id;
$task->item_type = TaskModel::TYPE_REFUND;
$task->item_info = ['refund' => $refund->toArray()];
$task->priority = TaskModel::PRIORITY_HIGH;
$task->status = TaskModel::STATUS_PENDING;
$task->create();
return $refund;
}
protected function findOrFail($id)
{
$validator = new RefundValidator();
$result = $validator->checkRefund($id);
return $result;
}
protected function handleRefunds($pager)
{
if ($pager->total_items > 0) {
$transformer = new RefundListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleUsers($pipeA);
$pipeC = $transformer->arrayToObject($pipeB);
$pager->items = $pipeC;
}
return $pager;
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Repos\Course as CourseRepo;
use App\Repos\Review as ReviewRepo;
use App\Transformers\ReviewList as ReviewListTransformer;
use App\Validators\Review as ReviewValidator;
class Review extends Service
{
public function getReviews()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$reviewRepo = new ReviewRepo();
$pager = $reviewRepo->paginate($params, $sort, $page, $limit);
return $this->handleReviews($pager);
}
public function getCourse($courseId)
{
$courseRepo = new CourseRepo();
$result = $courseRepo->findById($courseId);
return $result;
}
public function getReview($id)
{
$result = $this->findOrFail($id);
return $result;
}
public function updateReview($id)
{
$review = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new ReviewValidator();
$data = [];
if (isset($post['content'])) {
$data['content'] = $validator->checkContent($post['content']);
}
if (isset($post['rating'])) {
$data['rating'] = $validator->checkRating($post['rating']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
$review->update($data);
return $review;
}
public function deleteReview($id)
{
$review = $this->findOrFail($id);
if ($review->deleted == 1) return false;
$review->deleted = 1;
$review->update();
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($review->course_id);
$course->review_count -= 1;
$course->update();
}
public function restoreReview($id)
{
$review = $this->findOrFail($id);
if ($review->deleted == 0) return false;
$review->deleted = 0;
$review->update();
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($review->course_id);
$course->review_count += 1;
$course->update();
}
protected function findOrFail($id)
{
$validator = new ReviewValidator();
$result = $validator->checkReview($id);
return $result;
}
protected function handleReviews($pager)
{
if ($pager->total_items > 0) {
$transformer = new ReviewListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleCourses($pipeA);
$pipeC = $transformer->handleUsers($pipeB);
$pipeD = $transformer->arrayToObject($pipeC);
$pager->items = $pipeD;
}
return $pager;
}
}

View File

@ -0,0 +1,166 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Role as RoleModel;
use App\Repos\Role as RoleRepo;
use App\Validators\Role as RoleValidator;
class Role extends Service
{
public function getRoles()
{
$deleted = $this->request->getQuery('deleted', 'int', 0);
$roleRepo = new RoleRepo();
$roles = $roleRepo->findAll(['deleted' => $deleted]);
return $roles;
}
public function getRole($id)
{
$role = $this->findOrFail($id);
return $role;
}
public function createRole()
{
$post = $this->request->getPost();
$validator = new RoleValidator();
$data = [];
$data['name'] = $validator->checkName($post['name']);
$data['summary'] = $validator->checkSummary($post['summary']);
$data['type'] = RoleModel::TYPE_CUSTOM;
$role = new RoleModel();
$role->create($data);
return $role;
}
public function updateRole($id)
{
$role = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new RoleValidator();
$data = [];
$data['name'] = $validator->checkName($post['name']);
$data['summary'] = $validator->checkSummary($post['summary']);
$data['routes'] = $validator->checkRoutes($post['routes']);
$data['routes'] = $this->handleRoutes($data['routes']);
$role->update($data);
return $role;
}
public function deleteRole($id)
{
$role = $this->findOrFail($id);
if ($role->deleted == 1) {
return false;
}
if ($role->type == RoleModel::TYPE_SYSTEM) {
return false;
}
$role->deleted = 1;
$role->update();
return $role;
}
public function restoreRole($id)
{
$role = $this->findOrFail($id);
if ($role->deleted == 0) {
return false;
}
$role->deleted = 0;
$role->update();
return $role;
}
protected function findOrFail($id)
{
$validator = new RoleValidator();
$result = $validator->checkRole($id);
return $result;
}
/**
* 处理路由权限(补充关联权限)
*
* 新增操作 => 补充列表权限
* 修改操作 => 补充列表权限
* 删除操作 => 补充还原权限
* 课程操作 => 补充章节权限
* 搜索操作 => 补充列表权限
*
* @param array $routes
* @return array
*/
protected function handleRoutes($routes)
{
if (!$routes) return [];
$list = [];
foreach ($routes as $route) {
$list [] = $route;
if (strpos($route, '.add')) {
$list[] = str_replace('.add', '.create', $route);
$list[] = str_replace('.add', '.list', $route);
} elseif (strpos($route, '.edit')) {
$list[] = str_replace('.edit', '.update', $route);
$list[] = str_replace('.edit', '.list', $route);
} elseif (strpos($route, '.delete')) {
$list[] = str_replace('.delete', '.restore', $route);
} elseif (strpos($route, '.search')) {
$list[] = str_replace('.search', '.list', $route);
}
}
if (in_array('admin.course.list', $routes)) {
$list[] = 'admin.course.chapters';
$list[] = 'admin.chapter.sections';
}
if (array_intersect(['admin.course.add', 'admin.course.edit'], $routes)) {
$list[] = 'admin.chapter.add';
$list[] = 'admin.chapter.edit';
$list[] = 'admin.chapter.content';
}
if (in_array('admin.course.delete', $routes)) {
$list[] = 'admin.chapter.delete';
$list[] = 'admin.chapter.restore';
}
$result = array_values(array_unique($list));
return $result;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Http\Admin\Services;
class Service extends \Phalcon\Mvc\User\Component
{
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Http\Admin\Services;
use App\Validators\User as UserValidator;
class Session extends Service
{
/**
* @var $auth \App\Http\Admin\Services\AuthUser
*/
protected $auth;
public function __construct()
{
$this->auth = $this->getDI()->get('auth');
}
public function login()
{
$post = $this->request->getPost();
$validator = new UserValidator();
$user = $validator->checkLoginAccount($post['account']);
$validator->checkLoginPassword($user, $post['password']);
$validator->checkAdminLogin($user);
$config = new Config();
$captcha = $config->getSectionConfig('captcha');
/**
* 验证码是一次性的,放到最后检查,减少第三方调用
*/
if ($captcha->enabled) {
$validator->checkCaptchaCode($post['ticket'], $post['rand']);
}
$this->auth->setAuthUser($user);
}
public function logout()
{
$this->auth->removeAuthUser();
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Slide as SlideModel;
use App\Repos\Slide as SlideRepo;
use App\Services\Storage as StorageService;
use App\Validators\Slide as SlideValidator;
class Slide extends Service
{
public function getSlides()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$slideRepo = new SlideRepo();
$pager = $slideRepo->paginate($params, $sort, $page, $limit);
return $pager;
}
public function getSlide($id)
{
$slide = $this->findOrFail($id);
return $slide;
}
public function createSlide()
{
$post = $this->request->getPost();
$validator = new SlideValidator();
$data['title'] = $validator->checkTitle($post['title']);
$data['target'] = $validator->checkTarget($post['target']);
if ($post['target'] == SlideModel::TARGET_COURSE) {
$course = $validator->checkCourse($post['content']);
$data['content'] = $course->id;
$data['cover'] = $course->cover;
$data['summary'] = $course->summary;
} elseif ($post['target'] == SlideModel::TARGET_PAGE) {
$page = $validator->checkPage($post['content']);
$data['content'] = $page->id;
} elseif ($post['target'] == SlideModel::TARGET_LINK) {
$data['content'] = $validator->checkLink($post['content']);
}
$data['start_time'] = strtotime(date('Y-m-d'));
$data['end_time'] = strtotime('+15 days', $data['start_time']);
$data['priority'] = 10;
$data['published'] = 0;
$slide = new SlideModel();
$slide->create($data);
return $slide;
}
public function updateSlide($id)
{
$slide = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new SlideValidator();
$data = [];
if (isset($post['title'])) {
$data['title'] = $validator->checkTitle($post['title']);
}
if (isset($post['summary'])) {
$data['summary'] = $validator->checkSummary($post['summary']);
}
if (isset($post['cover'])) {
$data['cover'] = $validator->checkCover($post['cover']);
}
if (isset($post['content'])) {
if ($slide->target == SlideModel::TARGET_COURSE) {
$course = $validator->checkCourse($post['content']);
$data['content'] = $course->id;
} elseif ($slide->target == SlideModel::TARGET_PAGE) {
$page = $validator->checkPage($post['content']);
$data['content'] = $page->id;
} elseif ($slide->target == SlideModel::TARGET_LINK) {
$data['content'] = $validator->checkLink($post['content']);
}
}
if (isset($post['priority'])) {
$data['priority'] = $validator->checkPriority($post['priority']);
}
if (isset($post['start_time']) || isset($post['end_time'])) {
$data['start_time'] = $validator->checkStartTime($post['start_time']);
$data['end_time'] = $validator->checkEndTime($post['end_time']);
$validator->checkTimeRange($post['start_time'], $post['end_time']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
$slide->update($data);
return $slide;
}
public function deleteSlide($id)
{
$slide = $this->findOrFail($id);
if ($slide->deleted == 1) return false;
$slide->deleted = 1;
$slide->update();
return $slide;
}
public function restoreSlide($id)
{
$slide = $this->findOrFail($id);
if ($slide->deleted == 0) return false;
$slide->deleted = 0;
$slide->update();
return $slide;
}
protected function findOrFail($id)
{
$validator = new SlideValidator();
$result = $validator->checkSlide($id);
return $result;
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PaginateQuery;
use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel;
use App\Repos\Order as OrderRepo;
use App\Repos\Trade as TradeRepo;
use App\Repos\User as UserRepo;
use App\Transformers\TradeList as TradeListTransformer;
use App\Validators\Trade as TradeValidator;
class Trade extends Service
{
public function getTrades()
{
$pageQuery = new PaginateQuery();
$params = $pageQuery->getParams();
$sort = $pageQuery->getSort();
$page = $pageQuery->getPage();
$limit = $pageQuery->getLimit();
$tradeRepo = new TradeRepo();
$pager = $tradeRepo->paginate($params, $sort, $page, $limit);
return $this->handleTrades($pager);
}
public function getTrade($id)
{
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findById($id);
return $trade;
}
public function getOrder($sn)
{
$orderRepo = new OrderRepo();
$order = $orderRepo->findBySn($sn);
return $order;
}
public function getRefunds($sn)
{
$tradeRepo = new TradeRepo();
$refunds = $tradeRepo->findRefunds($sn);
return $refunds;
}
public function getUser($id)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($id);
return $user;
}
public function closeTrade($id)
{
$trade = $this->findOrFail($id);
$validator = new TradeValidator();
$validator->checkIfAllowClose($trade);
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
return $trade;
}
public function refundTrade($id)
{
$trade = $this->findOrFail($id);
$validator = new TradeValidator();
$validator->checkIfAllowRefund($trade);
$refund = new RefundModel();
$refund->subject = $trade->subject;
$refund->amount = $trade->amount;
$refund->user_id = $trade->user_id;
$refund->order_sn = $trade->order_sn;
$refund->trade_sn = $trade->sn;
$refund->apply_reason = '后台人工申请退款';
$refund->create();
return $trade;
}
protected function findOrFail($id)
{
$validator = new TradeValidator();
$result = $validator->checkTrade($id);
return $result;
}
protected function handleTrades($pager)
{
if ($pager->total_items > 0) {
$transformer = new TradeListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleUsers($pipeA);
$pipeC = $transformer->arrayToObject($pipeB);
$pager->items = $pipeC;
}
return $pager;
}
}

View File

@ -0,0 +1,181 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PaginateQuery;
use App\Library\Util\Password as PasswordUtil;
use App\Models\User as UserModel;
use App\Repos\Role as RoleRepo;
use App\Repos\User as UserRepo;
use App\Transformers\UserList as UserListTransformer;
use App\Validators\User as UserValidator;
class User extends Service
{
public function getRoles()
{
$roleRepo = new RoleRepo();
$roles = $roleRepo->findAll(['deleted' => 0]);
return $roles;
}
public function getUsers()
{
$pageQuery = new PaginateQuery();
$params = $pageQuery->getParams();
$sort = $pageQuery->getSort();
$page = $pageQuery->getPage();
$limit = $pageQuery->getLimit();
$userRepo = new UserRepo();
$pager = $userRepo->paginate($params, $sort, $page, $limit);
$result = $this->handleUsers($pager);
return $result;
}
public function getUser($id)
{
$user = $this->findOrFail($id);
return $user;
}
public function createUser()
{
$post = $this->request->getPost();
$validator = new UserValidator();
$name = $validator->checkName($post['name']);
$password = $validator->checkPassword($post['password']);
$eduRole = $validator->checkEduRole($post['edu_role']);
$adminRole = $validator->checkAdminRole($post['admin_role']);
$validator->checkIfNameTaken($name);
$data = [];
$data['name'] = $name;
$data['salt'] = PasswordUtil::salt();
$data['password'] = PasswordUtil::hash($password, $data['salt']);
$data['edu_role'] = $eduRole;
$data['admin_role'] = $adminRole;
$user = new UserModel();
$user->create($data);
if ($user->admin_role > 0) {
$this->updateAdminUserCount($user->admin_role);
}
return $user;
}
public function updateUser($id)
{
$user = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new UserValidator();
$data = [];
if (isset($post['title'])) {
$data['title'] = $validator->checkTitle($post['title']);
}
if (isset($post['about'])) {
$data['about'] = $validator->checkAbout($post['about']);
}
if (isset($post['edu_role'])) {
$data['edu_role'] = $validator->checkEduRole($post['edu_role']);
}
if (isset($post['admin_role'])) {
$data['admin_role'] = $validator->checkAdminRole($post['admin_role']);
}
if (isset($post['locked'])) {
$data['locked'] = $validator->checkLockStatus($post['locked']);
}
if (isset($post['locked_expiry'])) {
$data['locked_expiry'] = $validator->checkLockExpiry($post['locked_expiry']);
if ($data['locked_expiry'] < time()) {
$data['locked'] = 0;
}
}
$oldAdminRole = $user->admin_role;
$user->update($data);
if ($oldAdminRole > 0) {
$this->updateAdminUserCount($oldAdminRole);
}
if ($user->admin_role > 0) {
$this->updateAdminUserCount($user->admin_role);
}
return $user;
}
protected function findOrFail($id)
{
$validator = new UserValidator();
$result = $validator->checkUser($id);
return $result;
}
protected function updateAdminUserCount($roleId)
{
if (!$roleId) {
return false;
}
$roleRepo = new RoleRepo();
$role = $roleRepo->findById($roleId);
if (!$role) {
return false;
}
$userCount = $roleRepo->countUsers($roleId);
$role->user_count = $userCount;
$role->update();
}
protected function handleUsers($pager)
{
if ($pager->total_items > 0) {
$transformer = new UserListTransformer();
$pipeA = $pager->items->toArray();
$pipeB = $transformer->handleAdminRoles($pipeA);
$pipeC = $transformer->handleEduRoles($pipeB);
$pipeD = $transformer->arrayToObject($pipeC);
$pager->items = $pipeD;
}
return $pager;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Trade as TradeModel;
class WxpayTest extends PaymentTest
{
/**
* 获取测试二维码
*
* @param TradeModel $trade
* @return mixed
*/
public function getTestQrcode($trade)
{
$outOrder = [
'out_trade_no' => $trade->sn,
'total_fee' => 100 * $trade->amount,
'body' => $trade->subject,
];
$wxpayService = new WxpayService();
$qrcode = $wxpayService->qrcode($outOrder);
$result = $qrcode ?: false;
return $result;
}
/**
* 取消测试订单
*
* @param string $sn
*/
public function cancelTestOrder($sn)
{
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Repos\Course as CourseRepo;
class XmCourse extends Service
{
public function getAllCourses()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['deleted'] = 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseRepo = new CourseRepo();
$pager = $courseRepo->paginate($params, $sort, $page, $limit);
return $pager;
}
public function getPaidCourses()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['free'] = 0;
$params['deleted'] = 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseRepo = new CourseRepo();
$pager = $courseRepo->paginate($params, $sort, $page, $limit);
return $pager;
}
}

View File

@ -0,0 +1,76 @@
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>操作记录</cite></a>
</span>
</div>
<div class="kg-nav-right">
<a class="layui-btn layui-btn-sm" href="{{ url({'for':'admin.audit.search'}) }}">
<i class="layui-icon layui-icon-add-1"></i>搜索记录
</a>
</div>
</div>
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>用户编号</th>
<th>用户名称</th>
<th>用户IP</th>
<th>请求路由</th>
<th>请求路径</th>
<th>请求时间</th>
<th>请求内容</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
<tr>
<td>{{ item.user_id }}</td>
<td>{{ item.user_name }}</td>
<td><a class="kg-ip2region" href="javascript:;" title="查看位置" ip="{{ item.user_ip }}">{{ item.user_ip }}</a></td>
<td>{{ item.req_route }}</td>
<td>{{ item.req_path }}</td>
<td>{{ date('Y-m-d H:i:s',item.created_at) }}</td>
<td align="center">
<button class="kg-view layui-btn layui-btn-sm" audit-id="{{ item.id }}">浏览</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{{ partial('partials/ip2region') }}
<script>
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
$('.kg-view').on('click', function () {
var auditId = $(this).attr('audit-id');
var url = '/admin/audit/' + auditId + '/show';
layer.open({
type: 2,
title: '请求内容',
resize: false,
area: ['640px', '360px'],
content: [url]
});
});
});
</script>

View File

@ -0,0 +1,72 @@
<form class="layui-form kg-form" method="GET" action="{{ url({'for':'admin.audit.list'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>搜索记录</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="user_id" placeholder="用户编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户名称</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="user_name" placeholder="用户名称精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">请求路由</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="req_route" placeholder="请求路由精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">请求路径</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="req_path" placeholder="请求路径精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">请求时间</label>
<div class="layui-input-inline">
<input class="layui-input time-range" type="text" name="start_time" autocomplete="off">
</div>
<div class="layui-form-mid"> -</div>
<div class="layui-input-inline">
<input class="layui-input time-range" type="text" name="end_time" autocomplete="off">
</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">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
<script>
layui.use(['laydate'], function () {
var laydate = layui.laydate;
lay('.time-range').each(function () {
laydate.render({
elem: this,
type: 'datetime',
trigger: 'click'
});
});
});
</script>

View File

@ -0,0 +1,12 @@
<pre class="layui-code" id="kg-code"></pre>
<script>
layui.use(['jquery'], function () {
var $ = layui.jquery;
var obj = JSON.parse('{{ audit.req_data }}');
var str = JSON.stringify(obj, undefined, 2);
$('#kg-code').html(str);
});
</script>

View File

@ -0,0 +1,52 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.category.create'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>添加分类</legend>
</fieldset>
{% if parent_id > 0 %}
<div class="layui-form-item">
<label class="layui-form-label">父级</label>
<div class="layui-input-block">
<select name="parent_id" lay-verify="required">
<option value="">选择父类</option>
{% for category in top_categories %}
<option value="{{ category.id }}" {% if category.id == parent_id %}selected{% endif %}>{{ category.name }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" 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="text" name="priority" value="10" lay-verify="number">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发布</label>
<div class="layui-input-block">
<input type="radio" name="published" value="1" title="是" checked="checked">
<input type="radio" name="published" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="parent_id" value="{{ parent_id }}">
</div>
</div>
</form>

View File

@ -0,0 +1,37 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.category.update','id':category.id}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>编辑分类</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" value="{{ category.name }}" 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="text" name="priority" value="{{ category.priority }}" lay-verify="number">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发布</label>
<div class="layui-input-block">
<input type="radio" name="published" value="1" title="是" {% if category.published == 1 %}checked{% endif %}>
<input type="radio" name="published" value="0" title="否" {% if category.published == 0 %}checked{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>

View File

@ -0,0 +1,125 @@
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
{% if parent.id > 0 %}
<a class="kg-back" href="{{ url({'for':'admin.category.list'}) }}">
<i class="layui-icon layui-icon-return"></i> 返回
</a>
<a><cite>{{ parent.name }}</cite></a>
{% endif %}
<a><cite>分类管理</cite></a>
</span>
</div>
<div class="kg-nav-right">
<a class="layui-btn layui-btn-sm" href="{{ url({'for':'admin.category.add'},{'parent_id':parent.id}) }}">
<i class="layui-icon layui-icon-add-1"></i>添加分类
</a>
</div>
</div>
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="12%">
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>层级</th>
<th>课程数</th>
<th>排序</th>
<th>发布</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in categories %}
<tr>
<td>{{ item.id }}</td>
{% if item.level < 2 %}
<td><a href="{{ url({'for':'admin.category.list'}) }}?parent_id={{ item.id }}">{{ item.name }}</a></td>
{% else %}
<td>{{ item.name }}</td>
{% endif %}
<td><span class="layui-badge layui-bg-gray">{{ item.level }}</span></td>
<td><span class="layui-badge layui-bg-gray">{{ item.course_count }}</span></td>
<td><input class="layui-input kg-priority-input" type="text" name="priority" value="{{ item.priority }}" category-id="{{ item.id }}" title="数值越小排序越靠前"></td>
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="switch-published" category-id="{{ item.id }}" {% if item.published == 1 %}checked{% endif %}></td>
<td align="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <span class="layui-icon layui-icon-triangle-d"></span></button>
<ul>
<li><a href="{{ url({'for':'admin.category.edit','id':item.id}) }}">编辑</a></li>
{% if item.deleted == 0 %}
<li><a href="javascript:;" class="kg-delete" url="{{ url({'for':'admin.category.delete','id':item.id}) }}">删除</a></li>
{% else %}
<li><a href="javascript:;" class="kg-restore" url="{{ url({'for':'admin.category.restore','id':item.id}) }}">还原</a></li>
{% endif %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
layui.use(['jquery', 'form', 'layer'], function () {
var $ = layui.jquery;
var form = layui.form;
var layer = layui.layer;
$('input[name=priority]').on('change', function () {
var priority = $(this).val();
var categoryId = $(this).attr('category-id');
$.ajax({
type: 'POST',
url: '/admin/category/' + categoryId + '/update',
data: {priority: priority},
success: function (res) {
layer.msg(res.msg, {icon: 1});
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
}
});
});
form.on('switch(switch-published)', function (data) {
var categoryId = $(this).attr('category-id');
var checked = $(this).is(':checked');
var published = checked ? 1 : 0;
var tips = published == 1 ? '确定要发布分类?' : '确定要下线分类?';
layer.confirm(tips, function () {
$.ajax({
type: 'POST',
url: '/admin/category/' + categoryId + '/update',
data: {published: published},
success: function (res) {
layer.msg(res.msg, {icon: 1});
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
data.elem.checked = !checked;
form.render();
}
});
}, function () {
data.elem.checked = !checked;
form.render();
});
});
});
</script>

View File

@ -0,0 +1,30 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.create'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>添加章节</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="title" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">简介</label>
<div class="layui-input-block">
<textarea class="layui-textarea" name="summary"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="course_id" value="{{ course.id }}">
</div>
</div>
</form>

View File

@ -0,0 +1,50 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.create'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>添加课时</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">章节</label>
<div class="layui-input-block">
<select name="parent_id" lay-verify="required">
<option value="">选择章节</option>
{% for chapter in course_chapters %}
<option value="{{ chapter.id }}" {% if parent_id == chapter.id %}selected{% endif %}>{{ chapter.title }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="title" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">简介</label>
<div class="layui-input-block">
<textarea class="layui-textarea" name="summary"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">免费</label>
<div class="layui-input-block">
<input type="radio" name="free" value="1" title="是">
<input type="radio" name="free" value="0" title="否" checked="true">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="course_id" value="{{ course.id }}">
</div>
</div>
</form>

View File

@ -0,0 +1,36 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.update','id':chapter.id}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>编辑章节</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="title" value="{{ chapter.title }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">简介</label>
<div class="layui-input-block">
<textarea class="layui-textarea" name="summary">{{ chapter.summary }}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="priority" value="{{ chapter.priority }}" lay-verify="number">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>

View File

@ -0,0 +1,37 @@
{%- macro content_title(model) %}
{% if model == 'vod' %}
点播信息
{% elseif model == 'live' %}
直播信息
{% elseif model == 'article' %}
文章信息
{% endif %}
{%- endmacro %}
<fieldset class="layui-elem-field layui-field-title">
<legend>编辑课时</legend>
</fieldset>
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">基本信息</li>
<li>{{ content_title(course.model) }}</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('chapter/edit_lesson_basic') }}
</div>
<div class="layui-tab-item">
{% if course.model == 'vod' %}
{{ partial('chapter/edit_lesson_vod') }}
{% elseif course.model == 'live' %}
{{ partial('chapter/edit_lesson_live') }}
{% elseif course.model == 'article' %}
{{ partial('chapter/edit_lesson_article') }}
{% endif %}
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}">
<div class="layui-form-item">
<textarea name="content" class="layui-hide" id="kg-layedit">{{ article.content }}</textarea>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{{ partial('partials/layedit') }}

View File

@ -0,0 +1,40 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.update','id':chapter.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="title" value="{{ chapter.title }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">简介</label>
<div class="layui-input-block">
<textarea class="layui-textarea" name="summary">{{ chapter.summary }}</textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="priority" value="{{ chapter.priority }}" lay-verify="number">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">免费</label>
<div class="layui-input-block">
<input type="radio" name="free" value="1" title="是" {% if chapter.free == 1 %}checked="true"{% endif %}>
<input type="radio" name="free" value="0" title="否" {% if chapter.free == 0 %}checked="true"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>

View File

@ -0,0 +1,53 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开始时间</label>
<div class="layui-input-block">
{% if live.start_time > 0 %}
<input class="layui-input" type="text" name="start_time" autocomplete="off" value="{{ date('Y-m-d H:i:s',live.start_time) }}" {% if live.start_time < time() %}readonly="true"{% endif %} lay-verify="required">
{% else %}
<input class="layui-input" type="text" name="start_time" autocomplete="off" lay-verify="required">
{% endif %}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束时间</label>
<div class="layui-input-block">
{% if live.end_time > 0 %}
<input class="layui-input" type="text" name="end_time" autocomplete="off" value="{{ date('Y-m-d H:i:s',live.end_time) }}" {% if live.end_time < time() %}readonly="true"{% endif %} lay-verify="required">
{% else %}
<input class="layui-input" type="text" name="end_time" autocomplete="off" lay-verify="required">
{% endif %}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
<script>
layui.use(['laydate'], function () {
var laydate = layui.laydate;
laydate.render({
elem: 'input[name=start_time]',
type: 'datetime'
});
laydate.render({
elem: 'input[name=end_time]',
type: 'datetime'
});
});
</script>

View File

@ -0,0 +1,74 @@
{% if translated_files %}
<fieldset class="layui-elem-field layui-field-title">
<legend>视频信息</legend>
</fieldset>
<table class="kg-table layui-table">
<tr>
<th>格式</th>
<th>时长</th>
<th>分辨率</th>
<th>码率</th>
<th>大小</th>
<th width="16%">操作</th>
</tr>
{% for item in translated_files %}
<tr>
<td>{{ item.format }}</td>
<td>{{ item.duration }}</td>
<td>{{ item.width }} x {{ item.height }}</td>
<td>{{ item.bit_rate }}kbps</td>
<td>{{ item.size }}M</td>
<td>
<span class="layui-btn layui-btn-sm kg-preview" course-id="{{ chapter.course_id }}" chapter-id="{{ chapter.id }}" play-url="{{ item.play_url|url_encode }}">预览</span>
<span class="layui-btn layui-btn-sm kg-copy" data-clipboard-text="{{ item.play_url }}">复制</span>
</td>
</tr>
{% endfor %}
</table>
<br>
{% endif %}
<fieldset class="layui-elem-field layui-field-title">
<legend>上传视频</legend>
</fieldset>
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}">
<div class="layui-form-item" id="upload-block">
<label class="layui-form-label">视频文件</label>
<div class="layui-input-block">
<span class="layui-btn" id="upload-btn">选择视频</span>
<input class="layui-hide" type="file" name="file" accept="video/*,audio/*">
</div>
</div>
<div class="layui-form-item layui-hide" id="upload-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="upload-progress" style="top:10px;">
<div class="layui-progress-bar" lay-percent="0%"></div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">文件编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="file_id" value="{{ vod.file_id }}" readonly="true" 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 type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="chapter_id" value="{{ chapter.id }}">
</div>
</div>
</form>
{{ partial('partials/media_uploader') }}
{{ partial('partials/media_preview') }}
{{ partial('partials/clipboard_tips') }}

View File

@ -0,0 +1,97 @@
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a class="kg-back" href="{{ url({'for':'admin.course.chapters','id':course.id}) }}">
<i class="layui-icon layui-icon-return"></i> 返回
</a>
<a><cite>{{ course.title }}</cite></a>
<a><cite>{{ chapter.title }}</cite></a>
<a><cite>课时管理</cite></a>
</span>
</div>
<div class="kg-nav-right">
<a class="layui-btn layui-btn-sm" href="{{ url({'for':'admin.chapter.add'},{'course_id':course.id,'type':'chapter'}) }}">
<i class="layui-icon layui-icon-add-1"></i>添加章
</a>
<a class="layui-btn layui-btn-sm" href="{{ url({'for':'admin.chapter.add'},{'course_id':course.id,'parent_id':chapter.id,'type':'lesson'}) }}">
<i class="layui-icon layui-icon-add-1"></i>添加课
</a>
</div>
</div>
{% if course.model == 'vod' %}
{{ partial('chapter/lessons_vod') }}
{% elseif course.model == 'live' %}
{{ partial('chapter/lessons_live') }}
{% elseif course.model == 'article' %}
{{ partial('chapter/lessons_article') }}
{% endif %}
<script>
layui.use(['jquery', 'layer', 'form'], function () {
var $ = layui.jquery;
var layer = layui.layer;
var form = layui.form;
$('input[name=priority]').on('change', function () {
var priority = $(this).val();
var chapterId = $(this).attr('chapter-id');
$.ajax({
type: 'POST',
url: '/admin/chapter/' + chapterId + '/update',
data: {priority: priority},
success: function (res) {
layer.msg(res.msg, {icon: 1});
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
}
});
});
form.on('switch(switch-free)', function (data) {
var chapterId = $(this).attr('chapter-id');
var checked = $(this).is(':checked');
var free = checked ? 1 : 0;
$.ajax({
type: 'POST',
url: '/admin/chapter/' + chapterId + '/update',
data: {free: free},
success: function (res) {
layer.msg(res.msg, {icon: 1});
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
data.elem.checked = !checked;
form.render();
}
});
});
form.on('switch(switch-published)', function (data) {
var chapterId = $(this).attr('chapter-id');
var checked = $(this).is(':checked');
var published = checked ? 1 : 0;
$.ajax({
type: 'POST',
url: '/admin/chapter/' + chapterId + '/update',
data: {published: published},
success: function (res) {
layer.msg(res.msg, {icon: 1});
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
data.elem.checked = !checked;
form.render();
}
});
});
});
</script>

View File

@ -0,0 +1,46 @@
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="12%">
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>字数</th>
<th>排序</th>
<th>免费</th>
<th>发布</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in lessons %}
<tr>
<td>{{ item.id }}</td>
<td>
<span><a href="{{ url({'for':'admin.chapter.edit','id':item.id}) }}">{{ item.title }}</a></span>
<span class="layui-badge layui-bg-green">课</span>
</td>
<td><span class="layui-badge layui-bg-gray">{{ item.attrs.word_count }}</span></td>
<td><input class="layui-input kg-priority-input" type="text" name="priority" value="{{ item.priority }}" chapter-id="{{ item.id }}"></td>
<td><input type="checkbox" name="free" value="1" lay-skin="switch" lay-text="是|否" lay-filter="switch-free" chapter-id="{{ item.id }}" {% if item.free == 1 %}checked{% endif %}></td>
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="switch-published" chapter-id="{{ item.id }}" {% if item.published == 1 %}checked{% endif %}></td>
<td align="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <span class="layui-icon layui-icon-triangle-d"></span></button>
<ul>
<li><a href="{{ url({'for':'admin.chapter.edit','id':item.id}) }}">编辑</a></li>
<li><a href="javascript:;" class="kg-delete" url="{{ url({'for':'admin.chapter.delete','id':item.id}) }}">删除</a></li>
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -0,0 +1,54 @@
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="12%">
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>直播时间</th>
<th>排序</th>
<th>免费</th>
<th>发布</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in lessons %}
<tr>
<td>{{ item.id }}</td>
<td>
<span>{{ item.title }}</span>
<span class="layui-badge layui-bg-green">课</span>
</td>
<td>
{% if item.attrs.start_time > 0 %}
<p>开始:{{ date('Y-m-d H:i',item.attrs.start_time) }}</p>
<p>结束:{{ date('Y-m-d H:i',item.attrs.end_time) }}</p>
{% else %}
N/A
{% endif %}
</td>
<td><input class="layui-input kg-priority-input" type="text" name="priority" value="{{ item.priority }}" chapter-id="{{ item.id }}"></td>
<td><input type="checkbox" name="free" value="1" lay-skin="switch" lay-text="是|否" lay-filter="switch-free" chapter-id="{{ item.id }}" {% if item.free == 1 %}checked{% endif %}></td>
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="switch-published" chapter-id="{{ item.id }}"
{% if item.published == 1 %}checked{% endif %}></td>
<td align="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <span class="layui-icon layui-icon-triangle-d"></span></button>
<ul>
<li><a href="{{ url({'for':'admin.chapter.edit','id':item.id}) }}">编辑</a></li>
<li><a href="javascript:;" class="kg-delete" url="{{ url({'for':'admin.chapter.delete','id':item.id}) }}">删除</a></li>
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -0,0 +1,63 @@
{%- macro file_status(value) %}
{% if value == 'pending' %}
<span class="layui-badge layui-bg-gray">待上传</span>
{% elseif value == 'uploaded' %}
<span class="layui-badge layui-bg-black">已上传</span>
{% elseif value == 'translating' %}
<span class="layui-badge layui-bg-blue">转码中</span>
{% elseif value == 'translated' %}
<span class="layui-badge layui-bg-green">已转码</span>
{% elseif value == 'failed' %}
<span class="layui-badge layui-bg-red">已失败</span>
{% endif %}
{%- endmacro %}
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="10%">
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>视频状态</th>
<th>视频时长</th>
<th>排序</th>
<th>免费</th>
<th>发布</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in lessons %}
<tr>
<td>{{ item.id }}</td>
<td>
<span><a href="{{ url({'for':'admin.chapter.edit','id':item.id}) }}">{{ item.title }}</a></span>
<span class="layui-badge layui-bg-green">课</span>
</td>
<td>{{ file_status(item.attrs.file_status) }}</td>
<td>{{ item.attrs.duration|play_duration }}</td>
<td><input class="layui-input kg-priority-input" type="text" name="priority" value="{{ item.priority }}" chapter-id="{{ item.id }}"></td>
<td><input type="checkbox" name="free" value="1" lay-skin="switch" lay-text="是|否" lay-filter="switch-free" chapter-id="{{ item.id }}" {% if item.free == 1 %}checked{% endif %}></td>
<td><input type="checkbox" name="published" value="1" lay-skin="switch" lay-text="是|否" lay-filter="switch-published" chapter-id="{{ item.id }}" {% if item.published == 1 %}checked{% endif %}></td>
<td align="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <span class="layui-icon layui-icon-triangle-d"></span></button>
<ul>
<li><a href="{{ url({'for':'admin.chapter.edit','id':item.id}) }}">编辑</a></li>
<li><a href="javascript:;" class="kg-delete" url="{{ url({'for':'admin.chapter.delete','id':item.id}) }}">删除</a></li>
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -0,0 +1,85 @@
<div class="kg-qrcode-block">
{% if qrcode %}
<div id="qrcode" class="qrcode" qrcode-text="{{ qrcode }}"></div>
<input type="hidden" name="sn" value="{{ trade.sn }}">
<div id="success-tips" class="success-tips layui-hide">
<span><i class="layui-icon layui-icon-ok-circle"></i> 支付成功</span>
</div>
<div id="error-tips" class="error-tips layui-hide">
<span><i class="layui-icon layui-icon-close-fill"></i> 支付失败</span>
</div>
{% else %}
<div class="error-tips">
<span><i class="layui-icon layui-icon-close-fill"></i> 生成二维码失败</span>
</div>
{% endif %}
</div>
{% if qrcode %}
{{ javascript_include('lib/jquery.min.js') }}
{{ javascript_include('lib/jquery.qrcode.min.js') }}
<script>
layui.use(['jquery'], function () {
var $ = layui.jquery;
$('#qrcode').qrcode({
text: $('#qrcode').attr('qrcode-text'),
width: 150,
height: 150
});
var loopTime = 0;
var sn = $('input[name=sn]').val();
var interval = setInterval(function () {
$.ajax({
type: 'POST',
url: '/admin/test/alipay/status',
data: {sn: sn},
success: function (res) {
if (res.status == 'finished') {
$('#success-tips').removeClass('layui-hide');
$('#qrcode').addClass('layui-hide');
clearInterval(interval);
}
},
error: function (xhr) {
$('#error-tips').removeClass('layui-hide');
$('#qrcode').addClass('layui-hide');
clearInterval(interval);
}
});
loopTime += 5;
if (loopTime >= 300) {
$.ajax({
type: 'POST',
url: '/admin/test/alipay/cancel',
data: {sn: sn},
success: function (res) {
},
error: function (xhr) {
}
});
$('#error-tips').removeClass('layui-hide');
$('#qrcode').addClass('layui-hide');
clearInterval(interval);
}
}, 5000);
});
</script>
{% endif %}

View File

@ -0,0 +1,99 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.config.captcha'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>验证码配置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">App Id</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="app_id" value="{{ captcha.app_id }}" layui-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Secret Key</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="secret_key" value="{{ captcha.secret_key }}" layui-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 type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.test.captcha'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>验证码测试</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label"><i class="layui-icon layui-icon-vercode"></i></label>
<div class="layui-input-inline" style="width:200px;">
<span id="captcha-btn" class="layui-btn layui-btn-primary layui-btn-fluid">前台验证</span>
<span id="frontend-verify-tips" class="kg-btn-verify layui-btn layui-btn-primary layui-btn-fluid layui-btn-disabled layui-hide"><i class="layui-icon layui-icon-ok"></i>前台验证成功</span>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"><i class="layui-icon layui-icon-vercode"></i></label>
<div class="layui-input-inline" style="width:200px;">
<span id="verify-submit-btn" class="layui-btn layui-btn-primary layui-btn-fluid" disabled="true" lay-submit="true" lay-filter="backend-verify">后台验证</span>
<span id="backend-verify-tips" class="kg-btn-verify layui-btn layui-btn-primary layui-btn-fluid layui-btn-disabled layui-hide"><i class="layui-icon layui-icon-ok"></i>后台验证成功</span>
<input type="hidden" name="ticket">
<input type="hidden" name="rand">
</div>
</div>
</form>
<script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
<script>
layui.use(['jquery', 'form', 'layer'], function () {
var $ = layui.jquery;
var form = layui.form;
var layer = layui.layer;
var captcha = new TencentCaptcha(
$('#captcha-btn')[0],
$('input[name=app_id]').val(),
function (res) {
$('input[name=ticket]').val(res.ticket);
$('input[name=rand]').val(res.randstr);
$('#captcha-btn').remove();
$('#verify-submit-btn').removeAttr('disabled');
$('#frontend-verify-tips').removeClass('layui-hide');
}
);
form.on('submit(backend-verify)', function (data) {
$.ajax({
type: 'POST',
url: data.form.action,
data: data.field,
success: function (res) {
$('#verify-submit-btn').remove();
$('#backend-verify-tips').removeClass('layui-hide');
layer.msg(res.msg, {icon: 1});
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
}
});
return false;
});
});
</script>

View File

@ -0,0 +1,14 @@
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">推流配置</li>
<li>拉流配置</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('config/live_push') }}
</div>
<div class="layui-tab-item">
{{ partial('config/live_pull') }}
</div>
</div>
</div>

View File

@ -0,0 +1,150 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.config.live'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>基础配置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">拉流协议</label>
<div class="layui-input-block">
<input type="radio" name="pull_protocol" value="http" title="HTTP" {% if live.pull_protocol == "http" %}checked{% endif %}>
<input type="radio" name="pull_protocol" value="https" title="HTTPS" {% if live.pull_protocol == "https" %}checked{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">拉流域名</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="pull_domain" value="{{ live.pull_domain }}" layui-verify="required">
</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>鉴权配置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">开启鉴权</label>
<div class="layui-input-block">
<input type="radio" name="pull_auth_enabled" value="1" title="是" {% if live.pull_auth_enabled == 1 %}checked{% endif %}>
<input type="radio" name="pull_auth_enabled" value="0" title="否" {% if live.pull_auth_enabled == 0 %}checked{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">鉴权密钥</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="pull_auth_key" value="{{ live.pull_auth_key }}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">有效时间(秒)</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="pull_auth_delta" value="{{ live.pull_auth_delta }}">
</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>转码配置</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">开启转码</label>
<div class="layui-input-block">
<input type="radio" name="pull_trans_enabled" value="1" title="是" {% if live.pull_trans_enabled == 1 %}checked{% endif %}>
<input type="radio" name="pull_trans_enabled" value="0" title="否" {% if live.pull_trans_enabled == 0 %}checked{% endif %}>
</div>
</div>
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th>模板名称</th>
<th>模板描述</th>
<th>视频码率kbps</th>
<th>视频高度px</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="layui-input" type="text" name="ptt[id][fd]" value="{{ ptt.fd.id }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[summary][fd]" value="{{ ptt.fd.summary }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[bit_rate][fd]" value="{{ ptt.fd.bit_rate }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[height][fd]" value="{{ ptt.fd.height }}" readonly="true"></td>
</tr>
<tr>
<td><input class="layui-input" type="text" name="ptt[id][sd]" value="{{ ptt.sd.id }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[summary][sd]" value="{{ ptt.sd.summary }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[bit_rate][sd]" value="{{ ptt.sd.bit_rate }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[height][sd]" value="{{ ptt.sd.height }}" readonly="true"></td>
</tr>
<tr>
<td><input class="layui-input" type="text" name="ptt[id][hd]" value="{{ ptt.hd.id }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[summary][hd]" value="{{ ptt.hd.summary }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[bit_rate][hd]" value="{{ ptt.hd.bit_rate }}" readonly="true"></td>
<td><input class="layui-input" type="text" name="ptt[height][hd]" value="{{ ptt.hd.height }}" readonly="true"></td>
</tr>
</tbody>
</table>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
<form class="layui-form kg-form">
<fieldset class="layui-elem-field layui-field-title">
<legend>拉流测试</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">Stream Name</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="stream_name" value="test" readonly="true">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button type="button" class="layui-btn" id="show-pull-test">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
<script>
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
$('#show-pull-test').on('click', function () {
var url = '/admin/test/live/pull';
layer.open({
type: 2,
title: '拉流测试',
resize: false,
area: ['720px', '445px'],
content: [url, 'no']
});
});
});
</script>

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