mirror of
https://gitee.com/koogua/course-tencent-cloud.git
synced 2025-06-17 15:55:31 +08:00
后台学习资料部分完成
This commit is contained in:
parent
b987ddf083
commit
ebb2c36f9f
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@
|
||||
/config/xs.user.ini
|
||||
/config/alipay/*.crt
|
||||
/config/wxpay/*.pem
|
||||
/db/migrations/schema.php
|
||||
/public/robots.txt
|
||||
/public/sitemap.xml
|
||||
*KgTest*
|
||||
|
40
app/Builders/ResourceList.php
Normal file
40
app/Builders/ResourceList.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use App\Repos\Upload as UploadRepo;
|
||||
|
||||
class ResourceList extends Builder
|
||||
{
|
||||
|
||||
public function handleUploads($relations)
|
||||
{
|
||||
$uploads = $this->getUploads($relations);
|
||||
|
||||
foreach ($relations as $key => $value) {
|
||||
$relations[$key]['upload'] = $uploads[$value['upload_id']] ?? new \stdClass();
|
||||
}
|
||||
|
||||
return $relations;
|
||||
}
|
||||
|
||||
public function getUploads($relations)
|
||||
{
|
||||
$ids = kg_array_column($relations, 'upload_id');
|
||||
|
||||
$uploadRepo = new UploadRepo();
|
||||
|
||||
$columns = ['id', 'name', 'path', 'mime', 'md5', 'size'];
|
||||
|
||||
$uploads = $uploadRepo->findByIds($ids, $columns);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($uploads->toArray() as $upload) {
|
||||
$result[$upload['id']] = $upload;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
29
app/Caches/MaxUploadId.php
Normal file
29
app/Caches/MaxUploadId.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Caches;
|
||||
|
||||
use App\Models\Upload as UploadModel;
|
||||
|
||||
class MaxUploadId extends Cache
|
||||
{
|
||||
|
||||
protected $lifetime = 365 * 86400;
|
||||
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
public function getKey($id = null)
|
||||
{
|
||||
return 'max_upload_id';
|
||||
}
|
||||
|
||||
public function getContent($id = null)
|
||||
{
|
||||
$upload = UploadModel::findFirst(['order' => 'id DESC']);
|
||||
|
||||
return $upload->id ?? 0;
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ use App\Http\Admin\Services\ChapterContent as ChapterContentService;
|
||||
use App\Http\Admin\Services\Course as CourseService;
|
||||
use App\Models\ChapterLive as ChapterLiveModel;
|
||||
use App\Models\Course as CourseModel;
|
||||
use Phalcon\Mvc\View;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/admin/chapter")
|
||||
@ -14,6 +15,19 @@ use App\Models\Course as CourseModel;
|
||||
class ChapterController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/resources", name="admin.chapter.resources")
|
||||
*/
|
||||
public function resourcesAction($id)
|
||||
{
|
||||
$chapterService = new ChapterService();
|
||||
|
||||
$resources = $chapterService->getResources($id);
|
||||
|
||||
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
|
||||
$this->view->setVar('resources', $resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/{id:[0-9]+}/lessons", name="admin.chapter.lessons")
|
||||
*/
|
||||
@ -96,6 +110,12 @@ class ChapterController extends Controller
|
||||
|
||||
$this->view->pick('chapter/edit_lesson');
|
||||
|
||||
$resources = $chapterService->getResources($chapter->id);
|
||||
|
||||
$cos = $chapterService->getSettings('cos');
|
||||
|
||||
$this->view->setVar('cos', $cos);
|
||||
|
||||
switch ($course->model) {
|
||||
case CourseModel::MODEL_VOD:
|
||||
$vod = $contentService->getChapterVod($chapter->id);
|
||||
|
49
app/Http/Admin/Controllers/ResourceController.php
Normal file
49
app/Http/Admin/Controllers/ResourceController.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Controllers;
|
||||
|
||||
use App\Http\Admin\Services\Resource as ResourceService;
|
||||
|
||||
/**
|
||||
* @RoutePrefix("/admin/resource")
|
||||
*/
|
||||
class ResourceController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Post("/create", name="admin.resource.create")
|
||||
*/
|
||||
public function createAction()
|
||||
{
|
||||
$resourceService = new ResourceService();
|
||||
|
||||
$resourceService->createResource();
|
||||
|
||||
return $this->jsonSuccess(['msg' => '上传资源成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/update", name="admin.resource.update")
|
||||
*/
|
||||
public function updateAction($id)
|
||||
{
|
||||
$resourceService = new ResourceService();
|
||||
|
||||
$resourceService->updateResource($id);
|
||||
|
||||
return $this->jsonSuccess(['msg' => '更新资源成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Post("/{id:[0-9]+}/delete", name="admin.resource.delete")
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$resourceService = new ResourceService();
|
||||
|
||||
$resourceService->deleteResource($id);
|
||||
|
||||
return $this->jsonSuccess(['msg' => '删除资源成功']);
|
||||
}
|
||||
|
||||
}
|
@ -73,4 +73,22 @@ class UploadController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/sign", name="admin.upload.sign")
|
||||
*/
|
||||
public function signatureAction()
|
||||
{
|
||||
$service = new StorageService();
|
||||
|
||||
$token = $service->getFederationToken();
|
||||
|
||||
$data = [
|
||||
'credentials' => $token->getCredentials(),
|
||||
'expiredTime' => $token->getExpiredTime(),
|
||||
'startTime' => time(),
|
||||
];
|
||||
|
||||
return $this->jsonSuccess($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Admin\Services;
|
||||
|
||||
use App\Builders\ResourceList as ResourceListBuilder;
|
||||
use App\Caches\Chapter as ChapterCache;
|
||||
use App\Caches\CourseChapterList as CatalogCache;
|
||||
use App\Models\Chapter as ChapterModel;
|
||||
@ -11,12 +12,32 @@ use App\Models\ChapterVod as ChapterVodModel;
|
||||
use App\Models\Course as CourseModel;
|
||||
use App\Repos\Chapter as ChapterRepo;
|
||||
use App\Repos\Course as CourseRepo;
|
||||
use App\Repos\Resource as ResourceRepo;
|
||||
use App\Services\CourseStat as CourseStatService;
|
||||
use App\Validators\Chapter as ChapterValidator;
|
||||
|
||||
class Chapter extends Service
|
||||
{
|
||||
|
||||
public function getResources($id)
|
||||
{
|
||||
$resourceRepo = new ResourceRepo();
|
||||
|
||||
$resources = $resourceRepo->findByChapterId($id);
|
||||
|
||||
if ($resources->count() == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$builder = new ResourceListBuilder();
|
||||
|
||||
$items = $resources->toArray();
|
||||
|
||||
$items = $builder->handleUploads($items);
|
||||
|
||||
return $builder->objects($items);
|
||||
}
|
||||
|
||||
public function getLessons($parentId)
|
||||
{
|
||||
$deleted = $this->request->getQuery('deleted', 'int', 0);
|
||||
|
119
app/Http/Admin/Services/Resource.php
Normal file
119
app/Http/Admin/Services/Resource.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Admin\Services;
|
||||
|
||||
use App\Models\Resource as ResourceModel;
|
||||
use App\Models\Upload as UploadModel;
|
||||
use App\Repos\Upload as UploadRepo;
|
||||
use App\Services\Storage as StorageService;
|
||||
use App\Validators\Chapter as ChapterValidator;
|
||||
use App\Validators\Resource as ResourceValidator;
|
||||
use App\Validators\Upload as UploadValidator;
|
||||
|
||||
class Resource extends Service
|
||||
{
|
||||
|
||||
public function createResource()
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$validator = new ChapterValidator();
|
||||
|
||||
$chapter = $validator->checkChapter($post['chapter_id']);
|
||||
|
||||
$course = $validator->checkCourse($chapter->course_id);
|
||||
|
||||
$uploadRepo = new UploadRepo();
|
||||
|
||||
$upload = $uploadRepo->findByMd5($post['upload']['md5']);
|
||||
|
||||
if (!$upload) {
|
||||
|
||||
$upload = new UploadModel();
|
||||
|
||||
$upload->type = UploadModel::TYPE_RESOURCE;
|
||||
$upload->name = $post['upload']['name'];
|
||||
$upload->size = $post['upload']['size'];
|
||||
$upload->path = $post['upload']['path'];
|
||||
$upload->md5 = $post['upload']['md5'];
|
||||
$upload->mime = $post['upload']['mime'];
|
||||
|
||||
$upload->create();
|
||||
}
|
||||
|
||||
$resource = new ResourceModel();
|
||||
|
||||
$resource->course_id = $course->id;
|
||||
$resource->chapter_id = $chapter->id;
|
||||
$resource->upload_id = $upload->id;
|
||||
|
||||
$resource->create();
|
||||
|
||||
$chapter->resource_count += 1;
|
||||
$chapter->update();
|
||||
|
||||
$course->resource_count += 1;
|
||||
$course->update();
|
||||
|
||||
return $upload;
|
||||
}
|
||||
|
||||
public function updateResource($id)
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
$resource = $this->findOrFail($id);
|
||||
|
||||
$validator = new UploadValidator();
|
||||
|
||||
$upload = $validator->checkUpload($resource->upload_id);
|
||||
|
||||
$data = [];
|
||||
|
||||
if (isset($post['name'])) {
|
||||
$data['name'] = $validator->checkName($post['name']);
|
||||
}
|
||||
|
||||
$upload->update($data);
|
||||
|
||||
$resource->update();
|
||||
}
|
||||
|
||||
public function deleteResource($id)
|
||||
{
|
||||
$resource = $this->findOrFail($id);
|
||||
|
||||
$validator = new ResourceValidator();
|
||||
|
||||
$course = $validator->checkCourse($resource->course_id);
|
||||
$chapter = $validator->checkChapter($resource->chapter_id);
|
||||
|
||||
$validator = new UploadValidator();
|
||||
|
||||
$upload = $validator->checkUpload($resource->upload_id);
|
||||
|
||||
$storageService = new StorageService();
|
||||
|
||||
$storageService->deleteObject($upload->path);
|
||||
|
||||
$resource->delete();
|
||||
|
||||
if ($course->resource_count > 1) {
|
||||
$course->resource_count -= 1;
|
||||
$course->update();
|
||||
}
|
||||
|
||||
if ($chapter->resource_count > 1) {
|
||||
$chapter->resource_count -= 1;
|
||||
$chapter->update();
|
||||
}
|
||||
}
|
||||
|
||||
protected function findOrFail($id)
|
||||
{
|
||||
$validator = new ResourceValidator();
|
||||
|
||||
return $validator->checkResource($id);
|
||||
}
|
||||
|
||||
}
|
@ -148,6 +148,12 @@ class Role extends Service
|
||||
$list[] = 'admin.chapter.content';
|
||||
}
|
||||
|
||||
if (array_intersect(['admin.chapter.add', 'admin.chapter.edit'], $routes)) {
|
||||
$list[] = 'admin.resource.create';
|
||||
$list[] = 'admin.resource.update';
|
||||
$list[] = 'admin.resource.delete';
|
||||
}
|
||||
|
||||
if (in_array('admin.course.delete', $routes)) {
|
||||
$list[] = 'admin.chapter.delete';
|
||||
$list[] = 'admin.chapter.restore';
|
||||
|
@ -20,6 +20,7 @@
|
||||
<ul class="layui-tab-title kg-tab-title">
|
||||
<li class="layui-this">基本信息</li>
|
||||
<li>{{ content_title(course.model) }}</li>
|
||||
<li>学习资料</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<div class="layui-tab-item layui-show">
|
||||
@ -34,6 +35,9 @@
|
||||
{{ partial('chapter/edit_lesson_read') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="layui-tab-item">
|
||||
{{ partial('chapter/edit_resource') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -64,6 +68,9 @@
|
||||
|
||||
{% endif %}
|
||||
|
||||
{{ js_include('lib/cos-js-sdk-v5.min.js') }}
|
||||
{{ js_include('admin/js/chapter.resource.js') }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block inline_js %}
|
||||
|
34
app/Http/Admin/Views/chapter/edit_resource.volt
Normal file
34
app/Http/Admin/Views/chapter/edit_resource.volt
Normal file
@ -0,0 +1,34 @@
|
||||
{% set res_list_url = url({'for':'admin.chapter.resources','id':chapter.id}) %}
|
||||
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>资料列表</legend>
|
||||
</fieldset>
|
||||
|
||||
<div id="res-list" data-url="{{ res_list_url }}"></div>
|
||||
|
||||
<fieldset class="layui-elem-field layui-field-title">
|
||||
<legend>上传资料</legend>
|
||||
</fieldset>
|
||||
|
||||
<form class="layui-form kg-form" id="res-form">
|
||||
<div class="layui-form-item" id="res-upload-block">
|
||||
<label class="layui-form-label">资源文件</label>
|
||||
<div class="layui-input-block">
|
||||
<span class="layui-btn" id="res-upload-btn">选择文件</span>
|
||||
<input class="layui-hide" type="file" name="res_file" accept="*/*">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item layui-hide" id="res-progress-block">
|
||||
<label class="layui-form-label">上传进度</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-progress layui-progress-big" lay-showpercent="yes" lay-filter="res-upload-progress" style="top:10px;">
|
||||
<div class="layui-progress-bar" lay-percent="0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-hide">
|
||||
<input type="hidden" name="chapter_id" value="{{ chapter.id }}">
|
||||
<input type="hidden" name="bucket" value="{{ cos.bucket }}">
|
||||
<input type="hidden" name="region" value="{{ cos.region }}">
|
||||
</div>
|
||||
</form>
|
30
app/Http/Admin/Views/chapter/resources.volt
Normal file
30
app/Http/Admin/Views/chapter/resources.volt
Normal file
@ -0,0 +1,30 @@
|
||||
{% if resources %}
|
||||
<table class="kg-table layui-table">
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>类型</th>
|
||||
<th>大小</th>
|
||||
<th>日期</th>
|
||||
<th width="15%">操作</th>
|
||||
</tr>
|
||||
{% for item in resources %}
|
||||
{% set update_url = url({'for':'admin.resource.update','id':item.id}) %}
|
||||
{% set delete_url = url({'for':'admin.resource.delete','id':item.id}) %}
|
||||
{% set download_url = url({'for':'home.download','md5':item.upload.md5}) %}
|
||||
<tr>
|
||||
<td><input class="layui-input res-name" type="text" value="{{ item.upload.name }}" data-url="{{ update_url }}"></td>
|
||||
<td>{{ item.upload.mime }}</td>
|
||||
<td>{{ item.upload.size|human_size }}</td>
|
||||
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
|
||||
<td>
|
||||
<a class="layui-btn layui-btn-sm layui-bg-red res-btn-delete" href="javascript:" data-url="{{ delete_url }}">删除</a>
|
||||
<a class="layui-btn layui-btn-sm" href="{{ download_url }}" target="_blank">下载</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<br>
|
||||
{% else %}
|
||||
<div class="kg-center">没有相关资料</div>
|
||||
<br>
|
||||
{% endif %}
|
@ -19,6 +19,31 @@ class PublicController extends \Phalcon\Mvc\Controller
|
||||
use ResponseTrait;
|
||||
use SecurityTrait;
|
||||
|
||||
/**
|
||||
* @Get("/download/{md5}", name="home.download")
|
||||
*/
|
||||
public function downloadAction($md5)
|
||||
{
|
||||
$repo = new UploadRepo();
|
||||
|
||||
$file = $repo->findByMd5($md5);
|
||||
|
||||
if ($file) {
|
||||
|
||||
$service = new StorageService();
|
||||
|
||||
$location = $service->getFileUrl($file->path);
|
||||
|
||||
$this->response->redirect($location, true);
|
||||
|
||||
} else {
|
||||
|
||||
$this->response->setStatusCode(404);
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Get("/img/{id:[0-9]+}", name="home.img")
|
||||
*/
|
||||
|
@ -137,7 +137,7 @@ function kg_site_url()
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
function kg_site_setting($section, $key = null)
|
||||
function kg_setting($section, $key = null)
|
||||
{
|
||||
$cache = new SettingCache();
|
||||
|
||||
@ -148,6 +148,24 @@ function kg_site_setting($section, $key = null)
|
||||
return $settings[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点配置
|
||||
*
|
||||
* @param string $path
|
||||
* @param mixed $defaultValue
|
||||
* @return mixed
|
||||
*/
|
||||
function kg_config($path, $defaultValue = null)
|
||||
{
|
||||
/**
|
||||
* @var Config $config
|
||||
*/
|
||||
$config = Di::getDefault()->getShared('config');
|
||||
|
||||
return $config->path($path, $defaultValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取默认头像路径
|
||||
*
|
||||
@ -282,6 +300,23 @@ function kg_human_number($number)
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化大小
|
||||
*
|
||||
* @param int $bytes
|
||||
* @return string
|
||||
*/
|
||||
function kg_human_size($bytes)
|
||||
{
|
||||
if (!$bytes) return 0;
|
||||
|
||||
$symbols = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
|
||||
|
||||
$exp = floor(log($bytes) / log(1024));
|
||||
|
||||
return sprintf('%.2f ' . $symbols[$exp], ($bytes / pow(1024, floor($exp))));
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化之前时间
|
||||
*
|
||||
|
@ -72,6 +72,9 @@ class Audit extends Model
|
||||
|
||||
if (is_array($this->req_data) && !empty($this->req_data)) {
|
||||
foreach ($this->req_data as $key => $value) {
|
||||
if (!is_scalar($value)) {
|
||||
$value = kg_json_encode($value);
|
||||
}
|
||||
if (kg_strlen($value) > 255) {
|
||||
$this->req_data[$key] = kg_substr($value, 0, 255);
|
||||
}
|
||||
|
@ -132,6 +132,13 @@ class Chapter extends Model
|
||||
*/
|
||||
public $deleted;
|
||||
|
||||
/**
|
||||
* 资源数
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $resource_count;
|
||||
|
||||
/**
|
||||
* 课时数
|
||||
*
|
||||
@ -160,13 +167,6 @@ class Chapter extends Model
|
||||
*/
|
||||
public $like_count;
|
||||
|
||||
/**
|
||||
* 资源数
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $res_count;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
|
@ -179,6 +179,13 @@ class Course extends Model
|
||||
*/
|
||||
public $deleted;
|
||||
|
||||
/**
|
||||
* 资源数
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $resource_count;
|
||||
|
||||
/**
|
||||
* 学员数
|
||||
*
|
||||
@ -221,13 +228,6 @@ class Course extends Model
|
||||
*/
|
||||
public $favorite_count;
|
||||
|
||||
/**
|
||||
* 资源数
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $res_count;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
|
||||
class ChapterResource extends Model
|
||||
class Resource extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
@ -35,13 +33,6 @@ class ChapterResource extends Model
|
||||
*/
|
||||
public $upload_id;
|
||||
|
||||
/**
|
||||
* 删除标识
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $deleted;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*
|
||||
@ -58,19 +49,7 @@ class ChapterResource extends Model
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return 'kg_chapter_resource';
|
||||
}
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
$this->addBehavior(
|
||||
new SoftDelete([
|
||||
'field' => 'deleted',
|
||||
'value' => 1,
|
||||
])
|
||||
);
|
||||
return 'kg_resource';
|
||||
}
|
||||
|
||||
public function beforeCreate()
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Caches\MaxUploadId as MaxUploadIdCache;
|
||||
use Phalcon\Mvc\Model\Behavior\SoftDelete;
|
||||
|
||||
class Upload extends Model
|
||||
@ -13,7 +14,7 @@ class Upload extends Model
|
||||
const TYPE_COVER_IMG = 1; // 封面图
|
||||
const TYPE_CONTENT_IMG = 2; // 内容图
|
||||
const TYPE_AVATAR_IMG = 3; // 头像
|
||||
const TYPE_COURSE_RES = 4; // 课件资源
|
||||
const TYPE_RESOURCE = 4; // 课件资源
|
||||
const TYPE_IM_IMG = 5; // IM图片
|
||||
const TYPE_IM_FILE = 6; // IM文件
|
||||
|
||||
@ -114,4 +115,11 @@ class Upload extends Model
|
||||
$this->update_time = time();
|
||||
}
|
||||
|
||||
public function afterCreate()
|
||||
{
|
||||
$cache = new MaxUploadIdCache();
|
||||
|
||||
$cache->rebuild();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,8 +22,12 @@ class Volt extends Provider
|
||||
|
||||
$compiler = $volt->getCompiler();
|
||||
|
||||
$compiler->addFunction('site_setting', function ($resolvedArgs) {
|
||||
return 'kg_site_setting(' . $resolvedArgs . ')';
|
||||
$compiler->addFunction('config', function ($resolvedArgs) {
|
||||
return 'kg_config(' . $resolvedArgs . ')';
|
||||
});
|
||||
|
||||
$compiler->addFunction('setting', function ($resolvedArgs) {
|
||||
return 'kg_setting(' . $resolvedArgs . ')';
|
||||
});
|
||||
|
||||
$compiler->addFunction('full_url', function ($resolvedArgs) {
|
||||
@ -62,6 +66,10 @@ class Volt extends Provider
|
||||
return 'kg_human_number(' . $resolvedArgs . ')';
|
||||
});
|
||||
|
||||
$compiler->addFilter('human_size', function ($resolvedArgs) {
|
||||
return 'kg_human_size(' . $resolvedArgs . ')';
|
||||
});
|
||||
|
||||
$compiler->addFilter('time_ago', function ($resolvedArgs) {
|
||||
return 'kg_time_ago(' . $resolvedArgs . ')';
|
||||
});
|
||||
|
@ -8,7 +8,6 @@ use App\Models\ChapterLive as ChapterLiveModel;
|
||||
use App\Models\ChapterRead as ChapterReadModel;
|
||||
use App\Models\ChapterUser as ChapterUserModel;
|
||||
use App\Models\ChapterVod as ChapterVodModel;
|
||||
use App\Models\Comment as CommentModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
@ -153,14 +152,6 @@ class Chapter extends Repository
|
||||
]);
|
||||
}
|
||||
|
||||
public function countComments($chapterId)
|
||||
{
|
||||
return (int)CommentModel::count([
|
||||
'conditions' => 'chapter_id = :chapter_id: AND deleted = 0',
|
||||
'bind' => ['chapter_id' => $chapterId],
|
||||
]);
|
||||
}
|
||||
|
||||
public function countLikes($chapterId)
|
||||
{
|
||||
return (int)ChapterLikeModel::count([
|
||||
|
57
app/Repos/Resource.php
Normal file
57
app/Repos/Resource.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repos;
|
||||
|
||||
use App\Models\Resource as ResourceModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class Resource extends Repository
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return ResourceModel|Model|bool
|
||||
*/
|
||||
public function findById($id)
|
||||
{
|
||||
return ResourceModel::findFirst($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ids
|
||||
* @param array|string $columns
|
||||
* @return ResultsetInterface|Resultset|ResourceModel[]
|
||||
*/
|
||||
public function findByIds($ids, $columns = '*')
|
||||
{
|
||||
return ResourceModel::query()
|
||||
->columns($columns)
|
||||
->inWhere('id', $ids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $courseId
|
||||
* @return ResultsetInterface|Resultset|ResourceModel[]
|
||||
*/
|
||||
public function findByCourseId($courseId)
|
||||
{
|
||||
return ResourceModel::query()
|
||||
->where('course_id = :course_id:', ['course_id' => $courseId])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $chapterId
|
||||
* @return ResultsetInterface|Resultset|ResourceModel[]
|
||||
*/
|
||||
public function findByChapterId($chapterId)
|
||||
{
|
||||
return ResourceModel::query()
|
||||
->where('chapter_id = :chapter_id:', ['chapter_id' => $chapterId])
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,8 @@ namespace App\Repos;
|
||||
|
||||
use App\Models\Upload as UploadModel;
|
||||
use Phalcon\Mvc\Model;
|
||||
use Phalcon\Mvc\Model\Resultset;
|
||||
use Phalcon\Mvc\Model\ResultsetInterface;
|
||||
|
||||
class Upload extends Repository
|
||||
{
|
||||
@ -29,4 +31,17 @@ class Upload extends Repository
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ids
|
||||
* @param string|array $columns
|
||||
* @return ResultsetInterface|Resultset|UploadModel[]
|
||||
*/
|
||||
public function findByIds($ids, $columns = '*')
|
||||
{
|
||||
return UploadModel::query()
|
||||
->columns($columns)
|
||||
->inWhere('id', $ids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,9 +78,9 @@ class MyStorage extends Storage
|
||||
*
|
||||
* @return UploadModel|bool
|
||||
*/
|
||||
public function uploadCourseResource()
|
||||
public function uploadResource()
|
||||
{
|
||||
return $this->upload('/res/course/', self::MIME_FILE, UploadModel::TYPE_COURSE_RES);
|
||||
return $this->upload('/resource/', self::MIME_FILE, UploadModel::TYPE_RESOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,6 +4,13 @@ namespace App\Services;
|
||||
|
||||
use Phalcon\Logger\Adapter\File as FileLogger;
|
||||
use Qcloud\Cos\Client as CosClient;
|
||||
use TencentCloud\Common\Credential;
|
||||
use TencentCloud\Common\Exception\TencentCloudSDKException;
|
||||
use TencentCloud\Common\Profile\ClientProfile;
|
||||
use TencentCloud\Common\Profile\HttpProfile;
|
||||
use TencentCloud\Sts\V20180813\Models\GetFederationTokenRequest;
|
||||
use TencentCloud\Sts\V20180813\Models\GetFederationTokenResponse;
|
||||
use TencentCloud\Sts\V20180813\StsClient;
|
||||
|
||||
class Storage extends Service
|
||||
{
|
||||
@ -32,6 +39,74 @@ class Storage extends Service
|
||||
$this->client = $this->getCosClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取临时凭证
|
||||
*
|
||||
* @return GetFederationTokenResponse
|
||||
*/
|
||||
public function getFederationToken()
|
||||
{
|
||||
$secret = $this->getSettings('secret');
|
||||
|
||||
$resource = sprintf('qcs::cos:%s:uid/%s:%s/*',
|
||||
$this->settings['region'],
|
||||
$secret['app_id'],
|
||||
$this->settings['bucket']
|
||||
);
|
||||
|
||||
$policy = json_encode([
|
||||
'version' => '2.0',
|
||||
'statement' => [
|
||||
'effect' => 'allow',
|
||||
'action' => [
|
||||
'name/cos:PutObject',
|
||||
'name/cos:PostObject',
|
||||
'name/cos:InitiateMultipartUpload',
|
||||
'name/cos:ListMultipartUploads',
|
||||
'name/cos:ListParts',
|
||||
'name/cos:UploadPart',
|
||||
'name/cos:CompleteMultipartUpload',
|
||||
],
|
||||
'resource' => [$resource],
|
||||
],
|
||||
]);
|
||||
|
||||
try {
|
||||
|
||||
$credential = new Credential($secret['secret_id'], $secret['secret_key']);
|
||||
|
||||
$httpProfile = new HttpProfile();
|
||||
|
||||
$httpProfile->setEndpoint('sts.tencentcloudapi.com');
|
||||
|
||||
$clientProfile = new ClientProfile();
|
||||
|
||||
$clientProfile->setHttpProfile($httpProfile);
|
||||
|
||||
$client = new StsClient($credential, $this->settings['region'], $clientProfile);
|
||||
|
||||
$request = new GetFederationTokenRequest();
|
||||
|
||||
$params = json_encode([
|
||||
'Name' => 'foo',
|
||||
'Policy' => urlencode($policy),
|
||||
]);
|
||||
|
||||
$request->fromJsonString($params);
|
||||
|
||||
return $client->GetFederationToken($request);
|
||||
|
||||
} catch (TencentCloudSDKException $e) {
|
||||
|
||||
$this->logger->error('Get Tmp Token Exception' . kg_json_encode([
|
||||
'code' => $e->getCode(),
|
||||
'message' => $e->getMessage(),
|
||||
]));
|
||||
|
||||
throw new \Exception('Get Tmp Token Exception');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传字符内容
|
||||
*
|
||||
|
45
app/Validators/Resource.php
Normal file
45
app/Validators/Resource.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validators;
|
||||
|
||||
use App\Exceptions\BadRequest as BadRequestException;
|
||||
use App\Repos\Resource as ResourceRepo;
|
||||
|
||||
class Resource extends Validator
|
||||
{
|
||||
|
||||
public function checkResource($id)
|
||||
{
|
||||
$resourceRepo = new ResourceRepo();
|
||||
|
||||
$resource = $resourceRepo->findById($id);
|
||||
|
||||
if (!$resource) {
|
||||
throw new BadRequestException('resource.not_found');
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
public function checkCourse($id)
|
||||
{
|
||||
$validator = new Course();
|
||||
|
||||
return $validator->checkCourse($id);
|
||||
}
|
||||
|
||||
public function checkChapter($id)
|
||||
{
|
||||
$validator = new Chapter();
|
||||
|
||||
return $validator->checkChapter($id);
|
||||
}
|
||||
|
||||
public function checkUpload($id)
|
||||
{
|
||||
$validator = new Upload();
|
||||
|
||||
return $validator->checkUpload($id);
|
||||
}
|
||||
|
||||
}
|
57
app/Validators/Upload.php
Normal file
57
app/Validators/Upload.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validators;
|
||||
|
||||
use App\Caches\MaxUploadId as MaxUploadIdCache;
|
||||
use App\Exceptions\BadRequest as BadRequestException;
|
||||
use App\Repos\Upload as UploadRepo;
|
||||
|
||||
class Upload extends Validator
|
||||
{
|
||||
|
||||
public function checkUpload($id)
|
||||
{
|
||||
$this->checkId($id);
|
||||
|
||||
$uploadRepo = new UploadRepo();
|
||||
|
||||
$upload = $uploadRepo->findById($id);
|
||||
|
||||
if (!$upload) {
|
||||
throw new BadRequestException('upload.not_found');
|
||||
}
|
||||
|
||||
return $upload;
|
||||
}
|
||||
|
||||
public function checkId($id)
|
||||
{
|
||||
$id = intval($id);
|
||||
|
||||
$maxIdCache = new MaxUploadIdCache();
|
||||
|
||||
$maxId = $maxIdCache->get();
|
||||
|
||||
if ($id < 1 || $id > $maxId) {
|
||||
throw new BadRequestException('upload.not_found');
|
||||
}
|
||||
}
|
||||
|
||||
public function checkName($name)
|
||||
{
|
||||
$value = $this->filter->sanitize($name, ['trim', 'string']);
|
||||
|
||||
$length = kg_strlen($value);
|
||||
|
||||
if ($length < 2) {
|
||||
throw new BadRequestException('upload.name_too_short');
|
||||
}
|
||||
|
||||
if ($length > 100) {
|
||||
throw new BadRequestException('upload.name_too_long');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
@ -8,6 +8,7 @@ use App\Exceptions\NotFound as NotFoundException;
|
||||
use App\Exceptions\ServiceUnavailable as ServiceUnavailableException;
|
||||
use App\Exceptions\Unauthorized as UnauthorizedException;
|
||||
use App\Library\Logger as AppLogger;
|
||||
use Phalcon\Config;
|
||||
use Phalcon\Mvc\User\Component;
|
||||
|
||||
class HttpErrorHandler extends Component
|
||||
@ -70,7 +71,13 @@ class HttpErrorHandler extends Component
|
||||
*/
|
||||
protected function report($e)
|
||||
{
|
||||
$content = sprintf('%s(%d): %s', $e->getFile(), $e->getLine(), $e->getMessage());
|
||||
$config = $this->getConfig();
|
||||
|
||||
if ($config->get('env') == ENV_DEV) {
|
||||
$content = $e->getTraceAsString();
|
||||
} else {
|
||||
$content = sprintf('%s(%d): %s', $e->getFile(), $e->getLine(), $e->getMessage());
|
||||
}
|
||||
|
||||
$logger = $this->getLogger();
|
||||
|
||||
@ -127,6 +134,14 @@ class HttpErrorHandler extends Component
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->getDI()->getShared('config');
|
||||
}
|
||||
|
||||
protected function getLogger()
|
||||
{
|
||||
$logger = new AppLogger();
|
||||
|
@ -640,7 +640,7 @@ class InitTable extends Phinx\Migration\AbstractMigration
|
||||
'unique' => false,
|
||||
])
|
||||
->create();
|
||||
$this->table('kg_chapter_resource', [
|
||||
$this->table('kg_resource', [
|
||||
'id' => false,
|
||||
'primary_key' => ['id'],
|
||||
'engine' => 'InnoDB',
|
||||
@ -676,13 +676,6 @@ class InitTable extends Phinx\Migration\AbstractMigration
|
||||
'comment' => '上传编号',
|
||||
'after' => 'chapter_id',
|
||||
])
|
||||
->addColumn('deleted', 'integer', [
|
||||
'null' => false,
|
||||
'default' => '0',
|
||||
'limit' => MysqlAdapter::INT_REGULAR,
|
||||
'comment' => '删除标识',
|
||||
'after' => 'upload_id',
|
||||
])
|
||||
->addColumn('create_time', 'integer', [
|
||||
'null' => false,
|
||||
'default' => '0',
|
||||
|
14784
db/migrations/schema.php
14784
db/migrations/schema.php
File diff suppressed because it is too large
Load Diff
133
public/static/admin/js/chapter.resource.js
Normal file
133
public/static/admin/js/chapter.resource.js
Normal file
@ -0,0 +1,133 @@
|
||||
layui.use(['jquery', 'element', 'layer'], function () {
|
||||
|
||||
var $ = layui.jquery;
|
||||
var element = layui.element;
|
||||
var layer = layui.layer;
|
||||
|
||||
var $uploadBtn = $('#res-upload-btn');
|
||||
var $resFile = $('input[name=res_file]');
|
||||
var $uploadBlock = $('#res-upload-block');
|
||||
var $progressBlock = $('#res-progress-block');
|
||||
var chapterId = $('input[name=chapter_id]').val();
|
||||
|
||||
var myConfig = {
|
||||
bucket: $('input[name=bucket]').val(),
|
||||
region: $('input[name=region]').val(),
|
||||
storageClass: 'STANDARD'
|
||||
};
|
||||
|
||||
var cos = new COS({
|
||||
getAuthorization: function (options, callback) {
|
||||
$.get('/admin/upload/sign', {
|
||||
bucket: options.Bucket,
|
||||
region: options.Region,
|
||||
}, function (data) {
|
||||
console.log(data);
|
||||
var credentials = data && data.credentials;
|
||||
if (!data || !credentials) {
|
||||
layer.msg('获取临时凭证失败', {icon: 2});
|
||||
return console.error('invalid credentials');
|
||||
}
|
||||
callback({
|
||||
TmpSecretId: credentials.TmpSecretId,
|
||||
TmpSecretKey: credentials.TmpSecretKey,
|
||||
XCosSecurityToken: credentials.Token,
|
||||
ExpiredTime: data.expiredTime,
|
||||
StartTime: data.startTime
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
loadResourceList();
|
||||
|
||||
$uploadBtn.on('click', function () {
|
||||
$resFile.trigger('click');
|
||||
});
|
||||
|
||||
$resFile.on('change', function (e) {
|
||||
var file = this.files[0];
|
||||
var keyName = getKeyName(file.name);
|
||||
cos.putObject({
|
||||
StorageClass: myConfig.storageClass,
|
||||
Bucket: myConfig.bucket,
|
||||
Region: myConfig.region,
|
||||
Key: keyName,
|
||||
Body: file,
|
||||
onProgress: function (info) {
|
||||
if (!isNaN(info.percent)) {
|
||||
var percent = Math.ceil(100 * info.percent);
|
||||
element.progress('res-upload-progress', percent + '%');
|
||||
}
|
||||
console.log(info);
|
||||
}
|
||||
}, function (err, data) {
|
||||
if (data && data.statusCode === 200) {
|
||||
$.post('/admin/resource/create', {
|
||||
upload: {
|
||||
name: file.name,
|
||||
mime: file.type,
|
||||
size: file.size,
|
||||
path: keyName,
|
||||
md5: data.ETag.replace(/"/g, '')
|
||||
},
|
||||
chapter_id: chapterId,
|
||||
}, function () {
|
||||
$uploadBlock.removeClass('layui-hide');
|
||||
$progressBlock.addClass('layui-hide');
|
||||
loadResourceList();
|
||||
});
|
||||
}
|
||||
console.log(err || data);
|
||||
});
|
||||
$uploadBlock.addClass('layui-hide');
|
||||
$progressBlock.removeClass('layui-hide');
|
||||
});
|
||||
|
||||
$('body').on('change', '.res-name', function () {
|
||||
var url = $(this).data('url');
|
||||
$.post(url, {
|
||||
name: $(this).val()
|
||||
}, function (res) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
});
|
||||
});
|
||||
|
||||
$('body').on('click', '.res-btn-delete', function () {
|
||||
var url = $(this).data('url');
|
||||
layer.confirm('确定要删除吗?', function () {
|
||||
$.post(url, function (res) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
loadResourceList();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getKeyName(filename) {
|
||||
var ext = getFileExtension(filename);
|
||||
var date = new Date();
|
||||
var name = [
|
||||
date.getFullYear(),
|
||||
date.getMonth() + 1,
|
||||
date.getDate(),
|
||||
date.getHours(),
|
||||
date.getMinutes(),
|
||||
date.getSeconds(),
|
||||
Math.round(10000 * Math.random())
|
||||
].join('');
|
||||
return '/resource/' + name + '.' + ext;
|
||||
}
|
||||
|
||||
function getFileExtension(filename) {
|
||||
var index = filename.lastIndexOf('.');
|
||||
return filename.substr(index + 1);
|
||||
}
|
||||
|
||||
function loadResourceList() {
|
||||
var url = $('#res-list').data('url');
|
||||
$.get(url, function (html) {
|
||||
$('#res-list').html(html);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user