Merge remote-tracking branch 'origin/develop' into develop

dev_video
杨树明 5 years ago
commit 0640e05822

@ -17,9 +17,11 @@ class AttachmentsController < ApplicationController
redirect_to @file.cloud_url and return
end
pdf_attachment = params[:disposition] || "attachment"
if pdf_attachment == "inline"
type_attachment = params[:disposition] || "attachment"
if type_attachment == "inline"
send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf'
elsif type_attachment == "MP4"
send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true
else
send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream')
end
@ -202,4 +204,31 @@ class AttachmentsController < ApplicationController
end
end
def send_file_with_range(path, options = {})
logger.info("########request.headers: #{request.headers}")
logger.info("########request.headers: #{File.exist?(path)}")
if File.exist?(path)
size = File.size(path)
logger.info("########request.headers: #{request.headers}")
if !request.headers["Range"]
status_code = 200 # 200 OK
offset = 0
length = File.size(path)
else
status_code = 206 # 206 Partial Content
bytes = Rack::Utils.byte_ranges(request.headers, size)[0]
offset = bytes.begin
length = bytes.end - bytes.begin
end
response.header["Accept-Ranges"] = "bytes"
response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes
response.header["status"] = status_code
send_data IO.binread(path, length, offset), options
else
raise ActionController::MissingFile, "Cannot read file #{path}."
end
end
end

@ -1,6 +1,7 @@
class CoursesController < ApplicationController
include MessagesHelper
include ExportHelper
include CustomSortable
# model validation error
rescue_from ActiveRecord::RecordInvalid do |ex|
@ -23,7 +24,7 @@ class CoursesController < ApplicationController
:course_group_list, :set_course_group, :change_course_admin, :change_course_teacher,
:delete_course_teacher, :teacher_application_review, :students, :all_course_groups,
:transfer_to_course_group, :delete_from_course, :search_users, :add_students_by_search,
:base_info, :get_historical_courses, :create_group_by_importing_file,
:base_info, :get_historical_courses, :create_group_by_importing_file, :course_videos,
:attahcment_category_list,:export_member_scores_excel, :duplicate_course,
:switch_to_teacher, :switch_to_assistant, :switch_to_student, :exit_course,
:informs, :update_informs, :online_learning, :update_task_position, :tasks_list,
@ -100,6 +101,14 @@ class CoursesController < ApplicationController
@courses = @courses.preload(:school, :none_hidden_course_modules, teacher: :user_extension)
end
def course_videos
logger.info("########[#{@course}")
videos = @course.videos
videos = custom_sort(videos, params[:sort_by], params[:sort_direction])
@count = videos.count
@videos = paginate videos
end
def visits_plus_one
new_visits = @course.visits + 1
@course.update_visits(new_visits)

@ -242,7 +242,7 @@ class SubjectsController < ApplicationController
## 云上实验室过滤
@courses = @courses.where(id: current_laboratory.all_courses)
@none_shixun_ids = ShixunSchool.where("school_id != #{current_user.user_extension.try(:school_id).to_i}").pluck(:shixun_id)
@none_shixun_ids = @subject.shixuns.joins(:shixun_schools).where("school_id != #{current_user.user_extension.try(:school_id).to_i}").where(use_scope: 1).pluck(:id)
end
def send_to_course

@ -0,0 +1,15 @@
class Weapps::ShixunListsController < ApplicationController
before_action :require_login
def index
results = Weapps::ShixunSearchService.call(search_params, current_laboratory)
@total_count = results.size
@results = paginate results
end
private
def search_params
params.permit(:keyword, :type, :page, :limit, :order, :status, :diff, :sort, :no_jupyter)
end
end

@ -65,6 +65,8 @@ module CoursesHelper
"/courses/#{course.id}/course_groups"
when "statistics"
"/courses/#{course.id}/statistics"
when "video"
"/courses/#{course.id}/course_videos"
end
end

