Merge branch 'dev_aliyun' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun

dev_hjm
jingquan huang 6 years ago
commit 020ff548b7

1
.gitignore vendored

@ -46,6 +46,7 @@
/config/secrets.yml
/config/redis.yml
/config/elasticsearch.yml
/config/aliyun_vod.yml
public/upload.html
/config/configuration.yml

@ -0,0 +1,24 @@
class Callbacks::AliyunVodsController < Callbacks::BaseController
before_action :check_signature_valid!
def create
Videos::DispatchCallbackService.call(params)
render_ok
end
private
def check_signature_valid!
return if AliyunVod::Sign.verify?(header_signature, header_timestamp)
render_not_acceptable
end
def header_timestamp
request.headers['X-VOD-TIMESTAMP']
end
def header_signature
request.headers['X-VOD-SIGNATURE']
end
end

@ -0,0 +1,5 @@
class Callbacks::BaseController < ActionController::Base
include RenderHelper
skip_before_action :verify_authenticity_token
end

@ -7,6 +7,10 @@ module RenderHelper
render json: { status: -1, message: message }
end
def render_not_acceptable(message = '请求已拒绝')
render json: { status: 406, message: message }
end
def render_not_found(message = I18n.t('error.record_not_found'))
render json: { status: 404, message: message }
# render status: 404, json: { errors: errors }

@ -1154,6 +1154,12 @@ class CoursesController < ApplicationController
#课堂的作业信息
shixun_homeworks = homeworks.search_homework_type(4) #全部实训作业
shixun_titles = shixun_homeworks.pluck(:name) + ["总得分"]
# 更新实训作业成绩
shixun_homeworks.includes(:homework_challenge_settings, :published_settings, :homework_commons_shixun).each do |homework|
homework.update_homework_work_score
end
shixun_homeworks = shixun_homeworks&.includes(score_student_works: :user)
common_homeworks = homeworks.search_homework_type(1) #全部普通作业

@ -255,29 +255,7 @@ class HomeworkCommonsController < ApplicationController
def update_score
tip_exception("作业还未发布,暂不能计算成绩") if @homework.publish_time.nil? || @homework.publish_time > Time.now
begin
if @homework.unified_setting
student_works = @homework.student_works
user_ids = @course.students.pluck(:user_id)
else
user_ids = @course.students.where(course_group_id: @homework.published_settings.pluck(:course_group_id)).pluck(:user_id)
student_works = @homework.student_works.where(user_id: user_ids)
end
student_works = student_works.includes(:challenge_work_scores)
challenge_settings = @homework.homework_challenge_settings
challenge_setting_ids = challenge_settings.pluck(:challenge_id)
myshixuns = Myshixun.where(shixun_id: @homework.homework_commons_shixun&.shixun_id, user_id: user_ids).includes(:games)
myshixuns.find_each(batch_size: 100) do |myshixun|
work = student_works.select{|work| work.user_id == myshixun.user_id}.first
if work && myshixun
games = myshixun.games.select{|game| challenge_setting_ids.include?(game.challenge_id)}
HomeworksService.new.update_myshixun_work_score work, myshixun, games, @homework, challenge_settings
end
end
HomeworksService.new.update_student_eff_score @homework if (@homework.allow_late && @homework.late_time < Time.now) ||
(!@homework.allow_late && @homework.end_time < Time.now)
@homework.update_attribute('calculation_time', Time.now)
@homework.update_homework_work_score
normal_status("更新成功")
rescue Exception => e
uid_logger(e.message)

@ -21,7 +21,7 @@ class Users::BaseController < ApplicationController
def private_user_resources!
require_login
return if current_user.admin? || observed_logged_user?
return if current_user.admin_or_business? || observed_logged_user?
render_forbidden
end

@ -0,0 +1,26 @@
class Users::VideoAuthsController < Users::BaseController
before_action :private_user_resources!
def create
result = Videos::CreateAuthService.call(observed_user, create_params)
render_ok(data: result)
rescue Videos::CreateAuthService::Error => ex
render_error(ex.message)
end
def update
video = observed_user.videos.find_by(uuid: params[:video_id])
return render_error('该视频凭证不存在') if video.blank?
result = AliyunVod::Service.refresh_upload_video(video.uuid)
render_ok(data: result)
rescue AliyunVod::Error => _
render_error('刷新上传凭证失败')
end
private
def create_params
params.permit(:title, :file_name, :file_size, :description, :cover_url)
end
end

