一、上传视频至百度VOD
1.课程视频上传html代码
<!-- 在templates/admin/course/courses_pub.html文件中 -->
<div class="form-group">
<label for="docs-file-url">视频地址</label>
<div class="input-group">
{% if course %}
<input type="text" class="form-control" id="docs-file-url" name="docs-file-url"
placeholder="请上传视频或输入视频地址" value="{{ course.video_url }}">
{% else %}
<input type="text" class="form-control" id="docs-file-url" name="docs-file-url"
placeholder="请上传视频或输入视频地址">
{% endif %}
<div class="input-group-btn">
<label class="btn btn-default btn-file">
上传至服务器 <input type="file" id="upload-file-server">
</label>
</div>
</div>
</div>
<script src="{% static 'node_modules/@baiducloud/sdk/dist/baidubce-sdk.bundle.min.js' %}"></script>
2.百度VOD上传js代码
将云盘datas目录中的node_modules文件夹拷贝到项目static目录
// 在static/js/admin/course/courses_pub.js中添加如下代码:
// 创建static/js/admin/course/courses_pub.js文件
let sdk = baidubce.sdk;
let VodClient = sdk.VodClient;
const CONFIG = {
endpoint: 'http://vod.bj.baidubce.com', // 默认区域名
credentials: {
ak: 'caf0ac9c0b0d452fb8dc9ed294744f7b', // 填写你的百度云中ak和sk
sk: '620c7446966748b78899aeeb02a95c97'
}
};
let BAIDU_VOD_DOMAIN = 'jasmtyjgmzpkjcbvbtp.exp.bcevod.com'; // 百度云VOD域名
const CLIENT = new VodClient(CONFIG);
$(function () {
// 其他js代码省略
// ......
// ================== 上传文件至服务器 ================
let $upload_file_server = $("#upload-file-server");
$upload_file_server.change(function () {
// 先判断课程标题是否为空
let sTitle = $("#news-title").val(); // 获取课程标题
if (!sTitle) {
message.showError('请先填写课程标题之后,再上传视频!');
return
}
// 判断课程简介是否为空
let sDesc = $("#news-desc").val(); // 获取课程简介
if (!sDesc) {
message.showError('请先填写课程描述之后,再上传视频!');
return
}
let video_file = this.files[0]; // 获取文件
let video_file_type = video_file.type;
// 调用百度云VOD接口
let blob = new Blob([video_file], {type: video_file_type});
CLIENT.createMediaResource(sTitle, sDesc, blob)
// Node.js中<data>可以为一个Stream、<pathToFile>;在浏览器中<data>为一个Blob对象
.then(function (response) {
// 上传完成
message.showSuccess("视频上传成功");
let sMediaId = response.body.mediaId;
console.log('媒资ID为:', sMediaId);
let sVideoUrl = 'http://' + BAIDU_VOD_DOMAIN + '/' + sMediaId + '/' + sMediaId + '.mp4'; //mp4 与百度云上的转码设置有关
$courseFileUrl.val('');
$courseFileUrl.val(sVideoUrl);
})
.catch(function (error) {
console.log(error); // 上传错误
message.showError(error)
});
});
});
后端代码
from courses.models import Course,Teacher,CourseCategory
class CoursesManageView(PermissionRequiredMixin, View):
"""
route: /admin/courses/
"""
permission_required = ('course.add_course', 'course.view_course')
raise_exception = True
def handle_no_permission(self):
if self.request.method.lower() != 'get':
return to_json_data(errno=Code.ROLEERR, errmsg='没有操作权限')
else:
return super(CoursesManageView, self).handle_no_permission()
def get(self, request):
courses = Course.objects.select_related('category', 'teacher').\
only('title', 'category__name', 'teacher__name').filter(is_delete=False)
return render(request, 'admin/course/courses_manage.html', locals())
class CoursesEditView(PermissionRequiredMixin, View):
"""
route: /admin/courses/<int:course_id>/
"""
permission_required = ('course.change_course', 'course.delete_course')
raise_exception = True
def handle_no_permission(self):
if self.request.method.lower() != 'get':
return to_json_data(errno=Code.ROLEERR, errmsg='没有操作权限')
else:
return super(CoursesEditView, self).handle_no_permission()
def get(self, request, course_id):
"""
"""
course = Course.objects.filter(is_delete=False, id=course_id).first()
if course:
teachers = Teacher.objects.only('name').filter(is_delete=False)
categories = CourseCategory.objects.only('name').filter(is_delete=False)
return render(request, 'admin/course/courses_pub.html', locals())
else:
raise Http404('需要更新的课程不存在!')
def delete(self, request, course_id):
course = Course.objects.filter(is_delete=False, id=course_id).first()
if course:
course.is_delete = True
course.save(update_fields=['is_delete'])
return to_json_data(errmsg="课程删除成功")
else:
return to_json_data(errno=Code.PARAMERR, errmsg="需要删除的课程不存在")
def put(self, request, course_id):
course = Course.objects.filter(is_delete=False, id=course_id).first()
if not course:
return to_json_data(errno=Code.NODATA, errmsg='需要更新的课程不存在')
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
# 将json转化为dict
dict_data = json.loads(json_data.decode('utf8'))
form = forms.CoursesPubForm(data=dict_data)
if form.is_valid():
for attr, value in form.cleaned_data.items():
setattr(course, attr, value)
course.save()
return to_json_data(errmsg='课程更新成功')
else:
# 定义一个错误信息列表
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
err_msg_str = '/'.join(err_msg_list) # 拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)
class CoursesPubView(PermissionRequiredMixin, View):
"""
route: /admin/courses/pub/
"""
permission_required = ('course.add_course', 'course.view_course')
raise_exception = True
def handle_no_permission(self):
if self.request.method.lower() != 'get':
return to_json_data(errno=Code.ROLEERR, errmsg='没有操作权限')
else:
return super(CoursesPubView, self).handle_no_permission()
def get(self, request):
teachers = Teacher.objects.only('name').filter(is_delete=False)
categories = CourseCategory.objects.only('name').filter(is_delete=False)
return render(request, 'admin/course/courses_pub.html', locals())
def post(self, request):
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8'))
form = forms.CoursesPubForm(data=dict_data)
if form.is_valid():
courses_instance = form.save()
return to_json_data(errmsg='课程发布成功')
else:
# 定义一个错误信息列表
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
err_msg_str = '/'.join(err_msg_list) # 拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)
path('courses/', views.CoursesManageView.as_view(), name='courses_manage'),
path('courses/<int:course_id>/', views.CoursesEditView.as_view(), name='courses_edit'),
path('courses/pub/', views.CoursesPubView.as_view(), name='courses_pub'),
<!-- 创建templates/admin/course/courses_manage.html文件 -->
{% extends 'admin/base/base.html' %}
{% block title %}
课程管理页
{% endblock %}
{% block content_header %}
课程管理
{% endblock %}
{% block header_option_desc %}
课程管理
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12 col-xs-12 col-sm-12">
<div class="box box-primary">
{# <div class="box-header">#}
{# <button class="btn btn-primary pull-right" id="btn-add-tag">添加课程</button>#}
{# </div>#}
<div class="box-body">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>课程标题</th>
<th>课程类别</th>
<th>讲师名</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tbody">
{% for one_course in courses %}
<tr data-id="{{ one_course.id }}" data-name="{{ one_course.title }}">
<td>{{ one_course.title }}</td>
<td>{{ one_course.category.name }}</td>
<td>{{ one_course.teacher.name }}</td>
<td>
{# <button class="btn btn-xs btn-warning btn-edit">编辑</button>#}
<a href="{% url 'admin:courses_edit' one_course.id %}" class="btn btn-xs btn-warning btn-edit">编辑</a>
<button class="btn btn-xs btn-danger btn-del">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'js/admin/course/courses_manage.js' %}"></script>
{% endblock %}
// 创建static/js/admin/course/courses_manage.js文件
$(function () {
// 删除标签
let $courseDel = $(".btn-del"); // 1. 获取删除按钮
$courseDel.click(function () { // 2. 点击触发事件
let _this = this;
let sCourseId = $(this).parents('tr').data('id');
// let sCourseTitle = $(this).parents('tr').data('name');
fAlert.alertConfirm({
title: "确定删除文档吗?",
type: "error",
confirmText: "确认删除",
cancelText: "取消删除",
confirmCallback: function confirmCallback() {
$.ajax({
url: "/admin/courses/" + sCourseId + "/", // url尾部需要添加/
// 请求方式
type: "DELETE",
dataType: "json",
})
.done(function (res) {
if (res.errno === "200") {
message.showSuccess("文档删除成功");
$(_this).parents('tr').remove();
} else {
swal.showInputError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
});
});
// get cookie using jQuery
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// Setting the token on the AJAX request
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
});
<!-- 创建templates/admin/course/courses_pub.html文件 -->
{% extends 'admin/base/base.html' %}
{% block title %}
课程发布页
{% endblock %}
{% block content_header %}
课程发布
{% endblock %}
{% block header_option_desc %}
书是人类进步的阶梯
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12 col-xs-12 col-sm-12">
<div class="box box-primary">
<div class="box-body">
<div class="form-group" style="margin-top: 30px;">
<label for="news-title">课程标题(150个字以内)</label>
{% if course %}
<input type="text" class="form-control" id="news-title" name="news-title" placeholder="请输入课程标题"
value="{{ course.title }}">
{% else %}
<input type="text" class="form-control" id="news-title" name="news-title" placeholder="请输入课程标题"
autofocus>
{% endif %}
</div>
<div class="form-group">
<label for="news-desc">课程简介</label>
{% if course %}
<textarea name="news-desc" id="news-desc" placeholder="请输入课程简介" class="form-control"
style="height: 8rem; resize: none;">{{ course.profile }}</textarea>
{% else %}
<textarea name="news-desc" id="news-desc" placeholder="请输入课程简介" class="form-control"
style="height: 8rem; resize: none;"></textarea>
{% endif %}
</div>
<div class="form-group" id="container">
<label for="news-thumbnail-url">课程封面图</label>
<div class="input-group">
{% if course %}
<input type="text" class="form-control" id="news-thumbnail-url" name="news-thumbnail-url"
placeholder="请上传图片或输入封面图地址" value="{{ course.cover_url }}">
{% else %}
<input type="text" class="form-control" id="news-thumbnail-url" name="news-thumbnail-url"
placeholder="请上传图片或输入封面图地址">
{% endif %}
<div class="input-group-btn">
<label class="btn btn-default btn-file">
上传至服务器 <input type="file" id="upload-image-server">
</label>
<button class="btn btn-info" id="upload-image-btn">上传至七牛云</button>
</div>
</div>
</div>
<div class="form-group">
<div class="progress-bar" style="display: none">
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 0;">0%</div>
</div>
</div>
<div class="form-group">
<label for="docs-file-url">视频地址</label>
<div class="input-group">
{% if course %}
<input type="text" class="form-control" id="docs-file-url" name="docs-file-url"
placeholder="请上传视频或输入视频地址" value="{{ course.video_url }}">
{% else %}
<input type="text" class="form-control" id="docs-file-url" name="docs-file-url"
placeholder="请上传视频或输入视频地址">
{% endif %}
<div class="input-group-btn">
<label class="btn btn-default btn-file">
上传至服务器 <input type="file" id="upload-file-server">
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="course-time">课程时长(单位:分钟)</label>
{% if course %}
<input type="text" class="form-control" id="course-time" name="course-time"
placeholder="请输入课程时长" value="{{ course.duration }}">
{% else %}
<input type="text" class="form-control" id="course-time" name="course-time"
placeholder="请输入课程时长" autofocus>
{% endif %}
</div>
<div class="form-group">
<label for="course-teacher">课程讲师</label>
<select name="course-teacher" id="course-teacher" class="form-control">
<option value="0">-- 请选择讲师 --</option>
{% for teacher in teachers %}
{% if course and teacher == course.teacher %}
<option value="{{ teacher.id }}" selected>{{ teacher.name }}</option>
{% else %}
<option value="{{ teacher.id }}">{{ teacher.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="course-category">课程分类</label>
<select name="course-category" id="course-category" class="form-control">
<option value="0">-- 请选择分类 --</option>
{% for category in categories %}
{% if course and category == course.category %}
<option value="{{ category.id }}" selected>{{ category.name }}</option>
{% else %}
<option value="{{ category.id }}">{{ category.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="course-outline">课程大纲</label>
{% if course %}
<div id="course-outline">
<script>
window.onload = function () {
window.editor.txt.html('{{ course.outline|safe }}')
}
</script>
</div>
{% else %}
<div id="course-outline">
</div>
{% endif %}
</div>
</div>
<div class="box-footer">
{% if course %}
<a href="javascript:void (0);" class="btn btn-primary pull-right" id="btn-pub-news"
data-news-id="{{ course.id }}">更新课程 </a>
{% else %}
<a href="javascript:void (0);" class="btn btn-primary pull-right" id="btn-pub-news">发布课程 </a>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'node_modules/@baiducloud/sdk/dist/baidubce-sdk.bundle.min.js' %}"></script>
<script src="{% static 'js/admin/news/wangEditor.min.js' %}"></script>
<!-- 七牛云 客户端 并不经过服务端 服务器需要提供 token -->
<script src="https://cdn.bootcss.com/plupload/2.1.9/moxie.min.js"></script>
<script src="https://cdn.bootcss.com/plupload/2.1.9/plupload.dev.js"></script>
<script src="https://cdn.bootcss.com/qiniu-js/1.0.17.1/qiniu.min.js"></script>
<!--一定要在下面 js 文件顺序很重要 -->
<script src="{% static 'js/admin/base/fqiniu.js' %}"></script>
<script src="{% static 'js/admin/course/courses_pub.js' %}"></script>
{% endblock %}
// 创建static/js/admin/course/courses_pub.js文件
let sdk = baidubce.sdk;
let VodClient = sdk.VodClient;
const CONFIG = {
endpoint: 'http://vod.bj.baidubce.com',
credentials: {
ak: 'caf0ac9c0b0d452fb8dc9ed294744f7b',
sk: '620c7446966748b78899aeeb02a95c97'
}
};
let BAIDU_VOD_DOMAIN = 'jasmtyjgmzpkjcbvbtp.exp.bcevod.com';
const CLIENT = new VodClient(CONFIG);
$(function () {
let $e = window.wangEditor;
window.editor = new $e('#course-outline');
window.editor.create();
let $thumbnailUrl = $("#news-thumbnail-url"); // 获取缩略图输入框元素
let $courseFileUrl = $("#docs-file-url"); // 获取课程地址输入框元素
// ================== 上传图片文件至服务器 ================
let $upload_image_server = $("#upload-image-server");
$upload_image_server.change(function () {
// let _this = this;
let file = this.files[0]; // 获取文件
let oFormData = new FormData(); // 创建一个 FormData
oFormData.append("image_file", file); // 把文件添加进去
// 发送请求
$.ajax({
url: "/admin/news/images/",
method: "POST",
data: oFormData,
processData: false, // 定义文件的传输
contentType: false,
})
.done(function (res) {
if (res.errno === "200") {
message.showSuccess("图片上传成功");
let sImageUrl = res.data.image_url;
// let $inpuUrl = $(_this).parents('.input-group').find('input:nth-child(1)');
$thumbnailUrl.val('');
$thumbnailUrl.val(sImageUrl);
} else {
message.showError(res.errmsg)
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
});
// ================== 上传文件至服务器 ================
let $upload_file_server = $("#upload-file-server");
$upload_file_server.change(function () {
// 先判断课程标题是否为空
let sTitle = $("#news-title").val(); // 获取课程标题
if (!sTitle) {
message.showError('请先填写课程标题之后,再上传视频!');
return
}
// 判断课程简介是否为空
let sDesc = $("#news-desc").val(); // 获取课程简介
if (!sDesc) {
message.showError('请先填写课程描述之后,再上传视频!');
return
}
let video_file = this.files[0]; // 获取文件
let video_file_type = video_file.type;
// 调用百度云VOD接口
let blob = new Blob([video_file], {type: video_file_type});
CLIENT.createMediaResource(sTitle, sDesc, blob)
// Node.js中<data>可以为一个Stream、<pathToFile>;在浏览器中<data>为一个Blob对象
.then(function (response) {
// 上传完成
message.showSuccess("视频上传成功");
let sMediaId = response.body.mediaId;
console.log('媒资ID为:', sMediaId);
let sVideoUrl = 'http://' + BAIDU_VOD_DOMAIN + '/' + sMediaId + '/' + sMediaId + '.mp4';
$courseFileUrl.val('');
$courseFileUrl.val(sVideoUrl);
})
.catch(function (error) {
console.log(error); // 上传错误
message.showError(error)
});
});
// ================== 上传图片至七牛(云存储平台) ================
let $progressBar = $(".progress-bar");
QINIU.upload({
"domain": "http://pl3yncr1e.bkt.clouddn.com/", // 七牛空间域名
// 后台返回 token的地址 (后台返回的 url 地址) 不可能成功
"uptoken_url": "/admin/token/",
// 按钮
"browse_btn": "upload-image-btn",
// 成功
"success": function (up, file, info) {
let domain = up.getOption('domain');
let res = JSON.parse(info);
let filePath = domain + res.key;
console.log(filePath); // 打印文件路径
$thumbnailUrl.val('');
$thumbnailUrl.val(filePath);
},
// 失败
"error": function (up, err, errTip) {
// console.log('error');
console.log(up);
console.log(err);
console.log(errTip);
// console.log('error');
message.showError(errTip);
},
"progress": function (up, file) {
let percent = file.percent;
$progressBar.parent().css("display", 'block');
$progressBar.css("width", percent + '%');
$progressBar.text(parseInt(percent) + '%');
},
// 完成后 去掉进度条
"complete": function () {
$progressBar.parent().css("display", 'none');
$progressBar.css("width", '0%');
$progressBar.text('0%');
}
});
// ================== 发布文章 ================
let $docsBtn = $("#btn-pub-news");
$docsBtn.click(function () {
// 判断课程标题是否为空
let sTitle = $("#news-title").val(); // 获取文件标题
if (!sTitle) {
message.showError('请填写课程标题!');
return
}
// 判断课程简介是否为空
let sDesc = $("#news-desc").val(); // 获取课程简介
if (!sDesc) {
message.showError('请填写课程描述!');
return
}
// 判断课程缩略图url是否为空
let sThumbnailUrl = $thumbnailUrl.val();
if (!sThumbnailUrl) {
message.showError('请上传课程缩略图');
return
}
// 判断课程url是否为空
let sCourseFileUrl = $courseFileUrl.val();
if (!sCourseFileUrl) {
message.showError('请上传视频或输入视频地址');
return
}
// 判断视频时长是否为空
let sCourseTime = $('#course-time').val(); // 获取视频时长
if (!sCourseTime) {
message.showError('请填写视频时长!');
return
}
// 判断是否选择讲师
let sTeacherId = $("#course-teacher").val();
if (!sTeacherId || sTeacherId === '0') {
message.showError('请选择讲师');
return
}
// 判断是否选择课程分类
let sCategoryId = $("#course-category").val();
if (!sCategoryId || sCategoryId === '0') {
message.showError('请选择课程分类');
return
}
// 判断课程大纲是否为空
let sContentHtml = window.editor.txt.html();
// let sContentText = window.editor.txt.text();
if (!sContentHtml || sContentHtml === '<p><br></p>') {
message.showError('请填写课程大纲!');
return
}
// 获取coursesId 存在表示更新 不存在表示发表
let coursesId = $(this).data("news-id");
let url = coursesId ? '/admin/courses/' + coursesId + '/' : '/admin/courses/pub/';
let data = {
"title": sTitle,
"profile": sDesc,
"cover_url": sThumbnailUrl,
"video_url": sCourseFileUrl,
"duration": sCourseTime,
"outline": sContentHtml,
"teacher": sTeacherId,
"category": sCategoryId
};
$.ajax({
// 请求地址
url: url,
// 请求方式
type: coursesId ? 'PUT' : 'POST',
data: JSON.stringify(data),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "200") {
if (coursesId) {
fAlert.alertNewsSuccessCallback("课程更新成功", '跳到课程管理页', function () {
window.location.href = '/admin/courses/'
});
} else {
fAlert.alertNewsSuccessCallback("课程发表成功", '跳到课程管理页', function () {
window.location.href = '/admin/courses/'
});
}
} else {
fAlert.alertErrorToast(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
});
// get cookie using jQuery
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// Setting the token on the AJAX request
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
});
网友评论