@ -81,6 +81,10 @@ class Course < ApplicationRecord
# 老版的members弃用 现用course_members
has_many :members
# 视频
has_many :course_videos, dependent: :destroy
has_many :videos, through: :course_videos
validate :validate_sensitive_string
scope :hidden, ->(is_hidden = true) { where(is_hidden: is_hidden) }
@ -206,7 +210,7 @@ class Course < ApplicationRecord
end
def all_course_module_types
%w[activity announcement online_learning shixun_homework common_homework group_homework exercise attachment course_group graduation poll board statistics]
%w[activity announcement online_learning shixun_homework common_homework group_homework exercise attachment course_group graduation poll board statistics video]
end
def get_course_module_by_type(type)
@ -406,6 +410,7 @@ class Course < ApplicationRecord
when 'exercise' then '试卷'
when 'poll' then '问卷'
when 'attachment' then '资源'
when 'video' then '视频'
when 'board' then '讨论'
when 'course_group' then '分班'
when 'statistics' then '统计'
@ -425,9 +430,10 @@ class Course < ApplicationRecord
when 'exercise' then 8
when 'poll' then 9
when 'attachment' then 10
when 'board' then 11
when 'course_group' then 12
when 'statistics' then 13
when 'video' then 11
when 'board' then 12
when 'course_group' then 13
when 'statistics' then 14
else 100
end
end

@ -0,0 +1,4 @@
class CourseVideo < ApplicationRecord
belongs_to :course
belongs_to :video
end

@ -4,6 +4,7 @@ class Video < ApplicationRecord
belongs_to :user
has_many :video_applies, dependent: :destroy
has_many :course_videos, dependent: :destroy
has_one :processing_video_apply, -> { where(status: :pending) }, class_name: 'VideoApply'
aasm(:status) do

@ -27,6 +27,11 @@ class Videos::BatchPublishService < ApplicationService
video.video_applies.create!
video_ids << video.id
# 如果是课堂上传则创建课堂记录
if params[:course_id].present?
video.course_videos.create!(course_id: params[:course_id])
end
end
end

@ -20,6 +20,9 @@ class Videos::DispatchCallbackService < ApplicationService
return if video.cover_url.present?
video.update!(cover_url: params['CoverUrl'])
when 'TranscodeComplete' then # 转码完成
return if video.play_url.present?
video.update!(play_url: params['FileUrl'])
end
rescue => ex

@ -0,0 +1,53 @@
class Weapps::ShixunSearchService < ApplicationService
attr_reader :params, :laboratory
def initialize(params, laboratory)
@params = params
@laboratory = laboratory
end
def call
# 全部实训/我的实训
type = params[:type] || "all"
shixuns = laboratory.shixuns.published.no_jupyter
if type == "mine"
shixuns = shixuns.where(id: User.current.shixuns)
else
# 超级管理员用户显示所有未隐藏的实训、非管理员显示所有已发布的实训(对本单位公开且未隐藏未关闭)
if User.current.admin? || User.current.business? || !User.current.school_id
shixuns = shixuns.where(hidden: 0)
else
shixun_ids = ShixunSchool.where(school_id: User.current.school_id).pluck(:shixun_id)
shixun_ids = shixun_ids.reject(&:blank?).length == 0 ? -1 : shixun_ids.join(",")
shixuns = shixuns.where("use_scope = 0 or shixuns.id in (#{shixun_ids})").unhidden.publiced.or(shixuns.where(id: User.current.shixuns))
end
end
## 筛选 难度
if params[:diff].present? && params[:diff].to_i != 0
shixuns = shixuns.where(trainee: params[:diff])
end
unless params[:keyword].blank?
keyword = params[:keyword].strip
shixuns = shixuns.joins(:user).
where("concat(lastname, firstname) like :keyword or shixuns.name like :keyword",
keyword: "%#{keyword}%", name: "%#{keyword.split(" ").join("%")}%").distinct
end
shixuns.order("#{sort_str} #{order_str}")
end
private
def order_str
params[:order] || "desc"
end
def sort_str
params[:sort] || "myshixuns_count"
end
end

@ -0,0 +1,2 @@
json.count @count
json.videos @videos, partial: 'users/videos/video', as: :video

@ -4,12 +4,19 @@ json.courses @courses do |course|
json.created_at course.created_at
end
json.stages @subject.stages do |stage|
json.stages @subject.stages.includes(shixuns: [user: :user_extension]) do |stage|
index = 1
json.shixuns stage.shixuns do |shixun|
if shixun.status == 2 && !@none_shixun_ids.include?(shixun.id)
if shixun.status == 2 && !shixun.is_jupyter && !@none_shixun_ids.include?(shixun.id)
json.shixun_id shixun.id
json.id shixun.id
json.identifier shixun.identifier
json.shixun_name "#{stage.position}-#{index} #{shixun.name}"
json.title shixun.name
json.level level_to_s(shixun.trainee)
json.study_count shixun.myshixuns_count
json.author_name shixun.user.real_name
json.author_img url_to_avatar(shixun.user)
end
index += 1
end