@ -0,0 +1,58 @@
class Users::VideosController < Users::BaseController
before_action :private_user_resources!
helper_method :current_video
def index
videos = Users::VideoQuery.call(observed_user, search_params)
@count = videos.count
@videos = paginate videos
end
def update
return render_error('该状态下不能编辑视频信息') unless current_video.published?
current_video.update!(title: params[:title])
AliyunVod::Service.update_video_info(current_video.uuid, Title: current_video.title) rescue nil
end
def cancel
video = observed_user.videos.find_by(uuid: params[:video_id])
return render_not_found if video.blank?
return render_error('该状态下不能删除视频') unless video.pending?
video.destroy!
AliyunVod::Service.delete_video([video.uuid]) rescue nil
render_ok
end
def review
params[:status] = 'processing'
videos = Users::VideoQuery.call(observed_user, params)
@count = videos.count
@videos = paginate videos
end
def batch_publish
Videos::BatchPublishService.call(observed_user, batch_publish_params)
render_ok
end
private
def current_video
@_current_video ||= observed_user.videos.find_by(id: params[:id])
end
def search_params
params.permit(:keyword, :sort_by, :sort_direction)
end
def batch_publish_params
params.permit(videos: %i[video_id title])
end
end

@ -372,4 +372,12 @@ module TidingDecorator
I18n.t(locale_format(tiding_type)) % [container.try(:title) || extra]
end
end
def video_content
if tiding_type == 'System'
I18n.t(locale_format(tiding_type, status), reason: extra) % container.try(:title)
else
I18n.t(locale_format(tiding_type)) % [container.try(:title) || extra]
end
end
end

@ -0,0 +1,5 @@
module VideoDecorator
extend ApplicationDecorator
display_time_method :published_at, :created_at, :updated_at
end

@ -11,7 +11,7 @@ class ApplyTeacherRoleJoinCourseNotifyJob < ApplicationJob
belong_container_type tiding_type extra created_at updated_at]
same_attrs = {
trigger_user_id: user.id, container_id: course.id, container_type: 'JoinCourse',
trigger_user_id: user.id, container_id: course.id, container_type: 'JoinCourse', status: 0,
belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'Apply', extra: role
}
Tiding.bulk_insert(*attrs) do |worker|

@ -0,0 +1,23 @@
# 批量发布视频 消息任务
class BatchPublishVideoNotifyJob < ApplicationJob
queue_as :notify
def perform(user_id, video_ids)
user = User.find_by(id: user_id)
return if user.blank?
attrs = %i[user_id trigger_user_id container_id container_type tiding_type status created_at updated_at]
same_attrs = {
user_id: 1,
trigger_user_id: user.id,
container_type: 'Video',
tiding_type: 'Apply', status: 0
}
Tiding.bulk_insert(*attrs) do |worker|
user.videos.where(id: video_ids).each do |video|
worker.add same_attrs.merge(container_id: video.id)
end
end
end
end

@ -0,0 +1,17 @@
# 获取阿里云视频信息
class GetAliyunVideoInfoJob < ApplicationJob
queue_as :default
def perform(vod_video_id)
video = Video.find_by(uuid: vod_video_id)
return if video.blank? || video.vod_uploading?
result = AliyunVod::Service.get_play_info(video.uuid)
cover_url = result.dig('VideoBase', 'CoverURL')
file_url = (result.dig('PlayInfoList', 'PlayInfo') || []).first&.[]('PlayURL')
video.cover_url = cover_url if cover_url.present? && video.cover_url.blank?
video.file_url = file_url if file_url.present?
video.save!
end
end

@ -0,0 +1,5 @@
module AliyunVod
class << self
attr_accessor :access_key_id, :access_key_secret, :base_url, :cate_id, :callback_url, :signature_key
end
end

@ -0,0 +1,2 @@
class AliyunVod::Error < StandardError
end

@ -0,0 +1,8 @@
module AliyunVod::Service
extend AliyunVod::Service::Base
extend AliyunVod::Service::VideoUpload
extend AliyunVod::Service::VideoProcess
extend AliyunVod::Service::VideoManage
extend AliyunVod::Service::VideoPlay
end

@ -0,0 +1,46 @@
module AliyunVod::Service::Base
def request(method, params)
params = AliyunVod::Sign.format_params(params.compact) # 多层hash需要预先处理保证值为string
params[:Signature] = AliyunVod::Sign.generate(params, method: method.to_s.upcase)
Rails.logger.info("[AliyunVod] request => method: #{method}, params: #{params}")
response = Faraday.public_send(method, AliyunVod.base_url, params)
result = JSON.parse(response.body)
Rails.logger.info("[AliyunVod] response => status: #{response.status}, result: #{result}")
raise AliyunVod::Error, result['Code'] if response.status != 200
result
rescue => ex
::Util.logger_error(ex)
raise AliyunVod::Error, ex.message
end
def base_params
{
AccessKeyId: AliyunVod.access_key_id,
Format: 'JSON',
Version: '2017-03-21',
SignatureMethod: 'HMAC-SHA1',
SignatureVersion: '1.0',
SignatureNonce: signature_nonce,
Timestamp: timestamp,
UserData: user_data
}
end
def user_data
{ MessageCallback: { CallbackURL: AliyunVod.callback_url } }
end
def timestamp
Time.now.utc.iso8601
end
def signature_nonce
chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
chars.sample(16).join('')
end
end

@ -0,0 +1,40 @@
# 视频管理
module AliyunVod::Service::VideoManage
# 修改视频信息
def update_video_info(video_id, **opts)
params = {
Action: 'UpdateVideoInfo',
VideoId: video_id
}.merge(base_params)
params = opts.merge(params)
result = request(:post, params)
result
end
# 获取视频信息
def get_video_info(video_id)
params = {
Action: 'GetVideoInfo',
VideoId: video_id
}.merge(base_params)
result = request(:post, params)
result
end
# 删除视频信息
def delete_video(video_ids)
params = {
Action: 'DeleteVideo',
VideoIds: video_ids.join(',')
}.merge(base_params)
result = request(:post, params)
result
end
end

@ -0,0 +1,17 @@
# 视频播放
module AliyunVod::Service::VideoPlay
# 获取视频播放地址
# https://help.aliyun.com/document_detail/56124.html?spm=a2c4g.11186623.6.715.4d7e2d52dU1CTK
def get_play_info(video_id, **opts)
params = {
Action: 'GetPlayInfo',
VideoId: video_id
}.merge(base_params)
params = opts.merge(params)
result = request(:post, params)
result
end
end

@ -0,0 +1,17 @@
# 视频处理
module AliyunVod::Service::VideoProcess
# 提交媒体截图作业
def submit_snapshot_job(video_id, **opts)
params = {
Action: 'SubmitSnapshotJob',
VideoId: video_id
}.merge(base_params)
params = opts.merge(params)
result = request(:post, params)
raise AliyunVod::Error, '提交媒体截图作业失败' if result['SnapshotJob'].blank?
result
end
end

@ -0,0 +1,37 @@
# 视频上传
module AliyunVod::Service::VideoUpload
# 获取视频上传地址和凭证
def create_upload_video(title, filename, **opts)
params = {
Action: 'CreateUploadVideo',
Title: title,
FileName: filename
}.merge(base_params)
# 分类
cate_id = AliyunVod.cate_id
params[:CateId] = cate_id if cate_id.present?
params = opts.merge(params)
result = request(:post, params)
raise AliyunVod::Error, '获取上传凭证失败' if result['UploadAddress'].blank?
result
end
# 刷新视频上传凭证
def refresh_upload_video(video_id)
params = {
Action: 'RefreshUploadVideo',
VideoId: video_id
}.merge(base_params)
result = request(:post, params)
raise AliyunVod::Error, '刷新上传凭证失败' if result['UploadAddress'].blank?
result
end
end