@ -1,4 +1,4 @@
json.extract! video, :id, :title, :cover_url, :file_url, :vv
json.extract! video, :id, :title, :cover_url, :file_url, :play_url, :vv
json.play_duration video.video_play_duration
json.published_at video.display_published_at

@ -0,0 +1,9 @@
json.shixun_list @results do |obj|
json.(obj, :id, :identifier)
json.title obj.name
json.level level_to_s(obj.trainee)
json.study_count obj.myshixuns_count
json.author_name obj.user.real_name
json.author_img url_to_avatar(obj.user)
end
json.shixuns_count @total_count

@ -515,6 +515,7 @@ Rails.application.routes.draw do
get 'work_score'
get 'act_score'
get 'statistics'
get 'course_videos'
post :inform_up
post :inform_down
get :calculate_all_shixun_scores
@ -1021,6 +1022,7 @@ Rails.application.routes.draw do
post :cancel_sticky, on: :collection
end
resources :shixun_lists, only: [:index]
resources :subjects, path: :paths, only: [:index, :create, :update, :edit, :show]
resources :courses, only: [:create, :update, :edit, :show] do

@ -0,0 +1,12 @@
class AddVideoToCourseModule < ActiveRecord::Migration[5.2]
def change
Course.all.each do |course|
unless course.course_modules.exists?(module_type: "video")
atta_position = course.course_modules.find_by(module_type: 'attachment')&.position.to_i
video_position = atta_position != 0 ? (atta_position + 1) : 11
course.course_modules.where("position >= #{video_position}").update_all("position = position + 1")
course.course_modules << CourseModule.new(module_type: "video", hidden: 1, module_name: "视频", position: video_position)
end
end
end
end

@ -0,0 +1,9 @@
class CreateCourseVideos < ActiveRecord::Migration[5.2]
def change
create_table :course_videos do |t|
t.references :course
t.references :video
t.timestamps
end
end
end

@ -45,9 +45,6 @@ namespace :video do
task :get_play_url => :environment do
Video.all.each do |video|
if video.uuid.present?
end
result = AliyunVod::Service.get_play_info(video.uuid) rescue nil
if result.present? && result["PlayInfoList"]["PlayInfo"].present?
puts result