@ -0,0 +1,41 @@
module AliyunVod::Sign
# https://help.aliyun.com/document_detail/44434.html?spm=a2c4g.11186623.2.16.354c7853oqlhMb&/#SignatureNonce
def self.generate(params, **opts)
method = opts[:method] || 'POST'
key = opts[:key] || AliyunVod.access_key_secret + '&'
digest = OpenSSL::Digest.new('sha1')
str = params_to_string(params)
str = percent_encode(str)
str = "#{method}&%2F&#{str}"
Base64.encode64(OpenSSL::HMAC.digest(digest, key, str)).gsub(/\n/, '')
end
def self.verify?(signature, timestamp)
content = "#{AliyunVod.callback_url}|#{timestamp}|#{AliyunVod.signature_key}"
our_signature = Digest::MD5.hexdigest(content)
ActiveSupport::SecurityUtils.secure_compare(signature, our_signature)
end
def self.params_to_string(params)
params.sort.map { |k, v| "#{percent_encode(k)}=#{percent_encode(v)}" }.join('&')
end
def self.percent_encode(str)
return '' if str.blank?
CGI::escape(str.to_s).gsub(/\+/,'%20').gsub(/\*/,'%2A').gsub(/%7E/,'~')
end
def self.format_params(params)
params.each_with_object({}) do |arr, obj|
obj[arr[0]] = arr[1].is_a?(Hash) ? parse_hash_to_str(arr[1]) : arr[1]
end
end
def self.parse_hash_to_str(hash)
hash.each_with_object({}) do |h, obj|
obj[h[0]] = h[1].is_a?(Hash) ? parse_hash_to_str(h[1].clone) : h[1].to_s
end.to_json
end
end

@ -5,7 +5,7 @@ class BiddingUser < ApplicationRecord
belongs_to :project_package, counter_cache: true
aasm(:status) do
state :pending, initiali: true
state :pending, initial: true
state :bidding_won
state :bidding_lost

@ -327,7 +327,7 @@ class Course < ApplicationRecord
#创建课程后,给该用户发送消息
def send_tiding
self.tidings << Tiding.new(user_id: tea_id, trigger_user_id: tea_id, belong_container_id: id,
self.tidings << Tiding.new(user_id: tea_id, trigger_user_id: 1, belong_container_id: id,
belong_container_type: 'Course', tiding_type: 'System')
end

@ -28,7 +28,7 @@ class CourseMessage < ApplicationRecord
def send_deal_tiding deal_status
# 发送申请处理结果消息
Tiding.create!(
user_id: course_message_id, trigger_user: User.current, container_id: course_id, container_type: 'DealCourse',
user_id: course_message_id, trigger_user: 1, container_id: course_id, container_type: 'DealCourse',
belong_container: course, extra: content.to_i == 2 ? '9' : '7', tiding_type: 'System', status: deal_status
)
# 将申请消息置为已处理

@ -264,4 +264,30 @@ class HomeworkCommon < ApplicationRecord
def challenge_score challenge_id
homework_challenge_settings.find_by(challenge_id: challenge_id)&.score.to_f
end
def update_homework_work_score
if unified_setting
works = student_works
user_ids = course.students.pluck(:user_id)
else
user_ids = course.students.where(course_group_id: published_settings.pluck(:course_group_id)).pluck(:user_id)
works = student_works.where(user_id: user_ids)
end
works = works.includes(:challenge_work_scores)
challenge_settings = homework_challenge_settings
challenge_setting_ids = challenge_settings.pluck(:challenge_id)
myshixuns = Myshixun.where(shixun_id: homework_commons_shixun&.shixun_id, user_id: user_ids).includes(:games)
myshixuns.find_each(batch_size: 100) do |myshixun|
work = works.select{|work| work.user_id == myshixun.user_id}.first
if work && myshixun
games = myshixun.games.select{|game| challenge_setting_ids.include?(game.challenge_id)}
HomeworksService.new.update_myshixun_work_score work, myshixun, games, self, challenge_settings
end
end
HomeworksService.new.update_student_eff_score(self) if (allow_late && late_time < Time.now) ||
(!allow_late && end_time < Time.now)
update_attribute('calculation_time', Time.now)
end
end

@ -16,7 +16,7 @@ class Library < ApplicationRecord
validates :uuid, presence: true, uniqueness: true
aasm(:status) do
state :pending, initiali: true
state :pending, initial: true
state :processing
state :refused
state :published

@ -4,7 +4,7 @@ class LibraryApply < ApplicationRecord
belongs_to :library
aasm(:status) do
state :pending, initiali: true
state :pending, initial: true
state :refused
state :agreed

@ -17,7 +17,7 @@ class ProjectPackage < ApplicationRecord
scope :invisible, -> { where(status: %i[pending applying refused]) }
aasm(:status) do
state :pending, initiali: true
state :pending, initial: true
state :applying
state :refused
state :published

@ -4,7 +4,7 @@ class ProjectPackageApply < ApplicationRecord
belongs_to :project_package
aasm(:status) do
state :pending, initiali: true
state :pending, initial: true
state :refused
state :agreed

@ -123,10 +123,10 @@ class StudentWork < ApplicationRecord
# 更新作品成绩
def set_work_score
if work_status > 0 && homework_common && homework_common.homework_detail_manual && !self.ultimate_score
if work_status > 0 && homework_common && !self.ultimate_score
case homework_common.homework_type
when "normal", "group"
if !homework_common.homework_detail_manual.final_mode
if !homework_common&.homework_detail_manual&.final_mode
tea_ass_proportion = homework_common.homework_detail_manual.ta_proportion
tea_proportion = homework_common.homework_detail_manual.te_proportion
if self.teacher_score

@ -60,8 +60,8 @@ class User < ApplicationRecord
has_many :games, :dependent => :destroy
has_many :created_subjects, foreign_key: :user_id, class_name: 'Subject'
has_many :subjects, :through => :subject_members
has_many :subject_members, :dependent => :destroy
has_many :subjects, :through => :subject_members
has_many :grades, :dependent => :destroy
has_many :experiences, :dependent => :destroy
has_many :student_works, :dependent => :destroy
@ -135,6 +135,8 @@ class User < ApplicationRecord
# 教学案例
has_many :libraries, dependent: :destroy
# 视频
has_many :videos, dependent: :destroy
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }

@ -0,0 +1,36 @@
class Video < ApplicationRecord
include AASM
belongs_to :user
has_many :video_applies, dependent: :destroy
has_one :processing_video_apply, -> { where(status: :pending) }, class_name: 'VideoApply'
aasm(:status) do
state :pending, initial: true
state :processing
state :refused
state :published
event :apply_publish do
transitions from: :pending, to: :processing
end
event :refuse do
transitions from: :processing, to: :refused
end
event :publish do
transitions from: :processing, to: :published, guard: :vod_uploaded?
end
end
aasm(:vod_status, namespace: :vod) do
state :uploading, initial: true
state :uploaded
event :upload_success do
transitions from: :uploading, to: :uploaded
end
end
end

@ -0,0 +1,19 @@
class VideoApply < ApplicationRecord
include AASM
belongs_to :video
aasm(:status) do
state :pending, initial: true
state :refused
state :agreed
event :refuse do
transitions from: :pending, to: :refused
end
event :agree do
transitions from: :pending, to: :agreed
end
end
end

@ -0,0 +1,3 @@
class ApplicationQuery
include Callable
end

@ -0,0 +1,28 @@
class Users::VideoQuery < ApplicationQuery
include CustomSortable
sort_columns :published_at, :title, default_by: :published_at, default_direction: :desc
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
videos = user.videos
videos =
case params[:status]
when 'published' then videos.published
when 'processing' then videos.processing
else videos.published
end
keyword = params[:keyword].to_s.strip
videos = videos.where('title LIKE ?', "%#{keyword}%") if keyword.present?
custom_sort(videos, params[:sort_by], params[:sort_direction])
end
end

@ -22,14 +22,7 @@ module ElasticsearchAble
fragment_size: EduSetting.get('es_highlight_fragment_size') || 30,
tag: '<span class="highlight">',
fields: {
name: { type: 'plain' },
challenge_names: { type: 'plain' },
challenge_tag_names: { type: 'plain' },
description: { type: 'plain' },
subject_stages: { type: 'plain' },
content: { type: 'plain' },
descendants_contents: { type: 'plain' },
member_user_names: { type: 'plain' }
'*' => { type: 'plain', number_of_fragments: 3 }
}
}
end
@ -48,4 +41,4 @@ module ElasticsearchAble
def page
params[:page].to_i <= 0 ? 1 : params[:page].to_i
end
end
end

@ -0,0 +1,35 @@
class Videos::AgreeApplyService < ApplicationService
Error = Class.new(StandardError)
attr_reader :video_apply, :video, :user
def initialize(video_apply, user)
@video_apply = video_apply
@video = video_apply.video
@user = user
end
def call
raise Error, '该状态下不能进行此操作' unless video_apply.may_agree? && video.may_publish?
ActiveRecord::Base.transaction do
video_apply.agree!
video.published_at = Time.now
video.publish
video.save!
# 将消息改为已处理
Tiding.where(container_id: video.id, container_type: 'Video', tiding_type: 'Apply', status: 0).update_all(status: 1)
notify_video_author!
end
end
private
def notify_video_author!
Tiding.create!(user_id: video.user_id, trigger_user_id: 1,
container_id: video.id, container_type: 'Video',
tiding_type: 'System', status: 1)
end
end