@ -1,29 +1,22 @@
import React,{ Component } from "react";
import { WordsBtn,ActionBtn,getmyUrl } from 'educoder';
import {Tooltip,message,Input, Button} from 'antd';
import { WordsBtn } from 'educoder';
import {Tooltip,message} from 'antd';
import {Link} from 'react-router-dom';
import {getImageUrl} from 'educoder';
import axios from 'axios'
import {getUrl} from 'educoder';
import moment from 'moment'
import CoursesListType from '../coursesPublic/CoursesListType';
import Showoldfiles from "../coursesPublic/Showoldfiles";
import Modals from '../../modals/Modals';
import HeadlessModal from '../../user/usersInfo/common/HeadlessModal'
import ClipboardJS from 'clipboard'
import '../../user/usersInfo/video/InfosVideo.css'
let _clipboard = null;
class Fileslistitem extends Component{
constructor(props){
super(props);
this.state = {
videoModalObj:false,
file_url:null,
}
}
setVisible=(bool)=>{
this.setState({
videoModalObj:bool
})
}
settingList=()=>{
let {discussMessage}=this.props
@ -35,9 +28,6 @@ class Fileslistitem extends Component{
}
showfiles=(list)=>{
// console.log("showfiles");
// console.log(list);
if(this.props.checkIfLogin()===false){
this.props.showLoginDialog()
return
@ -56,20 +46,7 @@ class Fileslistitem extends Component{
if(list.is_history_file===false){
// this.props.DownloadFileA(list.title,list.url)
//window.location.href=list.url;
if(list.content_type){
if(list.content_type==="video/mp4"){
this.setState({
videoModalObj:true,
file_url:getmyUrl(list.url+'?file_name='+list.title),
})
return
}else{
window.open(list.url, '_blank');
}
}else{
window.open(list.url, '_blank');
}
window.open(list.url, '_blank');
}else{
let {discussMessage,coursesId}=this.props
let file_id=discussMessage.id
@ -88,22 +65,7 @@ class Fileslistitem extends Component{
//
// }
// this.props.DownloadFileA(result.data.title,result.data.url)
if(list.content_type){
if(list.content_type==="video/mp4"){
this.setState({
videoModalObj:true,
file_url:getmyUrl(list.url+'?file_name='+list.title),
})
return
}else{
window.open(list.url, '_blank');
}
}else{
window.open(list.url, '_blank');
}
window.open(list.url, '_blank');
}else{
this.setState({
Showoldfiles:true,
@ -125,16 +87,6 @@ class Fileslistitem extends Component{
})
}
Clicktobroadcastthevideo=(bool,url)=>{
this.setState({
videoModalObj:bool,
file_url:getmyUrl(url),
})
}
onDelete = (id) => {
this.setState({
@ -159,17 +111,6 @@ class Fileslistitem extends Component{
}
copyurls =()=>{
//复制网络链接
setTimeout(() => {
if (!_clipboard) {
_clipboard = new ClipboardJS('.copybtn');
_clipboard.on('success', (e) => {
this.props.showNotification("复制成功");
});
}
}, 200)
}
savedelete=(id)=>{
this.setState({
@ -211,37 +152,13 @@ class Fileslistitem extends Component{
}
render(){
const {videoModalObj,file_url}=this.state
const { checkBox,
discussMessage,index
} = this.props;
console.log("Fileslistitem");
console.log(file_url);
return(
<div className="graduateTopicList boardsList">
{
videoModalObj&&videoModalObj===true?
<HeadlessModal
visible={videoModalObj}
setVisible={(bool)=>this.setVisible(bool)}
className="showVideoModal"
width={800 - 1}
>
<video
autoplay="true"
src={file_url} controls="true" controlslist="nodownload">
您的浏览器不支持 video 标签
</video>
{/*<div className="df copyLine">*/}
{/* <Input value={file_url}*/}
{/* className="dark"*/}
{/* ></Input>*/}
{/* <ActionBtn className="copybtn" data-clipboard-text={file_url} onClick={() =>this.copyurls()}>复制视频地址</ActionBtn>*/}
{/*</div>*/}
</HeadlessModal>
:""
}
{/*提示*/}
{this.state.Modalstype&&this.state.Modalstype===true?<Modals
@ -256,7 +173,6 @@ class Fileslistitem extends Component{
{...this.props}
visible={this.state.Showoldfiles}
allfiles={this.state.allfiles}
Clicktobroadcastthevideo={(bool,urls)=>this.Clicktobroadcastthevideo(bool,urls)}
closaoldfilesprops={this.closaoldfilesprops}
/>
<style>{`
@ -450,4 +366,3 @@ class Fileslistitem extends Component{
}
}
export default Fileslistitem;

@ -1,6 +1,6 @@
import React,{ Component } from "react";
import { Input, Checkbox, Table, Tooltip, Pagination,Spin } from "antd";
import { WordsBtn,on, off, trigger } from 'educoder';
import { WordsBtn,on, off, trigger ,getUrl} from 'educoder';
import axios from 'axios';
import Modals from '../../modals/Modals';
import Sendtofilesmodal from "../coursesPublic/SendToFilesModal";
@ -15,7 +15,6 @@ import _ from 'lodash'
import './style.css';
import '../css/members.css';
import moment from 'moment';
class Fileslists extends Component{
constructor(props){
super(props);

@ -49,6 +49,9 @@ class Showoldfiles extends Component{
isaboxonClick=(item)=>{
this.props.Clicktobroadcastthevideo(true,item.url+'&file_name='+item.title);
}
isaboxonClicks=(item)=>{
this.props.Clicktobroadcastthevideo(true,item.url+'?file_name='+item.title);
}
render(){
let {visible,allfiles}=this.props;
@ -188,7 +191,7 @@ class Showoldfiles extends Component{
{
allfiles&&allfiles.content_type&&allfiles.content_type==="video/mp4"?
<a className={"isabox"} target="_blank" onClick={()=>this.isaboxonClick(allfiles)}>{allfiles.title}</a>
<a className={"isabox"} target="_blank" onClick={()=>this.isaboxonClicks(allfiles)}>{allfiles.title}</a>
:
<a className={"isabox"} href={allfiles.url} target="_blank" >{allfiles.title}</a>
}

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe CourseVideo, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
Loading…
Cancel
Save