@ -0,0 +1,31 @@
class Videos::BatchPublishService < ApplicationService
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
video_params = Array.wrap(params[:videos]).compact
return if video_params.blank?
video_ids = []
ActiveRecord::Base.transaction do
video_params.each do |param|
video = user.videos.find_by(uuid: param[:video_id])
next if video.blank? || video.processing_video_apply.present?
video.title = param[:title].to_s.strip.presence || video.title
video.apply_publish
video.save!
video.video_applies.create!
video_ids << video.id
end
end
BatchPublishVideoNotifyJob.perform_later(user.id, video_ids) if video_ids.present?
end
end

@ -0,0 +1,41 @@
class Videos::CreateAuthService < ApplicationService
Error = Class.new(StandardError)
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params.clone
end
def call
validate!
result = upload_video_result
Video.create!(user: user, uuid: result['VideoId'], title: title, cover_url: params[:cover_url])
result
end
private
def title
@_title ||= params.delete(:title).to_s.strip
end
def filename
@_filename ||= params.delete(:file_name).to_s.strip
end
def validate!
raise Error, '视频标题不能为空' if title.blank?
raise Error, '源文件名不能为空' if filename.blank?
end
def upload_video_result
AliyunVod::Service.create_upload_video(title, filename, params)
rescue AliyunVod::Error => _
raise Error, '获取视频上传凭证失败'
end
end

@ -0,0 +1,27 @@
class Videos::DispatchCallbackService < ApplicationService
attr_reader :video, :params
def initialize(params)
@video = Video.find_by(uuid: params[:VideoId])
@params = params
end
def call
return if video.blank?
# TODO:: 拆分事件分发
case params['EventType']
when 'FileUploadComplete' then # 视频上传完成
video.file_url = params['FileUrl']
video.upload_success
video.save!
when 'SnapshotComplete' then # 封面截图完成
return if video.cover_url.present?
video.update!(cover_url: params['CoverUrl'])
end
rescue => ex
Util.logger_error(ex)
end
end

@ -0,0 +1,38 @@
class Videos::RefuseApplyService < ApplicationService
Error = Class.new(StandardError)
attr_reader :video_apply, :video, :user, :params
def initialize(video_apply, user, params)
@video_apply = video_apply
@video = video_apply.video
@user = user
@params = params
end
def call
reason = params[:reason].to_s.strip
raise Error, '原因不能为空' if reason.blank?
raise Error, '该状态下不能进行此操作' unless video_apply.may_refuse?
ActiveRecord::Base.transaction do
video_apply.reason = reason
video_apply.refuse
video_apply.save!
video.refuse!
# 将消息改为已处理
Tiding.where(container_id: video.id, container_type: 'Video', tiding_type: 'Apply', status: 0).update_all(status: 1)
notify_video_author!
end
end
private
def notify_video_author!
Tiding.create!(user_id: video.user_id, trigger_user_id: 1,
container_id: video.id, container_type: 'Video',
tiding_type: 'System', status: 2, extra: video_apply.reason)
end
end

@ -0,0 +1,5 @@
json.extract! video, :id, :title, :cover_url, :file_url
json.published_at video.display_published_at
json.created_at video.display_created_at
json.updated_at video.display_updated_at

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

@ -0,0 +1,7 @@
json.count @count
json.videos do
json.array! @videos.each do |video|
json.partial! 'video', video: video
json.file_url nil
end
end

@ -0,0 +1 @@
json.partial! 'video', video: current_video

@ -0,0 +1,16 @@
defaults: &defaults
access_key_id: 'test'
access_key_secret: 'test'
base_url: 'http://vod.cn-shanghai.aliyuncs.com'
cate_id: '-1'
callback_url: 'http://47.96.87.25:48080/api/callbacks/aliyun_vod.json'
signature_key: 'test12345678'
development:
<<: *defaults
test:
<<: *defaults
production:
<<: *defaults

@ -0,0 +1,7 @@
config = Rails.application.config_for(:aliyun_vod)
AliyunVod.access_key_id = config['access_key_id']
AliyunVod.access_key_secret = config['access_key_secret']
AliyunVod.base_url = config['base_url'] || 'http://vod.cn-shanghai.aliyuncs.com'.freeze
AliyunVod.cate_id = config['cate_id']
AliyunVod.callback_url = config['callback_url']
AliyunVod.signature_key = config['signature_key']

@ -220,3 +220,8 @@
BiddingEnd_end: "你发布的众包任务:%s已进入选标阶段请尽快进行选择确认"
BiddingWon_end: "恭喜,你应征的众包任务:%s在评选环节中标了"
BiddingLost_end: "很遗憾,你应征投稿的众包任务:%s未中标"
Video:
Apply_end: "申请发布视频:%s"
System:
1_end: "你提交的发布视频申请:%s审核已通过"
2_end: "你提交的发布视频申请:%s审核未通过<br/><span>原因:%{reason}</span>"

@ -70,6 +70,16 @@ Rails.application.routes.draw do
resources :recent_contacts, only: [:index]
resource :private_message_details, only: [:show]
resource :unread_message_info, only: [:show]
# 视频
resources :videos, only: [:index, :update] do
collection do
get :review
post :batch_publish
post :cancel
end
end
resource :video_auths, only: [:create, :update]
end
@ -718,6 +728,8 @@ Rails.application.routes.draw do
scope module: :projects do
resources :project_applies, only: [:create]
end
post 'callbacks/aliyun_vod', to: 'callbacks/aliyun_vods#create'
end
#git 认证回调

@ -0,0 +1,20 @@
class CreateVideos < ActiveRecord::Migration[5.2]
def change
create_table :videos do |t|
t.references :user, index: true, type: :integer
t.string :title
t.string :uuid, comment: 'Aliyun媒体ID'
t.string :cover_url, comment: '视频封面'
t.string :file_url, comment: '视频地址'
t.string :status
t.string :vod_status, comment: 'Aliyun媒体状态'
t.datetime :published_at, index: true
t.timestamps
t.index :uuid, unique: true
end
end
end

@ -0,0 +1,10 @@
class CreateVideoApplies < ActiveRecord::Migration[5.2]
def change
create_table :video_applies do |t|
t.references :video, index: true, type: :integer
t.string :status
t.string :reason
t.timestamps
end
end
end

@ -580,7 +580,7 @@ class CoursesBanner extends Component {
<li className={"mt7 mr10im ml10"} style={{overflow:"hidden"}}>
<a>
<span className="color-grey-c fl font-16">邀请码</span>
<span className="color-grey-c fl font-16" style={{marginRight: "10px"}}>邀请码</span>
<span
className={coursedata.code_halt === true? "color-white fl font-16 bannerurli width75f" : "color-white fl font-16 bannerurli width107f marleftf10 color-orange-tip"}>
{coursedata.code_halt === true? "已停用" : coursedata.invite_code}

@ -1649,3 +1649,17 @@ input.ant-input-number-input:focus {
left: -95px;
top:120px;
}
/*.yslnobinput input{*/
/* width: 280px;*/
/* height: 40px !important;*/
/* background-color: #fafafa!important;*/
/*}*/
/*.yslnobinput .ant-input-number-input-wrap{*/
/* width: 280px;*/
/* height: 40px !important;*/
/* background-color: #fafafa!important;*/
/*}*/
.yslinputcourput .ant-form-explain{
padding-left: 0px !important;
}

@ -85,10 +85,10 @@ class JudgeDisplay extends Component{
{ question_choices.map((item, optionIndex) => {
// 单选
return (
<div key={optionIndex} className="fl mr30">
<div key={optionIndex} className="fl mr30 df">
<Radio disabled checked={item.standard_boolean}></Radio>
{/* <span>{item.choice_text}</span> */}
<MarkdownToHtml content={item.choice_text} selector={'judge_' + optionIndex}
<MarkdownToHtml content={item.choice_text} selector={'judge_' + (index + 1) + optionIndex}
className=""
></MarkdownToHtml>
{/* <span style={{ display: 'inline-block'}} className="markdown-body"

@ -103,14 +103,14 @@ class NullDisplay extends Component{
</div> */}
<div className="options">
{
standard_answer.map((answers, index) => {
return <div className="df answerRow" key={index}>
<span className="lineh-40">答案填空{index+1}</span>
standard_answer.map((answers, _index) => {
return <div className="df answerRow" key={_index}>
<span className="lineh-40">答案填空{_index+1}</span>
<div className="answers">
{ answers.answer_text.map((item, itemIndex) => {
return <MarkdownToHtml
className="answer" key={itemIndex}
content={item} selector={'null_' + (index + 1) + '' + (itemIndex + 1)}
content={item} selector={'null_' + (index + 1) + (_index + 1) + (itemIndex + 1)}
></MarkdownToHtml>
})}
{/* <span className="answer" key={itemIndex}>{item}</span> */}

@ -1,5 +1,5 @@
import React, {Component} from "React";
import {Form, Select, Input, Button, Checkbox, DatePicker,Spin,Icon,AutoComplete} from "antd";
import {Form, Select, Input, Button, Checkbox, DatePicker,Spin,Icon,AutoComplete,InputNumber} from "antd";
import ApplyForAddOrgModal from '../../user/modal/ApplyForAddOrgModal';
import axios from 'axios';
import "../css/Courses.css";
@ -539,7 +539,7 @@ class CoursesNew extends Component {
</div>
<div className="stud-class-set bor-bottom-greyE padding1020 coursenavbox coursenavboxtow">
<div className="stud-class-set bor-bottom-greyE padding1020 coursenavbox coursenavboxtow yslinputcourput">
<style>
{`
.ant-form-item{
@ -551,8 +551,18 @@ class CoursesNew extends Component {
label="总学时"
hasFeedback
>
{getFieldDecorator("period")(
<Input id="period" className="greyInput" placeholder="例如30"/>
{getFieldDecorator("period",
{
rules:[{
required:false,
pattern: new RegExp(/^[0-9]\d*$/, "g"),
message: ''
}],
getValueFromEvent: (event) => {
return event.target.value.replace(/\D/g,'')
}}
)(
<Input id="period" className="greyInput " placeholder="例如30"/>
)}
</Form.Item>
@ -560,8 +570,18 @@ class CoursesNew extends Component {
label="学分"
hasFeedback
>
{getFieldDecorator("credit")(
<Input id="credit" className={"greyInput"} placeholder="例如3"/>
{getFieldDecorator("credit",
{
rules:[{
required:false,
pattern: new RegExp(/^[0-9]\d*$/, "g"),
message: ''
}],
getValueFromEvent: (event) => {
return event.target.value.replace(/\D/g,'')
}}
)(
<Input id="credit" className={"greyInput "} placeholder="例如3"/>
)}
</Form.Item>
<Form.Item

@ -596,7 +596,7 @@ class Poll extends Component{
courseType={course_types}
item={item}
key={key}
checkBox={<Checkbox value={item.id} onClick={() => this.onItemClick(item)}></Checkbox>}
checkBox={<Checkbox value={item.id} key={item.id} onClick={() => this.onItemClick(item)}></Checkbox>}
></PollListItem>
)
})

@ -311,7 +311,6 @@
margin-top: 20%;
margin-left: 61%;
margin-right: 0%;
height: 33%;
}
/*成员列表*/
@ -348,9 +347,9 @@
margin-right: 43%;
}
.ysldiv31024{
margin-top: 18%;
margin-top: 19%;
margin-left: 1%;
margin-right: 38%;
margin-right: 34%;
}
/*目录管理*/
@ -427,8 +426,7 @@
margin-right: 39%;
}
.ysldiv51024{
margin-top: 34%;
margin-left: -1%;
margin-top: 30%;
margin-right: 27%;
}
@ -468,8 +466,8 @@
height: 53%;
}
.ysldiv61024{
margin-top: 26%;
margin-top: 31%;
margin-left: 27%;
margin-right: 0%;
margin-right: 0;
height: 40%;
}

@ -166,8 +166,13 @@ class AccountBasic extends Component {
}).then((result)=>{
if(result){
this.props.showNotification('保存成功')
// 如果是第一次完善资料,重新拉头部接口更新
if (!this.props.basicInfo.base_info_completed) {
this.props.fetchUser()
}
this.props.getBasicInfo();
this.props.history.push('/account/profile')
}
}).catch((error)=>{
console.log(error);

Loading…
Cancel
Save